Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
0299034fc1 Version Packages (#7207)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-29 13:30:44 -07:00
Joaquim VergesandGitHub 861e623a1b [SDK] Engine search transactions and batch transaction support (#7190) 2025-05-29 13:19:26 -07:00
MananTank 6a335bfa77 Fix requests to thirdwebstorage-dev.com not set with secret-key (#7205)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `fetch.ts` file in the `thirdweb` package to include a new URL for the thirdweb storage service.

### Detailed summary
- Added `".thirdwebstorage-dev.com"` to the list of URLs.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Chores**
  - Updated the list of recognized thirdweb service domains to include ".thirdwebstorage-dev.com".

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 20:09:50 +00:00
AmineAfia cb994dcb8f Re-enable webhooks sidebar link in project dashboard (#7196)
## [Dashboard] Feature: Add Webhooks to Project Sidebar

## Notes for the reviewer

This PR adds the Webhooks option to the project sidebar navigation. The option was previously commented out due to a scrolling issue, which has now been resolved.

## How to test

Navigate to a project page and verify that the Webhooks option appears in the sidebar with a "New" badge.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refining the handling of webhook payloads and enhancing the `ProjectSidebarLayout` component by reintroducing the webhooks link. It also updates the API fetch calls to ensure they consistently use HTTPS.

### Detailed summary
- Changed `params` type in `webhookPayloadUtils.ts` from `Record<string, unknown> | unknown[]` to `Record<string, unknown>`.
- Updated `params` in the `buildTransactionWebhookPayload` function to use an empty object instead of an empty array.
- Reintroduced the webhooks link in `ProjectSidebarLayout.tsx` with appropriate properties.
- Updated fetch calls in `webhooks.ts` to use HTTPS for the API domain in multiple functions (`getWebhooks`, `deleteWebhook`, `testWebhook`).

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Re-enabled the "Webhooks" link in the project sidebar, now visible with a "New" badge.

- **Bug Fixes**
  - Improved API reliability by ensuring all webhook-related network requests use the correct HTTPS scheme.

- **Refactor**
  - Updated internal data structures for webhook payloads to improve consistency and type safety.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 19:30:57 +00:00
Joaquim VergesandGitHub 4d38a7e8fe Update page.mdx (#7204)
Signed-off-by: Joaquim Verges <[email protected]>
2025-05-29 11:32:36 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
89e6af6baf Version Packages (#7164)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-29 11:10:34 -07:00
catalyst17 b345cc115e docs: add docs on aggregates (#7201)
# [Portal] Feature: Add Aggregation Examples and Documentation to Insight

## Notes for the reviewer

This PR adds comprehensive documentation for the Insight aggregation functionality across multiple pages:

- Added an "Aggregation Examples" section to the LLMs page with code samples
- Added detailed aggregation documentation to the Agents and LLMs page
- Added aggregation examples to the Blueprints page for Events, Transactions, Wallet Transactions, and Blocks
- Added FAQs about aggregation queries
- Added "Using Aggregation Presets" section to the Get Started page
- Enhanced Use Cases page with example aggregations for relevant scenarios

## How to test

Review the documentation for clarity and accuracy. The examples should be consistent with the actual API functionality.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Documentation**
  - Added comprehensive examples and explanations for using aggregation functions in Insight API queries across multiple documentation sections, including blueprints, use cases, FAQs, and getting started guides.
  - Introduced new sections detailing aggregation presets, usage patterns, and code snippets for blockchain data analysis.
  - Enhanced clarity on performing statistical and summary queries, with guidance on leveraging the Insight Playground for interactive exploration.
  - Updated the Insight landing page with a new feature card highlighting powerful aggregation capabilities and improved layout for feature cards.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the documentation for the `Insight` tool by adding new aggregation features, examples, and updating existing content to clarify how to utilize these functionalities effectively.

### Detailed summary
- Added `FunctionSquareIcon` to the import statement in `page.mdx`.
- Updated grid layout class from `lg:grid-cols-2` to `lg:grid-cols-3` in `page.mdx`.
- Introduced a new `FeatureCard` titled "Powerful Aggregations" in `page.mdx`.
- Added sections on aggregation queries and examples in `faqs/page.mdx`, `get-started/page.mdx`, and `agents-and-llms/page.mdx`.
- Provided aggregation examples in `use-cases/page.mdx` and `blueprints/page.mdx`.
- Enhanced documentation for wallet transactions and blocks, including aggregation examples in `agents-and-llms/page.mdx`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-29 17:37:27 +00:00
gregandGitHub 3d3c6f3d45 Fix: Transfer purchase data (#7202) 2025-05-29 10:30:15 -07:00
Robby UitbeijerseandGitHub ff3a79c884 chore: annotate getBuyWithFiatQuote as deprecated (#7198) 2025-05-29 09:00:53 -07:00
kumaryash90 16482cfa6a Stylus ERC20 template for CLI (#7192)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `create.ts` and `builder.ts` files for the Stylus project by adding project type selection and improving contract name handling during ABI processing. It introduces prompts for user input and refines how contract names are extracted and utilized.

### Detailed summary
- In `create.ts`, added prompts for selecting project type (Default or ERC20).
- Updated project creation logic based on the selected type.
- In `builder.ts`, improved contract name extraction to handle multiple contracts.
- Added prompts for selecting the entrypoint if multiple contracts are found.
- Refined usage of selected contract names throughout the build process.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added support for handling multiple contract ABIs, allowing users to select a contract entrypoint when more than one is present.
  - Introduced a prompt to select a project template type ("default" or "erc20") during project creation.

- **Improvements**
  - Enhanced error handling and user guidance when no contract is selected or found.
  - Updated success messages and console output for improved clarity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 12:12:00 +00:00
jnsdls e8a25d70b9 [Dashboard] replace chakra stats in accounts (#7188)
## Summary
- swap chakra `SimpleGrid`, `Stat`, `Skeleton` for shadcn `StatCard`
- use Tailwind grid utility classes

## Checklist
- [x] `pnpm biome check --apply`
- [x] `pnpm test` *(fails: spawn anvil ENOENT)*

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the `AccountsCount` component to replace the `Card` and `SimpleGrid` components from `@chakra-ui/react` with a new layout using `StatCard` from a different module, enhancing the presentation of the total accounts data.

### Detailed summary
- Removed imports for `SimpleGrid`, `Skeleton`, `Stat`, `StatLabel`, and `StatNumber`.
- Added import for `StatCard` from `../../overview/components/stat-card`.
- Changed the layout from `SimpleGrid` and `Card` to a `div` with a grid layout.
- Updated the rendering to use `StatCard` for displaying the total accounts.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Updated the accounts count display to use a new StatCard component with improved layout and styling for a more consistent and responsive user interface.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 00:55:53 +00:00
gregandGitHub 44a7460c83 [SDK] Feature: Remove Switch Network button (#7191) 2025-05-28 17:45:07 -07:00
gregandGitHub 0731831169 Fix: Webhook docs tags (#7189) 2025-05-28 17:11:21 -07:00
AmineAfia 91750eddc6 Temporarily remove webhooks sidebar link until scrolling issue is fixed (#7187)
## [Dashboard] Fix: Temporarily remove Webhooks from sidebar

## Notes for the reviewer

Commented out the Webhooks section in the project sidebar until we resolve a scrolling issue. Also removed the unused BellIcon import.

## How to test

Verify that the Webhooks option no longer appears in the project sidebar navigation.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
  - Temporarily removed the "Webhooks" link from the project sidebar to address a scrolling issue. The link and its badge are no longer visible.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-28 23:45:59 +00:00
iuwqyir 2fc083f1f2 token owners endpoint in insight playground (#7184)
## [Dashboard] Feature: Update Token and NFT API Endpoints

## Notes for the reviewer

This PR adds a new token endpoint and updates the name of an existing NFT endpoint:

1. Added a new endpoint for getting token owners by contract: `/v1/tokens/owners`
2. Renamed the "Get NFTs by owner" endpoint to simply "Get NFTs" to better reflect its functionality

## How to test

Verify the updated endpoint names appear correctly in the Insight section of the playground.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added a new API endpoint for retrieving token owners by contract.

- **Refactor**
  - Renamed the "Get NFTs by owner" endpoint to "Get NFTs" for improved clarity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-28 23:26:30 +00:00
AmineAfia 75ae536e61 Add scrollable overflow to webhook filter select dropdowns (#7186)
## [Dashboard] Fix: Add scrolling to webhook event and function signature dropdowns

## Notes for the reviewer

This PR adds scrolling functionality to the webhook event and function signature dropdown menus by adding `max-h-60 overflow-y-auto` classes. This ensures that when there are many options, users can still navigate through them effectively.

## How to test

1. Navigate to the webhooks section in a project
2. Open the event signature dropdown and verify it scrolls properly when there are many options
3. Similarly, test the function signature dropdown to ensure it maintains its max width while allowing vertical scrolling

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Style**
  - Improved dropdown menus for event and transaction signature selection by limiting their maximum height and enabling vertical scrolling, enhancing usability when many options are available.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `FilterDetailsStep` component by improving the usability of the dropdown content with better scrolling behavior.

### Detailed summary
- Updated the `className` of the first `<SelectContent>` from default to `max-h-60 overflow-y-auto` for better vertical scrolling.
- Updated the second `<SelectContent>` to include `overflow-y-auto` while retaining `max-w-[600px]`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-28 23:11:30 +00:00
gregandGitHub 41e59a9c20 [SDK] Feature: Adds explicit country param to onramps (#7185) 2025-05-28 16:11:09 -07:00
Joey BrightandGitHub fd97406fa2 chore: fix typos (#7180) 2025-05-28 16:10:17 -07:00
AmineAfia c336de3bfb Add webhook creation UI to dashboard (#7174)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `CreateWebhookModal` component and related functionalities in the webhook management system, including form handling, validation, and API integration for creating webhooks.

### Detailed summary
- Changed `createWebhook` function to `export async function createWebhook`.
- Updated `CreateWebhookModal` to accept `clientId` prop.
- Integrated form steps for creating a webhook with validation.
- Added utility functions for parsing addresses and building webhook payloads.
- Improved error handling and user feedback during webhook testing.
- Enhanced UI components for better user experience in the webhook creation process.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Introduced a multi-step modal form for creating webhooks, including form validation, ABI integration, and live endpoint testing.
  - Added components for basic info entry, filter configuration, and review before submission.
  - Enabled dynamic ABI fetching and signature extraction for blockchain event and transaction filters.
  - Improved error handling and user feedback throughout the webhook creation flow.

- **Bug Fixes**
  - Enhanced validation and error messages for webhook URLs and date handling in tables.

- **Chores**
  - Added comprehensive utility modules for ABI processing and webhook payload construction.
  - Implemented robust type definitions and validation schemas for webhook forms.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-28 22:02:27 +00:00
AmineAfia c420099dc3 Add webhook testing functionality to dashboard (#7173)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the webhook testing functionality in the dashboard application by exporting the `testWebhook` function, adding a testing feature in the `WebhooksTable`, and creating a custom hook `useTestWebhook` to manage the testing state and results.

### Detailed summary
- Changed `testWebhook` from a local function to an exported function in `webhooks.ts`.
- Imported `PlayIcon` in `WebhooksTable.tsx` and added it to the UI.
- Implemented `handleTestWebhook` function to test webhooks and display results.
- Created `useTestWebhook` hook to manage testing state and results.
- Added error handling and success notifications for webhook testing.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added the ability to test webhooks directly from the webhooks table with a "Test" button, providing immediate feedback and status indicators during testing.
- **User Interface**
  - Enhanced the actions column in the webhooks table for improved layout and usability, including real-time spinner icons and error notifications during webhook testing.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-28 22:02:27 +00:00
AmineAfia dcb412fc27 Add webhooks page to project dashboard (#7172)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `CreateWebhookModal` component and enhances the `Webhooks` functionality in the dashboard. It implements a `WebhooksTable` for displaying webhooks and their details, along with error handling and user notifications for actions like deleting webhooks.

### Detailed summary
- Added `CreateWebhookModal` component for creating new webhooks.
- Updated `ProjectSidebarLayout` to include a link to webhooks with a new badge.
- Changed `WebhookResponse` and `WebhookFilters` interfaces to `export`.
- Modified `WebhooksPage` to handle project and webhook loading with error management.
- Introduced `WebhooksTable` for displaying webhooks with actions.
- Implemented `RelativeTime` component to show formatted timestamps.
- Added deletion functionality for webhooks with user notifications.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a "Webhooks" section in the project sidebar, highlighted with a "New" badge.
  - Added a dedicated Webhooks page displaying a list of webhooks in a sortable table with options to copy URLs and delete entries.
  - Included a "New Webhook" button and modal for future webhook creation.
  - Implemented a component to display relative creation times for webhooks.
- **Bug Fixes**
  - Improved error handling and user feedback for webhook-related actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-28 22:02:26 +00:00
catalyst17 4705a599a2 feat: redesign aggregate parameter input with improved UI (#7179)
# [Dashboard] Feature: Redesign Aggregate Parameter Input Component

## Notes for the reviewer

This PR completely redesigns the aggregate parameter input component with a more user-friendly interface. The new design uses a popover pattern instead of the previous MultiSelect approach, making it easier to view and select aggregation presets.

## How to test

1. Open any insight blueprint that uses aggregation
2. Click on the aggregate parameter input
3. Test searching for presets, selecting/deselecting options
4. Verify that manual text entry still works in the textarea
5. Check that the selected aggregations display correctly when the popover is closed
2025-05-28 21:48:48 +00:00
catalyst17 f7331f15a2 feat: enhance aggregate parameter input with multi-select presets (#7168)
## [Dashboard] Feature: Enhance Aggregate Parameter Input with Multi-Select Functionality

## Notes for the reviewer
This PR replaces the simple dropdown select with a more robust multi-select component for aggregate parameter inputs. The new implementation allows users to select multiple presets and displays them as badges.

## How to test
Test the new aggregate parameter input by:
1. Opening the insight blueprint page
2. Selecting multiple presets from the dropdown
3. Verifying that selected values appear as badges
4. Testing the search functionality within the preset selector
5. Confirming that manual input still works alongside preset selection

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Enhanced aggregate parameter input with combined manual entry and multi-select for presets.
  - Added search functionality within the multi-select component for easier preset selection.
  - Updated parameter descriptions and examples for "aggregate" to clarify multi-selection usage.
- **Refactor**
  - Made input options optional and improved synchronization between manual input and multi-select.
  - Adjusted tooltip positioning for better UI alignment on aggregate parameters.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `ParameterSection` and `AggregateParameterInput` components to provide dynamic descriptions and examples based on the parameter type. It also refines the handling of selected values and manual input for aggregations, replacing the `Select` component with a `MultiSelect`.

### Detailed summary
- Updated `description` and `exampleToShow` in `ParameterSection` based on `param.name`.
- Introduced `MultiSelect` in `AggregateParameterInput` for selecting aggregation presets.
- Added state management for manual input and selected values.
- Removed the `Select` component in favor of a more flexible input system.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-28 21:48:47 +00:00
AmineAfia a5d6e769db Add webhook API functions for insight dashboard (#7171)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces functionality for managing webhooks in the application. It includes a temporary `WebhooksPage` for displaying webhook data and implements several API functions for creating, retrieving, deleting, and testing webhooks.

### Detailed summary
- Added `WebhooksPage` component in `page.tsx` to display webhook data.
- Implemented `getWebhooks` function to fetch webhooks from the API.
- Created interfaces for webhook responses and payloads.
- Added `createWebhook`, `deleteWebhook`, and `testWebhook` functions for managing webhooks.
- Handled API errors with appropriate responses.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced the ability to create, view, delete, and test webhooks through the dashboard interface, allowing users to manage webhook integrations more easily.
  - Added a new dashboard page displaying webhook data for better visibility and management.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-28 21:20:24 +00:00
jnsdls 97b8926024 [Dashboard] migrate external links form (#7156)
## Summary
- refactor contract publish resources inputs
- use shadcn/ui Button, Input, and Separator
- apply tailwind classes

## Testing
- `pnpm biome check --apply apps/dashboard/src/components/contract-components/contract-publish-form/external-links-fieldset.tsx apps/dashboard/src/components/contract-components/contract-publish-form/external-links-input.tsx`
- `pnpm test` *(fails: spawn anvil ENOENT)*

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the UI components in the `ExternalLinksFieldset` and `ExternalLinksInput` files to improve the styling and structure using custom components instead of those from `tw-components` and `@chakra-ui/react`.

### Detailed summary
- Replaced `Heading` and `Text` with custom HTML elements in `ExternalLinksFieldset`.
- Updated button styles in `ExternalLinksFieldset` to use `variant="outline"` and added a `className`.
- Refactored `ExternalLinksInput` to use custom components (`Button`, `Input`, `Label`, `Separator`) instead of Chakra UI components.
- Changed layout structure in `ExternalLinksInput` to use `div` elements with flexbox for better styling.
- Added error message handling below input fields in `ExternalLinksInput`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Style**
  - Updated form and button components to use new UI primitives and Tailwind CSS classes for a refreshed appearance.
  - Improved accessibility by linking input labels and error messages with appropriate HTML attributes.

- **Refactor**
  - Replaced Chakra UI components with custom or alternative UI components, maintaining existing functionality and validation logic.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-28 15:47:22 +00:00
Joaquim VergesandGitHub 655df037dd [SDK] fix hiddenWallets prop (#7183) 2025-05-28 01:46:03 -07:00
Joaquim VergesandGitHub cbde32e974 [Dashboard] add wallet user search (#7182) 2025-05-28 01:14:14 -07:00
Joaquim VergesandGitHub 8ed5ef3f81 [Dashboard] Change "Network" label to "Select Testnet" in analytics test transaction form (#7178) 2025-05-28 00:52:08 -07:00
Joe-Thirdweb e14a1349e1 [Dashboard] Add token request route to dashboard (#7112)
TOOL-4443
TOOL-4559

How to Test
1. Log into dashboard
2. Select a project
3. Click on Universal Bridge on left hand side
4. Click on Settings
5. Scroll down to add token section

-  Failure input
Chain: Ethereum
Token Address: 0x7613c48e0cd50e42dd9bf0f6c235063145f6f8db

-Success input
Chain: Ethereum
Token Address:0x7613c48e0cd50e42dd9bf0f6c235063145f6f8dc

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new validation schema for route discovery, enhances the UI for token submission, and integrates a new `RouteDiscovery` component to facilitate token discovery in the dashboard.

### Detailed summary
- Added `routeDiscoveryValidationSchema` in `validations.ts` for validating token discovery inputs.
- Created `RouteDiscovery` component to handle token submission.
- Updated `page.tsx` to include `RouteDiscovery` below `PayConfig`.
- Implemented `addUniversalBridgeTokenRoute` function in `tokens.ts` for API token submission.
- Added `RouteDiscoveryCard` component for better UI presentation of token discovery.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a Route Discovery workflow, allowing users to submit tokens for route discovery on selected blockchain networks.
  - Added a new card component for displaying route discovery information and actions.
  - Integrated Route Discovery into the Universal Bridge settings page for easier access.

- **Enhancements**
  - Added validation for token address and chain selection to ensure proper input.
  - Provided user feedback through success and error messages, including toast notifications.

- **Style**
  - Minor cleanup of extra blank lines in the Project General Settings page for improved readability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-27 22:14:03 +00:00
gregGitHubcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
acd8b9e535 Feat: tokenAddress and amount bridge params (#7176)
Signed-off-by: greg <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-05-27 13:28:36 -07:00
Robby UitbeijerseandGitHub 52d451ff70 [SDK] ensure that the default spinner stroke color gets inherited from the parentnode (#7157) 2025-05-27 12:28:10 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c03616666b chore(deps): bump @radix-ui/react-select from 2.2.2 to 2.2.5 (#7159)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 12:21:22 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
bb7a8988ef chore(deps): bump @radix-ui/react-separator from 1.1.4 to 1.1.7 (#7161)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 11:51:20 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8d0ffedd41 chore(deps): bump papaparse from 5.5.2 to 5.5.3 (#7162)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 11:51:04 -07:00
Jonas DanielsandGitHub 5edfb81ec5 [Dashboard] consolidate solution page rewrites with dynamic slug (#7175) 2025-05-27 11:50:23 -07:00
iuwqyir f115b4d2db new tokens route (#7170)
## [Dashboard] Feature: Add new tokens endpoint and deprecate ERC-20 balances endpoint

## Notes for the reviewer

This PR adds a new endpoint for getting tokens at `/v1/tokens` and marks the existing ERC-20 balances endpoint (`/v1/tokens/erc20/{ownerAddress}`) as deprecated.

## How to test

Verify that the new endpoint is accessible and that the deprecated flag is correctly set for both endpoints in the insight blueprints.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Introduced a new "Get tokens" endpoint for accessing token data.

- **Bug Fixes**
  - Marked the older ERC-20 balances endpoint as deprecated to guide users toward the updated endpoint.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-27 17:02:25 +00:00
Joaquim VergesandGitHub 0e4390b2c7 [Docs] Add Onramp extension to sidebar and typedoc (#7167) 2025-05-27 01:18:31 -07:00
Joaquim VergesandGitHub aec8d88af2 [Wallet] Move 7702 batch transaction test to integration tests (#7166) 2025-05-26 20:53:10 -07:00
gregandGitHub 80207ae240 [Dashboard] Refactor: Bridge UI Cleanup and Fixes (#7165) 2025-05-26 19:12:23 -07:00
Joaquim VergesandGitHub ec7bc2bb1e [Insight] Make clientId optional and improve error handling (#7158) 2025-05-26 14:34:12 -07:00
MananTank 12475027d1 Add Contract layout in team/project (#7152)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces enhancements to the handling of contract pages, adding new types for parameters and project metadata, while refactoring existing components to incorporate team and project-specific data.

### Detailed summary
- Added `PublicContractPageParams` and `ProjectContractPageParams` types.
- Introduced `ProjectMeta` type for project metadata.
- Refactored components to accept `teamSlug`, `projectSlug`, and `projectMeta`.
- Updated routing and links to include project-specific paths.
- Enhanced handling of contract page data retrieval and UI rendering.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/AccountPage.client.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/TokenIdPage.client.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/account-permissions/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/ContractNFTPage.client.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/contract-page-layout.client.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/explorer/shared-explorer-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/explorer/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/permissions/ContractPermissionsPage.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/shared-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/ContractChecklist.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/permissions/shared-permissions-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/split/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/accounts/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/account-permissions/shared-account-permissions-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/permissions/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/split/shared-split-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/accounts/shared-accounts-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/contract-page-layout.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/shared-modules-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/shared-proposals-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/account/shared-account-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/english-auctions/shared-english-auctions-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/direct-listings/shared-direct-listings-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/shared-nfts-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/data-table.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/primary-dashboard-button.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/claim-conditions/shared-claim-conditions-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/claim-conditions/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/shared-settings-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/shared-overview-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/shared-analytics-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/shared-nfts-token-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/ContractOverviewPage.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/events/events-feed.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/layout.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/MarketplaceDetails.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/shared-cross-chain-page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/shared-layout.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/layout.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Enhanced routing to include project and team slugs, enabling project-aware navigation for contract, NFT, marketplace, analytics, permissions, and proposal pages.
  - Passed project metadata (team and project info) throughout various pages and components for contextual consistency.
  - Introduced shared layout and page components for contract overview, NFTs, analytics, permissions, and settings, streamlining the user experience.

- **Refactor**
  - Removed internal data fetching, validation, and conditional rendering from pages, delegating logic to shared components.
  - Updated navigation links and breadcrumbs to dynamically incorporate project and team slugs when available.
  - Centralized metadata and layout generation into utility functions to reduce duplication.

- **Bug Fixes**
  - Improved handling of missing or invalid project/team data by triggering notFound responses appropriately.

- **Chores**
  - Extended component props and interfaces to accept projectMeta objects.
  - Simplified page components to directly render shared components with minimal logic.
  - Updated URL construction to utilize utility functions for project-aware paths.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-26 20:59:12 +00:00
catalyst17 9479d252b4 feat: add aggregate parameter input with presets (#7146)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces the `AggregateParameterInput` component to handle aggregate parameters in the `BlueprintPlayground`. It adds new presets for various data types and modifies the `ParameterSection` to utilize this new component, enhancing the functionality of the parameter input system.

### Detailed summary
- Added `AggregateParameterInput` component to handle aggregate parameters.
- Introduced multiple preset options for aggregates, transactions, events, and blocks.
- Updated `ParameterSection` to include a check for the `aggregate` parameter and render the new component.
- Modified placeholder logic in `ParameterSection` to use `param.description` or `param.name`.
- Passed `endpointPath` prop to `AggregateParameterInput` for context on preset selection.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a specialized input with preset selection for aggregate query parameters, enhancing usability when configuring aggregate values.
  - The playground UI now displays this new input component for aggregate parameters, providing relevant preset options based on the selected endpoint.
- **Improvements**
  - Placeholders for parameter inputs now more accurately reflect the parameter’s description or name for better clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-25 19:14:20 +00:00
Joaquim VergesandGitHub 5139857db9 [Portal] fix portal build (#7154) 2025-05-23 22:56:39 -07:00
Joaquim VergesandGitHub ad4341a7ad [Portal] Add AI chat functionality with markdown support (#7139) 2025-05-23 20:55:13 -07:00
Joaquim VergesandGitHub ebd58f49b6 [Portal] Redesign header with improved mobile menu layout (#7138) 2025-05-23 20:12:18 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
3862bc7a94 Version Packages (#7150)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-23 20:03:03 -07:00
gregfromstl 5277eddb70 [SDK] Fix: Webhook Schema (#7151)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `Webhook` schema in the `thirdweb` package to enhance the structure of `originToken` and `destinationToken` by replacing simple address strings with detailed objects containing additional properties.

### Detailed summary
- Updated `originToken` and `destinationToken` to be objects with properties:
  - `chainId`
  - `address`
  - `name`
  - `symbol`
  - `decimals`
  - `priceUsd`
  - `iconUri`
- Adjusted the test file to reflect these changes with appropriate object structures.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
  - Enhanced webhook support to include detailed token information such as chain ID, name, symbol, decimals, price, and icon for origin and destination tokens.
- **Tests**
  - Updated test data to match the new detailed token structure in webhook payloads.
- **Chores**
  - Added a changeset to document this patch update.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-23 23:10:14 +00:00
jnsdls 6c180b5c8a [Dashboard] migrate solidity inputs to shadcn (#7148)
## Summary
- migrate solidity bool, int, bytes, and string inputs to shadcn/ui and tailwind

## Testing
- `pnpm biome check --apply apps/dashboard/src/contract-ui/components/solidity-inputs/bool-input.tsx apps/dashboard/src/contract-ui/components/solidity-inputs/bytes-input.tsx apps/dashboard/src/contract-ui/components/solidity-inputs/int-input.tsx apps/dashboard/src/contract-ui/components/solidity-inputs/string-input.tsx`
- `pnpm test` *(fails: spawn anvil ENOENT)*

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the UI components in the `solidity-inputs` directory to use a consistent styling approach and improve the layout by replacing certain components with more flexible alternatives.

### Detailed summary
- Updated imports from `tw-components` to a local `@/components/ui` for `Button` and `Input`.
- Replaced `ButtonGroup` with a `div` for better styling control in `SolidityBoolInput`.
- Adjusted button variants from `solid` to `default` for consistency.
- Introduced a `showConversionButton` flag in `SolidityIntInput` for conditional rendering.
- Changed `InputGroup` to `div` in `SolidityIntInput` and `SolidityStringInput` for layout flexibility.
- Used `cn` utility for conditional class names in `Input` components.
- Updated button and input placements to enhance responsiveness and styling.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Style**
  - Updated input components to use custom UI elements and Tailwind CSS for consistent styling.
  - Replaced Chakra UI and third-party components with locally defined UI components and native HTML elements.
  - Improved button and input layouts for better visual consistency across boolean, integer, byte, and string input fields.

- **New Features**
  - Enhanced integer input with conditional display of a conversion button based on input value format.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-23 22:57:37 +00:00
Joaquim VergesandGitHub 3489ecec98 [SDK] Improve quote error handling (#7147) 2025-05-23 15:43:52 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f9b34ff2f1 Version Packages (#7149)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-23 14:04:03 -07:00
Jonas DanielsandGitHub f3abea3276 Add webhook verification functionality for secure payload validation (#7103) 2025-05-23 13:59:46 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
adc738e536 Version Packages (#7116)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-23 12:44:58 -07:00
Robby UitbeijerseandGitHub dd2fb1b13d [SDK] Feature: Allow for supported providers to be passed to PayEmbed (#7108) 2025-05-23 12:01:27 -07:00
Joaquim VergesandGitHub 69fdef07f5 [thirdweb] Ensure bigints are stringified before usage with server wallets (#7145) 2025-05-23 11:57:43 -07:00
MananTank 362c086bf6 Dashboard: Move project pages inside (sidebar) layout (#7144)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refactoring the codebase to organize components and hooks into a new `(sidebar)` directory structure, enhancing modularity and maintainability. It also updates import paths to reflect these changes.

### Detailed summary
- Moved various components and hooks to a new `(sidebar)` directory.
- Updated import paths across multiple files.
- Adjusted layout files to accommodate the new directory structure.
- Ensured proper functionality of components post-migration.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Chores**
  - Updated and standardized import paths across the dashboard and component files for improved code organization and maintainability.
  - No changes to end-user functionality or visible features.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-23 18:12:01 +00:00
jnsdls f9b609e7a2 [Dashboard] replace Chakra fieldset with shadcn (#7143)
Migrates the NetworksFieldset component from Chakra UI to shadcn/ui and Tailwind.

- Drop Chakra Flex, FormControl and Select
- Use shadcn Select primitives with tailwind classes
- Adjust layout with Tailwind via `cn`

Fixes #0

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the `NetworksFieldset` component in `networks-fieldset.tsx` to use a new `Select` component from the UI library, improving styling and functionality while maintaining the same overall behavior.

### Detailed summary
- Replaced `Flex` and `FormControl` components with a new `div` structure and `Select` component.
- Updated the `Select` component to use `SelectTrigger`, `SelectContent`, and `SelectItem`.
- Changed event handlers from `onChange` to `onValueChange` for better state management.
- Maintained layout and functionality with updated class names for styling.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Updated the network selection field to use a custom select component and Tailwind-based layout for a more consistent user interface.
- **Style**
  - Improved the visual appearance and spacing of the network selection field.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-23 18:00:04 +00:00
MananTank 916ecf494c [TOOL-4591] Fix resolveImplementation jsdoc code example in jsdoc (#7141)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the import path for the `resolveImplementation` function to reflect its new location within the `thirdweb/utils` directory.

### Detailed summary
- Changed import statement for `resolveImplementation` from `thirdweb` to `thirdweb/utils`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Documentation**
  - Updated example import path in documentation for improved clarity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-23 17:11:46 +00:00
MananTank d1ca9af2f4 Remove SIWA feedback buttons from nebula (#7142)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refactoring the `Chats` component and its related functionalities. It removes unused code and simplifies the props being passed, particularly the `teamId`. Additionally, it eliminates the feedback buttons and associated functionality.

### Detailed summary
- Removed `teamId` prop from various instances of the `Chats` component.
- Deleted the `FeedbackButtons` component and its related logic.
- Simplified imports in the `Chats` component by removing unused imports.
- Cleaned up the `RenderMessage` function by omitting `teamId` from props.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- None.

- **Bug Fixes**
	- None.

- **Refactor**
	- Removed the feedback feature, including thumbs up/down buttons and related tracking, from chat components.
	- Simplified chat components by removing the unused or redundant team selection prop.

- **Chores**
	- Cleaned up code by deleting unnecessary feedback-related imports and props.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-23 16:02:33 +00:00
jnsdls 317211a48e [Dashboard] migrate CurrencySelector to shadcn (#7137)
## Summary
- migrate shared CurrencySelector from Chakra UI to shadcn/ui
- use tailwindcss classes and Radix-based Select

## Checklist
- [x] `pnpm biome check apps/dashboard/src/components/shared/CurrencySelector.tsx --apply`
- [ ] `pnpm test` *(fails: spawn anvil ENOENT)*

<!-- start pr-codex -->

---

## PR-Codex overview
This PR refactors the `CurrencySelector` component to improve its structure and styling. It replaces certain UI elements with a new `Select` component, enhances props for better functionality, and updates the layout for a more modern appearance.

### Detailed summary
- Changed `CurrencySelectorProps` to include new props: `onChange`, `className`, `isDisabled`.
- Refactored the component to use a `Select` from the UI library instead of native HTML elements.
- Updated event handling to utilize `onValueChange` for the `Select`.
- Improved styling and layout for better user experience.
- Replaced `Flex` components with `div` and applied conditional class names for styling.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Updated the CurrencySelector component to use custom UI components and Tailwind CSS for styling, replacing Chakra UI elements.
  - Simplified the component's props and adjusted event handling for the new select interface.
  - Maintained existing functionality and behavior while improving consistency with the app's design system.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-23 04:52:16 +00:00
gregfromstl 6cd64e7368 [Dashboard] Fix: Routes page pagination beyond 1, add og image (#7136)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating titles for better clarity and modifying the pagination logic in the routes table component to directly use `routesToRender`, enhancing the user experience in the dashboard.

### Detailed summary
- Updated `title` in `page.tsx` files for clarity.
- Changed the condition from `paginatedRoutes.length` to `routesToRender.length` in the routes table component.
- Removed pagination logic variables (`activePage`, `pageSize`, `startIndex`, `endIndex`, `paginatedRoutes`).
- Updated pagination component to use `Number(props.searchParams.page) || 1`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Updated page titles and metadata for improved clarity and consistency, including changes from "On-Ramp" to "Onramp" and a new title for the routes page.
- **Refactor**
  - Simplified the routes list rendering by removing local pagination logic, now displaying data directly as provided without client-side slicing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-23 04:43:14 +00:00
jnsdls 005acce714 Update PostHog import to use React hook (#7135)
# Update PostHog import to use React hook

This PR updates the PostHog implementation in the `useTrack` hook to use the React-specific hook from PostHog instead of the direct import. The changes include:

- Replaced the direct import of `posthog` with `usePostHog` from `posthog-js/react`
- Added the PostHog instance as a dependency to the `useCallback` hook
- Added a null check before calling `posthog.capture()` to handle cases where PostHog might not be initialized

These changes ensure proper React integration with PostHog and prevent potential issues with the hook's dependency array.
2025-05-23 04:34:30 +00:00
jnsdls cde2dd941f Make free plan badge clickable to upgrade (#7134)
# Add Upgrade Paths for Free Plan Teams

This PR enhances the user experience for teams on the free plan by adding clear upgrade paths throughout the dashboard:

- Makes the free plan badge clickable, linking directly to the billing page with pricing modal open
- Adds analytics tracking to billing-related links for better conversion insights
- Implements a new `FreePlanUpsellBannerUI` component on the team dashboard page
- Ensures only one billing alert banner shows at a time (following priority order)
- Updates all `TeamPlanBadge` components to include the team slug parameter
- Improves billing alert banners with proper analytics tracking

These changes aim to increase conversions from free to paid plans by providing contextual upgrade opportunities throughout the product.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a promotional upsell banner for teams on the free plan, encouraging upgrades with a direct link to view available plans.
  - Added a customizable promotional banner component with multiple accent color themes for enhanced marketing displays.

- **Enhancements**
  - Team plan badges now provide direct links to billing settings for teams on the free plan, with analytics tracking for user interactions.
  - Improved analytics tracking on billing-related banners and links throughout the dashboard.

- **Refactor**
  - Updated multiple components to pass team identifiers to plan badges for more contextual display and interaction.
  - Streamlined the display logic for billing-related banners to prioritize service status and plan type.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `TeamPlanBadge` component by adding a `teamSlug` prop across various files and implementing a new `FreePlanUpsellBannerUI` component to encourage free plan users to upgrade. It also updates links to use `TrackedLinkTW` for better tracking.

### Detailed summary
- Added `teamSlug` prop to `TeamPlanBadge` in multiple components.
- Introduced `FreePlanUpsellBannerUI` to promote upgrades for free plan users.
- Replaced `<Link>` with `<TrackedLinkTW>` for better tracking in several instances.
- Updated conditional rendering logic for billing alerts in the team layout.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-23 04:21:27 +00:00
7130f9a929 [Dashboard] siwa chat widget (#7128)
Co-authored-by: Joaquim Verges <[email protected]>
2025-05-22 21:20:45 -07:00
1e0b1422f3 [SDK] Implement allowedSmsCountryCodes option (#7090)
Co-authored-by: Jonas Daniels <[email protected]>
2025-05-22 21:14:29 -07:00
jnsdls 70a519ada4 [Dashboard] migrate authorized wallets components to shadcn (#7131)
## Summary
- migrate authorized wallets revoke modal from Chakra to shadcn dialog
- replace Chakra button/text in AuthorizedWalletsTable with shadcn + tailwind
- drop Chakra provider wrapper from account devices page
- cleanup modal title/description and remove node cache

## Testing
- `pnpm biome check --apply apps/dashboard/src/components/settings/AuthorizedWallets/AuthorizedWalletRevokeModal.tsx .gitignore`
- `pnpm test` *(fails: spawn anvil ENOENT)*

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the `AccountDevicesPage` and the `AuthorizedWalletRevokeModal` components to replace the `Chakra UI` modal with a new `Dialog` component. It also updates the `AuthorizedWalletsTable` to use local state for modal visibility and modifies UI elements for consistency.

### Detailed summary
- Removed `ChakraProviderSetup` from `AccountDevicesPage`.
- Replaced `Modal` components with `Dialog` components in `AuthorizedWalletRevokeModal`.
- Updated modal structure and styling in `AuthorizedWalletRevokeModal`.
- Changed state management for modal visibility in `AuthorizedWalletsTable`.
- Updated text rendering from `Text` to `span` for consistency in `AuthorizedWalletsTable`.
- Changed button styles in `AuthorizedWalletsTable` for revocation actions.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Replaced Chakra UI components with custom UI library components in wallet authorization modals and tables.
  - Updated modal and button styling for a consistent look and feel.
  - Simplified modal state management in the authorized wallets table.

- **Chores**
  - Updated `.gitignore` to exclude dashboard node compilation cache.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-23 03:54:27 +00:00
gregandGitHub 678c00e322 Fix: Update routes page to use server-side pagination and filtering (#7133) 2025-05-22 17:26:36 -07:00
MananTank 5ab59ff400 Dashboard: Add token selector in asset creation wizard (#7132)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the token sale functionality within the application. It introduces new properties, refines existing structures, and improves the token selection process to accommodate native tokens and better handle token sales.

### Detailed summary
- Added `saleTokenAddress` to the form validation schema.
- Updated `CreateTokenAssetPage` to handle native token address logic.
- Changed `TokenSelector` to support selected tokens and improved token address handling.
- Enhanced `TokenSaleSection` to include a currency field for token sales.
- Refined state management in `PaymentLinkForm` for better token handling.
- Modified `TokenInfoFieldset` to trigger updates on chain changes.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-22 23:15:23 +00:00
MananTank 73629e9f0e [TOOL-4531] Dashboard: Add Token Asset creation wizard (#7081)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces several enhancements and features related to asset management, including deployment tracking, UI updates, and new forms for creating and managing tokens. It also refines existing components and adds new functionalities for improved user experience.

### Detailed summary
- Added `deploymentType` and `contractType` properties in various components.
- Introduced `revalidatePathAction` for path revalidation.
- Updated UI components for better asset management.
- Enhanced forms for token creation with validation.
- Implemented multi-step status tracking for token launch processes.
- Improved error handling and user feedback in forms.
- Added new charts for token distribution visualization.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/assets/create/launch/launch-token.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/assets/create/distribution/token-airdrop.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a comprehensive asset management interface for creating, importing, and managing ERC-20 tokens within team and project dashboards.
  - Added a multi-step token creation flow with form validation, including token info, distribution, sale settings, airdrop CSV upload, and launch process with real-time status tracking.
  - Implemented visual token allocation charts and multi-step progress indicators for enhanced user experience.
  - Added a dedicated "Assets" section in the sidebar with a "New" badge.
  - Added a horizontal segmented distribution bar chart component for visualizing token allocations.
  - Introduced a multi-step status component to track asynchronous step executions with retry capabilities.
  - Added token airdrop CSV upload and validation feature with user-friendly UI and error handling.
  - Added token sale configuration section with price and allocation inputs.
  - Enhanced import modal to support both contract and asset imports with contextual UI and behavior.
  - Added new pages and components to support token asset creation and management workflows, including cards for asset actions.
  - Added server-side path revalidation support for pages and layouts.

- **Improvements**
  - Enhanced contract and asset import workflows with clearer UI, context-aware dialogs, and improved contract table variant handling.
  - Updated number input fields to remove browser-native spinners for a cleaner appearance.
  - Improved analytics event tracking for user interactions and deployment steps.
  - Refined UI components for consistent styling and accessibility.
  - Fixed sidebar badge placement and updated UI text for clarity.
  - Updated dialogs and payment embeds to reference transaction chain instead of active wallet chain and set default payment amounts.
  - Changed token supply label to "Circulating Supply" for clarity.
  - Replaced Chakra UI spinner and styled elements with local components for simplified UI.

- **Bug Fixes**
  - Corrected UI typos and improved analytics event tracking for deployment errors.

- **Documentation**
  - Added Storybook stories for new components to facilitate UI testing and documentation.

- **Chores**
  - Refactored and extended types and props to support new asset and contract features, ensuring future extensibility.
  - Added tracking utilities for token deployment and asset creation analytics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-22 23:15:23 +00:00
23029db9ef Feature: Add image support to payment links (#7129)
Co-authored-by: Claude <[email protected]>
2025-05-22 12:47:05 -07:00
jnsdls 526b6b54cf Add useAuthToken hook for in-app wallet authentication (#7120)
# Add `useAuthToken` hook for in-app wallet authentication

This PR adds a new React hook `useAuthToken()` that returns a JWT authentication token for the currently active in-app wallet. This token can be used to authorize API calls to backend servers.

Key changes:
- Added `useAuthToken()` hook that returns the auth token when an in-app wallet is active
- Modified the `InAppConnector` interface to expose the storage property
- Updated the wallet interface to include a `getAuthToken()` method for in-app wallets
- Implemented token retrieval in the in-app wallet core implementation

The hook ensures the token is only returned when the active wallet is an in-app wallet and matches the active account.

Example usage:
```tsx
function MyComponent() {
  const authToken = useAuthToken();

  const fetchData = async () => {
    const response = await fetch('https://api.example.com/data', {
      headers: {
        'Authorization': `Bearer ${authToken}`
      }
    });
    // ... handle response
  };
}
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced a new hook to retrieve authentication tokens for in-app wallets, enabling secure authorization for API calls.
- **Documentation**
	- Added usage examples and clarifications regarding authentication token availability for wallets.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the wallet functionality by introducing an authentication token feature and updating the visibility of certain properties. It adds a method for retrieving an authentication token and modifies the storage property to be public.

### Detailed summary
- Changed `storage` property from `private` to `public` in `native-connector.ts` and `web-connector.ts`.
- Added `getAuthToken` method to the wallet interface in `wallet.ts` with documentation.
- Included `storage` in the `InAppConnector` interface.
- Implemented `authToken` handling in `createInAppWallet` function.
- Created `useAuthToken` hook for retrieving the JWT of the active wallet.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-22 18:01:48 +00:00
Jonas DanielsandGitHub f31116ea5a [SDK] fix avatar image detection on Node (#7123)
Signed-off-by: Jonas Daniels <[email protected]>
2025-05-22 11:00:48 -07:00
arcoraven c1f7aaadc1 chore: redirect /team to /team/~ to select a team (#7124)
## [Dashboard] Fix: Simplify team redirection logic

## Notes for the reviewer
This PR modifies the team redirection logic by:
1. Renaming `getLastVisitedTeamOrDefaultTeam()` to `getLastVisitedTeam()`
2. Removing the fallback to `getDefaultTeam()` when no last visited team is found
3. Updating the team root page to redirect to `/team/~` when no team is found instead of showing a 404

## How to test
Verify that users are properly redirected to their last visited team when available, and to `/team/~` when no team history exists.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
	- Improved handling when no last visited team is found by redirecting users to a fallback page instead of showing a not found error.

- **Refactor**
	- Updated team selection logic to remove fallback to a default team and streamline user redirection.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR refactors the `getLastVisitedTeamOrDefaultTeam` function to `getLastVisitedTeam`, changing its behavior to return `null` instead of a default team. It updates the `TeamRootPage` to handle the absence of a team by redirecting to a fallback URL.

### Detailed summary
- Renamed function `getLastVisitedTeamOrDefaultTeam` to `getLastVisitedTeam`.
- Changed return value of `getLastVisitedTeam` from `getDefaultTeam()` to `null`.
- Updated `TeamRootPage` to redirect to `/team/~` if no team is found, instead of calling `notFound()`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-22 06:42:40 +00:00
arcoraven 1ad109f3bc chore: update endpoint to rotate secret key (#7122)
## [Dashboard] Fix: Update Secret Key Rotation API Endpoint

## Notes for the reviewer

This PR updates the secret key rotation functionality to use the new team-based API endpoint structure. The `rotateSecretKeyClient` function now requires both `teamId` and `projectId` parameters, and the API endpoint has been updated to follow the `/v1/teams/{teamId}/projects/{projectId}/rotate-secret-key` pattern.

## How to test

Test the secret key rotation functionality in:
1. Project FTUX
2. Nebula FTUX
3. Project General Settings page

Verify that the secret key can be successfully rotated in all these locations.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Updated secret key rotation to require both team and project identifiers for enhanced security and context.
  - Modified components to include team ID alongside project data, improving integration and user experience in project-related sections.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the handling of project and team identifiers in the `NebulaFTUX` and related components, enhancing the functionality for rotating secret keys by including the `teamId` alongside the `projectId`.

### Detailed summary
- Added `teamId` prop to the `NebulaFTUX` component.
- Passed `teamId` from `NebulaFTUX` to `SecretKeySection`.
- Updated `rotateSecretKeyClient` to accept an object with `teamId` and `projectId`.
- Modified the API call in `rotateSecretKeyClient` to use the new parameters.
- Updated the `SecretKeySection` to handle the new `teamId` prop.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-22 06:25:22 +00:00
376bdb23d2 [Dashboard] Feature: Setup payment link UI (#7121)
Co-authored-by: Claude <[email protected]>
2025-05-21 22:40:59 -07:00
Joaquim VergesandGitHub dcd6b99e67 [SDK] Improve error handling in PayEmbed (#7119) 2025-05-21 21:52:31 -07:00
Jonas DanielsandGitHub 88533d9113 Remove advancedEnabled field from Account interface (#7118) 2025-05-21 16:16:21 -07:00
Jonas DanielsandGitHub 6c3a8d36a7 Remove unused account usage API and interface (#7117) 2025-05-21 16:09:16 -07:00
Robby UitbeijerseandGitHub a31ac0c065 [Docs] Chore: Remove Kado references (#7109) 2025-05-21 15:21:40 -07:00
7b72e88611 [SDK] Fix: Apply accentButtonText theme key to the Spinner inside of PayEmbed Buttons (#7110)
Co-authored-by: Joaquim Verges <[email protected]>
2025-05-21 15:21:14 -07:00
Joaquim VergesandGitHub 4b9a506fdf [service-utils] Improve 403 error messages with team and project details (#7114) 2025-05-21 14:07:40 -07:00
Joaquim VergesandGitHub 77da352366 [Dashboard] Add ecosystem name editing functionality (#7115) 2025-05-21 13:54:12 -07:00
Joaquim VergesandGitHub 72cc27a69a [Dashboard] Add ecosystem logo upload functionality (#7113) 2025-05-21 11:02:18 -07:00
Joaquim VergesGitHubJonas Danielsgraphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
46d1dab11e [Docs] Add Vault SDK documentation and guides (#7054)
Signed-off-by: Joaquim Verges <[email protected]>
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-21 11:01:56 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
a1c432ca17 Version Packages (#7105)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-21 09:59:10 -07:00
kumaryash90 243c661929 Update deploy config (#7111)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the value of `DEFAULT_FEE_BPS_NEW` in the `addresses.ts` file to reflect a new default fee setting.

### Detailed summary
- Updated the value of `DEFAULT_FEE_BPS_NEW` from `150` to `200` in `apps/dashboard/src/constants/addresses.ts`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Chores**
  - Updated the default fee value from 150 to 200.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-21 16:07:00 +00:00
GiselleNessi b64ffe369a Gi/portal knowledge base (#6993)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on enhancing the `thirdweb Knowledge Base` by adding new troubleshooting guides, updating existing resources, and improving documentation structure for better user support.

### Detailed summary
- Added a new troubleshooting guide for `Nebula`.
- Introduced detailed steps for resolving `device link errors` in `thirdweb CLI`.
- Updated support links in `Header.tsx`.
- Created new sections for `batch upload issues` and `airdrop contract troubleshooting`.
- Improved layout in `layout.tsx` with `DocLayout`.
- Removed unused `ContractsIcon` from `page.tsx`.
- Enhanced the `knowledge base` sidebar structure.
- Added visual guides and images in multiple `mdx` files for clarity.
- Improved metadata for better SEO and documentation clarity.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a comprehensive Knowledge Base with structured navigation, guides, and troubleshooting for web3 development and thirdweb support.
  - Added detailed documentation on deploying NFT mint sites, creating soulbound tokens, sharing console errors, and resolving CLI device link errors.
  - Launched troubleshooting guides for airdrop contracts, batch upload issues, embed feature changes, and Nebula-specific support.

- **Improvements**
  - Updated navigation links to add a Nebula API reference and consolidated support links to internal resources.

- **Removals**
  - Removed the "Contracts" article card from the learning resources section.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-21 10:32:06 +00:00
Joaquim VergesandGitHub 30fafd16db [SDK] Fix SIWE auth forcing unnecessary chain switch (#7104) 2025-05-20 23:28:53 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
99901dcfd7 Version Packages (#7079)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-20 15:48:08 -07:00
jnsdls f5072d882e Rename "Secret Key" to "Webhook Secret" for consistency (#7102)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Style**
  - Updated labels and tooltips to use "Webhook Secret" instead of "Secret Key" for improved clarity in the webhook creation interface.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-20 22:10:27 +00:00
iuwqyir e7f7a32dd4 show block gas limit on the chain page (#7101)
## [Dashboard] Feature: Add Block Gas Limit to Chain Live Stats

## Notes for the reviewer

as requested by @0xFirekeeper

This PR enhances the chain live stats component by adding the block gas limit information. The RPC method has been updated from `eth_blockNumber` to `eth_getBlockByNumber` to fetch additional block data including the gas limit.

## How to test

The block gas limit should now be displayed alongside other chain statistics in the dashboard. The component handles loading and error states appropriately.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added display of the latest block's gas limit in the live stats section, including proper handling of loading, error, and unavailable states.

- **Enhancements**
  - Improved the accuracy of live stats by retrieving and displaying more detailed block information.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the `live-stats.tsx` component to fetch the latest block information using the `eth_getBlockByNumber` method instead of `eth_blockNumber`. It also introduces the display of the block gas limit in the UI.

### Detailed summary
- Changed JSON-RPC method from `eth_blockNumber` to `eth_getBlockByNumber`.
- Updated parameters to fetch the latest block.
- Added parsing for `blockGasLimit` from the response.
- Modified return object to include `blockGasLimit`.
- Introduced a new UI section to display the block gas limit.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-20 21:55:31 +00:00
d2098ad0bf [SDK] Fix stray keccak256 reference (#7089)
Co-authored-by: Jonas Daniels <[email protected]>
2025-05-20 14:54:41 -07:00
+1
Joaquim VergesGitHubgregfromstlClaudekumaryash90Prithvish BaidyasaminaMaximilian Hubertgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Jonas Danielsarcoraven
d6239785cf [SDK] Always show pay modal even on non-supported UB chains (#7092)
Signed-off-by: samina <[email protected]>
Signed-off-by: Maximilian Hubert <[email protected]>
Co-authored-by: gregfromstl <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: kumaryash90 <[email protected]>
Co-authored-by: Prithvish Baidya <[email protected]>
Co-authored-by: samina <[email protected]>
Co-authored-by: Maximilian Hubert <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: arcoraven <[email protected]>
2025-05-20 14:51:58 -07:00
iuwqyir 20fdd5b6d7 show deprecation in playground UI (#7100)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on adding a `deprecated` property to various types and components within the application, allowing for better handling and display of deprecated links and features in the UI.

### Detailed summary
- Added `deprecated` property to `pathInfo` in `navLinks.ts`.
- Updated `MinimalBlueprintSpec` type in `utils.ts` to include `deprecated`.
- Modified `SidebarLink` type in `sidebar.tsx` to optionally include `crossedOut`.
- Enhanced UI to visually indicate deprecated items in `page.tsx`.
- Included `deprecated` status for various API endpoints in `insightBlueprints.ts`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-20 21:04:28 +00:00
iuwqyir 438c019f01 replace chain with chain_id in insight playground (#7099)
## [Playground] Fix: Update chain parameter handling in blueprint playground

## Notes for the reviewer
This PR updates the blueprint playground to use `chain_id` instead of `chain` parameter throughout the codebase. It also adds logic to remove deprecated parameters from the form schema and specifically removes the `chain` parameter if it's present.

## How to test
Test the blueprint playground with various chain selections to ensure the chain_id parameter is correctly populated and that deprecated parameters are not displayed in the form.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Updated all references from "chain" to "chain_id" for improved consistency across the playground interface.
  - Deprecated parameters are now excluded from form generation and display.

- **New Features**
  - Blueprint listings can now indicate if a blueprint is deprecated.

- **Chores**
  - Made the API domain configurable via environment variables with a default fallback.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces changes to the `BlueprintSection`, `fetchBlueprintSpec`, and other components to enhance the handling of blueprint parameters, including the addition of a `deprecated` flag and renaming of parameters for consistency. It also updates the API URL handling.

### Detailed summary
- Added optional `deprecated` property to `blueprints` in `BlueprintSection`.
- Introduced `THIRDWEB_INSIGHT_API_DOMAIN` for dynamic API URL in `fetchBlueprintSpec`.
- Renamed parameter `chain` to `chain_id` in multiple places for consistency.
- Removed `chain` parameter if present in `modifyParametersForPlayground`.
- Updated form handling to use `chain_id` instead of `chain` in `ParameterSection`.
- Skipped deprecated parameters in schema creation with `createParametersFormSchema`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-20 20:46:17 +00:00
Jonas DanielsandGitHub 61044013c8 update dependencies (#7098) 2025-05-20 11:52:26 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e6213db8af chore(deps-dev): bump @storybook/react-vite from 8.6.12 to 8.6.14 (#7088)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 11:24:24 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c46b2597be chore(deps-dev): bump vitest from 3.1.2 to 3.1.4 (#7087)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 11:24:12 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9ff97688ce chore(deps-dev): bump vite from 6.3.4 to 6.3.5 in the npm_and_yarn group (#7068)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 11:20:20 -07:00
gregandGitHub b4f5cc7b5a Fix: Bridge jsdoc to not mention ox (#7097) 2025-05-20 11:15:06 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
256850d1c6 chore(deps): bump @next/mdx from 15.3.1 to 15.3.2 (#7085)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 11:13:10 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
acb2b9f85f chore(deps): bump github/codeql-action from 3.28.17 to 3.28.18 (#7084)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 11:12:54 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6ac62ffe96 chore(deps): bump codecov/codecov-action from 5.4.2 to 5.4.3 (#7083)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 11:12:40 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
80f4bd4e53 chore(deps): bump changesets/action from 1.5.0 to 1.5.3 (#7082)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 11:12:25 -07:00
Joe-Thirdweb a3072c4335 [Dashboard] Adding EDU Chain custom info + assets (#7095)
Adding custom chain info for
EDU Mainnet ( 41923 )
EDU Testnet ( 656476 )

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new assets and metadata for an education-focused blockchain initiative, enhancing the dashboard with information about the `EDU Mainnet` and `EDU Testnet`.

### Detailed summary
- Added new image imports: `eduBanner` and `eduCTA`.
- Introduced metadata for `EDU Mainnet` (ID: 41923) with header image, description, and CTA.
- Introduced metadata for `EDU Testnet` (ID: 656476) with similar structure as `EDU Mainnet`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-20 17:53:29 +00:00
saminacodes 3277cf6e8e [Docs] Fixed broken links (#7091)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on updating references from "backend wallets" to "server wallets" across various files and adding a new `ArticleCardIndex` component in the `page.tsx` file.

### Detailed summary
- In `sidebar.tsx`, updated `href` for the "Overview" link.
- Added a new `ArticleCardIndex` for "Universal Bridge" in `page.tsx`.
- Changed text from "backend wallets" to "server wallets" in `page.mdx`.
- Updated link in `engine-overview.tsx` to point to server wallet documentation.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-20 17:44:56 +00:00
Jake LooandGitHub 77938c4d5c Update page.mdx for thirdweb-mcp (#7096)
Signed-off-by: Jake Loo <[email protected]>
2025-05-20 10:15:57 -07:00
Vasiliy KirochkaandGitHub 3080a9baba fixing code snippets (#7094) 2025-05-20 09:04:18 -07:00
Joaquim VergesandGitHub 6cf92c6b11 Add AGENTS.md with guidelines (#7093) 2025-05-20 00:18:02 -07:00
+1
Joaquim VergesGitHubgregfromstlClaudekumaryash90Prithvish BaidyasaminaMaximilian Hubertgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Jonas Danielsarcoraven
89ccc80d9c Add Bridge.Onramp functions + port old functions (#7076)
Signed-off-by: samina <[email protected]>
Signed-off-by: Maximilian Hubert <[email protected]>
Co-authored-by: gregfromstl <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: kumaryash90 <[email protected]>
Co-authored-by: Prithvish Baidya <[email protected]>
Co-authored-by: samina <[email protected]>
Co-authored-by: Maximilian Hubert <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: arcoraven <[email protected]>
2025-05-19 19:24:32 -07:00
saminacodes 713ebe0a2d [Docs] Update Engine Pricing (#7080)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the FAQ section in the `page.mdx` file regarding pricing calculations for Engine Cloud. It clarifies the types of requests that incur costs and modifies the note about legacy pricing plans.

### Detailed summary
- Updated pricing calculation to include both write and sign requests.
- Changed the cost description for requests to specify both write and sign requests.
- Revised the note on legacy pricing plans to include a link to the pricing page and clarify the fee specifics.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-19 15:51:01 +00:00
saminacodes bfdac10dcc [Docs] UB Migration & Other Small Things (#7075)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on restructuring the `pay` section of the portal by updating links, modifying sidebar navigation, and adding new assets and pages to improve the user experience and documentation clarity.

### Detailed summary
- Added new links to the `links` array for `Bridge`.
- Updated `href` values in `apisLinks` for the Insight API.
- Modified `FooterLinkItem` labels for consistency.
- Created and adjusted multiple `.mdx` pages for the `pay` section.
- Introduced `payRedirects` for improved navigation.
- Updated the sidebar structure for the `pay` section, including new links and icons.
- Added new layout and metadata to `layout.tsx` for better documentation presentation.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-19 04:26:51 +00:00
+1
gregGitHubClaudeJoaquim Vergeskumaryash90Prithvish BaidyasaminaMaximilian Hubertgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Jonas Danielsarcoraven
64964da22e [SDK] Refactor: Update Pay Functions to use UB (#7064)
Signed-off-by: samina <[email protected]>
Signed-off-by: Maximilian Hubert <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
Co-authored-by: kumaryash90 <[email protected]>
Co-authored-by: Prithvish Baidya <[email protected]>
Co-authored-by: samina <[email protected]>
Co-authored-by: Maximilian Hubert <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: arcoraven <[email protected]>
2025-05-19 16:05:15 +12:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
260c2fd370 Version Packages (#7078)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-18 19:12:58 -07:00
Joaquim VergesandGitHub c61e258ec3 [SDK] Expose getInstalledWallets() utility (#7077) 2025-05-18 19:07:42 -07:00
Joaquim VergesandGitHub fdd344184b [Dashboard] Remove taint for THIRDWEB_ENGINE_URL (#7074) 2025-05-17 00:51:54 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
0c853e8709 Version Packages (#7073)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-17 00:36:55 -07:00
arcoraven 7d47b4cde0 chore: Update Accelerate features list (#7053)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on updating the pricing plans and features for the embedded wallets in the dashboard application. It adjusts the plan requirements and modifies the features associated with different plans.

### Detailed summary
- Changed `authRequiredPlan` from `"accelerate"` to `"growth"`.
- Added `"Custom Wallet Branding & Auth"` to the features list.
- Removed `"Custom Wallet Branding & Auth"` from the `TEAM_PLANS` features.
- Increased `$400 Usage Credits` to `$500 Usage Credits` in the `TEAM_PLANS` features.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-17 07:01:00 +00:00
Jonas DanielsandGitHub feae304da2 Prioritize JWT over service API keys in authentication (#7020) 2025-05-16 16:39:45 -07:00
Joaquim VergesandGitHub 8f605d989c [SDK] Update package READMEs with correct naming and documentation links (#7071) 2025-05-16 05:55:22 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
d0c2ac63a9 Version Packages (#7042)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-16 05:06:17 -07:00
Joaquim VergesandGitHub 170f14fb54 [Dashboard] Add pagination to server wallets page (#7070) 2025-05-16 05:04:26 -07:00
Joaquim VergesandGitHub da2a2a0e86 [SDK] Fix buyWithCrypto false not respected when returning from quote (#7069) 2025-05-16 04:57:48 -07:00
saminaandGitHub e62fd3fe40 [Docs] Engine Update (#7052)
Signed-off-by: samina <[email protected]>
2025-05-16 04:09:33 -07:00
Joaquim VergesandGitHub 99a7cce624 [Engine] Add all testnets to test transaction options (#7066) 2025-05-16 04:08:17 -07:00
Joaquim VergesandGitHub 61152dd098 [Engine] Add FAILED status to execution result and update error handling (#7065) 2025-05-16 03:34:27 -07:00
Maximilian HubertandGitHub 05519fcdde [DOCS] fix link in contributing.md (#7063)
Signed-off-by: Maximilian Hubert <[email protected]>
2025-05-16 00:11:57 -07:00
saminaandGitHub 7257747520 [Docs] Engine (#7057)
Signed-off-by: samina <[email protected]>
2025-05-16 00:10:35 -07:00
Prithvish BaidyaandGitHub e71e7d70a6 [Vault] default to engine-cloud vault proxy (#7058) 2025-05-15 16:44:03 -07:00
Joaquim VergesandGitHub 8d47864967 [SDK] Export prepareUserOp and add support for new wallets (#7061) 2025-05-15 16:43:11 -07:00
kumaryash90 4a8adc143f [Docs] Stylus dev workflow (#7060)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces documentation and assets for integrating thirdweb tools into the Stylus contract development workflow, specifically for Rust. It includes setup instructions, publishing and deployment commands, and images to enhance the documentation.

### Detailed summary
- Added new sections in `page.mdx` for Arbitrum Stylus.
- Included prerequisites for Rust and Solidity installations.
- Provided setup commands for creating a Stylus project.
- Added commands for publishing and deploying Stylus contracts.
- Included images (`stylus-publish-page.png`, `stylus-deploy-page.png`) to illustrate the process.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-15 21:30:32 +00:00
MananTank db538473cf [NEB-248] Show native token balances in assets, show popular tokens on top (#7056)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `AssetsSection` component by integrating wallet balance retrieval, modifying how assets are displayed, and implementing new logic for sorting tokens. It also introduces a new constant for native tokens and adjusts rendering based on whether an asset is native.

### Detailed summary
- Added `NATIVE_TOKEN_ADDRESS` and `getWalletBalance` import from `thirdweb`.
- Updated `key` for `AssetItem` to include `chain_id`.
- Introduced `isNativeToken` to conditionally render the `ChainIconClient`.
- Modified the `href` for `Link` based on whether the asset is a native token.
- Added a new set `tokensToShowOnTop` for sorting specific tokens.
- Implemented `nativeBalances` query to fetch wallet balances for various chains.
- Combined `nativeBalances` and `assetsQuery` data for rendering in `AssetsSectionUI`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-15 20:52:49 +00:00
Manan TankandGitHub 2057546934 Nebula: Recent Activity improvements in sidebar (#7055) 2025-05-15 13:52:15 -07:00
gregandGitHub ba52e68eab [Dashboard] Fix: Trend markers and UB volume type dropdown (#7059) 2025-05-15 12:05:40 -07:00
0eff64824b [Dashboard] Feature: Add Universal Bridge to analytics dashboard (#7051)
Co-authored-by: Claude <[email protected]>
2025-05-14 18:07:07 -07:00
saminacodes ae7407dca4 [Docs] engine and ub changes (#7049)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the documentation and user guidance for the `thirdweb` platform, specifically around the Engine v3 features, onramp providers, and pricing details. It introduces new FAQs, clarifies migration steps, and adds visual content.

### Detailed summary
- Added new section for "Onramp Providers" in `sidebar.tsx`.
- Updated Engine FAQs in `page.mdx` with pricing and transaction details.
- Revised migration guide in `page.mdx` with clearer instructions and a callout for wallet migration.
- Enhanced `faqs/page.mdx` with additional questions about Universal Bridge and supported payment methods.
- Updated "Buy With Fiat" availability to 160+ countries.
- Included images for creating a vault and sending transactions in the `get-started/page.mdx`.
- Revised onramp providers section to clarify provider preferences and fees in `onramp-providers/page.mdx`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-14 22:53:11 +00:00
Joe-Thirdweb a443b309dc Adding NeoX custom chain info. (#7045)
Adding Custom chain info to

- NeoX Mainnet 47763
- NeoX Testnt 12227332

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new assets and metadata for the `Neo X` blockchain, enhancing the dashboard with images and information about its features and offerings.

### Detailed summary
- Added imports for `NeoXBanner` and `NeoXCTA` images.
- Introduced metadata for `Neo X` (ID: 47763) including:
  - `headerImgUrl`
  - `about` description
  - `cta` object with `backgroundImageUrl`, `title`, `buttonLink`, and `buttonText`.
- Duplicated metadata for `Neo X Testnet` (ID: 12227332) with similar structure.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-14 22:44:28 +00:00
Joaquim VergesGitHubgraphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
ae2ff743c0 [Engine] Update OpenAPI spec and configuration (#7050)
Signed-off-by: Joaquim Verges <[email protected]>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-14 15:43:30 -07:00
MananTank dfa8603593 [NEB-213] Nebula: Show wallet Assets and Activity in sidebar (#7046)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on enhancing the `AssetsSection` and `TransactionsSection` components by adding new features and improving the UI. It also modifies several existing components for better functionality and user experience.

### Detailed summary
- Changed `mergedSessions.push(session)` to `mergedSessions.unshift(session)` in `useSessionsWithLocalOverrides.ts`.
- Updated the width of the sidebar in `ChatPageLayout.tsx` from `280px` to `300px`.
- Added `customDetailsButton` prop to `NebulaConnectWallet` in `NebulaConnectButton.tsx`.
- Implemented conditional rendering for `customDetailsButton` in `NebulaConnectWallet`.
- Created storybook stories for `AssetsSection` and `TransactionsSection` with stubs for tokens and transactions.
- Enhanced `AssetsSection` and `TransactionsSection` components to handle loading states and display messages when there are no assets or transactions.
- Introduced `WalletDetails` component in `ChatSidebar` to manage assets and transactions tabs.
- Refactored `WalletSelector` to integrate `useActiveWallet` for better wallet management and user feedback.
- Improved button components with tooltips for better accessibility and UX.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-14 21:47:42 +00:00
saminacodes 9f1ca54c26 Update page.mdx (#7048)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the `preferredProvider` options in the `detailsModal` configuration for the `connectbutton` component, enhancing payment provider flexibility.

### Detailed summary
- Modified `preferredProvider` options in `detailsModal`:
  - Removed `"KADO"`
  - Added `"COINBASE"`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-14 17:44:23 +00:00
MananTank 7df81b7323 [TOOL-4493] Dashboard: Add starter legacy discontinuation banner (#7047)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a `StarterLegacyDiscontinuedBanner` component to the `RootTeamLayout` in `layout.tsx`. It displays a notification for users on the "starter_legacy" billing plan, informing them of the discontinuation and providing a link to select a new plan.

### Detailed summary
- Added a conditional rendering for `StarterLegacyDiscontinuedBanner` based on the team's billing plan.
- Created the `StarterLegacyDiscontinuedBanner` component to inform users about the discontinuation of starter legacy plans.
- Included a button with a link to the billing settings for selecting a new plan, featuring an `ArrowRightIcon`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-14 16:25:35 +00:00
GiselleNessi ab74fcdaf8 add new apis tab to nav (#7044)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds a new section to the `Header` component in the `Header.tsx` file, introducing `apisLinks` for API documentation links and integrating a `DropdownLinks` component for displaying these links.

### Detailed summary
- Added a new constant `apisLinks` containing links to API documentation.
- Introduced a `DropdownLinks` component to display `apisLinks` in the header.
- Adjusted padding in the header's layout from `xl:px-2` to `xl:px-1` for the new dropdown.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-14 15:09:12 +00:00
Joaquim VergesandGitHub 1191d5dad9 [Engine] Add EOA read/create permissions for server wallets (#7043) 2025-05-14 22:32:35 +12:00
Joaquim VergesandGitHub 20b5ba943a [SDK] Propagate 401 errors when connecting in-app wallet (#7040) 2025-05-14 02:03:41 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f0f8da8fab Version Packages (#7032)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-13 19:50:56 -07:00
Joaquim VergesandGitHub da3fc99c0b [SDK] Default to in-memory storage for inAppWallet outside browser (#7038) 2025-05-13 18:32:37 -07:00
fb4c190eb3 [Dashboard] Feature: Default analytics range to 30 days and save preference (#7039)
Co-authored-by: Claude <[email protected]>
2025-05-13 15:25:09 -07:00
FirekeeperandGitHub 1c31a1e4df [Portal] .NET ThirdwebBridge Onramping & InAppWallet 7702 Account Abstraction (#7017) 2025-05-13 14:14:45 -07:00
MananTank 04c10f82fd [TOOL-4471] GatedSwitch updates, Make In-app wallet branding gated by starter+ (#7021)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the billing and plan management features in the dashboard. It introduces new props for handling plan highlights and open plan sheets, modifies tooltip styles, and adjusts billing card components to improve user experience.

### Detailed summary
- Updated `tooltip.tsx` to change padding in tooltip content.
- Changed `requiredPlan` from `authRequiredPlan` to `brandingRequiredPlan` in `Configure/index.tsx`.
- Added `openPlanSheetButtonByDefault` and `highlightPlan` props in various components.
- Modified `PlanInfoCardClient` and `Billing` components to utilize new props.
- Updated `TeamPlanBadge` to include an optional postfix.
- Enhanced `BillingPricing` logic to handle plan highlights.
- Adjusted `GatedSwitch` tooltip content for better user guidance.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-13 20:22:10 +00:00
MananTank ab173886ac [TOOL-4483] Move changelog to team landing page from notifications (#7037)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring notification handling in the application, removing unused functions, and enhancing the UI components related to notifications and alerts.

### Detailed summary
- Removed `getChangelogNotifications` and related props from several components.
- Introduced `DismissibleAlert` component for better alert management.
- Updated `NotificationButtonUI` to streamline notification handling.
- Adjusted UI elements for consistency and clarity.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-13 19:19:55 +00:00
Joe-Thirdweb df06bec699 [Dashboard]Adding zkCandy + Onyx chain updates (#7035)
Adding custom chain info to

- Onyx Mainnet
- zkCandy Mainnet

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new assets and updates the `chainMetaRecord` object to include information for the `Onyx` and `zkCandy` blockchains, enhancing the dashboard with additional details and visuals.

### Detailed summary
- Added imports for `onyxBanner`, `onyxCTA`, and `zkCandyBanner` images.
- Introduced `Onyx` blockchain entry in `chainMetaRecord` with header image, description, and CTA.
- Added `zkCandy` blockchain entry in `chainMetaRecord` with header image, description, and CTA.
- Updated comment for `zkCandy` entry to specify it as "zkCandy Testnet".

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-13 15:42:08 +00:00
Joaquim VergesandGitHub 6000dacfbb [Docs] Add Engine v2 to v3 migration guide (#7036) 2025-05-13 04:12:27 -07:00
Joaquim VergesandGitHub fc6535069b [SDK] Preload wallet balances on pay embed (#7034) 2025-05-13 17:08:26 +12:00
Joaquim VergesandGitHub b15c3c0747 Add contract availability documentation to ERC methods (#7033) 2025-05-12 17:47:28 -07:00
Joaquim VergesandGitHub 1985de89aa [SDK] Expose inMemoryStorage for inAppWallet backend usage (#7016) 2025-05-12 16:04:22 -07:00
MananTank 2b87b7bb75 [NEB-246] Nebula: handle aborted session with no messages (#7025)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `ChatSidebar`, `useSessionsWithLocalOverrides`, and `ChatPageContent` components. It improves session handling, updates the chat sidebar title, and adds a user-friendly message for sessions without messages.

### Detailed summary
- Updated `title` prop in `<ChatSidebarLink>` to default to "Untitled Chat".
- Merged new sessions with existing ones in `useSessionsWithLocalOverrides`.
- Introduced a message and icon for sessions with no messages in `ChatPageContent`.
- Conditional rendering of `<Chats>` based on message availability.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-12 20:54:35 +00:00
MananTank d99a985ab4 [NEB-238] Show ENS name in selected account badge (#7024)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the rendering of the `AccountName` component in the `ChatBar.tsx` file to improve the styling and structure of the address display.

### Detailed summary
- Replaced `span` with `AccountName` component for displaying the selected address.
- Added `className="text-xs"` to the `AccountName` component.
- Updated `loadingComponent` and `fallbackComponent` to use a consistent `span` with `className="text-xs"`.
- Changed `className` of `span` in `WalletSelector` from `font-mono text-sm` to `text-sm` for a consistent style.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-12 20:54:35 +00:00
MananTank e74574ccf5 [NEB-239] Fix chain search not working properly for chain name starting with number (#7023)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the code by replacing `Number.parseInt` with `Number` for checking if a value is an integer across multiple files, enhancing readability and consistency.

### Detailed summary
- In `TokenSelector.tsx`, changed `Number.parseInt(searchValue)` to `Number(searchValue)`.
- In `single-network-selector.tsx`, modified the same integer check.
- In `ConfigureCustomChain.tsx`, updated integer check for `chainSlug`.
- In `NetworkSelectors.tsx`, replaced the integer check similarly.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-12 20:54:35 +00:00
MananTank 98d05d6fb6 [NEB-247] Show error event in chat UI (#7022)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces error handling for chat responses in the `ChatPageContent` and `chat.ts` files. It adds functionality to display error messages when an error event is received.

### Detailed summary
- Added handling for `"error"` case in `ChatPageContent.tsx` to update messages with error details.
- Implemented parsing of error data in `chat.ts` to extract error code and message.
- Updated `ChatStreamedEvent` type to include an `"error"` event with relevant data structure.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-13 02:09:03 +05:30
MananTank f4ac3c2a3a [NEB-245] Limit image format types for upload (#7019)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `ImageUploadButton` component by allowing it to accept specific image file types and updating the `ChatBar` component to utilize this feature.

### Detailed summary
- In `ChatBar.tsx`, added `accept` attribute with value `"image/jpeg,image/png,image/webp"` to the file input.
- In `image-upload-button.tsx`, changed `accept` property from a static value `"image/*"` to a dynamic value using `props.accept`.
- Updated `ImageUploadButton` to include `accept` in its props definition.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-12 20:26:53 +00:00
MananTank a3a4bf4361 Nebula: Fix User Image message type (#7018)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the image handling in the `types.ts` and related components by modifying the structure of image objects, allowing for optional `image_url` and `b64` properties.

### Detailed summary
- In `types.ts`, changed the `image` type to include `image_url: string | null` and `b64: string | null`.
- In `ChatBar.tsx`, added `image_url: null` when pushing the image object to `userMessage.content`.
- In `Chats.tsx`, updated the condition to check if `msg.b64` is a string and used `msg.image_url ?? ""` for the URL.
- In `NebulaImage.tsx`, adjusted the skeleton div to have a minimum height of 300px.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-12 17:15:52 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
e8ec54e6e2 Version Packages (#7014)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-12 01:43:18 -07:00
Joaquim VergesandGitHub 58e343c10c [SDK] Improve EIP-5792 wallet capabilities support (#7003) 2025-05-12 01:25:30 -07:00
Prithvish BaidyaandGitHub bad9db9760 postfix slash to engine url (#7013) 2025-05-12 00:41:45 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
6a1e54ec69 Version Packages (#7010)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-11 16:16:33 -07:00
7ddedfbed1 Fix: Catch siwe doLogin errors (#7012)
Co-authored-by: Claude <[email protected]>
2025-05-12 10:45:32 +12:00
98eda9dd3d Fix: Auto-signin to autoconnect for all cases (#7011)
Co-authored-by: Claude <[email protected]>
2025-05-11 14:41:47 -07:00
5a13ad4521 [SDK] Fix: Automatically trigger login for connected wallets whe… (#7009)
Co-authored-by: Claude <[email protected]>
2025-05-11 14:02:23 -07:00
gregGitHubgraphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
557a29a960 [Dashboard] Fix: Copying payment link on iOS (#7008)
Signed-off-by: greg <[email protected]>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-11 08:03:07 -07:00
gregandGitHub ff565cb0f8 Fix: Trend badge formatting (#7007) 2025-05-10 10:40:23 -07:00
gregfromstl 816aa6137b [Dashboard] Fix: Display latest value in PayNewCustomers component (#7002)
🤖 Generated with [Claude Code](https://claude.ai/code)

<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on improving the logic used to display the total value in the `SkeletonContainer` of the `PayNewCustomers` component. It ensures that when `graphData` is empty, it returns "NA", and otherwise, it retrieves the last value or defaults to 0.

### Detailed summary
- Updated the logic in `SkeletonContainer`:
  - Changed the expression to return `graphData[graphData.length - 1]?.value || 0` when `graphData` is not empty.
  - Maintained the output of "NA" when `isEmpty` is true.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-10 17:10:32 +00:00
MananTank 7de273c59d Dashboard: Reset last-used-* cookies on login page (#7005)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on adding a `deleteCookie` function to manage cookies and incorporates a new component, `ResetLastUsedCookies`, in the `LoginPage` to clear specific cookies upon rendering.

### Detailed summary
- Added `deleteCookie` function in `cookie.ts` to delete cookies by name.
- Imported constants `LAST_USED_PROJECT_ID`, `LAST_USED_TEAM_ID`, and `LAST_VISITED_TEAM_PAGE_PATH` in `LoginPage.tsx`.
- Implemented `ResetLastUsedCookies` component to delete specific cookies on mount.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-10 13:18:36 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
95cc441edd Version Packages (#7006)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-10 06:18:04 -07:00
Joaquim VergesandGitHub 6f1d4b1fcf [SDK] Handle tx with value on engineAccount (#7004) 2025-05-10 06:13:54 -07:00
7ede361870 Fix testnet filtering in NetworkSelectors (#7001)
Co-authored-by: Claude <[email protected]>
2025-05-09 23:05:51 -07:00
8b50779229 [Dashboard] Feature: Disable testnets in Payment Link form (#7000)
Co-authored-by: Claude <[email protected]>
2025-05-09 22:51:41 -07:00
gregGitHubClaudegraphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
282d9f2c39 [Dashboard] Fix: Use x-client-id + x-team-id for all UB requests (#6999)
Signed-off-by: greg <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-09 22:17:01 -07:00
jnsdls e739de158d Only set secret key or client ID when not using auth token (#6994)
This PR modifies the header setting logic in `getClientFetch` to ensure that secret keys and client IDs are only set when not using an auth token. This prevents potential conflicts between different authentication methods by making the header setting logic mutually exclusive.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR modifies the logic in the `fetch.ts` file to ensure that the `x-secret-key` and `x-client-id` headers are only set when not using the authentication token, enhancing security and clarity in header management.

### Detailed summary
- Added an `else` clause to only set `x-secret-key` if not using the auth token.
- Rearranged the condition for setting `x-client-id` to follow the new logic.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-10 02:54:20 +00:00
gregandGitHub 451594c80c Feature: Migrate Universal Bridge Analytics (#6988) 2025-05-09 18:16:43 -07:00
gregfromstl 9634e49113 Feature: Add client id to checkout form url (#6997)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `CheckoutLinkForm` component by passing a `client` prop to it and improving its functionality. It also refines the styling and class management using the `cn` utility for better readability and maintainability.

### Detailed summary
- Modified `page.tsx` to create a `client` using `getClientThirdwebClient` and pass it to `CheckoutLinkForm`.
- Changed the `CheckoutLinkForm` function signature to accept a `client` prop.
- Updated multiple instances in `CheckoutLinkForm` to utilize `client.clientId`.
- Replaced static class names with the `cn` utility for dynamic class management in the button and div elements.
- Adjusted the conditional rendering of classes for better readability and performance in the component.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-10 01:05:38 +00:00
gregandGitHub a4e12c5516 Fix: Auth method for checkout page api requests to ub service (#6996) 2025-05-09 17:29:49 -07:00
MananTank 2297915633 [NEB-210] Nebula: Add image upload (#6995)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces image upload functionality within the chat application, enhancing user interaction by allowing users to send images along with text messages. It also includes updates to various components to support this feature.

### Detailed summary
- Added `allowImageUpload` prop to multiple components.
- Introduced `ImageUploadButton` for image selection.
- Modified `ChatBar` to handle image uploads and message composition.
- Updated `FloatingChatContentLoggedIn`, `ChatPageContent`, and `EmptyStateChatPageContent` to accommodate new props.
- Enhanced `NebulaImage` for displaying uploaded images.
- Implemented image preview and removal functionality in `ChatBar`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-09 23:19:08 +00:00
gregandGitHub 585ba2f575 Feature: Checkout link creation page (#6989) 2025-05-09 13:51:26 -07:00
MananTank a68ec74d25 Dashboard: Use React taint API for envs, envs cleanup (#6992)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refactoring the environment variables used throughout the application, shifting from a centralized `env` file to a more modular `public-envs` and `server-envs` structure. This enhances organization and clarity regarding environment variable usage.

### Detailed summary
- Deleted `env.ts` and introduced `public-envs.ts` and `server-envs.ts`.
- Updated imports across various files to use new environment variable structure.
- Replaced direct `process.env` calls with imported constants.
- Added `taint` feature for sensitive server environment variables.
- Adjusted fetch URLs to use `NEXT_PUBLIC_THIRDWEB_API_HOST` instead of `API_SERVER_URL`.

> The following files were skipped due to too many changes: `apps/dashboard/src/@/constants/server-envs.ts`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageMetadata.ts`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/getContractPageMetadataSetup.ts`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-09 17:12:44 +00:00
MananTank 7ae3a81a11 [NEB-241] Nebula: /chat endpoint schema update related changes (#6982)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the message structure in the chat components to utilize a new `NebulaUserMessage` type, enhancing the way messages are handled and displayed throughout the application.

### Detailed summary
- Updated message structure in `Chats` and `ChatBar` to use `NebulaUserMessage`.
- Changed message content handling in `FloatingChatContent` and `ChatPageContent`.
- Modified `NebulaImage` component to accept new props.
- Adjusted `EmptyStateChatPageContent` and `Chats` to support new message format.
- Enhanced message rendering logic in `RenderMessage` and `RenderResponse` components.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-09 17:03:50 +00:00
MananTank ccb9cd5d0a Dashboard: Fix team links in header on pages outside /team layout (#6981)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR modifies the `href` attribute of the `Link` component in the `TeamSelectionUI.tsx` file to ensure that the URL path is correctly constructed when navigating between team pages.

### Detailed summary
- Updated the `href` logic to check if `pathname` starts with `/team`.
- Changed the `replace` method to use the full path for `currentTeam.slug` and `team.slug`.
- Ensured that the fallback URL for the `Link` defaults to `/team/${team.slug}` when conditions are not met.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-09 11:33:34 +00:00
MananTank 3dc11c087a Dashboard: Contract pages cleanup (#6991)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on refactoring the contract-related code to consistently use `clientContract` and `serverContract` variables instead of `contract`. This improves clarity and ensures that the correct contract instance is used in various components.

### Detailed summary
- Replaced instances of `info.contract` with `info.clientContract` or `info.serverContract` across multiple files.
- Updated conditions checking for localhost to use `info.isLocalhostChain`.
- Modified return statements in several components to use the appropriate contract variable.
- Adjusted metadata fetching and handling to align with the new contract structure.
- Removed `ContractAnalyticsPageClient` usage in favor of direct `ContractAnalyticsPage` with `clientContract`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-05-09 11:18:35 +00:00
Joaquim VergesandGitHub 49479d40b0 [Dashboard] Replace serverThirdwebClient with getUserThirdwebClient (#6990) 2025-05-09 00:23:29 -07:00
Joaquim VergesGitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3d7c2d4f67 [Auth] Replace mainnet with ANVIL_CHAIN in verifyTypedData tests (#6985)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-08 22:01:46 -07:00
Manan TankandGitHub 5b40f76b4d Dashboard: thirdweb client refactor (#6987) 2025-05-09 03:15:41 +02:00
1136 changed files with 46982 additions and 25659 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ We use [Turborepo](https://turbo.build/repo/docs) to manage the repository, and
We use [pnpm](https://pnpm.io) for package management across the repo. `pnpm` is similar to `npm` or `yarn` but with more efficient disk space usage.
**With the v5 SDK, we've consolidated everything into a single project at [/packages/thirdweb](./packages/thirdweb). You can still find the legacy packages at [/legacy_packages](./legacy_packages).**
**With the v5 SDK, we've consolidated everything into a single project at [/packages/thirdweb](../packages/thirdweb). You can still find the legacy packages at [/legacy_packages](../legacy_packages).**
This single package provides a performant & lightweight SDK to interact with any EVM chain across Node, React, and React Native. Learn more about how to use the thirdweb SDK in our [documentation](https://portal.thirdweb.com/typescript/v5).
+1 -1
View File
@@ -89,7 +89,7 @@ jobs:
- run: pnpm test
- name: Code Coverage
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
with:
directory: packages/
flags: packages
+3 -3
View File
@@ -46,7 +46,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -59,7 +59,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -72,4 +72,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
- name: Create release Pull Request or publish to NPM
id: changesets
uses: changesets/action@746c25e23caa47dceb6a48ee85b4cbc5a9f5f293 #v1.4.0
uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba #v1.4.0
with:
publish: pnpm release
version: pnpm version-packages
+2 -1
View File
@@ -31,4 +31,5 @@ storybook-static
.aider*
tsconfig.tsbuildinfo
.cursor
.cursor
apps/dashboard/node-compile-cache
+123
View File
@@ -0,0 +1,123 @@
🤖 Codex Agent Guidelines for thirdweb-dev/js
Welcome, AI copilots! This guide captures the coding standards, architectural decisions, and workflow conventions that every automated agent (and human contributor!) must follow. Unless a rule explicitly targets a sub‑project, it applies repo‑wide.
⸻
1. GitHub Workflow & Etiquette
- Pull‑request titles must start with the affected workspace in brackets (e.g. [SDK], [Dashboard], [Portal], [Playground]).
- Begin the PR description with a one‑sentence summary, then add a checklist of changes and reference issues with Fixes #123.
- Keep commits small and topical – one logical change per commit.
- Branch names should follow area/brief-topic (e.g. sdk/fix-gas-estimate). Avoid personal names.
- Request at least one core maintainer review. Do not self‑merge unless you are the sole owner of that package.
- All CI checks (type‑check, Biome, tests) must pass before merging.
⸻
2. Formatting & Linting
- Biome governs formatting and linting; its rules live in biome.json.
- Run pnpm biome check --apply before committing.
- Avoid editor‑specific configs; rely on the shared settings.
⸻
3. TypeScript Style Guide
- Write idiomatic TypeScript: explicit function declarations and return types.
- Limit each file to one stateless, single‑responsibility function for clarity and testability.
- Re‑use shared types from @/types or local types.ts barrels.
- Prefer type aliases over interface except for nominal shapes.
- Avoid any and unknown unless unavoidable; narrow generics whenever possible.
- Choose composition over inheritance; leverage utility types (Partial, Pick, etc.).
⸻
4. Testing Strategy
- Co‑locate tests: foo.ts ↔ foo.test.ts.
- Use real function invocations with stub data; avoid brittle mocks.
- For network interactions, use Mock Service Worker (MSW) to intercept fetch/HTTP calls, mocking only scenarios that are hard to reproduce.
- Keep tests deterministic and side‑effect free; Jest is pre‑configured.
⸻
5. packages/thirdweb
5.1 Public API Surface
- Export everything via the exports/ directory, grouped by feature.
- Every public symbol must have comprehensive TSDoc:
- Include at least one @example block that compiles.
- Tag with one custom annotation (@beta, @internal, @experimental, etc.).
- Comment only ambiguous logic; avoid restating TypeScript in prose.
5.2 Performance
- Lazy‑load heavy dependencies inside async paths to keep the initial bundle lean:
`const { jsPDF } = await import("jspdf");`
⸻
6. apps/dashboard & apps/playground
6.1 Core UI Toolkit
- Import primitives from @/components/ui/\_ (e.g. Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator).
- Use NavLink for internal navigation so active states are handled automatically.
- Group feature‑specific components under feature/components/\_ and expose a barrel index.ts when necessary.
6.2 Styling Conventions
- Tailwind CSS is the styling system – no inline styles or CSS modules.
- Merge class names with cn() from @/lib/utils to keep conditional logic readable.
- Stick to design tokens: backgrounds (bg-card), borders (border-border), muted text (text-muted-foreground), etc.
- Expose a className prop on the root element of every component for overrides.
6.3 Component Patterns
- Server Components (run on the Node edge):
-- Read cookies/headers with next/headers.
-- Access server‑only environment variables or secrets.
-- Perform heavy data fetching that should not ship to the client.
-- Implement redirect logic with redirect() from next/navigation.
-- Start files with import "server-only"; to prevent client bundling.
- Client Components (run in the browser):
-- Begin files with 'use client'; before imports.
-- Handle interactive UI relying on React hooks (useState, useEffect, React Query, wallet hooks).
-- Access browser APIs (localStorage, window, IntersectionObserver, etc.).
-- Support fast transitions where data is prefetched on the client.
6.4 Data Fetching Guidelines
- Server Side
-- Always call getAuthToken() to retrieve the JWT from cookies.
-- Inject the token as an Authorization: Bearer header – never embed it in the URL.
-- Return typed results (Project[], User[], …) – avoid any.
- Client Side
-- Wrap calls in React Query (@tanstack/react-query).
-- Use descriptive, stable queryKeys for cache hits.
-- Configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s).
-- Keep tokens secret by calling internal API routes or server actions.
⸻
7. Performance & Bundle Size
- Track bundle budgets via package.json#size-limit.
- Lazy‑import optional features; avoid top‑level side‑effects.
- De‑duplicate dependencies across packages through pnpm workspace hoisting.
⸻
8. Documentation & Developer Experience
- Each change in packages/\* should contain a changeset for the appropriate package, with the appropriate version bump
- patch for changes that don't impact the public API
- minor for any new/modified public API
- Surface breaking changes prominently in PR descriptions.
- For new UI components, add Storybook stories (\*.stories.tsx) alongside the code.
+1 -4
View File
@@ -27,10 +27,7 @@ module.exports = [
// -- engine
"/engine",
// -- solutions pages --
"/solutions/gaming",
"/solutions/consumer-apps",
"/solutions/defi",
"/solutions/ecosystem",
"/solutions/:solution_slug",
// -- campaigns --
// "/unlimited-wallets", -- OFF for now
// -- TPP --
+1
View File
@@ -123,6 +123,7 @@ const baseNextConfig: NextConfig = {
webpackBuildWorker: true,
webpackMemoryOptimizations: true,
serverSourceMaps: false,
taint: true,
},
serverExternalPackages: ["pino-pretty"],
async headers() {
+22 -22
View File
@@ -40,8 +40,8 @@
"@radix-ui/react-popover": "^1.1.10",
"@radix-ui/react-progress": "^1.1.4",
"@radix-ui/react-radio-group": "^1.3.3",
"@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-separator": "^1.1.4",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-switch": "^1.2.2",
"@radix-ui/react-tooltip": "1.2.3",
@@ -71,13 +71,13 @@
"ioredis": "^5.6.1",
"ipaddr.js": "^2.2.0",
"lucide-react": "0.503.0",
"next": "15.3.1",
"next": "15.3.2",
"next-plausible": "^3.12.4",
"next-themes": "^0.4.6",
"nextjs-toploader": "^1.6.12",
"nuqs": "^2.4.3",
"p-limit": "^6.2.0",
"papaparse": "^5.5.2",
"papaparse": "^5.5.3",
"pluralize": "^8.0.0",
"posthog-js": "1.67.1",
"prettier": "3.5.3",
@@ -89,7 +89,7 @@
"react-dropzone": "^14.3.8",
"react-error-boundary": "^5.0.0",
"react-hook-form": "7.55.0",
"react-markdown": "^9.0.1",
"react-markdown": "10.1.0",
"react-table": "^7.8.0",
"recharts": "2.15.3",
"remark-gfm": "4.0.1",
@@ -105,30 +105,30 @@
"thirdweb": "workspace:*",
"tiny-invariant": "^1.3.3",
"use-debounce": "^10.0.4",
"zod": "3.24.3"
"zod": "3.25.24"
},
"devDependencies": {
"@chakra-ui/cli": "^2.4.1",
"@chromatic-com/storybook": "3.2.6",
"@next/bundle-analyzer": "15.3.1",
"@next/eslint-plugin-next": "15.3.1",
"@next/bundle-analyzer": "15.3.2",
"@next/eslint-plugin-next": "15.3.2",
"@playwright/test": "1.52.0",
"@storybook/addon-essentials": "8.6.12",
"@storybook/addon-interactions": "8.6.12",
"@storybook/addon-links": "8.6.12",
"@storybook/addon-onboarding": "8.6.12",
"@storybook/addon-viewport": "8.6.12",
"@storybook/blocks": "8.6.12",
"@storybook/nextjs": "8.6.12",
"@storybook/react": "8.6.12",
"@storybook/test": "8.6.12",
"@storybook/addon-essentials": "8.6.14",
"@storybook/addon-interactions": "8.6.14",
"@storybook/addon-links": "8.6.14",
"@storybook/addon-onboarding": "8.6.14",
"@storybook/addon-viewport": "8.6.14",
"@storybook/blocks": "8.6.14",
"@storybook/nextjs": "8.6.14",
"@storybook/react": "8.6.14",
"@storybook/test": "8.6.14",
"@types/color": "4.2.0",
"@types/node": "22.14.1",
"@types/papaparse": "^5.3.15",
"@types/papaparse": "^5.3.16",
"@types/pluralize": "^0.0.33",
"@types/qrcode": "^1.5.5",
"@types/react": "19.1.2",
"@types/react-dom": "19.1.2",
"@types/react": "19.1.4",
"@types/react-dom": "19.1.5",
"@types/react-table": "^7.7.20",
"@types/spdx-correct": "^3.1.3",
"@types/swagger-ui-react": "^5.18.0",
@@ -140,10 +140,10 @@
"eslint-config-biome": "1.9.4",
"eslint-plugin-react-compiler": "19.0.0-beta-e993439-20250405",
"eslint-plugin-storybook": "0.12.0",
"knip": "5.50.5",
"knip": "5.56.0",
"next-sitemap": "^4.2.3",
"postcss": "8.5.3",
"storybook": "8.6.12",
"storybook": "8.6.14",
"tailwindcss": "3.4.17",
"typescript": "5.8.3"
}
+2 -2
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function acceptInvite(options: {
teamId: string;
@@ -17,7 +17,7 @@ export async function acceptInvite(options: {
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${options.teamId}/invites/${options.inviteId}/accept`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/invites/${options.inviteId}/accept`,
{
method: "POST",
headers: {
+2 -2
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function reSubscribePlan(options: {
teamId: string;
@@ -14,7 +14,7 @@ export async function reSubscribePlan(options: {
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${options.teamId}/checkout/resubscribe-plan`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/checkout/resubscribe-plan`,
{
method: "PUT",
headers: {
+13 -10
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function confirmEmailWithOTP(otp: string) {
const token = await getAuthToken();
@@ -12,16 +12,19 @@ export async function confirmEmailWithOTP(otp: string) {
};
}
const res = await fetch(`${API_SERVER_URL}/v1/account/confirmEmail`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/account/confirmEmail`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
confirmationToken: otp,
}),
},
body: JSON.stringify({
confirmationToken: otp,
}),
});
);
if (!res.ok) {
const json = await res.json();
@@ -2,7 +2,8 @@
import { defineDashboardChain } from "lib/defineDashboardChain";
import { ZERO_ADDRESS, isAddress, toTokens } from "thirdweb";
import { getWalletBalance } from "thirdweb/wallets";
import { getUserThirdwebClient } from "../../app/(app)/api/lib/getAuthToken";
import { MORALIS_API_KEY } from "../constants/server-envs";
import { serverThirdwebClient } from "../constants/thirdweb-client.server";
type BalanceQueryResponse = Array<{
balance: string;
@@ -23,7 +24,6 @@ export async function getTokenBalancesFromMoralis(params: {
error: string;
}
> {
const client = await getUserThirdwebClient();
const { contractAddress, chainId } = params;
if (!isAddress(contractAddress)) {
@@ -39,7 +39,7 @@ export async function getTokenBalancesFromMoralis(params: {
const balance = await getWalletBalance({
address: contractAddress,
chain,
client,
client: serverThirdwebClient,
});
return [
{
@@ -61,7 +61,7 @@ export async function getTokenBalancesFromMoralis(params: {
const resp = await fetch(tokenBalanceEndpoint, {
method: "GET",
headers: {
"x-api-key": process.env.MORALIS_API_KEY || "",
"x-api-key": MORALIS_API_KEY,
},
});
@@ -12,7 +12,8 @@ import type { WalletNFT } from "lib/wallet/nfts/types";
import { getVercelEnv } from "../../lib/vercel-utils";
import { isAlchemySupported } from "../../lib/wallet/nfts/isAlchemySupported";
import { isMoralisSupported } from "../../lib/wallet/nfts/isMoralisSupported";
import { DASHBOARD_THIRDWEB_CLIENT_ID } from "../constants/env";
import { NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID } from "../constants/public-envs";
import { MORALIS_API_KEY } from "../constants/server-envs";
type WalletNFTApiReturn =
| { result: WalletNFT[]; error?: undefined }
@@ -59,13 +60,13 @@ export async function getWalletNFTs(params: {
}
}
if (isMoralisSupported(chainId) && process.env.MORALIS_API_KEY) {
if (isMoralisSupported(chainId) && MORALIS_API_KEY) {
const url = generateMoralisUrl({ chainId, owner });
const response = await fetch(url, {
method: "GET",
headers: {
"X-API-Key": process.env.MORALIS_API_KEY,
"X-API-Key": MORALIS_API_KEY,
},
next: {
revalidate: 10, // cache for 10 seconds
@@ -148,7 +149,7 @@ async function getWalletNFTsFromInsight(params: {
const response = await fetch(url, {
headers: {
"x-client-id": DASHBOARD_THIRDWEB_CLIENT_ID,
"x-client-id": NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
},
});
+8 -13
View File
@@ -1,7 +1,11 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL, THIRDWEB_ENGINE_CLOUD_URL } from "../constants/env";
import {
NEXT_PUBLIC_ENGINE_CLOUD_URL,
NEXT_PUBLIC_THIRDWEB_API_HOST,
} from "../constants/public-envs";
import { ANALYTICS_SERVICE_URL } from "../constants/server-envs";
type ProxyActionParams = {
pathname: string;
@@ -76,22 +80,13 @@ async function proxy<T>(
}
export async function apiServerProxy<T>(params: ProxyActionParams) {
return proxy<T>(API_SERVER_URL, params);
return proxy<T>(NEXT_PUBLIC_THIRDWEB_API_HOST, params);
}
export async function engineCloudProxy<T>(params: ProxyActionParams) {
return proxy<T>(THIRDWEB_ENGINE_CLOUD_URL, params);
}
export async function payServerProxy<T>(params: ProxyActionParams) {
return proxy<T>(
process.env.NEXT_PUBLIC_PAY_URL
? `https://${process.env.NEXT_PUBLIC_PAY_URL}`
: "https://pay.thirdweb-dev.com",
params,
);
return proxy<T>(NEXT_PUBLIC_ENGINE_CLOUD_URL, params);
}
export async function analyticsServerProxy<T>(params: ProxyActionParams) {
return proxy<T>(process.env.ANALYTICS_SERVICE_URL || "", params);
return proxy<T>(ANALYTICS_SERVICE_URL, params);
}
@@ -0,0 +1,10 @@
"use server";
import { revalidatePath } from "next/cache";
export async function revalidatePathAction(
path: string,
type: "page" | "layout",
) {
revalidatePath(path, type);
}
+14 -11
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function sendTeamInvites(options: {
teamId: string;
@@ -40,17 +40,20 @@ async function sendInvite(
invite: { email: string; role: "OWNER" | "MEMBER" },
token: string,
) {
const res = await fetch(`${API_SERVER_URL}/v1/teams/${teamId}/invites`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamId}/invites`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
inviteEmail: invite.email,
inviteRole: invite.role,
}),
},
body: JSON.stringify({
inviteEmail: invite.email,
inviteRole: invite.role,
}),
});
);
if (!res.ok) {
const errorMessage = await res.text();
@@ -2,13 +2,12 @@ import "server-only";
import Stripe from "stripe";
import type { Team } from "../api/team";
import { STRIPE_SECRET_KEY } from "../constants/server-envs";
let existingStripe: Stripe | undefined;
function getStripe() {
if (!existingStripe) {
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
if (!STRIPE_SECRET_KEY) {
throw new Error("STRIPE_SECRET_KEY is not set");
}
@@ -1,6 +1,6 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function updateAccount(values: {
name?: string;
@@ -13,7 +13,7 @@ export async function updateAccount(values: {
throw new Error("No Auth token");
}
const res = await fetch(`${API_SERVER_URL}/v1/account`, {
const res = await fetch(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/account`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
+75 -11
View File
@@ -6,11 +6,14 @@ import type {
InAppWalletStats,
RpcMethodStats,
TransactionStats,
UniversalBridgeStats,
UniversalBridgeWalletStats,
UserOpStats,
WalletStats,
WalletUserStats,
} from "types/analytics";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { ANALYTICS_SERVICE_URL } from "../constants/server-envs";
import { getChains } from "./chain";
async function fetchAnalytics(
@@ -29,34 +32,38 @@ async function fetchAnalytics(
}
// create a new URL object for the analytics server
const ANALYTICS_SERVICE_URL = new URL(
process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
const analyticsServiceUrl = new URL(
ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
);
ANALYTICS_SERVICE_URL.pathname = pathname;
analyticsServiceUrl.pathname = pathname;
for (const param of searchParams?.split("&") || []) {
const [key, value] = param.split("=");
if (!key || !value) {
throw new Error("Invalid input, no key or value provided");
}
ANALYTICS_SERVICE_URL.searchParams.append(
analyticsServiceUrl.searchParams.append(
decodeURIComponent(key),
decodeURIComponent(value),
);
}
// client id DEBUG OVERRIDE
// ANALYTICS_SERVICE_URL.searchParams.delete("clientId");
// ANALYTICS_SERVICE_URL.searchParams.delete("accountId");
// ANALYTICS_SERVICE_URL.searchParams.append(
// "clientId",
// "...",
// analyticsServiceUrl.searchParams.delete("projectId");
// analyticsServiceUrl.searchParams.delete("teamId");
// analyticsServiceUrl.searchParams.append(
// "teamId",
// "team_cm0lde33r02344w129k5hm2xz",
// );
// analyticsServiceUrl.searchParams.append(
// "projectId",
// "prj_cm4rqwx9b002qrnsnr37wqpo6",
// );
return fetch(ANALYTICS_SERVICE_URL, {
return fetch(analyticsServiceUrl, {
...init,
headers: {
"content-type": "application/json",
...init?.headers,
authorization: `Bearer ${token}`,
},
});
}
@@ -367,3 +374,60 @@ export async function getEcosystemWalletUsage(args: {
return json.data as EcosystemWalletStats[];
}
export async function getUniversalBridgeUsage(args: {
teamId: string;
projectId?: string;
from?: Date;
to?: Date;
period?: "day" | "week" | "month" | "year" | "all";
}) {
const searchParams = buildSearchParams(args);
const res = await fetchAnalytics(`v2/universal?${searchParams.toString()}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (res?.status !== 200) {
const reason = await res?.text();
console.error(
`Failed to fetch universal bridge stats: ${res?.status} - ${res.statusText} - ${reason}`,
);
return [];
}
const json = await res.json();
return json.data as UniversalBridgeStats[];
}
export async function getUniversalBridgeWalletUsage(args: {
teamId: string;
projectId: string;
from?: Date;
to?: Date;
period?: "day" | "week" | "month" | "year" | "all";
}) {
const searchParams = buildSearchParams(args);
const res = await fetchAnalytics(
`v2/universal/wallets?${searchParams.toString()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
if (res?.status !== 200) {
const reason = await res?.text();
console.error(
`Failed to fetch universal bridge wallet stats: ${res?.status} - ${res.statusText} - ${reason}`,
);
return null;
}
const json = await res.json();
return json.data as UniversalBridgeWalletStats[];
}
+16 -12
View File
@@ -1,21 +1,25 @@
import "server-only";
import type { ChainMetadata } from "thirdweb/chains";
import type { ChainService } from "../../app/(app)/(dashboard)/(chain)/types/chain";
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { API_SERVER_SECRET } from "../constants/server-envs";
export async function getGasSponsoredChains() {
if (!THIRDWEB_API_SECRET) {
if (!API_SERVER_SECRET) {
throw new Error("API_SERVER_SECRET is not set");
}
const res = await fetch(`${API_SERVER_URL}/v1/chains/gas-sponsored`, {
headers: {
"Content-Type": "application/json",
"x-service-api-key": THIRDWEB_API_SECRET,
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/chains/gas-sponsored`,
{
headers: {
"Content-Type": "application/json",
"x-service-api-key": API_SERVER_SECRET,
},
next: {
revalidate: 15 * 60, //15 minutes
},
},
next: {
revalidate: 15 * 60, //15 minutes
},
});
);
if (!res.ok) {
console.error(
@@ -37,7 +41,7 @@ export async function getGasSponsoredChains() {
export function getChains() {
return fetch(
`${API_SERVER_URL}/v1/chains`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/chains`,
// revalidate every 60 minutes
{ next: { revalidate: 60 * 60 } },
).then((res) => res.json()) as Promise<{ data: ChainMetadata[] }>;
@@ -45,7 +49,7 @@ export function getChains() {
export function getChainServices() {
return fetch(
`${API_SERVER_URL}/v1/chains/services`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/chains/services`,
// revalidate every 60 minutes
{ next: { revalidate: 60 * 60 } },
).then((res) => res.json()) as Promise<{
@@ -0,0 +1,207 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use server";
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
import { THIRDWEB_INSIGHT_API_DOMAIN } from "constants/urls";
export interface WebhookResponse {
id: string;
name: string;
team_id: string;
project_id: string;
webhook_url: string;
webhook_secret: string;
filters: WebhookFilters;
suspended_at: string | null;
suspended_reason: string | null;
disabled: boolean;
created_at: string;
updated_at: string | null;
}
export interface WebhookFilters {
"v1.events"?: {
chain_ids?: string[];
addresses?: string[];
signatures?: Array<{
sig_hash: string;
abi?: string;
params?: Record<string, unknown>;
}>;
};
"v1.transactions"?: {
chain_ids?: string[];
from_addresses?: string[];
to_addresses?: string[];
signatures?: Array<{
sig_hash: string;
abi?: string;
params?: Record<string, unknown>;
}>;
};
}
interface CreateWebhookPayload {
name: string;
webhook_url: string;
filters: WebhookFilters;
}
interface WebhooksListResponse {
data: WebhookResponse[];
error?: string;
}
interface WebhookSingleResponse {
data: WebhookResponse | null;
error?: string;
}
interface TestWebhookPayload {
webhook_url: string;
type?: "event" | "transaction";
}
interface TestWebhookResponse {
success: boolean;
error?: string;
}
export async function createWebhook(
payload: CreateWebhookPayload,
clientId: string,
): Promise<WebhookSingleResponse> {
try {
const authToken = await getAuthToken();
const response = await fetch(
`https://${THIRDWEB_INSIGHT_API_DOMAIN}/v1/webhooks`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-client-id": clientId,
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify(payload),
},
);
if (!response.ok) {
const errorText = await response.text();
return {
data: null,
error: `Failed to create webhook: ${errorText}`,
};
}
return (await response.json()) as WebhookSingleResponse;
} catch (error) {
return {
data: null,
error: `Network or parsing error: ${error instanceof Error ? error.message : "Unknown error"}`,
};
}
}
export async function getWebhooks(
clientId: string,
): Promise<WebhooksListResponse> {
try {
const authToken = await getAuthToken();
const response = await fetch(
`https://${THIRDWEB_INSIGHT_API_DOMAIN}/v1/webhooks`,
{
method: "GET",
headers: {
"x-client-id": clientId,
Authorization: `Bearer ${authToken}`,
},
},
);
if (!response.ok) {
const errorText = await response.text();
return {
data: [],
error: `Failed to get webhooks: ${errorText}`,
};
}
return (await response.json()) as WebhooksListResponse;
} catch (error) {
return {
data: [],
error: `Network or parsing error: ${error instanceof Error ? error.message : "Unknown error"}`,
};
}
}
export async function deleteWebhook(
webhookId: string,
clientId: string,
): Promise<WebhookSingleResponse> {
try {
const authToken = await getAuthToken();
const response = await fetch(
`https://${THIRDWEB_INSIGHT_API_DOMAIN}/v1/webhooks/${encodeURIComponent(webhookId)}`,
{
method: "DELETE",
headers: {
"x-client-id": clientId,
Authorization: `Bearer ${authToken}`,
},
},
);
if (!response.ok) {
const errorText = await response.text();
return {
data: null,
error: `Failed to delete webhook: ${errorText}`,
};
}
return (await response.json()) as WebhookSingleResponse;
} catch (error) {
return {
data: null,
error: `Network or parsing error: ${error instanceof Error ? error.message : "Unknown error"}`,
};
}
}
export async function testWebhook(
payload: TestWebhookPayload,
clientId: string,
): Promise<TestWebhookResponse> {
try {
const authToken = await getAuthToken();
const response = await fetch(
`https://${THIRDWEB_INSIGHT_API_DOMAIN}/v1/webhooks/test`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-client-id": clientId,
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify(payload),
},
);
if (!response.ok) {
const errorText = await response.text();
return {
success: false,
error: `Failed to test webhook: ${errorText}`,
};
}
return (await response.json()) as TestWebhookResponse;
} catch (error) {
return {
success: false,
error: `Network or parsing error: ${error instanceof Error ? error.message : "Unknown error"}`,
};
}
}
+8 -5
View File
@@ -1,5 +1,5 @@
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export type LinkedWallet = {
createdAt: string;
@@ -14,11 +14,14 @@ export async function getLinkedWallets() {
return null;
}
const res = await fetch(`${API_SERVER_URL}/v1/account/wallets`, {
headers: {
Authorization: `Bearer ${token}`,
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/account/wallets`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
});
);
if (res.ok) {
const json = (await res.json()) as {
+3 -3
View File
@@ -1,5 +1,5 @@
import "server-only";
import { API_SERVER_URL } from "@/constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import type { ProjectResponse } from "@thirdweb-dev/service-utils";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
@@ -13,7 +13,7 @@ export async function getProjects(teamSlug: string) {
}
const teamsRes = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/projects`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/projects`,
{
headers: {
Authorization: `Bearer ${token}`,
@@ -34,7 +34,7 @@ export async function getProject(teamSlug: string, projectSlug: string) {
}
const teamsRes = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/projects/${projectSlug}`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/projects/${projectSlug}`,
{
headers: {
Authorization: `Bearer ${token}`,
+15 -10
View File
@@ -1,4 +1,6 @@
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "../constants/env";
import "server-only";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { API_SERVER_SECRET } from "../constants/server-envs";
export type SMSCountryTiers = {
tier1: string[];
@@ -9,18 +11,21 @@ export type SMSCountryTiers = {
};
export async function getSMSCountryTiers() {
if (!THIRDWEB_API_SECRET) {
if (!API_SERVER_SECRET) {
throw new Error("API_SERVER_SECRET is not set");
}
const res = await fetch(`${API_SERVER_URL}/v1/sms/list-country-tiers`, {
headers: {
"Content-Type": "application/json",
"x-service-api-key": THIRDWEB_API_SECRET,
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/sms/list-country-tiers`,
{
headers: {
"Content-Type": "application/json",
"x-service-api-key": API_SERVER_SECRET,
},
next: {
revalidate: 15 * 60, //15 minutes
},
},
next: {
revalidate: 15 * 60, //15 minutes
},
});
);
if (!res.ok) {
console.error(
+2 -2
View File
@@ -1,5 +1,5 @@
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export type TeamInvite = {
id: string;
@@ -25,7 +25,7 @@ export async function getTeamInvites(
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamId}/invites?skip=${options.start}&take=${options.count}`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamId}/invites?skip=${options.start}&take=${options.count}`,
{
headers: {
Authorization: `Bearer ${authToken}`,
+3 -3
View File
@@ -1,5 +1,5 @@
import "server-only";
import { API_SERVER_URL } from "@/constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
const TeamAccountRole = {
@@ -33,7 +33,7 @@ export async function getMembers(teamSlug: string) {
}
const teamsRes = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/members`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/members`,
{
headers: {
Authorization: `Bearer ${token}`,
@@ -56,7 +56,7 @@ export async function getMemberById(teamSlug: string, memberId: string) {
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/members/${memberId}`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/members/${memberId}`,
{
headers: {
Authorization: `Bearer ${token}`,
@@ -1,5 +1,5 @@
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import type { ProductSKU } from "../lib/billing";
type InvoiceLine = {
@@ -47,7 +47,7 @@ export async function getTeamSubscriptions(slug: string) {
}
const teamRes = await fetch(
`${API_SERVER_URL}/v1/teams/${slug}/subscriptions`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${slug}/subscriptions`,
{
headers: {
Authorization: `Bearer ${token}`,
+20 -13
View File
@@ -1,5 +1,6 @@
import "server-only";
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "@/constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { API_SERVER_SECRET } from "@/constants/server-envs";
import type { TeamResponse } from "@thirdweb-dev/service-utils";
import { cookies } from "next/headers";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
@@ -14,11 +15,14 @@ export async function getTeamBySlug(slug: string) {
return null;
}
const teamRes = await fetch(`${API_SERVER_URL}/v1/teams/${slug}`, {
headers: {
Authorization: `Bearer ${token}`,
const teamRes = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${slug}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
});
);
if (teamRes.ok) {
return (await teamRes.json())?.result as Team;
}
@@ -26,11 +30,14 @@ export async function getTeamBySlug(slug: string) {
}
export async function service_getTeamBySlug(slug: string) {
const teamRes = await fetch(`${API_SERVER_URL}/v1/teams/${slug}`, {
headers: {
"x-service-api-key": THIRDWEB_API_SECRET,
const teamRes = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${slug}`,
{
headers: {
"x-service-api-key": API_SERVER_SECRET,
},
},
});
);
if (teamRes.ok) {
return (await teamRes.json())?.result as Team;
@@ -49,7 +56,7 @@ export async function getTeams() {
return null;
}
const teamsRes = await fetch(`${API_SERVER_URL}/v1/teams`, {
const teamsRes = await fetch(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams`, {
headers: {
Authorization: `Bearer ${token}`,
},
@@ -66,7 +73,7 @@ export async function getDefaultTeam() {
return null;
}
const res = await fetch(`${API_SERVER_URL}/v1/teams/~`, {
const res = await fetch(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/~`, {
headers: {
Authorization: `Bearer ${token}`,
},
@@ -77,7 +84,7 @@ export async function getDefaultTeam() {
return null;
}
export async function getLastVisitedTeamOrDefaultTeam() {
export async function getLastVisitedTeam() {
const token = await getAuthToken();
if (!token) {
return null;
@@ -93,5 +100,5 @@ export async function getLastVisitedTeamOrDefaultTeam() {
}
}
return getDefaultTeam();
return null;
}
@@ -0,0 +1 @@
export const UB_BASE_URL = process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST;
@@ -1,7 +1,8 @@
"use server";
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST } from "../../constants/public-envs";
const UB_BASE_URL = process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST;
const UB_BASE_URL = NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST;
type Webhook = {
url: string;
@@ -15,13 +16,15 @@ type Webhook = {
export async function getWebhooks(props: {
clientId: string;
teamId: string;
}) {
const authToken = await getAuthToken();
const res = await fetch(`${UB_BASE_URL}/v1/developer/webhooks`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-client-id-override": props.clientId,
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
});
@@ -37,12 +40,20 @@ export async function getWebhooks(props: {
export async function createWebhook(props: {
clientId: string;
teamId: string;
version?: number;
url: string;
label: string;
secret?: string;
}) {
const authToken = await getAuthToken();
console.log(
"UB_BASE_URL",
UB_BASE_URL,
props.clientId,
props.teamId,
authToken,
);
const res = await fetch(`${UB_BASE_URL}/v1/developer/webhooks`, {
method: "POST",
body: JSON.stringify({
@@ -53,7 +64,8 @@ export async function createWebhook(props: {
}),
headers: {
"Content-Type": "application/json",
"x-client-id-override": props.clientId,
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
});
@@ -68,6 +80,7 @@ export async function createWebhook(props: {
export async function deleteWebhook(props: {
clientId: string;
teamId: string;
webhookId: string;
}) {
const authToken = await getAuthToken();
@@ -77,7 +90,8 @@ export async function deleteWebhook(props: {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"x-client-id-override": props.clientId,
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
},
@@ -108,7 +122,7 @@ export async function getFees(props: {
headers: {
"Content-Type": "application/json",
"x-team-id": props.teamId,
"x-client-id-override": props.clientId,
"x-client-id": props.clientId,
Authorization: `Bearer ${authToken}`,
},
});
@@ -133,8 +147,8 @@ export async function updateFee(props: {
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-client-id-override": props.clientId,
"x-team-id": props.teamId,
"x-client-id": props.clientId,
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify({
@@ -150,3 +164,87 @@ export async function updateFee(props: {
return;
}
export type PaymentsResponse = {
data: Payment[];
meta: {
totalCount: number;
};
};
export type Payment = {
id: string;
blockNumber?: bigint;
transactionId: string;
clientId: string;
sender: string;
receiver: string;
developerFeeRecipient: string;
developerFeeBps: number;
transactions: Array<{
chainId: number;
transactionHash: string;
}>;
status: "PENDING" | "COMPLETED" | "FAILED" | "NOT_FOUND";
type: "buy" | "sell" | "transfer";
originAmount: bigint;
destinationAmount: bigint;
purchaseData: unknown;
originToken: {
address: string;
symbol: string;
decimals: number;
chainId: number;
};
destinationToken: {
address: string;
symbol: string;
decimals: number;
chainId: number;
};
createdAt: string;
};
export async function getPayments(props: {
clientId: string;
teamId: string;
limit?: number;
offset?: number;
}) {
const authToken = await getAuthToken();
// Build URL with query parameters if provided
let url = `${UB_BASE_URL}/v1/developer/payments`;
const queryParams = new URLSearchParams();
if (props.limit) {
queryParams.append("limit", props.limit.toString());
}
if (props.offset) {
queryParams.append("offset", props.offset.toString());
}
// Append query params to URL if any exist
const queryString = queryParams.toString();
if (queryString) {
url = `${url}?${queryString}`;
}
const res = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
});
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
const json = await res.json();
return json as PaymentsResponse;
}
@@ -0,0 +1,47 @@
import "server-only";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { UB_BASE_URL } from "./constants";
type PaymentLink = {
clientId: string;
title?: string;
imageUrl?: string;
receiver: string;
destinationToken: {
address: string;
symbol: string;
decimals: number;
chainId: number;
};
amount: bigint | undefined;
purchaseData: unknown;
};
export async function getPaymentLink(props: {
paymentId: string;
}) {
const res = await fetch(`${UB_BASE_URL}/v1/links/${props.paymentId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
},
});
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
const { data } = await res.json();
return {
clientId: data.clientId,
title: data.title,
imageUrl: data.imageUrl,
receiver: data.receiver,
destinationToken: data.destinationToken,
amount: data.amount ? BigInt(data.amount) : undefined,
purchaseData: data.purchaseData,
} as PaymentLink;
}
@@ -0,0 +1,71 @@
"use server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import type { ProjectResponse } from "@thirdweb-dev/service-utils";
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
import { UB_BASE_URL } from "./constants";
export type TokenMetadata = {
name: string;
symbol: string;
address: string;
decimals: number;
chainId: number;
iconUri?: string;
};
export async function getUniversalBridgeTokens(props: {
chainId?: number;
}) {
const url = new URL(`${UB_BASE_URL}/v1/tokens`);
if (props.chainId) {
url.searchParams.append("chainId", String(props.chainId));
}
url.searchParams.append("limit", "1000");
const res = await fetch(url.toString(), {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
} as Record<string, string>,
});
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
const json = await res.json();
return json.data as Array<TokenMetadata>;
}
export async function addUniversalBridgeTokenRoute(props: {
chainId: number;
tokenAddress: string;
project: ProjectResponse;
}) {
const authToken = await getAuthToken();
const url = new URL(`${UB_BASE_URL}/v1/tokens`);
const res = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
"x-client-id": props.project.publishableKey,
} as Record<string, string>,
body: JSON.stringify({
chainId: props.chainId,
tokenAddress: props.tokenAddress,
}),
});
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
const json = await res.json();
return json.data as Array<TokenMetadata>;
}
+3 -2
View File
@@ -1,5 +1,6 @@
import "server-only";
import { unstable_cache } from "next/cache";
import { ANALYTICS_SERVICE_URL } from "../../constants/server-envs";
export type RPCUsageDataItem = {
date: string;
@@ -16,7 +17,7 @@ export const fetchRPCUsage = unstable_cache(
to: string;
period: "day" | "week" | "month" | "year" | "all";
}) => {
const analyticsEndpoint = process.env.ANALYTICS_SERVICE_URL as string;
const analyticsEndpoint = ANALYTICS_SERVICE_URL;
const url = new URL(`${analyticsEndpoint}/v2/rpc/usage-types`);
url.searchParams.set("teamId", params.teamId);
if (params.projectId) {
@@ -79,7 +80,7 @@ export const getLast24HoursRPCUsage = unstable_cache(
projectId?: string;
authToken: string;
}) => {
const analyticsEndpoint = process.env.ANALYTICS_SERVICE_URL as string;
const analyticsEndpoint = ANALYTICS_SERVICE_URL;
const url = new URL(`${analyticsEndpoint}/v2/rpc/24-hours`);
url.searchParams.set("teamId", params.teamId);
if (params.projectId) {
+3 -3
View File
@@ -2,7 +2,7 @@
import "server-only";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export type VerifiedDomainResponse =
| {
@@ -27,7 +27,7 @@ export async function checkDomainVerification(
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamIdOrSlug}/verified-domain`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamIdOrSlug}/verified-domain`,
{
headers: {
Authorization: `Bearer ${token}`,
@@ -54,7 +54,7 @@ export async function createDomainVerification(
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamIdOrSlug}/verified-domain`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamIdOrSlug}/verified-domain`,
{
method: "POST",
body: JSON.stringify({ domain }),
@@ -71,7 +71,7 @@ export function MultiNetworkSelector(props: {
return false;
}
if (Number.isInteger(Number.parseInt(searchValue))) {
if (Number.isInteger(Number(searchValue))) {
return String(chain.chainId).startsWith(searchValue);
}
return chain.name.toLowerCase().includes(searchValue.toLowerCase());
@@ -144,18 +144,26 @@ export function SingleNetworkSelector(props: {
side?: "left" | "right" | "top" | "bottom";
disableChainId?: boolean;
align?: "center" | "start" | "end";
disableTestnets?: boolean;
placeholder?: string;
client: ThirdwebClient;
}) {
const { allChains, idToChain } = useAllChainsData();
const chainsToShow = useMemo(() => {
if (!props.chainIds) {
return allChains;
let chains = allChains;
if (props.disableTestnets) {
chains = chains.filter((chain) => !chain.testnet);
}
const chainIdSet = new Set(props.chainIds);
return allChains.filter((chain) => chainIdSet.has(chain.chainId));
}, [allChains, props.chainIds]);
if (props.chainIds) {
const chainIdSet = new Set(props.chainIds);
chains = chains.filter((chain) => chainIdSet.has(chain.chainId));
}
return chains;
}, [allChains, props.chainIds, props.disableTestnets]);
const options = useMemo(() => {
return chainsToShow.map((chain) => {
@@ -173,7 +181,7 @@ export function SingleNetworkSelector(props: {
return false;
}
if (Number.isInteger(Number.parseInt(searchValue))) {
if (Number.isInteger(Number(searchValue))) {
return String(chain.chainId).startsWith(searchValue);
}
return chain.name.toLowerCase().includes(searchValue.toLowerCase());
@@ -218,6 +226,7 @@ export function SingleNetworkSelector(props: {
<SelectWithSearch
searchPlaceholder="Search by Name or Chain ID"
value={String(props.chainId)}
showCheck={false}
options={options}
onValueChange={(chainId) => {
props.onChange(Number(chainId));
@@ -0,0 +1,84 @@
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type React from "react";
export function RouteDiscoveryCard(
props: React.PropsWithChildren<{
bottomText: React.ReactNode;
header?: {
description: string | undefined;
title: string;
};
errorText: string | undefined;
noPermissionText: string | undefined;
saveButton?: {
onClick?: () => void;
disabled: boolean;
isPending: boolean;
type?: "submit";
variant?:
| "ghost"
| "default"
| "primary"
| "destructive"
| "outline"
| "secondary";
className?: string;
label?: string;
};
}>,
) {
return (
<div className="relative rounded-lg border border-border bg-card">
<div
className={cn(
"relative border-border border-b px-4 py-6 lg:px-6",
props.noPermissionText && "cursor-not-allowed",
)}
>
{props.header && (
<>
<h3 className="font-semibold text-xl tracking-tight">
{props.header.title}
</h3>
{props.header.description && (
<p className="mt-1.5 mb-4 text-foreground text-sm">
{props.header.description}
</p>
)}
</>
)}
{props.children}
</div>
<div className="flex min-h-[60px] items-center justify-between gap-2 px-4 py-3 lg:px-6">
{props.noPermissionText ? (
<p className="text-muted-foreground text-sm">
{props.noPermissionText}
</p>
) : props.errorText ? (
<p className="text-destructive-text text-sm">{props.errorText}</p>
) : (
<p className="text-muted-foreground text-sm">{props.bottomText}</p>
)}
{props.saveButton && !props.noPermissionText && (
<Button
size="sm"
className={cn("gap-2", props.saveButton.className)}
onClick={props.saveButton.onClick}
disabled={props.saveButton.disabled || props.saveButton.isPending}
variant={props.saveButton.variant || "outline"}
type={props.saveButton.type}
>
{props.saveButton.isPending && <Spinner className="size-3" />}
{props.saveButton.label ||
(props.saveButton.isPending ? "Submit" : "Submit Token")}
</Button>
)}
</div>
</div>
);
}
@@ -137,7 +137,7 @@ function RenderSidebarGroup(props: {
}
if ("separator" in link) {
return <SidebarSeparator className="my-1" />;
return <SidebarSeparator key={Math.random()} className="my-1" />;
}
return (
@@ -0,0 +1,63 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import {
BadgeContainer,
storybookThirdwebClient,
} from "../../../stories/utils";
import { TokenSelector } from "./TokenSelector";
const meta = {
title: "blocks/Cards/TokenSelector",
component: Story,
parameters: {
nextjs: {
appDirectory: true,
},
},
} satisfies Meta<typeof Story>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Variants: Story = {
args: {},
};
function Story() {
return (
<div className="container flex max-w-6xl flex-col gap-8 py-10">
<Variant label="No Chains selected by default" />
</div>
);
}
function Variant(props: {
label: string;
selectedChainId?: number;
}) {
const [token, setToken] = useState<
| {
address: string;
chainId: number;
}
| undefined
>(undefined);
return (
<BadgeContainer label={props.label}>
<TokenSelector
addNativeTokenIfMissing={false}
showCheck={false}
selectedToken={token}
chainId={props.selectedChainId}
client={storybookThirdwebClient}
onChange={(v) => {
setToken({
address: v.address,
chainId: v.chainId,
});
}}
/>
</BadgeContainer>
);
}
@@ -0,0 +1,190 @@
import { useCallback, useMemo } from "react";
import {
NATIVE_TOKEN_ADDRESS,
type ThirdwebClient,
getAddress,
} from "thirdweb";
import { shortenAddress } from "thirdweb/utils";
import { useAllChainsData } from "../../../hooks/chains/allChains";
import { useTokensData } from "../../../hooks/tokens/tokens";
import { replaceIpfsUrl } from "../../../lib/sdk";
import { fallbackChainIcon } from "../../../utils/chain-icons";
import type { TokenMetadata } from "../../api/universal-bridge/tokens";
import { cn } from "../../lib/utils";
import { Badge } from "../ui/badge";
import { Img } from "./Img";
import { SelectWithSearch } from "./select-with-search";
type Option = { label: string; value: string };
const checksummedNativeTokenAddress = getAddress(NATIVE_TOKEN_ADDRESS);
export function TokenSelector(props: {
selectedToken: { chainId: number; address: string } | undefined;
onChange: (token: TokenMetadata) => void;
className?: string;
popoverContentClassName?: string;
chainId?: number;
side?: "left" | "right" | "top" | "bottom";
disableAddress?: boolean;
align?: "center" | "start" | "end";
placeholder?: string;
client: ThirdwebClient;
disabled?: boolean;
enabled?: boolean;
showCheck: boolean;
addNativeTokenIfMissing: boolean;
}) {
const tokensQuery = useTokensData({
chainId: props.chainId,
enabled: props.enabled,
});
const { idToChain } = useAllChainsData();
const tokens = useMemo(() => {
if (!tokensQuery.data) {
return [];
}
if (props.addNativeTokenIfMissing) {
const hasNativeToken = tokensQuery.data.some(
(token) => token.address === checksummedNativeTokenAddress,
);
if (!hasNativeToken && props.chainId) {
return [
{
name:
idToChain.get(props.chainId)?.nativeCurrency.name ??
"Native Token",
symbol:
idToChain.get(props.chainId)?.nativeCurrency.symbol ?? "ETH",
decimals: 18,
chainId: props.chainId,
address: checksummedNativeTokenAddress,
} satisfies TokenMetadata,
...tokensQuery.data,
];
}
}
return tokensQuery.data;
}, [
tokensQuery.data,
props.chainId,
props.addNativeTokenIfMissing,
idToChain,
]);
const addressChainToToken = useMemo(() => {
const value = new Map<string, TokenMetadata>();
for (const token of tokens) {
value.set(`${token.chainId}:${token.address}`, token);
}
return value;
}, [tokens]);
const options = useMemo(() => {
return (
tokens.map((token) => {
return {
label: token.symbol,
value: `${token.chainId}:${token.address}`,
};
}) || []
);
}, [tokens]);
const searchFn = useCallback(
(option: Option, searchValue: string) => {
const token = addressChainToToken.get(option.value);
if (!token) {
return false;
}
if (Number.isInteger(Number(searchValue))) {
return String(token.chainId).startsWith(searchValue);
}
return (
token.name.toLowerCase().includes(searchValue.toLowerCase()) ||
token.symbol.toLowerCase().includes(searchValue.toLowerCase()) ||
token.address.toLowerCase().includes(searchValue.toLowerCase())
);
},
[addressChainToToken],
);
const renderOption = useCallback(
(option: Option) => {
const token = addressChainToToken.get(option.value);
if (!token) {
return option.label;
}
const resolvedSrc = token.iconUri
? replaceIpfsUrl(token.iconUri, props.client)
: fallbackChainIcon;
return (
<div className="flex items-center justify-between gap-4">
<span className="flex grow gap-2 truncate text-left">
<Img
// render different image element if src changes to avoid showing old image while loading new one
key={resolvedSrc}
className={cn("size-5 rounded-full object-contain")}
src={resolvedSrc}
loading={"lazy"}
alt=""
// eslint-disable-next-line @next/next/no-img-element
fallback={<img src={fallbackChainIcon} alt="" />}
skeleton={
<div className="animate-pulse rounded-full bg-border" />
}
/>
{token.symbol}
</span>
{!props.disableAddress && (
<Badge variant="outline" className="gap-2 py-1 max-sm:hidden">
<span className="text-muted-foreground">Address</span>
{shortenAddress(token.address, 4)}
</Badge>
)}
</div>
);
},
[addressChainToToken, props.disableAddress, props.client],
);
const selectedValue = props.selectedToken
? `${props.selectedToken.chainId}:${props.selectedToken.address}`
: undefined;
return (
<SelectWithSearch
searchPlaceholder="Search by name or symbol"
value={selectedValue}
options={options}
onValueChange={(tokenAddress) => {
const token = addressChainToToken.get(tokenAddress);
if (!token) {
return;
}
props.onChange(token);
}}
closeOnSelect={true}
showCheck={props.showCheck}
placeholder={
tokensQuery.isPending
? "Loading Tokens..."
: props.placeholder || "Select Token"
}
overrideSearchFn={searchFn}
renderOption={renderOption}
className={props.className}
popoverContentClassName={props.popoverContentClassName}
disabled={tokensQuery.isPending || props.disabled}
side={props.side}
align={props.align}
/>
);
}
@@ -0,0 +1,75 @@
import type { Meta, StoryObj } from "@storybook/react";
import { ArrowRightIcon, RocketIcon, StarIcon } from "lucide-react";
import { BadgeContainer } from "../../../stories/utils";
import { UpsellBannerCard } from "./UpsellBannerCard";
function Story() {
return (
<div className="container flex max-w-4xl flex-col gap-8 py-10">
<BadgeContainer label="Green with icon (default)">
<UpsellBannerCard
title="Unlock more with thirdweb"
description="Upgrade to increase limits and access advanced features."
cta={{
text: "View plans",
icon: <ArrowRightIcon className="size-4" />,
link: "#",
}}
trackingCategory="storybook"
trackingLabel="green"
icon={<RocketIcon className="size-5" />}
accentColor="green"
/>
</BadgeContainer>
<BadgeContainer label="Blue accent">
<UpsellBannerCard
title="Need more storage?"
description="Add additional space to your account."
cta={{
text: "Upgrade storage",
icon: <ArrowRightIcon className="size-4" />,
link: "#",
}}
trackingCategory="storybook"
trackingLabel="blue"
icon={<StarIcon className="size-5" />}
accentColor="blue"
/>
</BadgeContainer>
<BadgeContainer label="Purple without leading icon">
<UpsellBannerCard
title="Join the beta"
description="Get early access to experimental features."
cta={{
text: "Request access",
icon: <ArrowRightIcon className="size-4" />,
link: "#",
}}
trackingCategory="storybook"
trackingLabel="purple"
accentColor="purple"
/>
</BadgeContainer>
</div>
);
}
const meta = {
title: "blocks/Banners/UpsellBannerCard",
component: Story,
parameters: {
nextjs: {
appDirectory: true,
},
},
} satisfies Meta<typeof Story>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Variants: Story = {
args: {},
};
@@ -0,0 +1,119 @@
"use client";
import { Button } from "@/components/ui/button";
import { TrackedLinkTW } from "@/components/ui/tracked-link";
import { cn } from "@/lib/utils";
import type React from "react";
const ACCENT = {
green: {
border: "border-green-600 dark:border-green-700",
bgFrom: "from-green-50 dark:from-green-900/20",
blur: "bg-green-600",
title: "text-green-900 dark:text-green-200",
desc: "text-green-800 dark:text-green-300",
iconBg: "bg-green-600 text-white",
btn: "bg-green-600 text-white hover:bg-green-700",
},
blue: {
border: "border-blue-600 dark:border-blue-700",
bgFrom: "from-blue-50 dark:from-blue-900/20",
blur: "bg-blue-600",
title: "text-blue-900 dark:text-blue-200",
desc: "text-blue-800 dark:text-blue-300",
iconBg: "bg-blue-600 text-white",
btn: "bg-blue-600 text-white hover:bg-blue-700",
},
purple: {
border: "border-purple-600 dark:border-purple-700",
bgFrom: "from-purple-50 dark:from-purple-900/20",
blur: "bg-purple-600",
title: "text-purple-900 dark:text-purple-200",
desc: "text-purple-800 dark:text-purple-300",
iconBg: "bg-purple-600 text-white",
btn: "bg-purple-600 text-white hover:bg-purple-700",
},
} as const;
type UpsellBannerCardProps = {
title: React.ReactNode;
description: React.ReactNode;
cta: {
text: React.ReactNode;
icon?: React.ReactNode;
link: string;
};
trackingCategory: string;
trackingLabel: string;
accentColor?: keyof typeof ACCENT;
icon?: React.ReactNode;
};
export function UpsellBannerCard(props: UpsellBannerCardProps) {
const color = ACCENT[props.accentColor || "green"];
return (
<div
className={cn(
"relative overflow-hidden rounded-lg border bg-gradient-to-r p-5",
color.border,
color.bgFrom,
)}
>
{/* Decorative blur */}
<div
className={cn(
"-right-10 -top-10 pointer-events-none absolute size-28 rounded-full opacity-20 blur-2xl",
color.blur,
)}
/>
<div className="relative flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-start gap-3">
{props.icon ? (
<div
className={cn(
"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-full",
color.iconBg,
)}
>
{props.icon}
</div>
) : null}
<div>
<h3
className={cn(
"font-semibold text-lg tracking-tight",
color.title,
)}
>
{props.title}
</h3>
<p className={cn("mt-0.5 text-sm", color.desc)}>
{props.description}
</p>
</div>
</div>
<Button
asChild
size="sm"
className={cn(
"mt-2 gap-2 hover:translate-y-0 hover:shadow-inner sm:mt-0",
color.btn,
)}
>
<TrackedLinkTW
href={props.cta.link}
category={props.trackingCategory}
label={props.trackingLabel}
>
{props.cta.text}
{props.cta.icon && <span className="ml-2">{props.cta.icon}</span>}
</TrackedLinkTW>
</Button>
</div>
</div>
);
}
@@ -0,0 +1,36 @@
"use client";
import { XIcon } from "lucide-react";
import { useLocalStorage } from "../../../hooks/useLocalStorage";
import { Button } from "../ui/button";
export function DismissibleAlert(props: {
title: React.ReactNode;
description: React.ReactNode;
localStorageId: string;
}) {
const [isVisible, setIsVisible] = useLocalStorage(
props.localStorageId,
true,
false,
);
if (!isVisible) return null;
return (
<div className="relative rounded-lg border border-border bg-card p-4">
<Button
onClick={() => setIsVisible(false)}
className="absolute top-4 right-4 h-auto w-auto p-1 text-muted-foreground"
aria-label="Close alert"
variant="ghost"
>
<XIcon className="size-5" />
</Button>
<div>
<h2 className="mb-0.5 font-semibold">{props.title} </h2>
<div className="text-muted-foreground text-sm">{props.description}</div>
</div>
</div>
);
}
@@ -0,0 +1,71 @@
import { cn } from "@/lib/utils";
export type Segment = {
label: string;
percent: number;
color: string;
};
type DistributionBarChartProps = {
segments: Segment[];
title: string;
};
export function DistributionBarChart(props: DistributionBarChartProps) {
const totalPercentage = props.segments.reduce(
(sum, segment) => sum + segment.percent,
0,
);
const invalidTotalPercentage = totalPercentage !== 100;
return (
<div>
<div className="mb-2 flex items-center justify-between">
<h3 className="font-medium text-sm">{props.title}</h3>
<div
className={cn(
"font-medium text-muted-foreground text-sm",
invalidTotalPercentage && "text-red-500",
)}
>
Total: {totalPercentage}%
</div>
</div>
{/* Bar */}
<div className="flex h-3 overflow-hidden rounded-lg">
{props.segments.map((segment) => {
return (
<div
key={segment.label}
className="flex h-full items-center justify-center transition-all duration-200"
style={{
width: `${segment.percent}%`,
backgroundColor: segment.color,
}}
/>
);
})}
</div>
{/* Legends */}
<div className="mt-3 flex flex-col gap-1 lg:flex-row lg:gap-6">
{props.segments.map((segment) => {
return (
<div key={segment.label} className="flex items-center gap-1.5">
<div
className="size-3 rounded-full"
style={{
backgroundColor: segment.color,
}}
/>
<p className="text-sm">
{segment.label}: {segment.percent}%
</p>
</div>
);
})}
</div>
</div>
);
}
@@ -0,0 +1,66 @@
import type { Meta, StoryObj } from "@storybook/react";
import { MultiStepStatus } from "./multi-step-status";
const meta = {
title: "Blocks/MultiStepStatus",
component: MultiStepStatus,
decorators: [
(Story) => (
<div className="container w-full max-w-md py-10">
<Story />
</div>
),
],
} satisfies Meta<typeof MultiStepStatus>;
export default meta;
type Story = StoryObj<typeof meta>;
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export const AllStates: Story = {
args: {
steps: [
{
status: "completed",
label: "Connect Wallet",
retryLabel: "Failed to connect wallet",
execute: async () => {
await sleep(1000);
},
},
{
status: "pending",
label: "Sign Message",
retryLabel: "Failed to sign message",
execute: async () => {
await sleep(1000);
},
},
{
status: "error",
label: "Approve Transaction",
retryLabel: "Transaction approval failed",
execute: async () => {
await sleep(1000);
},
},
{
status: "idle",
label: "Confirm Transaction",
retryLabel: "Transaction confirmation failed",
execute: async () => {
await sleep(1000);
},
},
{
status: "idle",
label: "Finalize",
retryLabel: "Finalization failed",
execute: async () => {
await sleep(1000);
},
},
],
},
};
@@ -0,0 +1,72 @@
"use client";
import { Button } from "@/components/ui/button";
import {
AlertCircleIcon,
CircleCheckIcon,
CircleIcon,
RefreshCwIcon,
} from "lucide-react";
import { DynamicHeight } from "../../ui/DynamicHeight";
import { Spinner } from "../../ui/Spinner/Spinner";
export type MultiStepState = {
status: "idle" | "pending" | "completed" | "error";
retryLabel: string;
label: string;
execute: () => Promise<void>;
};
export function MultiStepStatus(props: {
steps: MultiStepState[];
}) {
return (
<DynamicHeight>
<div className="space-y-4">
{props.steps.map((step) => (
<div key={step.label} className="flex items-start space-x-3 ">
{step.status === "completed" ? (
<CircleCheckIcon className="mt-0.5 size-5 flex-shrink-0 text-green-500" />
) : step.status === "pending" ? (
<Spinner className="mt-0.5 size-5 flex-shrink-0 text-foreground" />
) : step.status === "error" ? (
<AlertCircleIcon className="mt-0.5 size-5 flex-shrink-0 text-red-500" />
) : (
<CircleIcon className="mt-0.5 size-5 flex-shrink-0 text-muted-foreground/70" />
)}
<div className="flex-1">
<p
className={`font-medium ${
step.status === "pending"
? "text-foreground"
: step.status === "completed"
? "text-green-500"
: step.status === "error"
? "text-red-500"
: "text-muted-foreground/70"
}`}
>
{step.label}
</p>
{step.status === "error" && (
<div className="mt-1 space-y-2">
<p className="mb-1 text-red-500 text-sm">{step.retryLabel}</p>
<Button
variant="destructive"
size="sm"
className="gap-2"
onClick={() => step.execute()}
>
<RefreshCwIcon className="size-4" />
Retry
</Button>
</div>
)}
</div>
</div>
))}
</div>
</DynamicHeight>
);
}
@@ -34,6 +34,7 @@ interface SelectWithSearchProps
side?: "left" | "right" | "top" | "bottom";
align?: "center" | "start" | "end";
closeOnSelect?: boolean;
showCheck?: boolean;
}
export const SelectWithSearch = React.forwardRef<
@@ -52,6 +53,7 @@ export const SelectWithSearch = React.forwardRef<
popoverContentClassName,
searchPlaceholder,
closeOnSelect,
showCheck = true,
...props
},
ref,
@@ -126,7 +128,7 @@ export const SelectWithSearch = React.forwardRef<
<span
className={cn(
"truncate text-muted-foreground text-sm",
selectedOption && "text-foreground",
selectedOption && "w-full text-foreground",
)}
>
{renderOption && selectedOption
@@ -193,9 +195,11 @@ export const SelectWithSearch = React.forwardRef<
i === optionsToShow.length - 1 ? lastItemRef : undefined
}
>
<div className="flex size-4 items-center justify-center">
{isSelected && <CheckIcon className="size-4" />}
</div>
{showCheck && (
<div className="flex size-4 items-center justify-center">
{isSelected && <CheckIcon className="size-4" />}
</div>
)}
<div className="min-w-0 grow">
{renderOption ? renderOption(option) : option.label}
@@ -42,7 +42,7 @@ export function CopyTextButton(props: {
variant={props.variant || "outline"}
aria-label={props.tooltip}
className={cn(
"flex h-auto w-auto gap-2 rounded-lg px-2.5 py-1.5 font-normal text-foreground",
"flex h-auto w-auto gap-2 rounded-lg px-1.5 py-0.5 font-normal text-foreground",
props.className,
)}
onClick={(e) => {
@@ -12,6 +12,7 @@ export type ChartConfig = {
[k in string]: {
label?: React.ReactNode;
icon?: React.ComponentType;
isCurrency?: boolean;
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
@@ -0,0 +1,45 @@
"use client";
import { Button } from "@/components/ui/button";
import type React from "react";
import { useRef } from "react";
interface ImageUploadProps {
value: File | undefined;
onChange?: (files: File[]) => void;
children?: React.ReactNode;
variant?: React.ComponentProps<typeof Button>["variant"];
className?: string;
multiple?: boolean;
accept: string;
}
export function ImageUploadButton(props: ImageUploadProps) {
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
props.onChange?.(files);
};
return (
<div>
<Button
variant={props.variant}
onClick={() => fileInputRef.current?.click()}
className={props.className}
>
{props.children}
</Button>
<input
ref={fileInputRef}
type="file"
multiple={props.multiple}
accept={props.accept}
onChange={handleFileChange}
className="hidden"
aria-label="Upload image"
/>
</div>
);
}
@@ -54,11 +54,11 @@ export function ToolTipLabel(props: {
align={props.align}
sideOffset={10}
className={cn(
"max-w-[400px] whitespace-normal leading-relaxed",
"max-w-[400px] whitespace-normal p-0 leading-relaxed",
props.contentClassName,
)}
>
<div className="flex items-center gap-1.5 p-2 text-sm">
<div className="flex items-center gap-1.5 p-4 text-sm">
{props.leftIcon}
{props.label}
{props.rightIcon}
@@ -0,0 +1,11 @@
export const isProd =
(process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_VERCEL_ENV) ===
"production";
export const PROD_OR_DEV_URL = isProd ? "thirdweb.com" : "thirdweb-dev.com";
export const BASE_URL = isProd
? "https://thirdweb.com"
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
: "http://localhost:3000") || "https://thirdweb-dev.com";
-62
View File
@@ -1,62 +0,0 @@
export const DASHBOARD_THIRDWEB_CLIENT_ID =
process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID || "";
export const DASHBOARD_THIRDWEB_SECRET_KEY =
process.env.DASHBOARD_SECRET_KEY || "";
export const NEXT_PUBLIC_NEBULA_APP_CLIENT_ID =
process.env.NEXT_PUBLIC_NEBULA_APP_CLIENT_ID || "";
export const NEBULA_APP_SECRET_KEY = process.env.NEBULA_APP_SECRET_KEY || "";
export const THIRDWEB_VAULT_URL =
process.env.NEXT_PUBLIC_THIRDWEB_VAULT_URL || "";
export const THIRDWEB_ENGINE_CLOUD_URL =
process.env.NEXT_PUBLIC_ENGINE_CLOUD_URL || "";
export const THIRDWEB_API_SECRET = process.env.API_SERVER_SECRET || "";
export const IPFS_GATEWAY_URL =
(process.env.NEXT_PUBLIC_IPFS_GATEWAY_URL as string) ||
"https://{clientId}.ipfscdn.io/ipfs/{cid}/{path}";
export const isProd =
(process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_VERCEL_ENV) ===
"production";
export const PROD_OR_DEV_URL = isProd ? "thirdweb.com" : "thirdweb-dev.com";
export const DASHBOARD_STORAGE_URL =
process.env.NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER ||
"https://storage.thirdweb.com";
export const API_SERVER_URL =
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com";
export const BRIDGE_URL =
process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST || "https://bridge.thirdweb.com";
/**
* Faucet stuff
*/
// Cloudflare Turnstile Site key
export const TURNSTILE_SITE_KEY =
process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "";
export const THIRDWEB_ENGINE_FAUCET_WALLET =
process.env.NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET || "";
export const THIRDWEB_ENGINE_URL = process.env.THIRDWEB_ENGINE_URL;
export const THIRDWEB_ACCESS_TOKEN = process.env.THIRDWEB_ACCESS_TOKEN;
// Comma-separated list of chain IDs to disable faucet for.
export const DISABLE_FAUCET_CHAIN_IDS = process.env.DISABLE_FAUCET_CHAIN_IDS;
export const BASE_URL = isProd
? "https://thirdweb.com"
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
: "http://localhost:3000") || "https://thirdweb-dev.com";
export const NEXT_PUBLIC_NEBULA_URL = process.env.NEXT_PUBLIC_NEBULA_URL;
export const INSIGHT_SERVICE_API_KEY =
process.env.INSIGHT_SERVICE_API_KEY || "";
@@ -0,0 +1,36 @@
export const NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID =
process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID || "";
export const NEXT_PUBLIC_NEBULA_APP_CLIENT_ID =
process.env.NEXT_PUBLIC_NEBULA_APP_CLIENT_ID || "";
export const NEXT_PUBLIC_THIRDWEB_VAULT_URL =
process.env.NEXT_PUBLIC_THIRDWEB_VAULT_URL || "";
export const NEXT_PUBLIC_ENGINE_CLOUD_URL =
process.env.NEXT_PUBLIC_ENGINE_CLOUD_URL || "";
export const NEXT_PUBLIC_IPFS_GATEWAY_URL =
(process.env.NEXT_PUBLIC_IPFS_GATEWAY_URL as string) ||
"https://{clientId}.ipfscdn.io/ipfs/{cid}/{path}";
export const NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER =
process.env.NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER ||
"https://storage.thirdweb.com";
export const NEXT_PUBLIC_THIRDWEB_API_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com";
export const NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST || "https://bridge.thirdweb.com";
export const NEXT_PUBLIC_TURNSTILE_SITE_KEY =
process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "";
export const NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET =
process.env.NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET || "";
export const NEXT_PUBLIC_NEBULA_URL = process.env.NEXT_PUBLIC_NEBULA_URL || "";
export const NEXT_PUBLIC_DEMO_ENGINE_URL =
process.env.NEXT_PUBLIC_DEMO_ENGINE_URL || "";
@@ -0,0 +1,109 @@
import "server-only";
import { experimental_taintUniqueValue } from "react";
import { isProd } from "./env-utils";
// Make sure to taint the server only envs here with experimental_taintUniqueValue ONLY if they contain a UNIQUE sensitive value
// if an env has a generic value that may appear naturally in client components - do not taint it
export const DASHBOARD_THIRDWEB_SECRET_KEY =
process.env.DASHBOARD_SECRET_KEY || "";
experimental_taintUniqueValue(
"Do not pass DASHBOARD_THIRDWEB_SECRET_KEY to the client",
process,
DASHBOARD_THIRDWEB_SECRET_KEY,
);
export const NEBULA_APP_SECRET_KEY = process.env.NEBULA_APP_SECRET_KEY || "";
if (NEBULA_APP_SECRET_KEY) {
experimental_taintUniqueValue(
"Do not pass NEBULA_APP_SECRET_KEY to the client",
process,
NEBULA_APP_SECRET_KEY,
);
}
export const API_SERVER_SECRET = process.env.API_SERVER_SECRET || "";
if (API_SERVER_SECRET) {
experimental_taintUniqueValue(
"Do not pass API_SERVER_SECRET to the client",
process,
API_SERVER_SECRET,
);
}
export const THIRDWEB_ENGINE_URL = process.env.THIRDWEB_ENGINE_URL || "";
export const THIRDWEB_ACCESS_TOKEN = process.env.THIRDWEB_ACCESS_TOKEN || "";
if (THIRDWEB_ACCESS_TOKEN) {
experimental_taintUniqueValue(
"Do not pass THIRDWEB_ACCESS_TOKEN to the client",
process,
THIRDWEB_ACCESS_TOKEN,
);
}
// Comma-separated list of chain IDs to disable faucet for.
// Note: Can not taint this - because the value is too generic
export const DISABLE_FAUCET_CHAIN_IDS =
process.env.DISABLE_FAUCET_CHAIN_IDS || "";
export const INSIGHT_SERVICE_API_KEY =
process.env.INSIGHT_SERVICE_API_KEY || "";
// The value of INSIGHT_SERVICE_API_KEY on dev is generic - so we don't taint it
// TODO - have better INSIGHT_SERVICE_API_KEY on dev
if (isProd && INSIGHT_SERVICE_API_KEY) {
experimental_taintUniqueValue(
"Do not pass INSIGHT_SERVICE_API_KEY to the client",
process,
INSIGHT_SERVICE_API_KEY,
);
}
export const MORALIS_API_KEY = process.env.MORALIS_API_KEY || "";
if (MORALIS_API_KEY) {
experimental_taintUniqueValue(
"Do not pass MORALIS_API_KEY to the client",
process,
MORALIS_API_KEY,
);
}
export const ANALYTICS_SERVICE_URL = process.env.ANALYTICS_SERVICE_URL || "";
export const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY || "";
if (STRIPE_SECRET_KEY) {
experimental_taintUniqueValue(
"Do not pass STRIPE_SECRET_KEY to the client",
process,
STRIPE_SECRET_KEY,
);
}
export const API_ROUTES_CLIENT_ID = process.env.API_ROUTES_CLIENT_ID || "";
export const TURNSTILE_SECRET_KEY = process.env.TURNSTILE_SECRET_KEY || "";
if (TURNSTILE_SECRET_KEY) {
experimental_taintUniqueValue(
"Do not pass TURNSTILE_SECRET_KEY to the client",
process,
TURNSTILE_SECRET_KEY,
);
}
export const REDIS_URL = process.env.REDIS_URL || "";
if (REDIS_URL) {
experimental_taintUniqueValue(
"Do not pass REDIS_URL to the client",
process,
REDIS_URL,
);
}
@@ -0,0 +1,11 @@
import { getConfiguredThirdwebClient } from "./thirdweb.server";
export function getClientThirdwebClient(params?: {
jwt: string | undefined | null;
teamId: string | undefined | null;
}) {
return getConfiguredThirdwebClient({
secretKey: params?.jwt ?? undefined,
teamId: params?.teamId ?? undefined,
});
}
@@ -0,0 +1,9 @@
import "server-only";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "./server-envs";
import { getConfiguredThirdwebClient } from "./thirdweb.server";
export const serverThirdwebClient = getConfiguredThirdwebClient({
teamId: undefined,
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
});
@@ -4,7 +4,7 @@ import { useActiveAccount } from "thirdweb/react";
import type { GetAuthTokenResponse } from "../../app/(app)/api/auth/get-auth-token/route";
import { LAST_USED_TEAM_ID } from "../../constants/cookies";
import { getCookie } from "../../lib/cookie";
import { getThirdwebClient } from "./thirdweb.server";
import { getClientThirdwebClient } from "./thirdweb-client.client";
// returns a thirdweb client with optional JWT passed i
@@ -38,7 +38,7 @@ export function useThirdwebClient(jwt?: string) {
return useMemo(
// prefer jwt from props over the one from the token query if it exists
() =>
getThirdwebClient({
getClientThirdwebClient({
jwt: jwt || query.data,
teamId: lastUsedTeamId,
}),
@@ -1,9 +1,9 @@
import {
DASHBOARD_THIRDWEB_CLIENT_ID,
DASHBOARD_THIRDWEB_SECRET_KEY,
IPFS_GATEWAY_URL,
} from "@/constants/env";
NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
NEXT_PUBLIC_IPFS_GATEWAY_URL,
} from "@/constants/public-envs";
import {
THIRDWEB_BRIDGE_URL,
THIRDWEB_BUNDLER_DOMAIN,
THIRDWEB_INAPP_WALLET_DOMAIN,
THIRDWEB_INSIGHT_API_DOMAIN,
@@ -12,7 +12,7 @@ import {
THIRDWEB_SOCIAL_API_DOMAIN,
THIRDWEB_STORAGE_DOMAIN,
} from "constants/urls";
import { createThirdwebClient } from "thirdweb";
import { type ThirdwebClient, createThirdwebClient } from "thirdweb";
import { populateEip712Transaction } from "thirdweb/transaction";
import {
getTransactionDecorator,
@@ -22,15 +22,10 @@ import {
import { getZkPaymasterData } from "thirdweb/wallets/smart";
import { getVercelEnv } from "../../lib/vercel-utils";
// returns a thirdweb client with optional JWT passed in
export function getThirdwebClient(
options:
| {
jwt: string | null | undefined;
teamId: string | undefined;
}
| undefined,
) {
export function getConfiguredThirdwebClient(options: {
secretKey: string | undefined;
teamId: string | undefined;
}): ThirdwebClient {
if (getVercelEnv() !== "production") {
// if not on production: run this when creating a client to set the domains
setThirdwebDomains({
@@ -41,6 +36,7 @@ export function getThirdwebClient(
social: THIRDWEB_SOCIAL_API_DOMAIN,
bundler: THIRDWEB_BUNDLER_DOMAIN,
insight: THIRDWEB_INSIGHT_API_DOMAIN,
bridge: THIRDWEB_BRIDGE_URL,
});
}
@@ -78,12 +74,12 @@ export function getThirdwebClient(
}
return createThirdwebClient({
teamId: options?.teamId,
secretKey: options?.jwt ? options.jwt : DASHBOARD_THIRDWEB_SECRET_KEY,
clientId: DASHBOARD_THIRDWEB_CLIENT_ID,
teamId: options.teamId,
secretKey: options.secretKey,
clientId: NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
config: {
storage: {
gatewayUrl: IPFS_GATEWAY_URL,
gatewayUrl: NEXT_PUBLIC_IPFS_GATEWAY_URL,
},
},
});
+5 -4
View File
@@ -39,9 +39,10 @@ export function getFiltersFromSearchParams(params: {
};
const defaultInterval =
differenceInCalendarDays(range.to, range.from) > 30
? "week"
: ("day" as const);
params.interval ??
(differenceInCalendarDays(range.to, range.from) > 30
? ("week" as const)
: ("day" as const));
return {
range,
@@ -50,6 +51,6 @@ export function getFiltersFromSearchParams(params: {
? ("day" as const)
: params.interval === "week"
? ("week" as const)
: defaultInterval,
: (defaultInterval as "day" | "week"),
};
}
@@ -32,7 +32,6 @@ export type Account = {
image?: string | null;
name?: string;
email?: string;
advancedEnabled: boolean;
emailConfirmedAt?: string;
unconfirmedEmail?: string;
emailConfirmationWalletAddress?: string;
@@ -41,7 +40,6 @@ export type Account = {
billing: "email" | "none";
updates: "email" | "none";
};
// TODO - add image URL
};
interface UpdateAccountNotificationsInput {
@@ -53,27 +51,6 @@ interface ConfirmEmailInput {
confirmationToken: string;
}
interface UsageStorage {
sumFileSizeBytes: number;
}
export interface UsageBillableByService {
usage: {
storage: UsageStorage;
};
limits: {
storage: number;
};
rateLimits: {
storage: number;
rpc: number;
};
rateLimitedAt: {
storage?: string;
rpc?: string;
};
}
interface BillingProduct {
name: string;
id: string;
@@ -345,13 +322,14 @@ export type RotateSecretKeyAPIReturnType = {
};
};
export async function rotateSecretKeyClient(projectId: string) {
export async function rotateSecretKeyClient(params: {
teamId: string;
projectId: string;
}) {
const res = await apiServerProxy<RotateSecretKeyAPIReturnType>({
pathname: "/v2/keys/rotate-secret-key",
pathname: `/v1/teams/${params.teamId}/projects/${params.projectId}/rotate-secret-key`,
method: "POST",
body: JSON.stringify({
projectId,
}),
body: JSON.stringify({}),
headers: {
"Content-Type": "application/json",
},
@@ -11,8 +11,8 @@ import type { EngineBackendWalletType } from "lib/engine";
import { useState } from "react";
import { useActiveAccount } from "thirdweb/react";
import invariant from "tiny-invariant";
import type { ResultItem } from "../../../app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/metrics/components/StatusCodes";
import type { EngineStatus } from "../../../app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table";
import type { ResultItem } from "../../../app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/metrics/components/StatusCodes";
import type { EngineStatus } from "../../../app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table";
import { engineKeys } from "../cache-keys";
// Engine instances
@@ -1,29 +0,0 @@
"use client";
import { getSDKTheme } from "app/(app)/components/sdk-component-theme";
import { useV5DashboardChain } from "lib/v5-adapter";
import { useTheme } from "next-themes";
import type { ThirdwebClient } from "thirdweb";
import { PayEmbed } from "thirdweb/react";
export function UniversalBridgeEmbed({
chainId,
client,
}: { chainId?: number; client: ThirdwebClient }) {
const { theme } = useTheme();
const chain = useV5DashboardChain(chainId || 1);
return (
<PayEmbed
client={client}
payOptions={{
mode: "fund_wallet",
prefillBuy: {
chain,
amount: "0.01",
},
}}
theme={getSDKTheme(theme === "light" ? "light" : "dark")}
/>
);
}
@@ -1,40 +0,0 @@
import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Metadata } from "next";
import { UniversalBridgeEmbed } from "./components/client/UniversalBridgeEmbed";
const title = "Universal Bridge: Swap, Bridge, and On-Ramp";
const description =
"Swap, bridge, and on-ramp to any EVM chain with thirdweb's Universal Bridge.";
export const metadata: Metadata = {
title,
description,
openGraph: {
title,
description,
},
};
export default async function RoutesPage({
searchParams,
}: { searchParams: Record<string, string | string[]> }) {
const { chainId } = searchParams;
const client = getThirdwebClient(undefined);
return (
<div className="relative mx-auto flex h-screen w-full flex-col items-center justify-center overflow-hidden border py-10">
<main className="container z-10 flex justify-center">
<UniversalBridgeEmbed
chainId={chainId ? Number(chainId) : undefined}
client={client}
/>
</main>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt=""
src="/assets/login/background.svg"
className="-bottom-12 -right-12 pointer-events-none absolute lg:right-0 lg:bottom-0"
/>
</div>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

@@ -47,7 +47,7 @@ export const SearchInput: React.FC = () => {
<div className="group relative w-full">
<SearchIcon className="-translate-y-1/2 absolute top-[50%] left-3 size-4 text-muted-foreground" />
<Input
placeholder="Search with anything!"
placeholder="Search by token name or symbol"
className="h-10 rounded-lg bg-card py-2 pl-9 lg:min-w-[300px]"
defaultValue={searchParams?.get("query") || ""}
onChange={(e) => handleSearch(e.target.value)}
@@ -1,8 +1,8 @@
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import type { ThirdwebClient } from "thirdweb";
import { defineChain } from "thirdweb";
import { getChainMetadata } from "thirdweb/chains";
import { serverThirdwebClient } from "../../../../../../../@/constants/thirdweb-client.server";
type RouteListCardProps = {
originChainId: number;
@@ -15,7 +15,6 @@ type RouteListCardProps = {
destinationTokenIconUri?: string | null;
destinationTokenSymbol: string;
destinationTokenName: string;
client: ThirdwebClient;
};
export async function RouteListCard({
@@ -27,7 +26,6 @@ export async function RouteListCard({
destinationTokenAddress,
destinationTokenIconUri,
destinationTokenName,
client,
}: RouteListCardProps) {
const [
originChain,
@@ -42,13 +40,13 @@ export async function RouteListCard({
originTokenIconUri
? resolveSchemeWithErrorHandler({
uri: originTokenIconUri,
client,
client: serverThirdwebClient,
})
: undefined,
destinationTokenIconUri
? resolveSchemeWithErrorHandler({
uri: destinationTokenIconUri,
client,
client: serverThirdwebClient,
})
: undefined,
]);
@@ -1,7 +1,7 @@
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { TableCell, TableRow } from "@/components/ui/table";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import type { ThirdwebClient } from "thirdweb";
import { defineChain, getChainMetadata } from "thirdweb/chains";
type RouteListRowProps = {
@@ -15,7 +15,6 @@ type RouteListRowProps = {
destinationTokenIconUri?: string | null;
destinationTokenSymbol?: string;
destinationTokenName?: string;
client: ThirdwebClient;
};
export async function RouteListRow({
@@ -27,7 +26,6 @@ export async function RouteListRow({
destinationTokenAddress,
destinationTokenIconUri,
destinationTokenSymbol,
client,
}: RouteListRowProps) {
const [
originChain,
@@ -42,32 +40,32 @@ export async function RouteListRow({
originTokenIconUri
? resolveSchemeWithErrorHandler({
uri: originTokenIconUri,
client,
client: serverThirdwebClient,
})
: undefined,
destinationTokenIconUri
? resolveSchemeWithErrorHandler({
uri: destinationTokenIconUri,
client,
client: serverThirdwebClient,
})
: undefined,
]);
return (
<TableRow linkBox className="hover:bg-accent/50">
<TableRow linkBox className="group transition-colors hover:bg-accent/50">
<TableCell>
<div className="flex flex-row items-center gap-4">
<div className="flex items-center gap-1">
<div className="flex flex-row items-center gap-3">
<div className="flex items-center gap-2">
{resolvedOriginTokenIconUri ? (
// For now we're using a normal img tag because the domain for these images is unknown
// eslint-disable-next-line @next/next/no-img-element
<img
src={resolvedOriginTokenIconUri}
alt={originTokenAddress}
className="size-6 rounded-full border border-muted-foreground"
className="size-7 rounded-full border border-border/50 shadow-sm transition-transform group-hover:scale-105"
/>
) : (
<div className="size-6 rounded-full bg-muted-foreground" />
<div className="size-7 rounded-full bg-muted-foreground/20" />
)}
{originTokenSymbol && (
<CopyTextButton
@@ -78,7 +76,7 @@ export async function RouteListRow({
: originTokenSymbol
}
tooltip="Copy Token Address"
className="relative z-10 text-base"
className="relative z-10 font-medium text-base"
variant="ghost"
copyIconPosition="right"
/>
@@ -87,22 +85,22 @@ export async function RouteListRow({
</div>
</TableCell>
<TableCell className="text-muted-foreground">
{originChain.name}
<TableCell className="text-muted-foreground/90">
<span className="font-medium">{originChain.name}</span>
</TableCell>
<TableCell>
<div className="flex flex-row items-center gap-4">
<div className="flex items-center gap-1">
<div className="flex flex-row items-center gap-3">
<div className="flex items-center gap-2">
{resolvedDestinationTokenIconUri ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={resolvedDestinationTokenIconUri}
alt={destinationTokenAddress}
className="size-6 rounded-full border border-muted-foreground"
className="size-7 rounded-full border border-border/50 shadow-sm transition-transform group-hover:scale-105"
/>
) : (
<div className="size-6 rounded-full bg-muted-foreground" />
<div className="size-7 rounded-full bg-muted-foreground/20" />
)}
{destinationTokenSymbol && (
<CopyTextButton
@@ -113,7 +111,7 @@ export async function RouteListRow({
: destinationTokenSymbol
}
tooltip="Copy Token Address"
className="relative z-10 text-base"
className="relative z-10 font-medium text-base"
variant="ghost"
copyIconPosition="right"
/>
@@ -122,8 +120,8 @@ export async function RouteListRow({
</div>
</TableCell>
<TableCell className="text-muted-foreground">
{destinationChain.name}
<TableCell className="text-muted-foreground/90">
<span className="font-medium">{destinationChain.name}</span>
</TableCell>
</TableRow>
);
@@ -6,9 +6,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Address } from "thirdweb";
import { checksumAddress } from "thirdweb/utils";
import { getRoutes } from "../../../utils";
import { ChainlistPagination } from "../client/pagination";
import { RouteListCard } from "./routelist-card";
@@ -26,18 +24,15 @@ export type SearchParams = Partial<{
// 120 is divisible by 2, 3, and 4 so card layout looks nice
const DEFAULT_PAGE_SIZE = 120;
const DEFAULT_PAGE = 1;
async function getRoutesToRender(params: SearchParams) {
const filters: Partial<{
limit: number;
offset: number;
originQuery?: string;
destinationQuery?: string;
originChainId?: number;
originTokenAddress?: Address;
destinationChainId?: number;
destinationTokenAddress?: Address;
originTextQuery?: string;
destinationTextQuery?: string;
}> = {};
if (params.type === "origin" || typeof params.type === "undefined") {
@@ -46,7 +41,7 @@ async function getRoutesToRender(params: SearchParams) {
} else if (Number.isInteger(Number(params.query))) {
filters.originChainId = Number(params.query);
} else if (params.query) {
filters.originTextQuery = params.query;
filters.originQuery = params.query;
}
} else if (params.type === "destination") {
if (params.query?.startsWith("0x")) {
@@ -54,69 +49,20 @@ async function getRoutesToRender(params: SearchParams) {
} else if (Number.isInteger(Number(params.query))) {
filters.destinationChainId = Number(params.query);
} else if (params.query) {
filters.destinationTextQuery = params.query;
filters.destinationQuery = params.query;
}
}
// Temporary, will update this after the /routes endpoint
let routes = await getRoutes({ limit: 100_000 });
const totalCount = routes.length;
if (filters.originChainId) {
routes = routes.filter(
(route) => route.originToken.chainId === filters.originChainId,
);
}
if (filters.originTokenAddress) {
const originTokenAddress = filters.originTokenAddress;
routes = routes.filter(
(route) =>
checksumAddress(route.originToken.address) ===
checksumAddress(originTokenAddress),
);
}
if (filters.destinationChainId) {
routes = routes.filter(
(route) => route.destinationToken.chainId === filters.destinationChainId,
);
}
if (filters.destinationTokenAddress) {
const destinationTokenAddress = filters.destinationTokenAddress;
routes = routes.filter(
(route) =>
checksumAddress(route.destinationToken.address) ===
checksumAddress(destinationTokenAddress),
);
}
if (filters.originTextQuery) {
const originTextQuery = filters.originTextQuery.toLowerCase();
routes = routes.filter((route) => {
return (
route.originToken.name.toLowerCase().includes(originTextQuery) ||
route.originToken.symbol.toLowerCase().includes(originTextQuery)
);
});
}
if (filters.destinationTextQuery) {
const destinationTextQuery = filters.destinationTextQuery.toLowerCase();
routes = routes.filter((route) => {
return (
route.destinationToken.name
.toLowerCase()
.includes(destinationTextQuery) ||
route.destinationToken.symbol
.toLowerCase()
.includes(destinationTextQuery)
);
});
}
const routes = await getRoutes({
limit: DEFAULT_PAGE_SIZE,
offset: DEFAULT_PAGE_SIZE * ((params.page || 1) - 1),
originQuery: filters.originQuery,
destinationQuery: filters.destinationQuery,
});
return {
routesToRender: routes,
totalCount,
filteredCount: routes.length,
routesToRender: routes.data,
totalCount: routes.meta.totalCount,
filteredCount: routes.meta.filteredCount,
};
}
@@ -129,37 +75,37 @@ export async function RoutesData(props: {
props.searchParams,
);
// pagination
const totalPages = Math.ceil(routesToRender.length / DEFAULT_PAGE_SIZE);
const activePage = Number(props.searchParams.page || DEFAULT_PAGE);
const pageSize = DEFAULT_PAGE_SIZE;
const startIndex = (activePage - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedRoutes = routesToRender.slice(startIndex, endIndex);
const client = getThirdwebClient(undefined);
const totalPages = Math.ceil(filteredCount / DEFAULT_PAGE_SIZE);
return (
<>
<main>
{/* empty state */}
{paginatedRoutes.length === 0 ? (
{routesToRender.length === 0 ? (
<div className="flex h-[300px] items-center justify-center rounded-lg border p-8 lg:h-[500px]">
<p className="text-2xl">No Results found</p>
</div>
) : props.activeView === "table" ? (
<TableContainer>
<TableContainer className="overflow-hidden rounded-xl border border-border/50 bg-card/50 shadow-sm transition-all">
<Table>
<TableHeader className="z-0">
<TableRow>
<TableHead>Origin Token</TableHead>
<TableHead>Origin Chain</TableHead>
<TableHead>Destination Token</TableHead>
<TableHead>Destination Chain</TableHead>
<TableRow className="border-border/50 border-b bg-muted/50">
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
Origin Token
</TableHead>
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
Origin Chain
</TableHead>
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
Destination Token
</TableHead>
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
Destination Chain
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{paginatedRoutes.map((route) => (
{routesToRender.map((route) => (
<RouteListRow
key={`${route.originToken.chainId}:${route.originToken.address}-${route.destinationToken.chainId}:${route.destinationToken.address}`}
originChainId={route.originToken.chainId}
@@ -172,7 +118,6 @@ export async function RoutesData(props: {
destinationTokenIconUri={route.destinationToken.iconUri}
destinationTokenSymbol={route.destinationToken.symbol}
destinationTokenName={route.destinationToken.name}
client={client}
/>
))}
</TableBody>
@@ -180,7 +125,7 @@ export async function RoutesData(props: {
</TableContainer>
) : (
<ul className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{paginatedRoutes.map((route) => (
{routesToRender.map((route) => (
<li
key={`${route.originToken.chainId}:${route.originToken.address}-${route.destinationToken.chainId}:${route.destinationToken.address}`}
className="h-full"
@@ -196,7 +141,6 @@ export async function RoutesData(props: {
destinationTokenIconUri={route.destinationToken.iconUri}
destinationTokenSymbol={route.destinationToken.symbol}
destinationTokenName={route.destinationToken.name}
client={client}
/>
</li>
))}
@@ -205,12 +149,15 @@ export async function RoutesData(props: {
</main>
<div className="h-10" />
{totalPages > 1 && (
<ChainlistPagination totalPages={totalPages} activePage={activePage} />
<ChainlistPagination
totalPages={totalPages}
activePage={Number(props.searchParams.page) || 1}
/>
)}
<div className="h-4" />
<p className="text-balance text-center text-muted-foreground text-sm">
Showing{" "}
<span className="text-accent-foreground">{paginatedRoutes.length}</span>{" "}
<span className="text-accent-foreground">{routesToRender.length}</span>{" "}
out of{" "}
{filteredCount !== totalCount ? (
<>
@@ -1,3 +1,4 @@
import { ArrowUpRightIcon } from "lucide-react";
import type { Metadata } from "next";
import { headers } from "next/headers";
import { getAuthToken } from "../../../api/lib/getAuthToken";
@@ -9,7 +10,7 @@ import {
type SearchParams,
} from "./components/server/routes-table";
const title = "Routes: Swap, Bridge, and On-Ramp";
const title = "Universal Bridge Routes: Swap, Bridge, and Onramp";
const description =
"A list of token routes for swapping, bridging, and on-ramping between EVM chains with thirdweb.";
@@ -43,15 +44,15 @@ export default async function RoutesPage(props: {
return (
<section className="container mx-auto flex h-full flex-col px-4 py-10">
<header className="flex flex-col gap-4">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-row items-center justify-between gap-4 lg:flex-col lg:justify-start">
<header className="flex flex-col gap-6">
<div className="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-col gap-2">
<h1 className="font-semibold text-4xl tracking-tighter lg:text-5xl">
Routes
</h1>
</div>
<div className="flex flex-row items-end gap-4 lg:flex-col ">
<div className="flex w-full flex-row gap-4">
<div className="flex flex-row items-end gap-4 lg:flex-col">
<div className="flex w-full flex-row items-center gap-4">
<SearchInput />
<QueryType activeType={activeType} />
<RouteListView activeView={activeView} />
@@ -60,6 +61,29 @@ export default async function RoutesPage(props: {
</div>
</header>
<div className="h-10" />
<div className="relative overflow-hidden rounded-lg border-2 border-green-500/20 bg-gradient-to-br from-card/80 to-card/50 p-4 shadow-[inset_0_1px_2px_0_rgba(0,0,0,0.02)]">
<div className="absolute inset-0 bg-gradient-to-br from-green-500/5 to-transparent" />
<div className="relative flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div className="flex flex-col gap-1">
<h3 className="font-medium text-lg">
Get Started with Universal Bridge
</h3>
<p className="text-muted-foreground text-sm">
Simple, instant, and secure payments across any token and chain.
</p>
</div>
<a
href="https://portal.thirdweb.com/pay"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-md bg-green-600 px-4 py-2 font-medium text-sm text-white transition-all hover:bg-green-600/90 hover:shadow-sm"
>
Learn More
<ArrowUpRightIcon className="size-4" />
</a>
</div>
</div>
<div className="h-10" />
<RoutesData
searchParams={searchParams}
activeView={activeView}
@@ -1,35 +1,70 @@
import "server-only";
import { BRIDGE_URL, DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST } from "@/constants/public-envs";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import type { Address } from "thirdweb";
import type { Route } from "./types/route";
export async function getRoutes({
limit,
offset,
originQuery,
destinationQuery,
originChainId,
destinationChainId,
originTokenAddress,
destinationTokenAddress,
}: {
limit?: number;
offset?: number;
originQuery?: string;
destinationQuery?: string;
originChainId?: number;
destinationChainId?: number;
originTokenAddress?: Address;
destinationTokenAddress?: Address;
} = {}) {
const url = new URL(`${BRIDGE_URL}/v1/routes`);
const url = new URL(`${NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST}/v1/routes`);
if (limit) {
url.searchParams.set("limit", limit.toString());
}
if (offset) {
url.searchParams.set("offset", offset.toString());
}
if (originQuery) {
url.searchParams.set("originQuery", originQuery);
}
if (destinationQuery) {
url.searchParams.set("destinationQuery", destinationQuery);
}
if (originChainId) {
url.searchParams.set("originChainId", originChainId.toString());
}
if (destinationChainId) {
url.searchParams.set("destinationChainId", destinationChainId.toString());
}
if (originTokenAddress) {
url.searchParams.set("originTokenAddress", originTokenAddress);
}
if (destinationTokenAddress) {
url.searchParams.set("destinationTokenAddress", destinationTokenAddress);
}
url.searchParams.set("sortBy", "popularity");
// It's faster to filter client side, doesn't seem to be a big performance boost to paginate/filter server side
const token = await getAuthToken();
const routesResponse = await fetch(url, {
headers: token
? {
authorization: `Bearer ${token}`,
}
: {
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
},
headers: {
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
},
next: { revalidate: 60 * 60 },
});
if (!routesResponse.ok) {
throw new Error("Failed to fetch routes");
}
const routes: { data: Route[] } = await routesResponse.json();
const routes: {
data: Route[];
meta: { totalCount: number; filteredCount: number };
} = await routesResponse.json();
return routes.data;
return routes;
}
@@ -20,9 +20,9 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
THIRDWEB_ENGINE_FAUCET_WALLET,
TURNSTILE_SITE_KEY,
} from "@/constants/env";
NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET,
NEXT_PUBLIC_TURNSTILE_SITE_KEY,
} from "@/constants/public-envs";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -90,7 +90,7 @@ export function FaucetButton({
// eslint-disable-next-line no-restricted-syntax
const definedChain = mapV4ChainToV5Chain(chain);
const faucetWalletBalanceQuery = useWalletBalance({
address: THIRDWEB_ENGINE_FAUCET_WALLET,
address: NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET,
chain: definedChain,
client,
});
@@ -260,7 +260,7 @@ export function FaucetButton({
<FormControl>
<Turnstile
className="mt-4"
siteKey={TURNSTILE_SITE_KEY}
siteKey={NEXT_PUBLIC_TURNSTILE_SITE_KEY}
onSuccess={(token) => field.onChange(token)}
/>
</FormControl>
@@ -342,7 +342,7 @@ function SendFundsToFaucetModalContent(props: {
const sendNativeTokenTx = prepareTransaction({
chain: props.chain,
client: props.client,
to: THIRDWEB_ENGINE_FAUCET_WALLET,
to: NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET,
value: toWei(values.amount.toString()),
});
@@ -370,8 +370,8 @@ function SendFundsToFaucetModalContent(props: {
copyIconPosition="right"
variant="outline"
className="w-full justify-between bg-card py-2 font-mono"
textToCopy={THIRDWEB_ENGINE_FAUCET_WALLET}
textToShow={THIRDWEB_ENGINE_FAUCET_WALLET}
textToCopy={NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET}
textToShow={NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET}
tooltip={undefined}
/>
</div>
@@ -3,7 +3,8 @@
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Skeleton } from "@/components/ui/skeleton";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { DASHBOARD_THIRDWEB_CLIENT_ID, isProd } from "@/constants/env";
import { isProd } from "@/constants/env-utils";
import { NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID } from "@/constants/public-envs";
import { useQuery } from "@tanstack/react-query";
import { CircleCheckIcon, XIcon } from "lucide-react";
import { hostnameEndsWith } from "utils/url";
@@ -13,7 +14,7 @@ function useChainStatswithRPC(_rpcUrl: string) {
let rpcUrl = _rpcUrl.replace(
// eslint-disable-next-line no-template-curly-in-string
"${THIRDWEB_API_KEY}",
DASHBOARD_THIRDWEB_CLIENT_ID,
NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
);
// based on the environment hit dev or production
@@ -31,8 +32,8 @@ function useChainStatswithRPC(_rpcUrl: string) {
method: "POST",
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_blockNumber",
params: [],
method: "eth_getBlockByNumber",
params: ["latest", false],
id: 1,
}),
});
@@ -40,9 +41,12 @@ function useChainStatswithRPC(_rpcUrl: string) {
const json = await res.json();
const latency = (performance.now() - startTimeStamp).toFixed(0);
const blockNumber = Number.parseInt(json.result.number, 16);
const blockGasLimit = Number.parseInt(json.result.gasLimit, 16);
return {
latency,
blockNumber: Number.parseInt(json.result, 16),
blockNumber,
blockGasLimit,
};
},
refetchInterval: (query) => {
@@ -114,6 +118,21 @@ export function ChainLiveStats(props: { rpc: string }) {
</div>
)}
</PrimaryInfoItem>
{/* Block Gas Limit */}
<PrimaryInfoItem title="Block Gas Limit" titleIcon={<PulseDot />}>
{stats.isError ? (
<p className="fade-in-0 animate-in text-destructive-text">N/A</p>
) : stats.data ? (
<p className="fade-in-0 animate-in">
{stats.data.blockGasLimit ?? "N/A"}
</p>
) : (
<div className="flex h-[28px] w-[140px] py-1">
<Skeleton className="h-full w-full" />
</div>
)}
</PrimaryInfoItem>
</>
);
}
@@ -1,6 +1,5 @@
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import type { ThirdwebClient } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { ChainIcon } from "../../../../components/server/chain-icon";
import { PayModalButton } from "../client/PayModal";
@@ -9,7 +8,6 @@ import { SectionTitle } from "./SectionTitle";
export function BuyFundsSection(props: {
chain: ChainMetadata;
client: ThirdwebClient;
}) {
const sanitizedChainName = props.chain.name.replace("Mainnet", "").trim();
@@ -22,7 +20,6 @@ export function BuyFundsSection(props: {
<ChainIcon
className="-mr-2 size-12 rounded-full border p-1"
iconUrl={props.chain.icon?.url}
client={props.client}
/>
<CreditCardIcon
bg="hsl(var(--background))"
@@ -1,4 +1,3 @@
import type { ThirdwebClient } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { getFaucetClaimAmount } from "../../../../../../api/testnet-faucet/claim/claim-amount";
import { ChainIcon } from "../../../../components/server/chain-icon";
@@ -9,9 +8,8 @@ import { SectionTitle } from "./SectionTitle";
export async function FaucetSection(props: {
chain: ChainMetadata;
isLoggedIn: boolean;
client: ThirdwebClient;
}) {
const { chain, isLoggedIn, client } = props;
const { chain, isLoggedIn } = props;
// Check eligibility.
const sanitizedChainName = chain.name.replace("Mainnet", "").trim();
@@ -26,7 +24,6 @@ export async function FaucetSection(props: {
<ChainIcon
className="-mr-2 size-12 rounded-full border p-1"
iconUrl={props.chain.icon?.url}
client={client}
/>
<GiftIcon bg="hsl(var(--background))" className="-ml-2 size-12" />
</div>
@@ -1,7 +1,6 @@
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import Link from "next/link";
import type { ThirdwebClient } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { mapV4ChainToV5Chain } from "../../../../../../../../contexts/map-chains";
import { ChainIcon } from "../../../../components/server/chain-icon";
@@ -11,7 +10,6 @@ type ChainHeaderProps = {
headerImageUrl?: string;
logoUrl?: string;
chain: ChainMetadata;
client: ThirdwebClient;
};
// TODO: improve the behavior when clicking "Get started with thirdweb", currently just redirects to the dashboard
@@ -41,7 +39,6 @@ export function ChainHeader(props: ChainHeaderProps) {
{/* chain logo */}
<ChainIcon
client={props.client}
iconUrl={props.logoUrl}
className={cn(
"-translate-y-[50%] absolute top-0 left-0 size-20 overflow-hidden rounded-full border border-border bg-muted p-2 lg:size-36 lg:p-4",
@@ -13,7 +13,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { ChevronDownIcon, TicketCheckIcon } from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";
@@ -71,10 +71,7 @@ export default async function ChainPageLayout(props: {
}
const chainMetadata = await getChainMetadata(chain.chainId);
const client = getThirdwebClient({
jwt: authToken,
teamId: undefined,
});
const client = getClientThirdwebClient(undefined);
const chainPromptPrefix = `\
You are assisting users exploring the chain ${chain.name} (Chain ID: ${chain.chainId}). Provide concise insights into the types of applications and activities prevalent on this chain, such as DeFi protocols, NFT marketplaces, or gaming platforms. Highlight notable projects or trends without delving into technical details like consensus mechanisms or gas fees.
@@ -163,7 +160,6 @@ The following is the user's message:
headerImageUrl={chainMetadata?.headerImgUrl}
logoUrl={chain.icon?.url}
chain={chain}
client={client}
/>
<div className="h-4 md:h-8" />
@@ -1,8 +1,7 @@
/* eslint-disable @next/next/no-img-element */
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { ImageResponse } from "next/og";
import { download } from "thirdweb/storage";
import { fetchChain } from "utils/fetchChain";
import { serverThirdwebClient } from "../../../../../../@/constants/thirdweb-client.server";
// Route segment config
export const runtime = "edge";
@@ -79,7 +78,7 @@ export default async function Image({
chain.icon?.url && hasWorkingChainIcon
? download({
uri: chain.icon.url,
client: getThirdwebClient(undefined),
client: serverThirdwebClient,
}).then((res) => res.arrayBuffer())
: undefined,
// download the background image (based on chain)
@@ -1,6 +1,5 @@
import { CircleAlertIcon } from "lucide-react";
import { getRawAccount } from "../../../../account/settings/getAccount";
import { getUserThirdwebClient } from "../../../../api/lib/getAuthToken";
import { getChain, getChainMetadata } from "../../utils";
import NextSteps from "./components/client/NextSteps";
import { BuyFundsSection } from "./components/server/BuyFundsSection";
@@ -19,7 +18,6 @@ export default async function Page(props: {
const chainMetadata = await getChainMetadata(chain.chainId);
const isDeprecated = chain.status === "deprecated";
const client = await getUserThirdwebClient();
const account = await getRawAccount();
return (
@@ -44,9 +42,9 @@ export default async function Page(props: {
{/* Faucet / Buy Funds */}
{chain.testnet ? (
<FaucetSection chain={chain} isLoggedIn={!!account} client={client} />
<FaucetSection chain={chain} isLoggedIn={!!account} />
) : chain.services.find((c) => c.service === "pay" && c.enabled) ? (
<BuyFundsSection chain={chain} client={client} />
<BuyFundsSection chain={chain} />
) : null}
{/* Chain Overview */}
@@ -1,6 +1,7 @@
"use client";
import type { ThirdwebContract } from "thirdweb";
import type { ProjectMeta } from "../../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { ErrorPage, LoadingPage } from "../../_components/page-skeletons";
import { RedirectToContractOverview } from "../../_components/redirect-contract-overview.client";
import { useContractPageMetadata } from "../../_hooks/useContractPageMetadata";
@@ -9,6 +10,7 @@ import { ContractDirectListingsPage } from "./ContractDirectListingsPage";
export function ContractDirectListingsPageClient(props: {
contract: ThirdwebContract;
isLoggedIn: boolean;
projectMeta: ProjectMeta | undefined;
}) {
const metadataQuery = useContractPageMetadata(props.contract);
@@ -21,7 +23,12 @@ export function ContractDirectListingsPageClient(props: {
}
if (!metadataQuery.data.isDirectListingSupported) {
return <RedirectToContractOverview contract={props.contract} />;
return (
<RedirectToContractOverview
contract={props.contract}
projectMeta={props.projectMeta}
/>
);
}
return (
@@ -1,46 +1,18 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../../_utils/getContractFromParams";
import { getContractPageMetadata } from "../../_utils/getContractPageMetadata";
import { ContractDirectListingsPage } from "./ContractDirectListingsPage";
import { ContractDirectListingsPageClient } from "./ContractDirectListingsPage.client";
import type { PublicContractPageParams } from "../../types";
import { SharedDirectListingsPage } from "./shared-direct-listings-page";
export default async function Page(props: {
params: Promise<{
contractAddress: string;
chain_id: string;
}>;
params: Promise<PublicContractPageParams>;
}) {
const params = await props.params;
const account = await getRawAccount();
const info = await getContractPageParamsInfo(params);
if (!info) {
notFound();
}
if (info.chainMetadata.chainId === localhost.id) {
return (
<ContractDirectListingsPageClient
contract={info.contract}
isLoggedIn={!!account}
/>
);
}
const { isDirectListingSupported, isInsightSupported } =
await getContractPageMetadata(info.contract);
if (!isDirectListingSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
const [params, account] = await Promise.all([props.params, getRawAccount()]);
return (
<ContractDirectListingsPage
contract={info.contract}
<SharedDirectListingsPage
contractAddress={params.contractAddress}
chainIdOrSlug={params.chain_id}
projectMeta={undefined}
isLoggedIn={!!account}
isInsightSupported={isInsightSupported}
/>
);
}
@@ -0,0 +1,53 @@
import { notFound } from "next/navigation";
import type { ProjectMeta } from "../../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { redirectToContractLandingPage } from "../../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/utils";
import { getContractPageParamsInfo } from "../../_utils/getContractFromParams";
import { getContractPageMetadata } from "../../_utils/getContractPageMetadata";
import { ContractDirectListingsPage } from "./ContractDirectListingsPage";
import { ContractDirectListingsPageClient } from "./ContractDirectListingsPage.client";
export async function SharedDirectListingsPage(props: {
contractAddress: string;
chainIdOrSlug: string;
projectMeta: ProjectMeta | undefined;
isLoggedIn: boolean;
}) {
const info = await getContractPageParamsInfo({
contractAddress: props.contractAddress,
chainIdOrSlug: props.chainIdOrSlug,
teamId: props.projectMeta?.teamId,
});
if (!info) {
notFound();
}
if (info.isLocalhostChain) {
return (
<ContractDirectListingsPageClient
contract={info.clientContract}
isLoggedIn={props.isLoggedIn}
projectMeta={props.projectMeta}
/>
);
}
const { isDirectListingSupported, isInsightSupported } =
await getContractPageMetadata(info.serverContract);
if (!isDirectListingSupported) {
redirectToContractLandingPage({
chainIdOrSlug: props.chainIdOrSlug,
contractAddress: props.contractAddress,
projectMeta: props.projectMeta,
});
}
return (
<ContractDirectListingsPage
contract={info.clientContract}
isLoggedIn={props.isLoggedIn}
isInsightSupported={isInsightSupported}
/>
);
}
@@ -1,6 +1,7 @@
"use client";
import type { ThirdwebContract } from "thirdweb";
import type { ProjectMeta } from "../../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { ErrorPage, LoadingPage } from "../../_components/page-skeletons";
import { RedirectToContractOverview } from "../../_components/redirect-contract-overview.client";
import { useContractPageMetadata } from "../../_hooks/useContractPageMetadata";
@@ -9,6 +10,7 @@ import { ContractEnglishAuctionsPage } from "./ContractEnglishAuctionsPage";
export function ContractEnglishAuctionsPageClient(props: {
contract: ThirdwebContract;
isLoggedIn: boolean;
projectMeta: ProjectMeta | undefined;
}) {
const metadataQuery = useContractPageMetadata(props.contract);
@@ -21,7 +23,12 @@ export function ContractEnglishAuctionsPageClient(props: {
}
if (!metadataQuery.data.isEnglishAuctionSupported) {
return <RedirectToContractOverview contract={props.contract} />;
return (
<RedirectToContractOverview
contract={props.contract}
projectMeta={props.projectMeta}
/>
);
}
return (
@@ -1,47 +1,18 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../../_utils/getContractFromParams";
import { getContractPageMetadata } from "../../_utils/getContractPageMetadata";
import { ContractEnglishAuctionsPage } from "./ContractEnglishAuctionsPage";
import { ContractEnglishAuctionsPageClient } from "./ContractEnglishAuctionsPage.client";
import type { PublicContractPageParams } from "../../types";
import { SharedEnglishAuctionsPage } from "./shared-english-auctions-page";
export default async function Page(props: {
params: Promise<{
contractAddress: string;
chain_id: string;
}>;
params: Promise<PublicContractPageParams>;
}) {
const params = await props.params;
const info = await getContractPageParamsInfo(params);
if (!info) {
notFound();
}
const twAccount = await getRawAccount();
if (info.chainMetadata.chainId === localhost.id) {
return (
<ContractEnglishAuctionsPageClient
contract={info.contract}
isLoggedIn={!!twAccount}
/>
);
}
const { isEnglishAuctionSupported, isInsightSupported } =
await getContractPageMetadata(info.contract);
if (!isEnglishAuctionSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
const [params, account] = await Promise.all([props.params, getRawAccount()]);
return (
<ContractEnglishAuctionsPage
contract={info.contract}
isLoggedIn={!!twAccount}
isInsightSupported={isInsightSupported}
<SharedEnglishAuctionsPage
contractAddress={params.contractAddress}
chainIdOrSlug={params.chain_id}
projectMeta={undefined}
isLoggedIn={!!account}
/>
);
}
@@ -0,0 +1,52 @@
import { notFound } from "next/navigation";
import type { ProjectMeta } from "../../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { redirectToContractLandingPage } from "../../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/utils";
import { getContractPageParamsInfo } from "../../_utils/getContractFromParams";
import { getContractPageMetadata } from "../../_utils/getContractPageMetadata";
import { ContractEnglishAuctionsPage } from "./ContractEnglishAuctionsPage";
import { ContractEnglishAuctionsPageClient } from "./ContractEnglishAuctionsPage.client";
export async function SharedEnglishAuctionsPage(props: {
contractAddress: string;
chainIdOrSlug: string;
projectMeta: ProjectMeta | undefined;
isLoggedIn: boolean;
}) {
const info = await getContractPageParamsInfo({
contractAddress: props.contractAddress,
chainIdOrSlug: props.chainIdOrSlug,
teamId: props.projectMeta?.teamId,
});
if (!info) {
notFound();
}
if (info.isLocalhostChain) {
return (
<ContractEnglishAuctionsPageClient
contract={info.clientContract}
isLoggedIn={props.isLoggedIn}
projectMeta={props.projectMeta}
/>
);
}
const metadata = await getContractPageMetadata(info.serverContract);
if (!metadata.isEnglishAuctionSupported) {
redirectToContractLandingPage({
contractAddress: props.contractAddress,
chainIdOrSlug: props.chainIdOrSlug,
projectMeta: props.projectMeta,
});
}
return (
<ContractEnglishAuctionsPage
contract={info.clientContract}
isLoggedIn={props.isLoggedIn}
isInsightSupported={metadata.isInsightSupported}
/>
);
}
@@ -3,6 +3,8 @@ import { TrackedLinkTW } from "@/components/ui/tracked-link";
import { useMemo } from "react";
import { type NFT, ZERO_ADDRESS } from "thirdweb";
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { buildContractPagePath } from "../_utils/contract-page-path";
type NFTWithContract = NFT & { contractAddress: string; chainId: number };
@@ -29,6 +31,7 @@ interface NFTCardsProps {
trackingCategory: string;
isPending: boolean;
allNfts?: boolean;
projectMeta: ProjectMeta | undefined;
}
export const NFTCards: React.FC<NFTCardsProps> = ({
@@ -36,6 +39,7 @@ export const NFTCards: React.FC<NFTCardsProps> = ({
trackingCategory,
isPending,
allNfts,
projectMeta,
}) => {
const dummyData = useMemo(() => {
return Array.from({
@@ -87,7 +91,12 @@ export const NFTCards: React.FC<NFTCardsProps> = ({
<TrackedLinkTW
category={trackingCategory}
label="view_nft"
href={`/${token.chainId}/${token.contractAddress}/nfts/${tokenId}`}
href={buildContractPagePath({
projectMeta,
chainIdOrSlug: token.chainId.toString(),
contractAddress: token.contractAddress,
subpath: `/nfts/${tokenId}`,
})}
className="before:absolute before:inset-0"
>
{v.name}
@@ -126,8 +126,10 @@ const getClaimConditionTypeFromPhase = (
if (!phase.snapshot) {
return "public";
}
if (phase.snapshot) {
if (
phase.price === "0" &&
typeof phase.snapshot !== "string" &&
phase.snapshot.length === 1 &&
phase.snapshot.some(
@@ -3,10 +3,13 @@
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { useEffect, useRef } from "react";
import type { ThirdwebContract } from "thirdweb";
import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { buildContractPagePath } from "../_utils/contract-page-path";
import { LoadingPage } from "./page-skeletons";
export function RedirectToContractOverview(props: {
contract: ThirdwebContract;
projectMeta: ProjectMeta | undefined;
}) {
const router = useDashboardRouter();
const redirected = useRef(false);
@@ -17,8 +20,14 @@ export function RedirectToContractOverview(props: {
return;
}
redirected.current = true;
router.replace(`/${props.contract.chain.id}/${props.contract.address}`);
}, [router, props.contract]);
const landingPage = buildContractPagePath({
projectMeta: props.projectMeta,
chainIdOrSlug: props.contract.chain.id.toString(),
contractAddress: props.contract.address,
});
router.replace(landingPage);
}, [router, props]);
return <LoadingPage />;
}
@@ -2,7 +2,7 @@
import { useQuery } from "@tanstack/react-query";
import type { ThirdwebContract } from "thirdweb";
import { getContractPageMetadataSetup } from "../_utils/getContractPageMetadata";
import { getContractPageMetadataSetup } from "../_utils/getContractPageMetadataSetup";
export function useContractPageMetadata(contract: ThirdwebContract) {
return useQuery({
@@ -11,7 +11,7 @@ export function ConfigureCustomChain(props: {
chainSlug: string;
}) {
const { chainSlug } = props;
const isSlugNumber = Number.isInteger(Number.parseInt(chainSlug));
const isSlugNumber = Number.isInteger(Number(chainSlug));
const [isConfigured, setIsConfigured] = useState(false);
if (isConfigured) {
@@ -2,8 +2,9 @@
import { useQuery } from "@tanstack/react-query";
import type { MinimalTeamsAndProjects } from "components/contract-components/contract-deploy-form/add-to-project-card";
import type { ThirdwebClient, ThirdwebContract } from "thirdweb";
import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { ErrorPage, LoadingPage } from "../_components/page-skeletons";
import { useContractPageMetadata } from "../_hooks/useContractPageMetadata";
import { getContractPageSidebarLinks } from "../_utils/getContractPageSidebarLinks";
@@ -15,7 +16,7 @@ export function ContractPageLayoutClient(props: {
contract: ThirdwebContract;
children: React.ReactNode;
teamsAndProjects: MinimalTeamsAndProjects | undefined;
client: ThirdwebClient;
projectMeta: ProjectMeta | undefined;
}) {
const metadataQuery = useContractPageMetadata(props.contract);
const headerMetadataQuery = useQuery({
@@ -39,6 +40,7 @@ export function ContractPageLayoutClient(props: {
chainSlug: props.chainMetadata.slug,
contractAddress: props.contract.address,
metadata: metadataQuery.data,
projectMeta: props.projectMeta,
});
return (
@@ -4,8 +4,9 @@ import { SidebarLayout } from "@/components/blocks/SidebarLayout";
import type { DashboardContractMetadata } from "@3rdweb-sdk/react/hooks/useDashboardContractMetadata";
import type { MinimalTeamsAndProjects } from "components/contract-components/contract-deploy-form/add-to-project-card";
import { DeprecatedAlert } from "components/shared/DeprecatedAlert";
import type { ThirdwebClient, ThirdwebContract } from "thirdweb";
import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { ContractMetadata } from "./contract-metadata";
import { PrimaryDashboardButton } from "./primary-dashboard-button";
@@ -15,8 +16,8 @@ export function ContractPageLayout(props: {
children: React.ReactNode;
sidebarLinks: SidebarLink[];
dashboardContractMetadata: DashboardContractMetadata | undefined;
client: ThirdwebClient;
teamsAndProjects: MinimalTeamsAndProjects | undefined;
projectMeta: ProjectMeta | undefined;
externalLinks:
| {
name: string;
@@ -31,7 +32,7 @@ export function ContractPageLayout(props: {
dashboardContractMetadata,
externalLinks,
teamsAndProjects,
client,
projectMeta,
} = props;
return (
@@ -46,6 +47,7 @@ export function ContractPageLayout(props: {
externalLinks={externalLinks}
/>
<PrimaryDashboardButton
projectMeta={projectMeta}
contractAddress={contract.address}
chain={contract.chain}
contractInfo={{
@@ -54,7 +56,7 @@ export function ContractPageLayout(props: {
contractAddress: contract.address,
}}
teamsAndProjects={teamsAndProjects}
client={client}
client={contract.client}
/>
</div>
<DeprecatedAlert chain={chainMetadata} />
@@ -25,7 +25,9 @@ import { usePathname } from "next/navigation";
import { useState } from "react";
import { toast } from "sonner";
import type { Chain, ThirdwebClient } from "thirdweb";
import { useAddContractToProject } from "../../../../../team/[team_slug]/[project_slug]/hooks/project-contracts";
import { useAddContractToProject } from "../../../../../team/[team_slug]/[project_slug]/(sidebar)/hooks/project-contracts";
import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { buildContractPagePath } from "../_utils/contract-page-path";
const TRACKING_CATEGORY = "add_to_dashboard_upsell";
@@ -36,6 +38,7 @@ type AddToDashboardCardProps = {
hideCodePageLink?: boolean;
teamsAndProjects: MinimalTeamsAndProjects | undefined;
client: ThirdwebClient;
projectMeta: ProjectMeta | undefined;
};
export const PrimaryDashboardButton: React.FC<AddToDashboardCardProps> = ({
@@ -45,9 +48,17 @@ export const PrimaryDashboardButton: React.FC<AddToDashboardCardProps> = ({
hideCodePageLink,
teamsAndProjects,
client,
projectMeta,
}) => {
const pathname = usePathname();
const codePagePath = buildContractPagePath({
projectMeta,
chainIdOrSlug: contractInfo.chainSlug,
contractAddress: contractAddress,
subpath: "/code",
});
// if user is not logged in
if (!teamsAndProjects) {
if (hideCodePageLink) {
@@ -57,9 +68,7 @@ export const PrimaryDashboardButton: React.FC<AddToDashboardCardProps> = ({
if (!pathname?.endsWith("/code")) {
return (
<Button variant="outline" asChild className="gap-2">
<Link
href={`/${contractInfo.chainSlug}/${contractInfo.contractAddress}/code`}
>
<Link href={codePagePath}>
<CodeIcon className="size-4" />
Code Snippets
</Link>
@@ -70,6 +79,11 @@ export const PrimaryDashboardButton: React.FC<AddToDashboardCardProps> = ({
return null;
}
// if user is on a project page
if (projectMeta) {
return null;
}
return (
<AddToProjectButton
contractAddress={contractAddress}
@@ -147,6 +161,8 @@ function AddToProjectModalContent(props: {
teamId: params.teamId,
projectId: params.projectId,
chainId: props.chainId,
deploymentType: undefined,
contractType: undefined,
},
{
onSuccess: () => {
@@ -0,0 +1,15 @@
import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
export function buildContractPagePath(params: {
projectMeta: ProjectMeta | undefined;
chainIdOrSlug: string;
contractAddress: string;
subpath?: `/${string}`;
}) {
const { projectMeta, chainIdOrSlug, contractAddress, subpath } = params;
if (projectMeta) {
return `/team/${projectMeta.teamSlug}/${projectMeta.projectSlug}/contract/${chainIdOrSlug}/${contractAddress}${subpath || ""}`;
}
return `/${chainIdOrSlug}/${contractAddress}${subpath || ""}`;
}
@@ -1,17 +1,17 @@
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { mapV4ChainToV5Chain } from "contexts/map-chains";
import { cookies } from "next/headers";
import { getAddress, getContract, isAddress } from "thirdweb";
import { LAST_USED_TEAM_ID } from "../../../../../../../constants/cookies";
import { localhost } from "thirdweb/chains";
import { fetchChainWithLocalOverrides } from "../../../../../../../utils/fetchChainWithLocalOverrides";
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
import { getUserThirdwebClient } from "../../../../../api/lib/getAuthToken";
export async function getContractPageParamsInfo(params: {
contractAddress: string;
chain_id: string;
chainIdOrSlug: string;
teamId: string | undefined;
}) {
const contractAddress = getCheckSummedAddress(params.contractAddress);
const chainSlugOrId = params.chain_id;
const chainSlugOrId = params.chainIdOrSlug;
const chainMetadata = await fetchChainWithLocalOverrides(chainSlugOrId);
if (!chainMetadata) {
@@ -19,24 +19,29 @@ export async function getContractPageParamsInfo(params: {
}
// attempt to get the auth token
const token = await getAuthToken();
const cookiesObj = await cookies();
const teamId = cookiesObj.get(LAST_USED_TEAM_ID)?.value;
const contract = getContract({
const serverContract = getContract({
address: contractAddress,
// eslint-disable-next-line no-restricted-syntax
chain: mapV4ChainToV5Chain(chainMetadata),
client: serverThirdwebClient,
});
const clientContract = getContract({
address: contractAddress,
// eslint-disable-next-line no-restricted-syntax
chain: mapV4ChainToV5Chain(chainMetadata),
// if we have the auth token pass it into the client
client: getThirdwebClient({
jwt: token || undefined,
teamId,
client: await getUserThirdwebClient({
teamId: params.teamId,
}),
});
return {
chainMetadata,
contract,
serverContract,
clientContract,
isLocalhostChain: chainMetadata.chainId === localhost.id,
};
}
@@ -1,26 +1,7 @@
import { resolveFunctionSelectors } from "lib/selectors";
import "server-only";
import type { ThirdwebContract } from "thirdweb";
import { contractType as getContractType } from "thirdweb/extensions/thirdweb";
import {
isERC20ClaimConditionsSupported,
isERC721ClaimConditionsSupported,
} from "./detectedFeatures/claimConditions";
import {
isAccountContract,
isAccountFactoryContract,
isAccountPermissionsSupported,
} from "./detectedFeatures/erc4337";
import {
isDirectListingSupported,
isEnglishAuctionSupported,
} from "./detectedFeatures/marketplace";
import { isModularCoreContract } from "./detectedFeatures/modular";
import {
isPermissionsEnumerableSupported,
isPermissionsSupported,
} from "./detectedFeatures/permissions";
import { supportedERCs } from "./detectedFeatures/supportedERCs";
import { type EmbedTypeToShow, getEmbedTypeToShow } from "./getEmbedTypeToShow";
import { getContractPageMetadataSetup } from "./getContractPageMetadataSetup";
import type { EmbedTypeToShow } from "./getEmbedTypeToShow";
import { isInsightSupportedForChain } from "./isAnalyticsSupportedForChain";
export type ContractPageMetadata = {
@@ -49,54 +30,3 @@ export type ContractPageMetadata = {
export async function getContractPageMetadata(contract: ThirdwebContract) {
return getContractPageMetadataSetup(contract, isInsightSupportedForChain);
}
export async function getContractPageMetadataSetup(
contract: ThirdwebContract,
isAnalyticsSupportedFn: (chainId: number) => Promise<boolean>,
): Promise<ContractPageMetadata> {
const [
functionSelectorsResult,
isInsightSupportedResult,
contractTypeResult,
] = await Promise.allSettled([
resolveFunctionSelectors(contract),
isAnalyticsSupportedFn(contract.chain.id),
getContractType({ contract }),
]);
const functionSelectors =
functionSelectorsResult.status === "fulfilled"
? functionSelectorsResult.value
: [];
const isInsightSupported =
isInsightSupportedResult.status === "fulfilled"
? isInsightSupportedResult.value
: true;
const contractType =
contractTypeResult.status === "fulfilled" ? contractTypeResult.value : null;
return {
supportedERCs: supportedERCs(functionSelectors),
isDirectListingSupported: isDirectListingSupported(functionSelectors),
isEnglishAuctionSupported: isEnglishAuctionSupported(functionSelectors),
isPermissionsSupported: isPermissionsSupported(functionSelectors),
isPermissionsEnumerableSupported:
isPermissionsEnumerableSupported(functionSelectors),
isModularCore: isModularCoreContract(functionSelectors),
embedType: getEmbedTypeToShow(functionSelectors),
isInsightSupported: isInsightSupported,
isSplitSupported: contractType === "Split",
isVoteContract: contractType === "VoteERC20",
isERC721ClaimConditionsSupported:
isERC721ClaimConditionsSupported(functionSelectors),
isERC20ClaimConditionsSupported:
isERC20ClaimConditionsSupported(functionSelectors),
isAccountFactory: isAccountFactoryContract(functionSelectors),
isAccount: isAccountContract(functionSelectors),
isAccountPermissionsSupported:
isAccountPermissionsSupported(functionSelectors),
functionSelectors,
};
}
@@ -0,0 +1,97 @@
import { resolveFunctionSelectors } from "lib/selectors";
import type { ThirdwebContract } from "thirdweb";
import { contractType as getContractType } from "thirdweb/extensions/thirdweb";
import {
isERC20ClaimConditionsSupported,
isERC721ClaimConditionsSupported,
} from "./detectedFeatures/claimConditions";
import {
isAccountContract,
isAccountFactoryContract,
isAccountPermissionsSupported,
} from "./detectedFeatures/erc4337";
import {
isDirectListingSupported,
isEnglishAuctionSupported,
} from "./detectedFeatures/marketplace";
import { isModularCoreContract } from "./detectedFeatures/modular";
import {
isPermissionsEnumerableSupported,
isPermissionsSupported,
} from "./detectedFeatures/permissions";
import { supportedERCs } from "./detectedFeatures/supportedERCs";
import { type EmbedTypeToShow, getEmbedTypeToShow } from "./getEmbedTypeToShow";
type ContractPageMetadata = {
supportedERCs: {
isERC721: boolean;
isERC1155: boolean;
isERC20: boolean;
};
isDirectListingSupported: boolean;
isEnglishAuctionSupported: boolean;
isPermissionsSupported: boolean;
isVoteContract: boolean;
isPermissionsEnumerableSupported: boolean;
isModularCore: boolean;
embedType: EmbedTypeToShow;
isInsightSupported: boolean;
isSplitSupported: boolean;
isERC721ClaimConditionsSupported: boolean;
isERC20ClaimConditionsSupported: boolean;
isAccountFactory: boolean;
isAccount: boolean;
isAccountPermissionsSupported: boolean;
functionSelectors: string[];
};
export async function getContractPageMetadataSetup(
contract: ThirdwebContract,
isAnalyticsSupportedFn: (chainId: number) => Promise<boolean>,
): Promise<ContractPageMetadata> {
const [
functionSelectorsResult,
isInsightSupportedResult,
contractTypeResult,
] = await Promise.allSettled([
resolveFunctionSelectors(contract),
isAnalyticsSupportedFn(contract.chain.id),
getContractType({ contract }),
]);
const functionSelectors =
functionSelectorsResult.status === "fulfilled"
? functionSelectorsResult.value
: [];
const isInsightSupported =
isInsightSupportedResult.status === "fulfilled"
? isInsightSupportedResult.value
: true;
const contractType =
contractTypeResult.status === "fulfilled" ? contractTypeResult.value : null;
return {
supportedERCs: supportedERCs(functionSelectors),
isDirectListingSupported: isDirectListingSupported(functionSelectors),
isEnglishAuctionSupported: isEnglishAuctionSupported(functionSelectors),
isPermissionsSupported: isPermissionsSupported(functionSelectors),
isPermissionsEnumerableSupported:
isPermissionsEnumerableSupported(functionSelectors),
isModularCore: isModularCoreContract(functionSelectors),
embedType: getEmbedTypeToShow(functionSelectors),
isInsightSupported: isInsightSupported,
isSplitSupported: contractType === "Split",
isVoteContract: contractType === "VoteERC20",
isERC721ClaimConditionsSupported:
isERC721ClaimConditionsSupported(functionSelectors),
isERC20ClaimConditionsSupported:
isERC20ClaimConditionsSupported(functionSelectors),
isAccountFactory: isAccountFactoryContract(functionSelectors),
isAccount: isAccountContract(functionSelectors),
isAccountPermissionsSupported:
isAccountPermissionsSupported(functionSelectors),
functionSelectors,
};
}
@@ -1,4 +1,6 @@
import type { SidebarBaseLink, SidebarLink } from "@/components/blocks/Sidebar";
import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { buildContractPagePath } from "./contract-page-path";
import type { ContractPageMetadata } from "./getContractPageMetadata";
type SidebarBaseLinkWithHide = SidebarBaseLink & {
@@ -9,8 +11,13 @@ export function getContractPageSidebarLinks(data: {
metadata: ContractPageMetadata;
contractAddress: string;
chainSlug: string;
projectMeta: ProjectMeta | undefined;
}) {
const layoutPrefix = `/${data.chainSlug}/${data.contractAddress}`;
const layoutPrefix = buildContractPagePath({
projectMeta: data.projectMeta,
chainIdOrSlug: data.chainSlug,
contractAddress: data.contractAddress,
});
const generalLinks: SidebarBaseLinkWithHide[] = [
{
@@ -1,4 +1,4 @@
import { INSIGHT_SERVICE_API_KEY } from "@/constants/env";
import { INSIGHT_SERVICE_API_KEY } from "@/constants/server-envs";
import { getVercelEnv } from "lib/vercel-utils";
const thirdwebDomain =
@@ -1,6 +1,7 @@
"use client";
import type { ThirdwebContract } from "thirdweb";
import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { ErrorPage, LoadingPage } from "../_components/page-skeletons";
import { RedirectToContractOverview } from "../_components/redirect-contract-overview.client";
import { useContractPageMetadata } from "../_hooks/useContractPageMetadata";
@@ -8,6 +9,7 @@ import { AccountSigners } from "./components/account-signers";
export function AccountSignersClient(props: {
contract: ThirdwebContract;
projectMeta: ProjectMeta | undefined;
}) {
const metadataQuery = useContractPageMetadata(props.contract);
@@ -20,7 +22,12 @@ export function AccountSignersClient(props: {
}
if (!metadataQuery.data.isAccountPermissionsSupported) {
return <RedirectToContractOverview contract={props.contract} />;
return (
<RedirectToContractOverview
contract={props.contract}
projectMeta={props.projectMeta}
/>
);
}
return <AccountSigners contract={props.contract} />;

Some files were not shown because too many files have changed in this diff Show More