Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
01a20cb7d5 Version Packages (#8699)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-06 02:31:37 +07:00
FirekeeperandGitHub e13b9a784a [CLI] Deprecate create (#8700) 2026-03-06 02:27:51 +07:00
da4a4a540a Wallet details export screen (#8698)
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Yash <[email protected]>
2026-03-06 01:40:34 +07:00
5e5c82fb2e Sei chain description (#8651)
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: yashjoisar <[email protected]>
2026-03-05 21:40:45 +07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1d28988253 Version Packages (#8687)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-24 22:39:15 +13:00
5352152835 Connect modal approval changeset (#8686)
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Yash <[email protected]>
2026-02-24 22:36:53 +13:00
decbbb8f7a Connect modal require approval (#8683)
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Yash <[email protected]>
2026-02-24 22:01:29 +13:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ecd5ae1feb Version Packages (#8672)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-13 23:39:01 +07:00
FirekeeperandGitHub 1caf6dd460 changeset / fmt (#8671) 2026-02-13 23:34:21 +07:00
19204e4654 [SDK] Fix Safe wallet WalletConnect connection on Berachain (#8670)
Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-02-13 23:24:37 +07:00
Manan TankandGitHub 674c8d5e6c portal: Fix sidebar spacing on top (#8666) 2026-02-11 17:09:29 +13:00
1eb0cd9314 Feat/partner logo branding (#8668)
Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-02-10 14:52:06 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
b7dfde2d68 Version Packages (#8665)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-04 19:54:15 -08:00
Joaquim VergesandGitHub b50e304e0c changeset (#8664) 2026-02-04 17:56:41 -08:00
9baa251bb1 [SDK] Fix SiteLink and SiteEmbed stripping URL hash fragments (#8663)
Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-04 17:55:11 -08:00
Manan TankandGitHub 7f36303b0b portal: OpenApiEndpoint card UI tweaks (#8661) 2026-02-04 17:50:30 -08:00
Manan TankandGitHub 13a46b7ff6 portal: Update SDK Reference breadcrumbs UI (#8662) 2026-02-04 17:50:07 -08:00
Manan TankandGitHub b9d93e44ca portal: landing page ui improvements (#8647) 2026-01-30 12:09:55 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
fc9e81f99a Version Packages (#8644)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-20 16:27:12 -08:00
281759f06a Add base account sdk (#8634)
Co-authored-by: Joaquim Verges <[email protected]>
2026-01-20 16:04:13 -08:00
Joaquim VergesandGitHub 2d98fe2799 Support linking accounts with redirect mode (#8643) 2026-01-20 14:40:33 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
93bcdc557c Version Packages (#8640)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-15 17:26:04 -08:00
Joaquim VergesandGitHub d38deecaa1 Fix getCallStatus via EIP1193 provider (#8639) 2026-01-15 17:20:25 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
a3ea002afd Version Packages (#8638)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-14 22:56:41 +07:00
FirekeeperandGitHub 6390052f8b Add fallback for gas fee estimation in zkSync (#8637) 2026-01-14 22:54:24 +07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
621f187a8a Version Packages (#8622)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-13 13:36:58 -08:00
Joaquim VergesandGitHub 36ff327744 support x402 v2 headers (#8623) 2026-01-13 12:59:11 -08:00
Manan TankandGitHub f68c2c62a5 [MNY-233] SDK: Add Last Used badge in Connect UI (#8631) 2026-01-13 10:45:05 -08:00
5766c90fb4 [SDK] Fix: Parse x402 payment header (#8617)
Co-authored-by: Joaquim Verges <[email protected]>
2026-01-12 11:20:43 -08:00
MananTank 8d4b6a1fea [MNY-356] Add tokenEditable and amountEditable props on BuyWidget (#8621)
<!--

## 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 two new properties, `amountEditable` and `tokenEditable`, to enhance the `BuyWidget` component, allowing control over editing options for token selection and amount. This improves user experience by providing flexibility in the widget's functionality.

### Detailed summary
- Added `amountEditable` and `tokenEditable` properties to `BuyWidget`.
- Updated `BuyPlayground.tsx`, `CheckoutPlayground.tsx`, and `TransactionPlayground.tsx` to use new props.
- Modified `CodeGen.tsx` to incorporate editability options.
- Updated URL generation in `buildBuyIframeUrl.ts` for new props.
- Added editability options in `LeftSection.tsx`.
- Enhanced `BuyWidgetEmbed` to accept new props.
- Implemented stories in `BuyWidget.stories.tsx` for editable and non-editable states.
- Added documentation in `iframe/page.mdx` for new features.
- Adjusted `FundWallet.tsx` and `TokenSection.tsx` to respect new editability options.

> ✨ 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

## Release Notes

* **New Features**
  * Added `amountEditable` and `tokenEditable` props to the BuyWidget component, allowing developers to disable token selection and/or amount editing in the buy flow.
  * Enhanced UI controls to prevent interaction with locked fields when these options are disabled.
  * Updated documentation with examples of how to use the new editability options.

* **Tests**
  * Added story variants demonstrating token and amount lock scenarios.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-12 18:12:38 +00:00
FirekeeperandGitHub 49b80fe87f Add self-managed vault recovery option for lost secret key (#8619) 2026-01-12 23:16:45 +07:00
FirekeeperandGitHub 5ca4d7497b Preserve wallet address on vault credential rotation (#8618) 2026-01-12 22:27:08 +07:00
kumaryash90 0382d42a99 Enable multiple ref params for bytes input (#8610)
<!--

## 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 modifying the `Button` component in the `decoded-input-set.tsx` file to enhance its functionality based on certain conditions related to `param.type` and `fields`.

### Detailed summary
- Updated the `Button` component to disable it when `param.type` is "bytes" and `fields.length` is 1 or more.
- Maintained the existing `onClick` functionality to append a new input with a default value of an empty string.

> ✨ 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**
  * Fixed an issue in the contract publish form where the Add Parameter button was incorrectly disabled when working with bytes parameters. Users can now add multiple bytes parameters without restriction.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-08 15:23:52 +00:00
MananTank d35cad8655 [MNY-355] Playground: Add Buy Widget iframe playground (#8615)
<!--

## 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 `buy` widget functionality by introducing iframe support and improving the handling of integration types. It also adds new components for rendering iframe previews and updates the URL management for better integration experience.

### Detailed summary
- Updated `LeftSection.tsx` to allow both `checkout` and `buy` widgets for iframe integration.
- Modified `Page` component in `page.tsx` to handle async `searchParams` and pass `defaultTab` to `BuyPlayground`.
- Enhanced `CodeGen` to support widget type in iframe code generation.
- Added `CheckoutIframePreview` and `BuyWidgetIframePreview` components in `RightSection.tsx` for rendering respective iframes.
- Created `buildBuyIframeUrl` function in `buildBuyIframeUrl.ts` for generating URLs for the buy widget.
- Updated `BuyPlayground` to manage state and URL for integration type selection using tabs.

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

<!-- end pr-codex -->
2026-01-07 20:23:15 +00:00
MananTank c1275a5e38 [MNY-354] Portal: Add Buy Widget iframe docs (#8614)
<!--

## 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 the `Buy Widget`, enhancing the portal with a new iframe component for purchasing crypto. It includes documentation, features, and examples for users to integrate the widget easily.

### Detailed summary
- Added `IframeCodePreview` component in `iframe-code-preview.tsx`.
- Updated sidebar links to reflect the new `Buy Widget` and `Swap Widget`.
- Created documentation for the `Buy Widget` in `page.mdx`, detailing features and integration.
- Added example usage of the `BuyWidget` component in `react/page.mdx`.
- Developed `iframe/page.mdx` with customization options and event listening for the widget.

> ✨ 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 an iframe embed preview with code/preview tabs for trying the Buy widget.

* **Documentation**
  * Added comprehensive Buy widget docs (overview, React and iframe guides, examples, customization, event listening, live playgrounds).

* **Chores**
  * Updated site navigation: Widgets section reorganized to list Swap, Buy, and Checkout widgets with updated links.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-07 20:23:14 +00:00
MananTank 277874594b SDK: token details screen UI tweaks (#8612)
<!--

## 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 user interface of the `TokenInfoScreen` component in the `select-token-ui.tsx` file by enhancing loading states, adjusting layout heights, and refining token display logic.

### Detailed summary
- Removed initial loading and token not found states.
- Increased `Container` minHeight from `350px` to `450px`.
- Refactored conditional rendering for token details.
- Maintained structure for displaying token name, symbol, price, market cap, volume, and contract address.

> ✨ 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**
  * Consolidated rendering logic for the token information view to centralize pending, not-found, and content states.
  * Reorganized layout so detailed token sections render only when token data is present.
  * Adjusted minimum container height for token information screens in the bridge swap widget.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-07 19:26:05 +00:00
MananTank 4fc02b3618 [MNY-342] Dashboard: Add Buy Widget iframe (#8613)
<!--

## 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 `BuyWidget` feature, allowing users to purchase crypto tokens using various payment methods. It adds necessary environment variables, updates configuration for security policies, and implements the layout and functionality for the `BuyWidgetEmbed` component.

### Detailed summary
- Added `NEXT_PUBLIC_BUY_IFRAME_CLIENT_ID` to `public-envs.ts`.
- Updated `next.config.ts` with new Content Security Policy headers for `/bridge/buy-widget`.
- Created `BuyWidgetLayout` component in `layout.tsx` for the buy widget page.
- Implemented `BuyWidgetEmbed` component in `BuyWidgetEmbed.client.tsx` for handling crypto purchases.
- Enhanced `page.tsx` to manage search parameters and integrate `BuyWidgetEmbed`.

> ✨ 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

## Release Notes

* **New Features**
  * Added a new buy widget enabling direct cryptocurrency purchases with configurable payment methods (crypto and card) and multi-currency support
  * Implemented personalization options including custom titles, descriptions, images, and button labels
  * Enhanced security infrastructure to support the new widget functionality

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-07 19:16:44 +00:00
Yash094 26a91537d2 Etherlink shadownet chain (#8611)
<!--

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

-->
[SDK] Feature: Add Etherlink Shadownet Testnet chain

## Notes for the reviewer

Adds the Etherlink Shadownet testnet chain (ID 127823) to the predefined chains in the SDK. This includes its native currency (XTZ) and block explorer URL.

## How to test

- Verify the `etherlinkShadownet` chain can be imported from `thirdweb/chains`.
- Check that the chain object contains the correct `id`, `name`, `nativeCurrency`, and `blockExplorers` properties.
- Run `pnpm build` and `pnpm lint` to ensure no new issues are introduced.

---
[Slack Thread](https://thirdwebdev.slack.com/archives/C09DS2CKGP2/p1767789368607579?thread_ts=1767789368.607579&cid=C09DS2CKGP2)

<a href="https://cursor.com/background-agent?bcId=bc-a0f088a7-057b-46f0-b6a6-3ba957d1e261"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?id=bc-a0f088a7-057b-46f0-b6a6-3ba957d1e261"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a>

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the `Etherlink Shadownet` testnet chain to the project, defining its properties and adding it to the exports.

### Detailed summary
- Added a new chain definition for `etherlinkShadownet` in `etherlink-shadownet.ts`.
- Defined properties such as `id`, `name`, `nativeCurrency`, and `blockExplorers`.
- Exported `etherlinkShadownet` in `chains.ts` for use in the application.

> ✨ 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 Etherlink Shadownet testnet chain, enabling development and testing on this network with proper block explorer integration and native currency configuration (XTZ).

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-07 14:06:28 +00:00
MananTank a3ff7572d6 [MNY-319] SDK: Add token details screen in token selection UI (#8608)
<!--

## 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 selection UI by adding a token details screen and improving the display of token information within the `SwapWidget` and `BridgeWidget`.

### Detailed summary
- Added a token details screen in the token selection UI.
- Enhanced the `SelectedTokenButton` to include a more flexible display of the token symbol.
- Introduced `useTokenPrice` hook for fetching token prices.
- Updated `TokenButton` to include an info button for showing token details.
- Created `TokenInfoScreen` and `TokenInfoRow` components for displaying detailed token information.
- Integrated token info functionality into `TokenSelectionScreen`.

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

<!-- end pr-codex -->
2026-01-06 21:15:08 +00:00
MananTank 01004e8408 [MNY-353] SDK: TransactionWidget UI improvements (#8607)
<!--

## 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 UI improvements for the `TransactionWidget` in the `thirdweb` package, enhancing user experience with better layout, spacing, and loading indicators.

### Detailed summary
- Changed the `chain` in `simpleBuyRequest` from `baseSepolia` to `base`.
- Updated the story names and added new stories for `TransactionWidget`.
- Improved layout and spacing in `TransactionPayment`.
- Replaced static loading elements with `Skeleton` components for better loading states.
- Enhanced button styles and added a loading spinner.
- Refined contract info and address display for better 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

* **UI Improvements**
  * Improved TransactionWidget loading states with clearer visual spinner components
  * Enhanced visual presentation and layout of transaction details, addresses, and network information
  * Refined typography, spacing, and styling throughout the widget for better readability and consistency

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-06 21:15:08 +00:00
MananTank f497b614cb [MNY-352] Playground: add swap widget iframe (#8605)
<!--

## 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 `SwapWidgetPlayground`, allowing for integration with both `iframe` and `react` types. It modifies how themes and options are handled, improves URL management, and updates iframe handling for better user experience.

### Detailed summary
- Added `integrationType` to `SwapWidgetPlaygroundOptions`.
- Conditional rendering of `ColorFormGroup` based on `integrationType`.
- Created `getPreviewSrc` function to manage iframe URLs.
- Updated `persistTokenSelections` handling in various components.
- Introduced `SwapWidgetPlaygroundAsync` for async loading.
- Refactored `RightSection` to conditionally render `iframe` or `SwapWidget`.
- Implemented URL update logic based on selected integration type.
- Enhanced code generation for iframe and react integration types.

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

<!-- end pr-codex -->
2026-01-05 21:40:36 +00:00
MananTank f344fb5fbd [MNY-351] Portal: Add swap widget iframe docs (#8604)
<!--

## 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 `Swap Widget` functionality by adding an `iframe` integration option and a new `React Component`. It updates documentation and examples for better usability and expands features like dark/light mode support and customizable token selections.

### Detailed summary
- Added `links` for `React Component` and `Iframe` in `sidebar.tsx`.
- Introduced `IframeCodePreview` component in `iframe-code-preview.tsx`.
- Updated `page.mdx` for `iframe` integration with features and examples.
- Revised `page.mdx` for `react` with improved descriptions and features.
- Enhanced API reference and examples for token selection configurations.
- Added iframe event listening instructions for swap results.

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

<!-- end pr-codex -->
2026-01-05 21:40:36 +00:00
MananTank 9f265bf734 [MNY-344] Dashboard: Add swap-widget iframe (#8603)
<!--

## 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 `NEXT_PUBLIC_SWAP_IFRAME_CLIENT_ID` constant and enhances the swap functionality with a `SwapWidgetEmbed` component, while refactoring the existing `Providers` component to `BridgeProvidersLite`. It also adds necessary headers for security and updates layout components.

### Detailed summary
- Added `NEXT_PUBLIC_SWAP_IFRAME_CLIENT_ID` constant in `public-envs.ts`.
- Updated `next.config.ts` to include security headers for swap widget routes.
- Introduced `BridgeProvidersLite` component in `Providers.client.tsx`.
- Created `SwapWidgetEmbed` component for swap functionality.
- Refactored `page.tsx` to utilize `BridgeProvidersLite` and `SwapWidgetEmbed`.
- Removed the old `Providers` component logic.

> ✨ 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 a cross‑chain swap widget with page, embed component and layout, URL-prefill support, theming, and fiat currency options.
  * Added a lightweight provider wrapper for embed usage.

* **Changes**
  * Checkout widget no longer auto-connects preconfigured wallets; provider usage unified to the lightweight wrapper.

* **Chores**
  * Added CSP headers for swap widget routes.
  * Introduced public env var for swap iframe client ID.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-05 20:26:31 +00:00
MananTank e5519eaef9 Dashboard: Update bridge supported tokens count (#8602)
<!--

## 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 `supportedTokens` value in the `data.ts` file for the bridge statistics, reflecting an increase in the number of supported tokens.

### Detailed summary
- Updated `supportedTokens` from `"14,000+"` to `"17,000+"`.
- Changed `supportedTokens` from `14000` to `17000` in the `bridgeStatsNumbers` object.

> ✨ 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

* **Updates**
  * Increased supported tokens count from 14,000+ to 17,000+ on the bridge dashboard.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-05 16:11:01 +00:00
MananTank 805b838fd3 [MNY-346] Playground: Add CheckoutWidget iframe (#8591)
<!--

## 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 support for customizable payment methods in the `checkout` widget, allowing users to select between "crypto" and "card" options. It also refines the integration type handling and enhances the iframe functionality for the checkout experience.

### Detailed summary
- Added `integrationType` option to `BridgeComponentsPlaygroundOptions`.
- Introduced `paymentMethods` parsing in `page.tsx` for checkout widget.
- Updated `LeftSection` to conditionally render `ColorFormGroup`.
- Enhanced `CheckoutWidgetEmbed` to accept `paymentMethods`.
- Modified `RightSection` to support iframe rendering for checkout.
- Created `buildCheckoutIframeUrl` function to generate URLs with payment methods.
- Updated `CodeGen` to handle iframe code generation.
- Added documentation for payment methods in `iframe` page.
- Adjusted `CheckoutPlayground` to manage integration types and theme changes.

> ✨ 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**
  * Checkout widget: optional paymentMethods filter (crypto or card) and iframe integration.
  * Playground: tab-based switcher (React / Iframe), URL state persistence, and theme-aware preview.
  * Preview/code generation: iframe-specific preview and HTML snippet export for iframe integration.

* **Documentation**
  * Added "Payment Methods" docs for iframe integration with examples.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-30 11:17:26 +00:00
Yash094 ec73f1ff83 X402 playground chainlist (#8588)
## [Playground] Fix: Exclude deprecated chains from x402 network selector

## Notes for the reviewer

This change enables the existing `disableDeprecated` functionality of the `SingleNetworkSelector` component on the x402 playground page. This ensures that deprecated chains are no longer displayed in the network selection dropdown, consistent with patterns used elsewhere in the codebase.

## How to test

1.  Navigate to `https://playground.thirdweb.com/x402`.
2.  Open the network selector dropdown.
3.  Verify that deprecated chains (e.g., Mumbai) are not listed.

---
[Slack Thread](https://thirdwebdev.slack.com/archives/C09DS2CKGP2/p1766589810150189?thread_ts=1766589810.150189&cid=C09DS2CKGP2)

<a href="https://cursor.com/background-agent?bcId=bc-662b659b-43e5-4760-8634-cf73b25f1a02"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?id=bc-662b659b-43e5-4760-8634-cf73b25f1a02"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a>

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new property to a component, enhancing its functionality by allowing it to disable deprecated features.

### Detailed summary
- Added the `disableDeprecated` property to the `X402LeftSection` component.
- This change modifies the component's behavior, likely affecting how deprecated features are handled.

> ✨ 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**
  * Updated component configuration to disable deprecated options in the network selector.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-25 10:26:16 +00:00
saminacodes 245e9a688c delete outdated webhook docs and move webhooks to tokens (#8587)
<!--

## 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 the addition and enhancement of webhook documentation for both `Tokens` and `Bridge` functionalities in the application, while removing outdated files.

### Detailed summary
- Deleted files related to `webhooks` in `portal/src/app`.
- Added `webhooks` section in `tokens` documentation with use cases and configuration instructions.
- Updated `bridge` documentation to reflect the correct title and description.
- Enhanced explanations for creating webhooks in both `tokens` and `bridge` contexts.
- Included images for `TokenWebhooksScreen` and `BridgeWebhookScreen`.

> ✨ 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**
  * Reorganized webhooks docs into product-specific guides: Bridge (renamed from Payments Webhooks) and Tokens.
  * Added new Token Webhooks guide with use cases, setup steps, and an illustrative image.
  * Updated Bridge Webhooks content and dashboard guidance; added visuals and clarified structure and headings.
  * Removed the generic Webhooks overview in favor of the new guides.
  * Minor wording improvements in Wallets security docs.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-23 20:49:33 +00:00
saminacodes 8bdc0c0c27 Expand FAQ with Dedicated Relayer FAQs (#8585)
Added information about gas sponsorship and dedicated relayer features.

<!--

## 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 new content related to `Gas Sponsorship` in the `page.mdx` file, adding FAQs about the dedicated relayer service offered by thirdweb.

### Detailed summary
- Added a new section titled `Gas Sponsorship`.
- Included multiple FAQs regarding the dedicated relayer:
  - Its status as an add-on service.
  - Expected performance benchmarks.
  - Comparison to other alternatives.
  - Management of scaling on multiple relayer instances.
  - Transaction handling capacity.

> ✨ 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**
  * Expanded wallets FAQ with a new "Gas Sponsorship" section covering dedicated relayer plan/pricing, throughput benchmarks for single vs. multi-executor setups, comparisons with alternative queue approaches, scaling guidance for multiple relayer instances, and transaction-per-second expectations. Reordered content so Gas Sponsorship appears before Security & Recoverability; Ecosystem Wallets content retained.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-23 18:52:17 +00:00
MananTank 9c04b3f180 [MNY-338] Add filters when fetching chains in chain selection ui (#8573)
<!--

## 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 `SwapWidget` and related components by improving the chain selection logic and UI responsiveness, introducing a more reliable token selection process, and adjusting the timeout for modal unmounting.

### Detailed summary
- Updated `setTimeout` duration in `onModalUnmount` to 200ms.
- Changed `useBridgeChains` to `useBridgeChain` for better chain fetching.
- Added `type` and `selections` props to various components for handling buy/sell logic.
- Implemented a new `useBridgeChainsWithFilters` function for filtered chain queries.
- Enhanced UI components to accommodate new selection logic.
- Improved loading states and mobile responsiveness in `SelectBridgeChain` and `SelectToken`.

> ✨ 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**
  - Bridge filtering now supports testnet and mutually exclusive origin/destination filters.
  - Token selection accepts operation context (buy/sell) and selected-chain hints.

* **Improvements**
  - Filter-driven chain lists yield more relevant token choices.
  - Loading skeletons adapt sizing for mobile and desktop.
  - Deferred buy-token assignment reduces redundant fetches during modal close.
  - Slightly longer modal unmount delay for smoother transitions.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-19 22:46:35 +00:00
MananTank 800503da68 [MNY-345] Portal: Add Checkout widget iframe docs (#8581)
<!--

## 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 `Checkout Widget` and `Swap Widget` components to enhance their functionality and improve user experience, including new image formats, additional features, and documentation updates.

### Detailed summary
- Replaced `.png` images with `.jpg` for `swap-dark` and `swap-light`.
- Added `IframeCodePreview` component for iframe integration in `Checkout Widget`.
- Updated `sendMessageToParent` function to use a single object parameter.
- Enhanced sidebar with new links for `Checkout Widget` and `Swap Widget`.
- Expanded documentation for `Checkout Widget` and `Swap Widget` with features, examples, and API references.

> ✨ 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 an iframe code-preview component with "Code" and "Preview" tabs for live iframe examples.

* **Documentation**
  * New Checkout Widget docs (React + iframe) with integration guides, examples, options, and event-listening examples.
  * New Swap Widget docs with usage examples and API reference.
  * Swap page asset updates (image format changes).

* **Improvements**
  * Reorganized sidebar to surface Widgets (Bridge, Checkout, Swap).
  * Updated widget postMessage event payload shape — consumers should verify event format.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-19 22:19:15 +00:00
MananTank 0ff8b9ff60 [MNY-316] SDK: directly show token selection after wallet connection in bridge widgets (#8580)
<!--

## 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 behavior of the wallet connection process in the `WalletSwitcherConnectionScreen`. After a wallet is connected, it skips the "Choose Payment" screen and directly moves to the token selection screen, enhancing user experience in bridge widgets.

### Detailed summary
- Updated the `onConnect` function to remove the call to `props.onBack()`, preventing the return to the "Choose Payment" screen.
- Changed the flow to directly transition to the token selection screen after wallet connection.

> ✨ 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**
  * Improved wallet connection flow in bridge widgets. After connecting a wallet on the Choose Payment screen, users are now directed directly to token selection instead of returning to the payment screen.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-19 20:06:41 +00:00
MananTank 304e4e5be8 [MNY-348] SDK: Remove in-app wallet from default wallet list for bridge widgets (#8579)
<!--

## 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 wallet integration in bridge components by introducing custom wallet options and modifying default wallet selections for various widgets.

### Detailed summary
- Removed in-app wallet from default wallet list for bridge widgets.
- Added `CustomWallets` component in `BuyWidget.stories.tsx` and `SwapWidget.stories.tsx`.
- Implemented `getDefaultWalletsForBridgeComponents` to return a list of wallets.
- Updated `FundWallet`, `SwapUI`, and `TransactionPayment` components to use custom wallet options.
- Introduced new wallet options in `TransactionWidget.stories.tsx`.
- Added `CustomWallets` functionality in `BridgeWidget` stories.

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

<!-- end pr-codex -->
2025-12-19 19:29:48 +00:00
MananTank fdd2557663 [MNY-343] Add checkout widget iframe (#8578)
<!--

## 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 `CheckoutWidget` and related components in the `thirdweb` application, including the removal of duplicate fiat price displays, adding new environment constants, and refining query parameter parsing for improved functionality and usability.

### Detailed summary
- Removed duplicate fiat price display in `CheckoutWidget`.
- Added `NEXT_PUBLIC_CHECKOUT_IFRAME_CLIENT_ID` constant.
- Introduced `parseQueryParams` utility for better query handling.
- Updated `CheckoutWidgetEmbed` to utilize new constants and improved query parsing.
- Added validation for required parameters in the checkout page.
- Refactored currency validation to use `isValidCurrency`.
- Modified tests to skip certain cases based on environment variables.

> ✨ 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**
  * Embeddable checkout widget with a dedicated checkout page/layout and a new public env var for iframe client ID.
  * Content-Security-Policy headers applied to checkout widget routes.

* **Bug Fixes**
  * Removed duplicate fiat price display in the checkout button.

* **Chores / Refactor**
  * Centralized query-parameter parsing and currency validation; added input sanitization utilities.
  * Added a patch-level changeset entry.

* **Tests**
  * Marked several integration tests as skipped by default; enabled one parallel-execution test.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-19 19:29:48 +00:00
MananTank 56a1c0a467 [MNY-341] Payment Details UI improvements (#8576)
<!--

## 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 Payment Details UI for bridge widget components in the `thirdweb` package, enhancing layout, styling, and functionality.

### Detailed summary
- Adjusted `padding` in `TokenBalanceRow.tsx`.
- Replaced `OutlineWalletIcon` with a styled `Container` in `WalletRow.tsx`.
- Updated `borderRadius` and `borderBottom` styles in `PaymentOverview.tsx`.
- Enhanced `StepConnectorArrow` styles and added `iconSize` for `ChevronDownIcon`.
- Modified `OutlineWalletIcon` definition to accept `size`, `color`, and `style` props.
- Refactored `cryptoPaymentMethod` and `ethCryptoPaymentMethod` structures for clarity.
- Updated `PaymentDetails.tsx` styles, including `gap`, `borderRadius`, and button styles.

> ✨ 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**
  * Improved bridge payment details UI with enhanced spacing, borders, and styling
  * Updated wallet row display with better visual presentation when disconnected
  * Refined typography and color usage across payment interface components
  * Enhanced border radius and padding consistency throughout bridge widget

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-19 12:57:51 +00:00
saminacodes 6e87b9205f added audits (#8577)
<!--

## 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 documentation for building and deploying tokens programmatically, generating bridge links, and auditing wallets. It enhances the user experience by providing clear instructions and examples for developers.

### Detailed summary
- Added a section titled `Build with tokens` in `page.mdx`.
- Introduced `Deploy Programmatically through API` section with details on ERC-20, ERC-721, and ERC-1155 tokens in `deploy-programmatically/page.mdx`.
- Included a guide on generating bridge links with steps in `link/page.mdx`.
- Added `Wallets Audits` section with links to audit reports in `security/page.mdx`.

> ✨ 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**
  * Added Bridge Link guide with step-by-step instructions for generating payment links and hosting them on websites
  * Added Build with Tokens guide
  * Added Deploying Tokens Programmatically guide with API examples for ERC-20, ERC-721, and ERC-1155 tokens
  * Added Wallet Security Audits section highlighting audit reports

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-18 20:59:07 +00:00
MananTank 9383ccdf4e Portal: Add bridge widget playground links (#8575)
<!--

## 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 `Bridge Widget` to enhance its integration options and improve user experience by adding a live playground feature. It modifies the integration types, updates the code logic, and introduces new UI components for better functionality.

### Detailed summary
- Changed `href` in `sidebar.tsx` for the Playground link.
- Updated `integrationType` options in `types.ts` to include "react".
- Adjusted code language conditions in `code.tsx` based on `integrationType`.
- Added `PlayIcon` and `ArticleIconCard` components in multiple `.mdx` files.
- Introduced a "Try it out" section in several `.mdx` files for the Playground.
- Modified the `Page` component to accept `searchParams` and determine the valid tab.
- Enhanced `BridgeWidgetPlayground` to manage URL updates based on the selected integration type.

> ✨ 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**
  * Playground now supports URL parameters to pre-select integration tabs and keeps the browser URL in sync when switching tabs
  * React integration tab standardized and labeled consistently with other tabs

* **Documentation**
  * Added "Try it out" call-to-action cards throughout docs linking to the live playground

* **Chores**
  * Sidebar playground link updated to point to the bridge-specific playground URL

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-18 18:08:11 +00:00
eabdelmoneim ac326e24f1 [Portal] Fix Transactions link in Archived Documentation section (#8574)
https://linear.app/thirdweb/issue/PRO-149/fix-archive-link-for-transactions

Update the Transactions link to point to /engine instead of /wallets/server/send-transactions.

🤖 Generated with [Claude Code](https://claude.com/claude-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 updating the `href` property of the `ArticleCardIndex` component in the `page.tsx` file to redirect users to a new location.

### Detailed summary
- Changed the `href` property of the `ArticleCardIndex` from `"/wallets/server/send-transactions"` to `"/engine"`

> ✨ 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 Transactions card navigation link destination in the Archive section.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-18 17:23:51 +00:00
FirekeeperandGitHub 7ebc32a0c4 Improve portfolio fetch concurrency and error handling (#8572) 2025-12-17 06:06:03 +07:00
FirekeeperandGitHub 5f0e25b5a8 Add wallet portfolio fetching and display to user table (#8570) 2025-12-17 05:31:39 +07:00
MananTank aad3e6477e [MNY-226] Add Bridge Widget playgrounds (script, iframe, react) (#8571)
<!--

## 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 `BridgeWidget` component within the playground by introducing new features, improving styling, and refining existing functionalities. It also includes updates to metadata and the handling of token selections and amounts.

### Detailed summary
- Removed `PayIcon` and replaced with `BringToFrontIcon`.
- Added `showThirdwebBranding` option to `UniversalBridgeEmbed`.
- Updated `BridgeWidget` to support token amounts and selections.
- Enhanced sorting of bridge chains in `chains.ts`.
- Improved styling for various components, including `FeatureCard` and `ChatPageContent`.
- Updated documentation for widget parameters.
- Refactored code for better readability and maintainability in multiple components.

> ✨ 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**
  * Bridge Widget playground: interactive preview, code generation (script/component/iframe), theme, token/amount prefills, and preview modes.
  * Unified buy/swap experience under a single Bridge widget and a new branding toggle (hide/show branding).
  * Expanded connection options for wallet/connect flows.

* **Documentation**
  * Added docs for token amounts, branding, and iframe examples.

* **Style**
  * Minor typography and spacing refinements.

* **Chores**
  * Navigation and feature card updates; small test adjustment (one image test skipped).

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-16 20:42:47 +00:00
MananTank 4bab6f1ae8 [PRO-125] Dashboard: Update token creation modal UI (#8561)
<!--

## 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 UI components in the dashboard by improving dialog descriptions, layout adjustments, and introducing images for token cards, along with some styling updates.

### Detailed summary
- Added `DialogDescription` to provide context in the dialog.
- Increased `DialogContent` max width from `!max-w-md` to `!max-w-2xl`.
- Changed layout of `Cards` from a column to a grid.
- Introduced `images` for token cards with light and dark themes.
- Updated descriptions for token creation.
- Enhanced styling of `CardLink` components, including rounded corners.

> ✨ 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 theme-aware image rendering with light and dark mode support for card components.
  * Implemented responsive 2-column grid layout on larger screens.

* **Style**
  * Increased border radius on cards and dialog components for a more refined appearance.
  * Enhanced card title typography sizing.
  * Expanded dialog width for improved content display.
  * Improved responsive behavior with compact icons on smaller viewports.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-15 18:38:38 +00:00
MananTank f9a435051d [MNY-336] Portal: Fix spacing issue on menu with link (#8560)
<!--

## 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 modifying the sidebar configuration and adjusting styles in the `Sidebar` component for better layout and design.

### Detailed summary
- In `apps/portal/src/app/bridge/sidebar.tsx`:
  - Added an entry for `name: "Guides"` in the sidebar configuration.
- In `apps/portal/src/components/others/Sidebar.tsx`:
  - Updated the `className` of a `div` to include `py-1.5` and `px-3`.
  - Modified the `className` of a `Link` to remove padding classes `py-1.5` and `px-3`.

> ✨ 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**
  * Improved sidebar category spacing with enhanced padding distribution
  * Expanded interactive click target area for sidebar navigation items
  * Better visual organization of the Guides section

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-15 17:55:21 +00:00
MananTank 27d72e5d3e [PRO-124] Dashboard: Project Wallet UI tweaks (#8559)
<!--

## 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 refining the user interface and functionality of the `ProjectWallet` component in the dashboard application. It includes style adjustments, component updates, and the introduction of new dropdown menu options for better user interaction.

### Detailed summary
- Adjusted the size of `ChevronDownIcon` in `select-with-search.tsx`.
- Removed `XIcon` from `project-wallet.tsx` and updated the heading style.
- Renamed `projectWallet` variables to `serverWallet` in `project-wallet.stories.tsx`.
- Introduced a new `Variant` function for rendering `ProjectWalletSectionUI`.
- Added new icons in `project-wallet-details.tsx` and refined the dropdown menu.
- Enhanced wallet detail presentation with better layout and added new dropdown options for actions like Deposit, Withdraw, and Change Project Wallet.

> ✨ 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

## Release Notes

* **New Features**
  * Added dropdown menu with wallet action options: Deposit Funds, Withdraw Funds, Swap Tokens, View Transactions, and Change Project Wallet.
  * Added copy-address button in the wallet header.

* **Style**
  * Redesigned wallet header layout with improved visual structure.
  * Refined icon sizing and spacing throughout the wallet interface.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-15 17:44:00 +00:00
MananTank 151127d668 [PRO-144] SDK: Update in-app wallet icon in wide connect ui (#8556)
<!--

## 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 in-app wallet feature by updating icons, adding new components for different authentication methods, and improving the UI for wallet connection options.

### Detailed summary
- Updated the in-app wallet icon in the wide connect UI.
- Changed `defaultAuthOptions` from a constant to an exported variable.
- Modified `EmailIcon` component for better styling and structure.
- Added new functions for various modal configurations (`ConfiguredInAppWalletWideModal`, `GoogleLoginWideModal`, `GithubLoginWideModal`).
- Introduced `InAppWalletIcon` component to handle multiple authentication methods visually.
- Created comprehensive storybook examples for `InAppWalletIcon` with various authentication options.

> ✨ 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 an in-app wallet icon that displays available authentication methods and adapts layout for single vs. multiple options.

* **UI/UX Improvements**
  * Updated wallet icon visuals and dynamic color handling for more consistent rendering in the connect modal.
  * Simplified email icon visuals for clearer strokes.

* **Stories**
  * Added Storybook examples showcasing the in-app wallet icon across themes and auth-option permutations.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-12 21:03:26 +00:00
MananTank 759f41b11e Dashboard: Update Bridge get started section (#8555)
<!--

## 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 `QuickstartSection.client.tsx` file to improve the presentation of the bridge widget feature. It modifies the `FeatureCard` components to enhance descriptions, titles, and links for better clarity and usability.

### Detailed summary
- Changed the title of the first `FeatureCard` from "Buy Widget Component" to "Bridge Widget".
- Updated the description of the first `FeatureCard` to "Integrate cross-chain swaps and fiat onramp into your app using bridge widget".
- Altered features in the first `FeatureCard` to include "Integrate with iframe, script, or React component".
- Updated the link URL in the first `FeatureCard` to point to the bridge widget documentation.
- Removed the "Customizable UI" feature from the first `FeatureCard`.

> ✨ 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

* **Updates**
  * Bridge Widget quickstart section now supports integration via iframe, script tag, and React component.
  * Enhanced feature card descriptions for improved clarity in the onboarding flow.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-12 19:22:00 +00:00
MananTank 5ec84bd3b6 [MNY-314] SDK: Token Selection UX improvements in SwapWidget (#8552)
<!--

## 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 user experience in the `SwapWidget` by automatically selecting the native token when no sell or buy token is selected.

### Detailed summary
- Added logic to set the `sellToken` as the native token of the buy token's chain if no sell token is selected.
- Added logic to set the `buyToken` as the native token of the sell token's chain if no buy token is selected.

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

<!-- end pr-codex -->
2025-12-12 18:49:07 +00:00
MananTank 38f4a89076 [MNY-334] SDK: Fix Spinner animation missing sometimes in Next.js (#8553)
<!--

## 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 addresses an issue with the `Spinner` component's animation in a Next.js environment, ensuring that the animation works consistently.

### Detailed summary
- Updated the `Spinner` component to use a `<circle>` element instead of a `Circle` styled component.
- Removed the `StyledCircle` import from `elements.js`.
- Added animation styles directly to the `<circle>` element.
- Defined the `tw-spinner-circle-dash` keyframe animation within the component.

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

<!-- end pr-codex -->
2025-12-12 18:35:49 +00:00
MananTank 31e24f1c58 [MNY-331] SDK: Alphabetically sort chains in SwapWidget UI (#8551)
<!--

## 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 `SwapWidget` UI by alphabetically sorting the chains returned by the `use-bridge-chains.ts` module, ensuring that chains with names starting with numbers are placed at the end of the list.

### Detailed summary
- Updated the `queryFn` in `use-bridge-chains.ts` to be asynchronous.
- Introduced a copy of the fetched data to sort it.
- Implemented sorting logic to place chains starting with numbers at the end.
- Used `localeCompare` for alphabetical sorting of chain names.

> ✨ 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**
  * Chains in the SwapWidget are now alphabetically sorted for improved navigation; chain names beginning with digits appear after alphabetic entries.

* **Chores**
  * Patch release entry added to the changelog.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-12 18:35:49 +00:00
MananTank 2bb6a4f9a3 [MNY-332] Dashboard: Checkout link error message improvements (#8550)
<!--

## 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 error handling in the `billing.ts` files by checking for a specific error structure in the JSON response. If the error is present and formatted correctly, it returns a detailed error message instead of a generic one.

### Detailed summary
- Added error handling for JSON responses in `apps/dashboard/src/@/actions/billing.ts`:
  - Checks if `error` and `message` exist in the response.
  - Validates that `json.error.message` is a string.
  - Returns a structured error object with the message and status.

- Similar changes were made in `apps/dashboard/src/app/(app)/(stripe)/utils/billing.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

## Bug Fixes
* Improved error message handling for billing operations to display API-provided error messages to users instead of generic fallback errors, enabling clearer feedback when checkout transactions encounter issues.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-12 17:56:56 +00:00
eabdelmoneim e281ed185e [Playground] Fix bridge/payments documentation links (#8549)
https://linear.app/thirdweb/issue/PRO-148/fix-playground-doc-links-for-bridge-components

Update documentation links for bridge/payments widgets to follow consistent naming convention:
- Buy Widget: wallets/sponsor-gas → references/typescript/v5/BuyWidget
- Checkout Widget: payments → references/typescript/v5/CheckoutWidget
- Transaction Widget: wallets/sponsor-gas → references/typescript/v5/TransactionWidget
- Transaction Button: Add missing utm_source=playground parameter

All links now point to /references/typescript/v5/{WidgetName} with utm_source tracking.

🤖 Generated with [Claude Code](https://claude.com/claude-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 updates the `docsLink` properties in several components to point to the correct documentation pages for various widgets in the `playground-web` application.

### Detailed summary
- Updated `docsLink` in `apps/playground-web/src/app/bridge/buy-widget/page.tsx` to point to `BuyWidget`.
- Updated `docsLink` in `apps/playground-web/src/app/bridge/checkout-widget/page.tsx` to point to `CheckoutWidget`.
- Updated `docsLink` in `apps/playground-web/src/app/bridge/transaction-button/page.tsx` to include `?utm_source=playground`.
- Updated `docsLink` in `apps/playground-web/src/app/bridge/transaction-widget/page.tsx` to point to `TransactionWidget`.

> ✨ 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 documentation links across playground widgets to reference current TypeScript v5 documentation resources.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-12 17:52:12 +00:00
MananTank 116975286f [MNY-327] SDK: Display error returned from API in login with phone number UI (#8548)
<!--

## 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 error handling and state management in the OTP authentication process. It enhances the way error messages are displayed and modifies the structure of `AccountStatus` to accommodate additional information.

### Detailed summary
- In `otp.ts`, enhanced error handling by parsing the response for a detailed message.
- Updated `AccountStatus` type in `OTPLoginUI.tsx` to an object structure, allowing for an error message.
- Modified state management for `accountStatus` to use the new object structure.
- Adjusted UI rendering logic to handle the new `AccountStatus` format.

> ✨ 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 error message handling in OTP verification to display detailed server error messages to users instead of generic messages.
  * Improved error status display in OTP login interface.

* **Style**
  * Updated OTP login UI layout and spacing for better status presentation.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-12 17:08:22 +00:00
FirekeeperandGitHub ff8f4130e9 Add Dedicated Relayer docs and sidebar link (#8546) 2025-12-12 23:54:19 +07:00
MananTank ffd8c402f4 Dashboard: Fix webhooks link in project/contracts page (#8547)
<!--

## 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 URL structure for webhooks in the `page.tsx` file, changing the path from `/webhooks/contracts` to `/tokens/webhooks`.

### Detailed summary
- Modified the `href` property in the object from:
  - `href: `/team/${params.team_slug}/${params.project_slug}/webhooks/contracts``
  - to `href: `/team/${params.team_slug}/${params.project_slug}/tokens/webhooks``

> ✨ 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

## Release Notes

* **Bug Fixes**
  * Fixed the "webhooks" navigation link in the Contracts page to correctly redirect to the tokens section instead of the contracts section.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-12 16:32:05 +00:00
MananTank d365efe997 Update Next.js version (#8545)
<!--

## 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 updates various dependencies across multiple `package.json` files, particularly upgrading the `next` version from `15.3.6` to `15.3.8` and `@next/eslint-plugin-next` from `15.3.6` to `15.3.8`. It also modifies the `pnpm-lock.yaml` to reflect these updates.

### Detailed summary
- Updated `next` from `15.3.6` to `15.3.8` in:
  - `packages/ui/package.json`
  - `apps/wallet-ui/package.json`
  - `apps/portal/package.json`
  - `apps/playground-web/package.json`
  - `apps/nebula/package.json`
  - `apps/dashboard/package.json`
- Updated `@next/eslint-plugin-next` from `15.3.6` to `15.3.8` in:
  - `apps/wallet-ui/package.json`
  - `apps/nebula/package.json`
  - `apps/dashboard/package.json`
- Updated `@next/mdx` from `15.3.6` to `15.3.8` in `apps/portal/package.json`.
- Adjusted `pnpm-lock.yaml` to reflect the above dependency updates.

> The following files were skipped due to too many changes: `pnpm-lock.yaml`

> ✨ 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**
  * Bumped Next.js and related Next packages (minor/patch updates) across multiple apps and packages for stability and consistency.
  * Normalized package manifest formatting across projects; no functional or public API changes introduced.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-12 11:06:15 +00:00
FirekeeperandGitHub 1558b48056 relayer prod (#8535) 2025-12-12 06:14:14 +07:00
FirekeeperandGitHub 3fba7e4e88 relayer updates (#8541) 2025-12-12 05:41:59 +07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
57ccadbef2 Version Packages (#8539)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-11 02:52:55 -08:00
Joaquim VergesandGitHub 20e41ee164 [SDK] Fix null options handling in useFetchWithPayment hook (#8540) 2025-12-11 02:47:50 -08:00
Joaquim VergesandGitHub f5ab57f365 [SDK] Cache and reuse x402 permit signatures for upto schemes (#8538) 2025-12-11 02:40:55 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
01dce0e581 Version Packages (#8536)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-10 17:43:25 -08:00
Joaquim VergesandGitHub 18425f9d74 [SDK] Add minPrice property for x402 payments using 'upto' schema (#8534) 2025-12-10 17:36:03 -08:00
Joaquim VergesandGitHub bd2761af15 [Portal] Improve documentation for upto payment scheme (#8531) 2025-12-10 16:06:05 -08:00
FirekeeperandGitHub f3d2739df1 Improve relayer analytics (#8533) 2025-12-11 06:47:03 +07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1aa4912b17 Version Packages (#8532)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-11 04:36:33 +07:00
7967d7f17e Dedicated Relayer (#8479)
Co-authored-by: Manan Tank <[email protected]>
2025-12-11 04:26:03 +07:00
Joaquim VergesandGitHub 1487de6808 Fix typo in "Create Project" button text (#8530) 2025-12-10 10:53:06 -08:00
MananTank 18917b632a Fix bridge iframe config (#8529)
<!--

## 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 references from `/bridge/embed` to `/bridge/widget` in the configuration files, reflecting a change in the iframe page used in the application.

### Detailed summary
- Updated `PAYMENT_METHOD_CONFIGURATION` comment in `.env.example` from `/bridge/embed` to `/bridge/widget`.
- Changed `source` in `next.config.ts` from `/bridge/embed` to `/bridge/widget`.
- Updated `source` path pattern in `next.config.ts` from `/bridge/embed/:path*` to `/bridge/widget/:path*`.

> ✨ 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 configuration documentation.

* **Chores**
  * Updated internal routing paths in application configuration.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-10 18:35:11 +00:00
MananTank 41919024c3 Add bridge widget iframe docs, update path, link to docs (#8528)
<!--

## 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 `Bridge` and `Bridge Widget` functionalities, improving UI elements, adding new features, and updating documentation. It includes modifications to components, styling adjustments, and expanded capabilities for currency selection.

### Detailed summary
- Updated `Sidebar.tsx` to remove padding.
- Changed `page.mdx` headers from "Key Features" to "Features".
- Enhanced `reportBridgePageLinkClick` to include "integrate-bridge-widget".
- Modified `page.tsx` to include client ID instructions.
- Adjusted header font sizes in `header.tsx`.
- Improved `bridgeRedirects` to include new paths.
- Updated `bridge-widget` links in `sidebar.tsx`.
- Added `persistTokenSelections` and `currency` props to `UniversalBridgeEmbed`.
- Created `IframeCodePreview` component for iframe integration documentation.
- Added a new `BadgeLink` component for better user interaction.
- Expanded `BuyAndSwapEmbed` props to support currency and token selections.
- Updated documentation for `BridgeWidget` and `Iframe` integrations with examples.
- Enhanced `BridgePageUI` to include the new `AddBridgeWidgetLink`.
- Introduced multiple currency options for fiat values in widget interfaces.

> ✨ 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**
  * Bridge widget available via Iframe, Script, and React component
  * Fiat currency selection for buy/swap widgets
  * Token selection persistence option
  * "Add Bridge widget" integration link added to Bridge page

* **Documentation**
  * New detailed guides and examples for Iframe, Script, and React integration
  * Code preview and embedding examples added

* **Style**
  * Typography, spacing, and layout refinements across Bridge pages
  * Navigation reorganized into a Bridge Widget group

* **Chores**
  * Added redirects for Bridge widget paths

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-10 17:53:09 +00:00
github-actions[bot]andJonas Daniels 56eb1d2491 Version Packages 2025-12-10 01:08:35 -08:00
Jonas Daniels 70ff608c72 Add UTF-8 JSON string parsing support 2025-12-10 00:20:56 -08:00
MananTank 7e442a4c66 [MNY-328] Dashboard: Add Bridge iframe page (#8525)
<!--

## 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 enhances the `bridge` functionality by introducing iframe support, updating component structures, and modifying environment variables. It also includes changes in layout and security policies related to the `bridge` feature.

### Detailed summary
- Added `NEXT_PUBLIC_BRIDGE_IFRAME_CLIENT_ID` to environment variables.
- Modified `BridgeProviders` to accept `clientId` and `forcedTheme`.
- Updated `UniversalBridgeEmbed` to use dynamic `pageType`.
- Created a new layout for `BridgeEmbedLayout`.
- Enhanced security headers for `/bridge/embed`.
- Refactored imports and paths for `data` and components.
- Added `bridge-iframe` to `pageType` types across multiple files.
- Implemented new parsing logic for search parameters in the iframe page.

> ✨ 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 a new /bridge/embed page to support an embeddable bridge iframe and a "bridge-iframe" flow.

* **Security**
  * Updated content security headers and frame-ancestor rules to allow safe embedding of the bridge iframe.

* **Chores**
  * Added environment variable for the bridge iframe client ID.
  * Simplified theme behavior to rely on the default dark theme.

* **Analytics**
  * Added "bridge-iframe" page type to reporting to track iframe usage.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-09 22:05:25 +00:00
MananTank a5b888576f Dashboard: Login page UI tweaks, remove phone login notice (#8524)
<!--

## 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 `LoginPage.tsx` component by adding a theme toggle button, updating the layout, and improving the UI elements. It incorporates new components and refines the existing structure for better aesthetics and functionality.

### Detailed summary
- Added imports for `MoonIcon`, `SunIcon`, `DotsBackgroundPattern`, `Button`, and `Skeleton`.
- Updated header background and layout, adjusting padding and margins.
- Introduced a `ToggleThemeButton` component for theme switching.
- Removed deprecated login message and background image.
- Enhanced the `LoginPageContainer` structure for cleaner rendering.

> ✨ 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 decorative dots background pattern to enhance the login page visual design.
  * Added theme toggle button enabling light and dark mode switching.
  * Enhanced header with brand text display alongside the logo.

* **Style**
  * Streamlined login page layout with a simplified and cleaner container structure.
  * Adjusted spacing, padding, and overall dimensions for improved visual hierarchy.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-09 17:49:25 +00:00
FirekeeperandGitHub 53b6d1b4d6 bal override path (#8516) 2025-12-09 02:36:38 +07:00
MananTank 9856ee4010 [PRO-138] Dashboard: Update Project highlights card revenue tab (#8515)
<!--

## 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 analytics components by adding new functionalities and refining existing code for better performance and readability. It introduces new data handling for `x402Settlements`, updates chart configurations, and improves conditional rendering in various components.

### Detailed summary
- Added `x402Settlements` data handling in `AsyncAppHighlightsCard`.
- Updated `CombinedBarChartConfig` to include `stackedKeys`.
- Improved conditional rendering for the `EmptyChartStateGetStartedCTA`.
- Refined the `BarChart` component for better data processing.
- Enhanced `ProjectHighlightsCard` to include `bridgeRevenue` and `x402Revenue`.
- Introduced utility functions for generating mock data in stories.
- Updated types in various components for better type safety.

> ✨ 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 stacked data visualization support to charts.
  * Integrated x402 revenue tracking into analytics highlights.
  * Added ability to hide specific chart tabs from display.

* **Improvements**
  * Refined USD currency formatting to display 2 decimal places for cleaner presentation.
  * Made analytics CTA buttons conditionally displayed based on configuration.

* **Refactor**
  * Simplified settlement data model with inheritance to reduce redundancy.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-08 18:55:16 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
3c768a0ef9 Version Packages (#8503)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-05 23:07:48 -08:00
FirekeeperandGitHub a388439022 Add Solana facilitator guide to docs (#8511) 2025-12-06 05:53:27 +07:00
MananTank 7b873d38db [MNY-322] SDK: Update success message shown in SwapWidget component (#8509)
<!--

## 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 success messages in the `SuccessScreen` component across various widgets in the `thirdweb` package, specifically for payment and swap transactions.

### Detailed summary
- Updated success message type in `SwapWidget` to `"swap-success"`.
- Updated success message type in `BuyWidget` and `CheckoutWidget` to `"payment-success"`.
- Added a new story `BasicSwapSuccess` in `SuccessScreen.stories.tsx`.
- Modified `SuccessScreen` to handle different message types.
- Updated displayed titles and descriptions based on the success type.
- Changed button text from "View Payment Receipt" to "View Transaction Receipt".

> ✨ 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**
  * Success screens now display dynamic, context-appropriate messaging tailored to transaction type, with distinct titles and descriptions for swap versus payment transactions.
  * Enhanced user experience with improved terminology—the option to "View Transaction Receipt" now displays consistently across all payment flows for better clarity and consistency.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-05 20:46:01 +00:00
Jonas Daniels d33a943176 Add AI privacy policy and terms to framer-rewrites.js 2025-12-05 12:38:02 -08:00
FirekeeperandGitHub d5e0b6eb73 Update API reference links in dashboard and portal (#8508) 2025-12-06 02:57:42 +07:00
Joaquim VergesandGitHub 75e9be0358 [SDK] Update token balances query to use client instead of clientId (#8507) 2025-12-04 18:32:44 -08:00
joaquim-verges a704c3f2b0 [SDK] Allow passing activeWallet to SwapWidget (#8504)
<!--

## 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 `SwapWidget` by allowing the passing of an `activeWallet` prop and improving the project wallet functionality in the dashboard. It also includes updates to the VSCode settings and introduces new components for swapping tokens.

### Detailed summary
- Added `activeWallet` prop to `SwapWidgetProps`.
- Modified `useActiveWalletInfo` to accept an `activeWalletOverride`.
- Introduced `SwapProjectWalletModalContent` for token swapping.
- Updated `ProjectWalletDetailsSection` to include swap functionality.
- Adjusted VSCode settings for formatting preferences.

> ✨ 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 in-app token swap functionality for project wallets, allowing users to exchange tokens directly from the wallet interface with credentials management.
  * SwapWidget now supports wallet pre-selection, enabling faster and more streamlined token swap initiation.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-05 00:03:49 +00:00
MananTank b0eccf365a [MNY-318] SDK: Remove tabs from token selection ui in SwapWidget (#8501)
<!--

## 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 `SelectToken` component by simplifying token limit management, improving UI consistency, and refactoring token display logic. It also removes unnecessary tabs and optimizes the selection process for better user experience.

### Detailed summary
- Replaced constants `ALL_TOKENS_LIMIT` and `OWNED_TOKENS_LIMIT` with `INITIAL_LIMIT`.
- Unified token limit state management into a single `limit` state.
- Refactored token fetching and display logic to streamline data handling.
- Removed unnecessary tab UI for better clarity.
- Simplified the structure of token data handling in `SelectTokenUI`.
- Updated the rendering logic for owned and other tokens to improve performance and readability.

> ✨ 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

* **UI Improvements**
  * Redesigned token selection UI in SwapWidget: removed tab navigation and consolidated token lists into dedicated "Your Tokens" and "Other Tokens" sections for a clearer, more intuitive experience.

* **Bug Fixes**
  * Enhanced search functionality to properly reset token filtering, improving responsiveness.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-04 22:09:50 +00:00
MananTank f3525c7c01 [PRO-123] Dashboard: Move project webhooks (#8500)
<!--

## 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 and reorganizing the `webhooks` feature within the application, including changes to routing and component structure, as well as the introduction of a `bridge` section for webhooks.

### Detailed summary
- Deleted multiple files related to `webhooks` under the previous structure.
- Updated routing paths from `/webhooks/payments` to `/bridge/webhooks`.
- Refactored components to align with the new `bridge` structure.
- Adjusted imports to reflect new file locations.
- Removed unused `WebhookIcon` and streamlined sidebar links.
- Introduced new layout and page components under `tokens/(general)/webhooks`.

> ✨ 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

## Release Notes

* **Navigation & Organization**
  - Webhooks functionality relocated to the Bridge section for improved contextual organization
  - Updated navigation paths and sidebar structure accordingly

* **UI Improvements**
  - Redesigned empty state for webhooks with enhanced visual styling and icon display

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-04 21:35:46 +00:00
MananTank e516b4ddc1 [PRO-147] Dashboard: Update empty state for project analytics charts (#8499)
<!--

## 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 handling of empty chart states across various components in the dashboard by standardizing the `EmptyChartState` usage and enhancing the user experience with informative content.

### Detailed summary
- Updated `EmptyChartState` components to use `content` prop instead of `type`.
- Enhanced `EmptyChartState` with more descriptive text and links.
- Introduced `EmptyChartStateGetStartedCTA` for better guidance in empty states.
- Adjusted multiple charts to incorporate the new empty state handling.
- Simplified chart rendering logic when no data is available.

> ✨ 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

* **UI/UX Improvements**
  * Unified and simplified empty-chart visuals (smaller message, cleaner layout, adjusted loading background) and consistent spacing across analytics cards.
  * Empty states now show contextual CTAs linking to relevant configuration/details pages.

* **New Features**
  * Reusable "Get Started" CTA for empty charts with title, description and action.

* **Refactor**
  * Charts can now accept and render provided empty-state content for a more consistent UX.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-04 18:38:25 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
dffa457a39 Version Packages (#8498)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-03 19:50:37 -08:00
Joaquim VergesandGitHub 859032dc17 [SDK] Skip swap review step when coming from swap widget (#8496) 2025-12-03 19:23:42 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
e0ee69b061 Version Packages (#8494)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-03 17:47:50 -08:00
Joaquim VergesandGitHub b6be8d67ef [SDK] Faster token balance load query (#8495) 2025-12-03 17:38:16 -08:00
Joaquim VergesandGitHub 6ddb15cf02 [SDK] Optimize payment methods query by removing quotes and improving balance checks (#8493) 2025-12-03 17:00:25 -08:00
joaquim-verges 551ec680df [SDK] Add payment selection tracking (#8490)
<!--

## 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 payment selection tracking throughout the `thirdweb` package by replacing `useQuery` calls with `useEffect` hooks to ensure that payment events are tracked only once per component lifecycle.

### Detailed summary
- Replaced `useQuery` for tracking payment events with `useEffect` in multiple components.
- Added `useRef` to prevent multiple event firings.
- Updated tracking events in:
  - `UnsupportedTokenScreen`
  - `BridgeWidget`
  - `ErrorBanner`
  - `QuoteLoader`
  - `PaymentSelection`
  - `PaymentDetails`
  - `SwapWidget`
  - `SuccessScreen`
  - `CheckoutWidget`
  - `TransactionWidget`
  - `BuyWidget`
  - `BridgeWidget`

> ✨ 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**
  * Refined Bridge telemetry: replaced reactive query-based tracking with guarded one-time render and interaction events to reduce duplicates.
  * Expanded analytics coverage for widgets, payment selection, loading/quote stages, payment details, swaps, unsupported-token and success flows.
  * Cleaned up and simplified tracking behavior: some legacy query-driven trackers were removed or consolidated for clearer, more reliable events.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-04 00:32:15 +00:00
Jonas Daniels 7b9ce493c8 upgrade next and react 2025-12-03 14:38:01 -08:00
0xFirekeeper b27fcd3ba0 Add Solana wallet docs and sidebar link (#8489)
Added a new sidebar entry for Solana Transactions in the wallets section and introduced a comprehensive Solana server wallet documentation page covering wallet management, balances, transactions, signing, swaps, and example API workflows.

Closes BLD-414

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces support for managing Solana transactions in the application, including wallet creation, balance checking, token transfers, and swaps. It enhances the user interface with detailed documentation and examples for developers.

### Detailed summary
- Added a new sidebar entry for `Solana Transactions`.
- Created a detailed `page.mdx` for Solana server wallets.
- Included metadata for the `Solana Server Wallets` page.
- Documented capabilities: wallet management, balance checking, signing, token swaps, and custom transactions.
- Provided example workflows using `curl` commands for various operations.
- Defined network identifiers for `Mainnet` and `Devnet`.

> ✨ 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 Solana Transactions link in Server Wallets navigation for streamlined access to Solana wallet operations

* **Documentation**
  * New Solana server wallets documentation page with getting started guides, API workflows for wallet creation, balance retrieval, token transfers, transaction signing, and token swap operations

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-03 19:08:53 +00:00
MananTank c40603f36e [PRO-134] Add Bridge Volume pie chart in project overview page (#8488)
<!--

## 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 analytics components by refining data formatting, adding currency support, and improving the UI for better data visualization in the dashboard.

### Detailed summary
- Added `maximumFractionDigits` to formatting utilities.
- Integrated currency formatting in `PieChart` and `TotalValueChartHeader`.
- Updated `ProjectHighlightsCard` to remove total volume and adjust labels.
- Introduced `BridgeChartCard` for displaying bridge usage data.
- Enhanced `TotalVolumePieChart` to conditionally display total volume and adjusted data handling.

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

<!-- end pr-codex -->
2025-12-03 18:46:13 +00:00
MananTank 458246fea1 Dashboard: Project analytics code organization refactor (#8487)
<!--

## 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 analytics components for a dashboard application, enhancing the project's data visualization capabilities. It includes new types for page parameters and search parameters, as well as multiple asynchronous chart components for different metrics.

### Detailed summary
- Added `PageParams` and `PageSearchParams` types for handling routing parameters.
- Implemented `AsyncRPCRequestsChartCard` and `RPCRequestsChartCard` for RPC usage analytics.
- Created `AsyncAiAnalytics` and `AIAnalyticsChartCard` for AI usage analytics.
- Introduced `AsyncIndexerRequestsChartCard` and `IndexerRequestsChartCard` for indexer request analytics.
- Developed `AsyncX402RequestsChart` and `X402RequestsChartCard` for x402 settlements analytics.
- Added `AsyncAppHighlightsCard` and `ProjectHighlightCard` for displaying project highlights.
- Refactored `ProjectAnalytics` function to integrate new chart components.
- Removed redundant code and improved structure for better maintainability.

> ✨ 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

## Release Notes

* **New Features**
  * Added AI token volume analytics chart to project dashboard
  * Added indexer/gateway request analytics chart
  * Added RPC usage analytics chart
  * Added X402 settlements analytics chart
  * Introduced project highlights card displaying wallet and bridge usage metrics

* **Refactor**
  * Reorganized project analytics dashboard structure for improved layout and user experience

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-03 18:46:13 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1b3e938b06 Version Packages (#8470)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-03 01:13:36 -08:00
Joaquim VergesandGitHub 3ebca18f9e [SDK] Add error tracking for payment execution failures (#8486) 2025-12-03 00:55:49 -08:00
Joaquim VergesandGitHub 4c5696e414 [SDK] Add caching and timeout for fetching capabilities (#8485) 2025-12-02 17:50:58 -08:00
Joaquim VergesandGitHub 9e43da4ec0 [SDK] Support ERC5792 batch transactions for swaps, add slippage option to Bridge API (#8484) 2025-12-02 15:49:37 -08:00
MananTank 22cea5bb8f Dashboard: Update bridge stats (#8483)
<!--

## 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 `supportedTokens` values in the `data.ts` file to reflect an increase in the number of supported tokens for the bridge feature.

### Detailed summary
- Updated `supportedTokens` from `"10,000+"` to `"14,000+"` (string format).
- Updated `supportedTokens` from `10000` to `14000` (numeric format).

> ✨ 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

* **Updates**
  * Bridge now supports 14,000+ tokens, increased from 10,000+.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-02 21:43:51 +00:00
MananTank d0e4db26ce [PRO-136] Add x402 requests chart in project overview (#8482)
<!--

## 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 chart components and their presentation in the dashboard, including layout adjustments, new chart configurations, and improved user feedback for empty states.

### Detailed summary
- Updated `Card` component in `bar-chart.tsx` to include additional class names.
- Adjusted `aspect-auto` for `ThirdwebBarChart` in `AiTokenUsageChartCard`.
- Modified `WaitingForIntegrationCard` to conditionally render `TabButtons`.
- Enhanced empty state in `EmptyChartState` with a visual indicator and message.
- Added `aspect-auto` class to `SkeletonBarChart`.
- Introduced `X402RequestsChartCardUI` for handling X402 request data.
- Refactored project analytics page to include `X402RequestsChartCardUI` and manage its data fetching.

> ✨ 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 X402 requests analytics chart with per-period totals to the project dashboard.

* **Bug Fixes**
  * Tab selector now appears only when multiple code tabs exist.
  * Time filters are shown only when relevant data is present.
  * Empty-chart state now shows a clear "No data available" display.

* **Style**
  * Header styling refined for improved visuals on large screens.
  * Chart containers adjusted for better aspect/overflow handling.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-02 21:32:12 +00:00
MananTank 1056696696 [PRO-145] Dashboard: Remove Engine Cloud Requests chart from project overview (#8481)
<!--

## 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 the removal of the `EngineCloudChartCard` component and its related files, as well as the `EngineCloudStats` interface from the analytics types. It also updates the `ProjectAnalytics` component to eliminate the usage of `EngineCloudChartCardAsync`.

### Detailed summary
- Deleted files related to `EngineCloudChartCard`:
  - `index.tsx`
  - `EngineCloudBarChartCardUI.tsx`
  - `EngineCloudBarChartCard.stories.tsx`
- Removed `EngineCloudStats` interface from `analytics.ts`.
- Eliminated `EngineCloudChartCardAsync` import and usage in `ProjectAnalytics`.
- Added revalidation settings to cache functions in `analytics.ts`.

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

<!-- end pr-codex -->
2025-12-02 17:55:23 +00:00
MananTank 48297993d8 [PRO-132] Dashboard: Update Wallet Connection charts in project (#8480)
<!--

## 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 analytics and chart components related to wallet connections in the application. It introduces new types, refactors existing components, and updates links and chart functionalities to improve usability and data representation.

### Detailed summary
- Deleted unused files related to wallet analytics charts.
- Introduced `WalletStatsWithName` type for wallet statistics.
- Updated links to point to new endpoints.
- Refactored chart components to improve data handling and display.
- Added new `EOAConnectionsChart` and `UserWalletConnectionsChart` components.
- Replaced `InAppWalletAnalytics` with `UserWalletConnectionsChart`.
- Enhanced `RPCRequestsChartUI` to include an `isPending` prop.
- Updated metrics and labels in various charts for better clarity.
- Improved error handling in API calls for wallet connections.
- Removed deprecated code and streamlined analytics data fetching.

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

<!-- end pr-codex -->
2025-12-02 17:55:23 +00:00
MananTank cd9e42911e [PRO-135] Add AI token volume chart in project overview page (#8472)
<!--

## 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 AI features in the dashboard by updating UI components, improving navigation, and adding analytics capabilities for AI token usage.

### Detailed summary
- Changed `variant` of a `Button` in `ChatBar.tsx` from `primary` to `default`.
- Added `viewMoreLink` prop to `AiTokenUsageChartCardUI` in `index.tsx`.
- Updated sidebar structure in `ProjectSidebarLayout.tsx` to include submenus and links for AI.
- Modified redirection logic in `page.tsx` for missing sessions.
- Adjusted styling and structure of `ExportToCSVButton` in `ExportToCSVButton.tsx`.
- Removed the `Analytics` link from `ChatSidebar.tsx`.
- Removed unused state for token symbol in `project-wallet-details.tsx`.
- Introduced `AsyncAiAnalytics` function to fetch AI usage data in `page.tsx`.
- Updated `AiTokenUsageChartCardUI` to handle new props and improve chart presentation.
- Enhanced empty state message in `AiTokenUsageChartCardUI` with a button linking to AI chat documentation.

> ✨ 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**
  * AI token usage analytics card added to project dashboard with totals and chart.
  * AI sidebar reorganized into Chat and Analytics entries; AI analytics accessible from project pages.

* **Bug Fixes**
  * Missing chat session now redirects back to the AI section instead of showing an error.

* **Style**
  * Button and spinner visuals refined (export and send controls); chart sizing and empty-state text adjusted; chat sidebar streamlined.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-12-02 17:55:23 +00:00
Jonas Daniels 41e2ccb67d Add isLegacyPlan flag to Team type and UI components 2025-12-01 14:22:34 -08:00
Joaquim VergesandGitHub 68a1dcac26 [Playground] Improve token selection and UI for x402 playground (#8471) 2025-11-30 17:07:30 -08:00
Joaquim VergesandGitHub 98d8f292db [Dashboard] Show project server wallet balance for any token (#8469) 2025-11-30 15:01:14 -08:00
FirekeeperandGitHub 4f2dbccba4 small fixes to dotnet docs (#8468) 2025-11-27 17:38:46 +07:00
MananTank 908142f8fa [PRO-116] Move Server wallets table to separate page (#8467)
<!--

## 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 `Wallets` section to the application, enhancing the wallet management interface. It updates the routing and adds functionality to handle EVM and Solana wallets, including error handling and data fetching improvements.

### Detailed summary
- Added a new route for `Wallets` in `layout.tsx`.
- Updated the redirect path in `page.tsx` for unauthorized access.
- Removed deprecated error handling for EVM wallets.
- Implemented new logic for fetching and displaying wallets, including Solana accounts.
- Enhanced error handling for wallet retrieval and connection issues.

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

<!-- end pr-codex -->
2025-11-26 22:17:40 +00:00
MananTank 4d065c3774 [PRO-137] Add indexer and RPC charts in project overview page (#8464)
<!--

## 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 chart components in the dashboard by updating properties for better data visualization and integrating new functionalities for displaying RPC and status code requests.

### Detailed summary
- Updated `xAxis` property from `sameDay` to `showHour` in `rate-graph.tsx` and `count-graph.tsx`.
- Increased height of `Card` in `RpcMethodBarChartCardUI.tsx`.
- Changed title and description handling in `RequestsByStatusGraph.tsx`.
- Added `className` prop to `ThirdwebBarChart` component.
- Replaced `RequestsGraph` with `RPCRequestsChartUI` in `RPCAnalytics.tsx`.
- Introduced a new component `TotalValueChartHeader` for displaying total values.
- Updated `RequestsGraph` to include a breakdown by hour.
- Added asynchronous functions for fetching indexer and RPC request data in `page.tsx`.

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

<!-- end pr-codex -->
2025-11-26 22:05:19 +00:00
MananTank 426229050c [PRO-129] Dashboard: Remove AA analytics from project overview (#8463)
<!--

## 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 renaming and updating references from `Account Abstraction` to `Gas Sponsorship` across various components in the codebase, as well as removing the `TotalSponsoredCard` functionality.

### Detailed summary
- Removed `total-sponsored.tsx`.
- Updated `EmptyStateCard` metric from `Account Abstraction` to `Gas Sponsorship`.
- Modified `featureDescription` in `UpsellContent` to include `Gas Sponsorship`.
- Changed alert title from `Account Abstraction service is disabled` to `Gas Sponsorship is disabled`.
- Increased padding in `usage/layout.tsx`.
- Renamed usage path from `Account Abstraction` to `Gas Sponsorship`.
- Removed `TotalSponsoredCard` and its related functionality from `page.tsx`.

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

<!-- end pr-codex -->
2025-11-26 22:05:18 +00:00
MananTank 0fd55a2a13 [PRO-130] Move RPC methods chart from project overview to RPC tab (#8462)
<!--

## 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 UI components of the RPC analytics dashboard by refining styles, improving layout, and optimizing the rendering of charts and tables.

### Detailed summary
- Added `titleClassName` to `RequestsGraph` for improved styling.
- Removed unnecessary `showLegend` from `RequestsGraph`.
- Simplified `CardHeader` and adjusted padding in `RpcMethodBarChartCardCardUI`.
- Reintroduced `RpcMethodBarChartCardAsync` in `RPCAnalytics` with proper parameters.
- Updated layout and styling in `TopRPCMethodsTable`, replacing `Card` with a `div` and enhancing header styles.

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

<!-- end pr-codex -->
2025-11-26 22:05:18 +00:00
MananTank 59ad7e8b09 [PRO-127] Dashboard: Move user wallets table to separate tab (#8460)
<!--

## 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 user wallet components in the dashboard application, replacing the `InAppWalletUsersPageContent` with `UserWalletsTable` and enhancing the user wallet functionalities.

### Detailed summary
- Added a new route for `Users` in `layout.tsx`.
- Replaced `InAppWalletUsersPageContent` with `UserWalletsTable` in `page.tsx`.
- Introduced a new `Page` component for user wallets with authentication and project validation.
- Updated `UserWalletsTable` to include necessary props and improve layout.

> ✨ 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 a "Users" tab in the wallets area and a dedicated Users page showing user wallets (search, download and pagination available).

* **Refactor**
  * Consolidated and simplified the user-wallets layout and table for clearer hierarchy and tighter spacing.
  * Moved action controls (search/download) into the header area and adjusted alignment for improved usability.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-26 22:05:18 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
fda2284036 Version Packages (#8466)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-26 14:03:39 -08:00
Joaquim VergesandGitHub 629efc13b6 [SDK] Add Monad chain definition (#8465) 2025-11-26 13:46:02 -08:00
Joaquim VergesandGitHub 512f9dd3c9 [Docs] Fix AI model response handling in server example (#8459) 2025-11-26 11:05:01 -08:00
jnsdls 919230f0c8 Add daily revalidation to TypeScript pages (#8456)
Added daily revalidation to TypeScript reference pages

This PR adds a revalidation period of 24 hours (86400 seconds) to TypeScript reference pages to ensure content stays fresh while minimizing unnecessary rebuilds.

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

* **Chores**
  * Introduced daily automatic revalidation across multiple portal pages and layouts so content refreshes every 24 hours. This enables incremental static regeneration for affected pages, improving cache freshness and helping ensure users see up-to-date content with reduced stale data risk.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a `revalidate` constant set to `86400` seconds (1 day) in multiple files, which specifies that the cached data should be revalidated every day.

### Detailed summary
- Added `export const revalidate = 86400; // revalidate every day` to:
  - `apps/portal/src/app/contracts/extensions/page.tsx`
  - `apps/portal/src/app/react/v5/[...slug]/page.tsx`
  - `apps/portal/src/app/contracts/layout.tsx`
  - `apps/portal/src/app/typescript/v5/[...slug]/page.tsx`
  - `apps/portal/src/app/react/v5/layout.tsx`
  - `apps/portal/src/app/typescript/v5/layout.tsx`
  - `apps/portal/src/app/references/typescript/[version]/[[...slug]]/page.tsx`

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

<!-- end pr-codex -->
2025-11-25 17:23:17 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
3ac8cba2e8 Version Packages (#8458)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-25 17:04:57 -08:00
Joaquim VergesandGitHub 35aaf24e8a [SDK] Add payment scheme and improve type documentation for x402 (#8457) 2025-11-25 16:49:40 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ad2d1757e2 Version Packages (#8452)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-24 15:18:50 -08:00
Joaquim VergesandGitHub 52aba0ffb8 [SDK] Skip chain switching for Coinbase Wallet if already on correct chain (#8453) 2025-11-24 15:13:30 -08:00
Joaquim VergesandGitHub 9809d5cf66 [SDK] Add useFetchWithPayment React hook for x402 payment handling (#8444) 2025-11-24 14:42:50 -08:00
0xFirekeeper 8b10674481 Port gas sponsorship analytics to Team usage tab (#8445)
Updated AccountAbstractionAnalytics to support both team and project variants, allowing for flexible rendering based on context. Refactored usage and summary components to accept optional projectId, and updated the team usage page to use the new analytics and summary components. Adjusted SponsoredTransactionsTable usage to match the new props structure.

Closes BLD-500

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `AccountAbstractionAnalytics` and `AccountAbstractionSummary` components to support additional parameters for date ranges and project identification, improving data handling for user operation statistics.

### Detailed summary
- Added `from` and `to` props to `AccountAbstractionSummary` and `AccountAbstractionAnalytics`.
- Made `projectId` optional in `AsyncAccountAbstractionSummary` and `AccountAbstractionSummary`.
- Updated `AccountAbstractionAnalytics` to handle different variants (`project` and `team`).
- Introduced date range handling in the main page logic to fetch user operation statistics based on selected ranges.

> ✨ 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**
  * Account abstraction views now support team- and project-level variants and explicit date-range (from/to) filtering.
  * Analytics components expose flexible props to render project or team analytics based on resolved ranges.

* **Improvements**
  * Data loading sequence refined to fetch authentication, team lookup, and usage metrics before render.
  * UI updated to show a summary section followed by analytics with improved spacing and conditional rendering.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-24 15:45:06 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
a3c76d5f7d Version Packages (#8435)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-19 17:59:14 -08:00
Joaquim VergesandGitHub f5f6848ead [SDK] Add fundWalletLink prop to x402 responses (#8443) 2025-11-19 17:56:53 -08:00
FirekeeperandGitHub 82ba2b20fc Add gas metrics chart to account abstraction analytics (#8442) 2025-11-20 07:29:20 +07:00
FirekeeperandGitHub b0e69b8794 General doc improvements + gaming sdk snippets (#8441) 2025-11-20 04:38:59 +07:00
MananTank dd983dab8f Dashboard: Update payment links (#8440)
<!--

## 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 URLs and labels related to the payment and bridge functionalities in the dashboard application.

### Detailed summary
- Updated `href` in `layout.tsx` from "https://portal.thirdweb.com/payments/webhooks" to "https://portal.thirdweb.com/bridge/webhooks".
- Changed `href` in `QuickstartSection.client.tsx` from "https://portal.thirdweb.com/payments/products" to "https://portal.thirdweb.com/bridge/products".
- Modified `link` in `highlights-card.tsx` to point to `/bridge/configuration` instead of `/payments/settings`.
- Changed label from "Fee Revenue" to "Bridge Fee Revenue".

> ✨ 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 dashboard links and documentation references to point to Bridge resources instead of Payments.
  * Updated fee-related labels to reflect Bridge branding terminology.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-19 20:51:21 +00:00
MananTank 068cb592dc Dashboard: Add Indexer service filter in chainlist (#8439)
<!--

## 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 service entry for "Indexer" in the `services` array within the `products.ts` file, including an associated icon.

### Detailed summary
- Added import for `InsightIcon`.
- Introduced a new service object:
  - `icon`: `InsightIcon`
  - `id`: "insight"
  - `link`: "https://thirdweb.com/gateway"
  - `name`: "Indexer"

> ✨ 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 new Indexer service now available in the dashboard, delivering enhanced data insights and advanced blockchain indexing capabilities. This integration provides users with streamlined gateway access, enabling efficient data querying, improved management workflows, and better access to blockchain data for comprehensive analysis and operations.<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-19 19:56:25 +00:00
MananTank 359d008d38 [PRO-106] Dashboard: Update Bridge stats (#8430)
<!--

## 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 statistics related to supported chains, tokens, and routes in the bridge application. It enhances the data representation by introducing a new object for numerical values and modifies the `bridge-page` component to utilize these updated statistics.

### Detailed summary
- Updated `supportedChains` from "90+" to "95+".
- Updated `supportedTokens` from "6700+" to "10,000+".
- Updated `supportedRoutes` from "14M+" to "15M+".
- Introduced a new constant `bridgeStatsNumbers` with numerical values for chains, tokens, and routes.
- Modified `bridge-page.tsx` to use `bridgeStatsNumbers` for displaying data in `DataSquare` components.

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

<!-- end pr-codex -->
2025-11-19 19:36:32 +00:00
MananTank 7b5682ce27 Link to team billing page in faucet button on free plan (#8429)
<!--

## 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 `FaucetButton` component by introducing a new condition to handle cases where a paid plan is required to access the faucet. It provides a link to upgrade the plan if the user is on a free plan.

### Detailed summary
- Added a check for `canClaimFaucetQuery.data.type` being `"paid-plan-required"`.
- If true, renders a button linking to the billing page with an upgrade message.
- Removed a TODO comment regarding upsell paths for subscription plans.
- Cleaned up the conditional rendering logic for the button.

> ✨ 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 faucet button for paid-plan scenarios: when claiming is blocked by plan limits, the button now links directly to billing/subscription management and shows an inline arrow indicator for a clearer, actionable path.
* **Bug Fixes**
  * Preserved existing disabled states for throttle and unsupported-chain cases to avoid regressions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-19 19:36:32 +00:00
MananTank 51f46d34cf [PRO-120] Dashboard: Update Explore page (#8437)
<!--

## 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 and enhancing the `Explore` page in the dashboard application, including the removal of obsolete files, updates to UI components, and the introduction of new contract card components to improve usability and visual consistency.

### Detailed summary
- Deleted obsolete files: `tokens-section.tsx`, `page.tsx`, `opengraph-image.png`, `publish-submit.tsx`.
- Updated class names for improved z-index in links.
- Changed heading style from `font-bold` to `font-semibold`.
- Removed unnecessary paragraphs and added new `PopularSection` component.
- Updated `ContractRow` to accept an optional `prefixCard` prop.
- Introduced `TokenContractCard`, `NFTCollectionCard`, and `CoinCard` components.
- Adjusted `ExploreCategory` interface to allow optional `description`.
- Simplified contract categories and removed unused ones.
- Enhanced overall layout and spacing on the `ExplorePage`.

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

<!-- end pr-codex -->
2025-11-19 19:23:39 +00:00
MananTank 63a45e21cd [PRO-121] Dashboard: show all token contracts in tokens tab, conditionally show contracts tab in sidebar (#8436)
<!--

## 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 contract import functionality by introducing contract type validation (token vs non-token) and improving contract fetching methods, along with various updates to the project structure and dependencies.

### Detailed summary
- Added `allowedContractType` prop to `ImportModal` for contract type validation.
- Introduced `projectContractsCacheTag` function for caching project contracts.
- Updated `revalidatePathAction` to include `revalidateTag`.
- Modified contract fetching methods to filter by type.
- Refactored components to use new contract fetching logic.
- Updated `ContractTable` to include project and team IDs.
- Enhanced error handling in `ImportForm` for invalid contract types.
- Various dependency updates including `p-limit` and others.

> The following files were skipped due to too many changes: `pnpm-lock.yaml`

> ✨ 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**
  * Implemented type-based contract filtering to distinguish between token contracts and non-token contracts within projects.
  * Added new Tokens section to project dashboard for dedicated token asset management.
  * Enhanced contract import process with validation to ensure contracts are imported to the correct section based on type.

* **Chores**
  * Updated project dependencies.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-19 19:23:39 +00:00
Joaquim VergesandGitHub c11fe95416 [Dashboard] Add x402 fee configuration (#8434) 2025-11-18 17:02:33 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
851fee1c83 Version Packages (#8433)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-18 14:19:27 -08:00
Joaquim VergesandGitHub 9983e6ccfd [SDK] Improve SIWE chain management for external wallets (#8432) 2025-11-18 14:03:34 -08:00
MananTank 73c924b440 [PRO-112] Dashboard: Project FTUX copy changes (#8428)
<!--

## 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 titles and text in the `ProjectFTUX` components to reflect changes in the API endpoint and improve the clarity of the secret key information.

### Detailed summary
- Changed the `title` in `ProjectFTUX.tsx` from "POST /v1/auth/initiate" to "POST /v1/transactions".
- Updated the text in `SecretKeySection.tsx` to clarify the reference to the full secret key, removing a comma for better readability.

> ✨ 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**
  * Corrected punctuation in descriptive text within the project setup guide

* **Documentation**
  * Updated API endpoint example in the getting started section

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-18 16:30:46 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2976af3a63 Version Packages (#8426)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-17 15:56:04 -08:00
Joaquim VergesandGitHub 419fb31601 [SDK] Better eip5792 handling (#8425) 2025-11-17 15:49:57 -08:00
c76fe10dd0 started portal docs (#8410)
Co-authored-by: 0xFirekeeper <[email protected]>
2025-11-17 23:37:34 +07:00
MananTank c63ef1c4e9 [MNY-310] Dashboard: Improve login redirect path validation (#8422)
<!--

## 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 `isValidEncodedRedirectPath` function to enhance its validation logic for decoded paths, ensuring they start with a single slash and belong to the `thirdweb.com` domain.

### Detailed summary
- Removed comments about decoding URI components and path validation.
- Added a check to ensure `decodedPath` starts with a single slash.
- Introduced a `URL` object to validate that the hostname is `thirdweb.com`.
- Simplified the return logic for invalid paths.

> ✨ 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 login redirect validation to ensure redirects are properly verified and authenticated for the correct domain.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-17 13:09:54 +00:00
Jonas Daniels c7ff3b1020 Add /gateway to framer-rewrites.js 2025-11-14 15:00:19 -08:00
MananTank b06a9184c1 [PRO-105] Dashboard: Update service names (#8419)
<!--

## 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 renaming and refactoring the usage of `products` to `services` across various components, updating service definitions, and improving the rendering of product cards in the UI.

### Detailed summary
- Renamed `products` to `services` in relevant components.
- Updated service definitions with new titles and descriptions.
- Modified the `ChainListRow` and `ChainServiceFilter` components to use `services`.
- Enhanced `SupportedProductsSection` to conditionally render `ProductCard`.
- Added new `ProductCard` component for UI rendering.

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

<!-- end pr-codex -->
2025-11-14 21:08:47 +00:00
MananTank 5080b4707f [PRO-98] Update Project FTUX UI (#8418)
<!--

## 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 updating the UI components by replacing the `NebulaIcon` with `BotIcon`, enhancing styling, and refactoring code for better readability and functionality across various components.

### Detailed summary
- Replaced `NebulaIcon` with `BotIcon` in multiple components.
- Improved styles for headings and paragraphs in `ClientIDSection` and `SecretKeySection`.
- Updated layout properties in `ProjectWalletSection` and other components.
- Added a new `CodeShowcase` component to display code examples.
- Enhanced `ProjectFTUX` to accept a `projectWalletSection` prop.
- Refactored several components for better structure and readability.
- Adjusted margins and paddings for improved UI consistency.

> ✨ 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**
  * Multi-language code showcase with a language selector for integration examples
  * Configurable wallet layouts and ability to inject a wallet area into onboarding/FTUX

* **UI/UX Improvements**
  * Refined spacing, typography, and responsive layouts across project pages and sidebar
  * Added popular framework/product brand icons; replaced several AI/sidebar icons with a unified Bot icon
  * Simplified sidebar footer links and adjusted paddings and avatar sizes

* **Chores**
  * Updated story examples to include new layout args
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-14 21:08:47 +00:00
Yash094 d6bc43c34d support form changes (#8414)
<!--

## 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 and enhancing various support forms in the application, including the removal of outdated components and the introduction of new fields to improve user input for support tickets.

### Detailed summary
- Deleted `payments`, `contracts`, and `AffectedAreaInput` components.
- Updated `OtherSupportForm` with a new label.
- Modified `TokensMarketplaceSupportForm` to include a new input for "Support ID in logs".
- Removed dynamic imports for `ContractSupportForm` and `PaymentsSupportForm`.
- Updated `productOptions` labels for clarity.
- Replaced `ENGINE_TYPES` and `ENGINE_PROBLEM_AREAS` with `API_SDK_OPTIONS`.
- Enhanced `EngineSupportForm` with new inputs and logic based on selected SDK.
- Introduced new components for user wallet issues and account abstraction.
- Updated `PROBLEM_AREAS` to include new components for social/email login and user wallet custom auth.

> ✨ 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 "Agents/x402 and AI" support option and an optional "Support ID in logs" field.

* **Improvements**
  * Reorganized and simplified support forms: Wallets → Working with User Wallets, Transactions → API/SDKs, Tokens/Marketplace → Creating, Bridging and swapping tokens; token input accepts address or symbol.
  * Streamlined Connect and Engine flows with focused issue categories (social/email login, custom auth, account abstraction, third‑party wallets, API/SDK selection).

* **Chores**
  * Removed legacy Contracts, Payments and affected-area form screens.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-14 20:55:30 +00:00
github-actions[bot]andJonas Daniels ef577028fd Version Packages 2025-11-14 08:28:40 -08:00
MananTank 304700f318 Improve Mobile detection in WC connection (#8415)
<!--

## 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 `isMobile` function to include viewport detection for mobile devices and refines the QR overlay creation logic for desktop environments by ensuring existing overlays are cleaned up before creating new ones.

### Detailed summary
- Added viewport detection in `isMobile` function to return `true` if `window.innerWidth` is less than 640.
- Refactored QR overlay creation in `create-wallet.ts` to ensure existing overlays are destroyed before creating a new one.

> ✨ 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**
  * Improved mobile device detection based on viewport size.
  * Enhanced QR code overlay display with better error handling and resource cleanup.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-14 16:10:19 +00:00
kumaryash90 6cd681c9ce Fix resolve implementation (#8413)
<!--

## 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 `resolveImplementation` function in the `thirdweb` package by enhancing the implementation resolution process for proxy contracts.

### Detailed summary
- Replaced the existing implementation resolution logic with a more robust approach using `Promise.all` to fetch implementations from both storage and contract calls.
- Introduced a loop to handle multiple implementations, ensuring that valid addresses are checked before proceeding.
- Updated the return logic to ensure correct addresses and bytecode are returned based on the resolved implementation.

> ✨ 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**
  * Improved the reliability of contract implementation resolution to handle edge cases more robustly when detecting implementations across proxy contracts and various contract architecture types. Processing is now more efficient through concurrent implementation detection and optimized fallback handling, ensuring more consistent and faster results in complex contract scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-14 15:29:13 +00:00
FirekeeperandGitHub 144bb33ffd [Dashboard] Move Contracts back to top level sidebar (#8412) 2025-11-14 01:47:06 +07:00
jnsdls 01116bb650 consolidate gateway navbar (#8411)
# [Dashboard] Feature: Reorganize Gateway section in project sidebar

## Notes for the reviewer

This PR reorganizes the project sidebar by:
- Creating a new "Gateway" submenu that contains both RPC and Indexer (formerly Insight)
- Moving the Insight page to Gateway/Indexer
- Simplifying the Indexer FTUX component
- Updating all related file paths and imports

## How to test

Navigate to the project sidebar and verify:
- The Gateway submenu appears with RPC and Indexer options
- The former Insight page now appears under Gateway/Indexer
- All functionality continues to work as expected

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

* **New Features**
  * Added Indexer page and onboarding card; introduced Gateway submenu grouping RPC and Indexer.
  * Introduced a Wallets submenu consolidating wallet-related links; added a Bridge link and highlighted a monetization item with a "New" badge.
  * Sidebar now shows an Engine link when dedicated engines exist.

* **Refactor**
  * Removed standalone Insight product/page and its onboarding card.
  * Simplified engine onboarding and empty-state flow.

* **Chores**
  * Added redirects routing old insight/rpc paths to gateway equivalents.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on restructuring the `dashboard` application by reorganizing components and updating routes. It removes unused files, modifies imports, and adjusts the sidebar layout to improve navigation and functionality.

### Detailed summary
- Deleted several unused components related to `insight`.
- Moved `RpcAnalytics` and `MethodsTable` to a new path under `gateway`.
- Updated `loginRedirect` paths in several components.
- Added a new `InsightFTUX` component.
- Adjusted sidebar links and layout to reflect new component structure.
- Removed references to `InsightIcon` and related logic in various files.

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

<!-- end pr-codex -->
2025-11-13 13:00:24 +00:00
joaquim-verges f07b4b56c5 [SDK] Rename otherWallet to allConnectedWallets in autoConnect callback (#8408)
<!--

## 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 renaming the `otherWallet` parameter to `allConnectedWallets` in the `onConnect` callback to improve clarity and consistency in the code.

### Detailed summary
- Updated the `onConnect` callback type in `packages/thirdweb/src/wallets/connection/types.ts` to use `OnConnectCallback`.
- Renamed `otherWallet` to `allConnectedWallets` in the `onConnect` function signature.

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

<!-- end pr-codex -->
2025-11-12 22:46:33 +00:00
joaquim-verges 9442dc7729 [SDK] Rename otherWallets to allConnectedWallets in onConnect callback (#8407)
<!--

## 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 `onConnect` callback in the `AutoConnect` component to improve clarity in variable naming, changing `otherWallets` to `allConnectedWallets`.

### Detailed summary
- Changed the parameter name in `onConnect` from `otherWallets` to `allConnectedWallets`.
- Updated the console log statement to reflect the new parameter name, changing it from "other wallets that were also connected" to "all connected wallets".

> ✨ 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

* **Breaking Changes**
  * Updated parameter naming in the AutoConnect callback configuration to align with updated documentation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-12 22:46:32 +00:00
MananTank 6c4e684ee6 [PRO-90] Dashboard: Project > Bridge page changes (#8406)
<!--

## 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 payments and bridge components in the dashboard application. It updates routes, modifies component structures, and enhances the user interface for better functionality and clarity.

### Detailed summary
- Deleted `view-tx-status.tsx` and `project-settings-breadcrumb.tsx`.
- Updated payment-related paths to bridge configuration.
- Refactored `FeatureCard` and `QuickstartSection` components.
- Changed `TableRow` to `PurchaseTableRow` in `PaymentsTableRow`.
- Modified layout and routing in `bridge/layout.tsx` and `bridge/page.tsx`.
- Enhanced UI components for better responsiveness and 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**
  * Added a Bridge configuration page with Overview and Configuration tabs.
  * Transaction hashes now show as copyable links in the payments table.

* **Improvements**
  * Updated Quickstart cards and labels to streamline onboarding and emphasize bridge setup.
  * Navigation paths updated to the Bridge configuration flow.
  * Added a styling hook for link icons and refined payments table layout and semantics.

* **Removals**
  * Removed a legacy breadcrumb component and an old transaction status widget.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-12 22:35:49 +00:00
nischitpra 68978711f8 default sort desc for tokens (#8405)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces a new sorting option for retrieving tokens, allowing results to be ordered in descending order based on their balance.

### Detailed summary
- Added a new property `sort_order` with the value `"desc"` to the object passed to `getV1Tokens`.

> ✨ 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

* **Improvements**
  * Token balances are now displayed in consistent descending order.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-12 21:53:55 +00:00
MananTank eca4ce2b82 Dashboard: Update announcement banner (#8404)
<!--

## 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 `label` prop of the `AnnouncementBannerUI` component to provide more detailed information about recent changes related to Wallets in the Project Dashboard.

### Detailed summary
- Updated the `label` prop of the `AnnouncementBannerUI` component from:
  - "Project View Update - Wallets"
  - To: "Transactions, Vault, and Account Abstraction are now part of Wallets in Project Dashboard. View changes"

> ✨ 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 dashboard announcement banner with information about recent Wallets feature changes, including details on Transactions, Vault, and Account Abstraction integration.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-12 20:59:56 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
80d5595e7f Version Packages (#8397)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-12 12:51:24 -08:00
Joaquim VergesandGitHub d0bbbfd39d [SDK] Add all connected wallets in onConnect callbacks (#8403) 2025-11-12 12:44:46 -08:00
Joaquim VergesandGitHub e26d81ce1d [SDK] Update useAuthToken to find auth token for any connected wallet (#8402) 2025-11-12 11:06:26 -08:00
MananTank be3316ec1b [PRO-96] Playground: Update product grouping (#8401)
<!--

## 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 restructuring the `payments` and `contracts` sections of the codebase, consolidating them under a new `bridge` section, and updating various links and components to reflect the new structure.

### Detailed summary
- Deleted multiple files related to `payments` and `contracts`.
- Introduced new `bridge` components and pages.
- Updated links in various `.mdx` and `.tsx` files to point to the new `bridge` paths.
- Renamed components such as `BuyPlayground` to `BuyWidget`.
- Updated sidebar links for improved navigation.
- Adjusted feature cards to reflect the new structure and titles.

> ✨ 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**
  * Reorganized navigation menu with new Bridge section consolidating payment widgets
  * Added x402 as prominent top-level navigation item
  * Expanded Account Abstraction section with new submenu items

* **Changes**
  * Updated widget names and playground URLs across documentation and dashboard links
  * Restructured Transactions and Account Abstraction into dedicated submenus under Wallets
  * Renamed and relocated playground widget pages to Bridge section

* **Removals**
  * Removed Contracts section from navigation and playground
  * Removed standalone Payments overview page
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-12 18:13:07 +00:00
FirekeeperandGitHub 872369770f [Dashboard] Add date range filtering to transaction analytics (#8400) 2025-11-12 21:31:10 +07:00
Joaquim VergesandGitHub 3d48826928 [Dashboard] Change default x402 metric from volume to payments (#8399) 2025-11-11 23:26:15 -08:00
Joaquim VergesandGitHub fa640ad64b [Dashboard] Add x402 chain analytics chart and empty state (#8398) 2025-11-11 17:32:03 -08:00
MananTank eb1a9c3c3e Add callout for verifying webhook in dashboard and portal (#8396)
<!--

## 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 `BridgeWebhookModalContent` component by adding a warning alert for payload verification and modifies the documentation for webhook creation and payload parsing to reflect changes in terminology and content structure.

### Detailed summary
- Added `UnderlineLink` and `Alert` components to `webhooks.client.tsx`.
- Introduced a warning alert regarding payload verification in the webhook modal.
- Updated the version selection to only display for specific conditions.
- Changed documentation headings and improved clarity on webhook creation and payload parsing.
- Revised example payloads and verification instructions in the documentation.

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

<!-- end pr-codex -->
2025-11-11 23:20:42 +00:00
MananTank a54c1aba35 SDK: Add verify parameter in Bridge.Webhook.parse function (#8395)
<!--

## 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 `verify` parameter to the `Bridge.Webhook.parse` function in the `thirdweb` library, allowing for validation of webhook payloads against specified criteria such as receiver address, destination token address, chain ID, and minimum destination amount.

### Detailed summary
- Introduced `verify` parameter in `Bridge.Webhook.parse` function.
- Added validation checks for:
  - `receiverAddress`
  - `destinationTokenAddress`
  - `destinationChainId`
  - `minDestinationAmount`
- Enhanced error handling for mismatched values.
- Updated tests to cover verification scenarios for both onchain and onramp transactions.

> ✨ 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**
  * Webhook parsing now supports optional payload verification: enforce receiver address (case-insensitive), destination token, destination chain ID, and minimum destination amount. Verification works for both on-chain and on-ramp payloads and returns descriptive errors on mismatch to help ensure incoming webhooks meet your requirements.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-11 23:20:42 +00:00
Joaquim VergesandGitHub bea4f01742 [Dashboard] Add x402 payments section (#8394) 2025-11-11 14:43:04 -08:00
0xFirekeeper e19f7a21ad [Dashboard] New Project > Wallets product grouping and subsections (#8359)
Reorganized wallet-related routes under a new sidebar structure, replacing legacy pages with redirects to new user, server, and sponsored gas wallet sections. Added new layout and overview/configuration pages for server wallets and sponsored gas, updated sidebar navigation to reflect new wallet grouping, and removed legacy Account Abstraction and Vault pages in favor of the new structure.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on renaming "in-app wallets" to "user wallets" throughout the application, along with restructuring related components, routes, and API calls. It also introduces new configurations and adjusts UI elements to reflect these changes.

### Detailed summary
- Renamed `in-app wallets` to `user wallets` in various components and texts.
- Deleted unused files related to `in-app wallets`.
- Updated API error messages to reflect the new terminology.
- Adjusted routing paths for user wallet configurations.
- Updated UI elements and titles to align with the new terminology.
- Refactored sidebar links to include new wallet types.
- Added new components for user wallet management.
- Implemented new pagination and analytics features for user wallets.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/page.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/user-wallets/configuration/components/sms-country-select/utils.ts`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/user-wallets/configuration/components/index.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**
  * Pages and layouts for Server Wallets, Sponsored Gas, and User Wallets (overview + configuration); key management and analytics pages added.
  * In-app/User wallet settings UI: branding, JWT/custom auth endpoint, image uploads, SMS country selector, and SMS country data/utilities.

* **Navigation Updates**
  * Sidebar reorganized to group wallet features; submenu discovery and mobile behavior improved.

* **Behavioral Changes**
  * Legacy analytics/settings pages replaced with redirects to new wallet routes.

* **UX**
  * Wallet tables: URL-driven pagination, page-size-aware ranges, clearer “Showing X–Y of Z” messaging; wording updated from “In-App” to “User” wallets; announcement banner added.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-10 22:32:28 +00:00
944 changed files with 37169 additions and 20937 deletions
+5 -2
View File
@@ -1,4 +1,6 @@
{
"editor.insertSpaces": true,
"editor.tabSize": 2,
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
},
@@ -18,8 +20,9 @@
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.fixAll.biome": "explicit"
"quickfix.biome": "always",
"source.fixAll.biome": "always",
"source.organizeImports.biome": "always"
},
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
+2 -2
View File
@@ -18,7 +18,7 @@ Welcome, AI copilots! This guide captures the coding standards, architectural de
2. Formatting & Linting
- Biome governs formatting and linting; its rules live in biome.json.
- Run pnpm biome check --apply before committing.
- Run `pnpm fix` & `pnpm lint` before committing, make sure there are no linting errors.
- Avoid editor‑specific configs; rely on the shared settings.
- make sure everything builds after each file change by running `pnpm build`
@@ -136,7 +136,7 @@ export function reportContractDeployed(properties: {
}
```
- **Client-side only**: never import `posthog-js` in server components.
- **Client-side only**: never import `posthog-js` in server components.
- **Housekeeping**: Inform **#eng-core-services** before renaming or removing an existing event.
⸻
+3
View File
@@ -66,3 +66,6 @@ ANALYTICS_SERVICE_URL=""
STRIPE_SECRET_KEY=""
GROWTH_PLAN_SKU=""
PAYMENT_METHOD_CONFIGURATION=""
# required for bridge iframe (/bridge/widget) page
NEXT_PUBLIC_BRIDGE_IFRAME_CLIENT_ID=""
+4
View File
@@ -21,6 +21,7 @@ module.exports = [
"/rpc",
"/insight",
"/storage",
"/gateway",
// -- end scale category
// -- ai
@@ -67,4 +68,7 @@ module.exports = [
"/token",
"/vault",
"/monetize/bridge",
// ai
"/ai-privacy-policy",
"/ai-terms",
];
+77
View File
@@ -18,6 +18,11 @@ const ContentSecurityPolicy = `
block-all-mixed-content;
`;
const EmbedContentSecurityPolicy = `
${ContentSecurityPolicy}
frame-ancestors *;
`;
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
@@ -138,6 +143,78 @@ const baseNextConfig: NextConfig = {
// Apply these headers to all routes in your application.
source: "/(.*)",
},
{
headers: [
{
key: "Content-Security-Policy",
value: EmbedContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
],
source: "/bridge/widget",
},
{
headers: [
{
key: "Content-Security-Policy",
value: EmbedContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
],
source: "/bridge/widget/:path*",
},
{
headers: [
{
key: "Content-Security-Policy",
value: EmbedContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
],
source: "/bridge/checkout-widget",
},
{
headers: [
{
key: "Content-Security-Policy",
value: EmbedContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
],
source: "/bridge/checkout-widget/:path*",
},
{
headers: [
{
key: "Content-Security-Policy",
value: EmbedContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
],
source: "/bridge/swap-widget",
},
{
headers: [
{
key: "Content-Security-Policy",
value: EmbedContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
],
source: "/bridge/swap-widget/:path*",
},
{
headers: [
{
key: "Content-Security-Policy",
value: EmbedContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
],
source: "/bridge/buy-widget",
},
{
headers: [
{
key: "Content-Security-Policy",
value: EmbedContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
],
source: "/bridge/buy-widget/:path*",
},
];
},
images: {
+8 -7
View File
@@ -44,18 +44,19 @@
"ioredis": "^5.6.1",
"ipaddr.js": "^2.2.0",
"lucide-react": "0.525.0",
"next": "15.3.5",
"next": "15.3.8",
"next-themes": "^0.4.6",
"nextjs-toploader": "^1.6.12",
"nuqs": "^2.4.3",
"p-limit": "^7.2.0",
"papaparse": "^5.5.3",
"pluralize": "^8.0.0",
"posthog-js": "1.256.1",
"prettier": "3.6.2",
"react": "19.1.0",
"react": "19.2.1",
"react-children-utilities": "^2.10.0",
"react-day-picker": "^8.10.1",
"react-dom": "19.1.0",
"react-dom": "19.2.1",
"react-dropzone": "^14.3.8",
"react-error-boundary": "6.0.0",
"react-hook-form": "7.55.0",
@@ -81,8 +82,8 @@
"devDependencies": {
"@biomejs/biome": "2.0.6",
"@chromatic-com/storybook": "4.0.1",
"@next/bundle-analyzer": "15.3.5",
"@next/eslint-plugin-next": "15.3.5",
"@next/bundle-analyzer": "15.3.8",
"@next/eslint-plugin-next": "15.3.8",
"@storybook/addon-docs": "9.0.15",
"@storybook/addon-links": "9.0.15",
"@storybook/addon-onboarding": "9.0.15",
@@ -91,8 +92,8 @@
"@types/node": "22.14.1",
"@types/papaparse": "^5.3.16",
"@types/pluralize": "^0.0.33",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
"@types/react-table": "^7.7.20",
"@types/spdx-correct": "^3.1.3",
"@types/swagger-ui-react": "^5.18.0",
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+10
View File
@@ -115,6 +115,16 @@ const projectPageRedirects = [
permanent: false,
source: `${projectRoute}/connect/analytics`,
},
{
destination: `${projectRoute}/gateway/indexer/:path*`,
permanent: false,
source: `${projectRoute}/insight/:path*`,
},
{
destination: `${projectRoute}/gateway/rpc/:path*`,
permanent: false,
source: `${projectRoute}/rpc/:path*`,
},
];
const teamPageRedirects = [
+12
View File
@@ -109,6 +109,18 @@ export async function getChainInfraCheckoutURL(options: {
}
const json = await res.json();
if (
"error" in json &&
"message" in json.error &&
typeof json.error.message === "string"
) {
return {
error: json.error.message,
status: "error",
} as const;
}
if (!json.result) {
return {
error: "An unknown error occurred, please try again later.",
+5 -1
View File
@@ -1,6 +1,6 @@
"use server";
import { revalidatePath } from "next/cache";
import { revalidatePath, revalidateTag } from "next/cache";
export async function revalidatePathAction(
path: string,
@@ -8,3 +8,7 @@ export async function revalidatePathAction(
) {
revalidatePath(path, type);
}
export async function revalidateCacheTagAction(tag: string) {
revalidateTag(tag);
}
+4 -4
View File
@@ -232,13 +232,13 @@ type TokenSwapParams = {
buyTokenAddress: string;
sellTokenChainId: number;
sellTokenAddress: string;
pageType: "asset" | "bridge" | "chain";
pageType: "asset" | "bridge" | "chain" | "bridge-iframe";
};
type TokenBuyParams = {
buyTokenChainId: number | undefined;
buyTokenAddress: string | undefined;
pageType: "asset" | "bridge" | "chain";
pageType: "asset" | "bridge" | "chain" | "bridge-iframe";
};
/**
@@ -298,7 +298,7 @@ export function reportTokenSwapSuccessful(properties: TokenSwapParams) {
* @MananTank
*/
export function reportSwapWidgetShown(properties: {
pageType: "asset" | "bridge" | "chain";
pageType: "asset" | "bridge" | "chain" | "bridge-iframe";
}) {
posthog.capture("swap widget shown", properties);
}
@@ -683,7 +683,7 @@ export function reportProductFeedback(properties: {
*
*/
export function reportBridgePageLinkClick(params: {
linkType: "bridge-docs" | "trending-tokens";
linkType: "bridge-docs" | "trending-tokens" | "integrate-bridge-widget";
}) {
posthog.capture("bridge page link clicked", params);
}
+182 -48
View File
@@ -1,21 +1,24 @@
import "server-only";
import { unstable_cache } from "next/cache";
import { getWalletInfo, type WalletId } from "thirdweb/wallets";
import { ANALYTICS_SERVICE_URL } from "@/constants/server-envs";
import { normalizeTime } from "@/lib/time";
import type {
AIUsageStats,
AnalyticsQueryParams,
EcosystemWalletStats,
EngineCloudStats,
InAppWalletStats,
TransactionStats,
UniversalBridgeStats,
UniversalBridgeWalletStats,
UserOpStats,
WalletStats,
WalletStatsWithName,
WebhookLatencyStats,
WebhookSummaryStats,
X402QueryParams,
X402SettlementStats,
} from "@/types/analytics";
import { getChains } from "./chain";
@@ -146,13 +149,7 @@ const cached_getWalletConnections = unstable_cache(
}
const json = await res.json();
return (json.data as WalletStats[]).filter(
(w) =>
w.walletType !== "smart" &&
w.walletType !== "smartWallet" &&
w.walletType !== "inApp" &&
w.walletType !== "inAppWallet",
);
return json.data as WalletStats[];
},
["getWalletConnections"],
{
@@ -160,11 +157,116 @@ const cached_getWalletConnections = unstable_cache(
},
);
export function getWalletConnections(
const cache_getEOAWalletConnections = unstable_cache(
async (
params: AnalyticsQueryParams,
authToken: string,
): Promise<WalletStatsWithName[]> => {
const result = await cached_getWalletConnections(
normalizedParams(params),
authToken,
);
const filteredResult = result.filter(
(w) =>
w.walletType !== "smart" &&
w.walletType !== "smartWallet" &&
w.walletType !== "inApp" &&
w.walletType !== "inAppWallet",
);
const modifiedResult = await Promise.all(
filteredResult.map(async (w) => {
const wallet = await getWalletInfo(w.walletType as WalletId).catch(
() => undefined,
);
if (!wallet) {
return {
...w,
walletName: w.walletType,
};
}
return {
...w,
walletName: wallet.name,
};
}),
);
return modifiedResult;
},
["getEOAWalletConnections"],
{
revalidate: 60 * 60, // 1 hour
},
);
export async function getEOAWalletConnections(
params: AnalyticsQueryParams,
authToken: string,
) {
return cached_getWalletConnections(normalizedParams(params), authToken);
return cache_getEOAWalletConnections(normalizedParams(params), authToken);
}
const cache_getEOAAndInAppWalletConnections = unstable_cache(
async (
params: AnalyticsQueryParams,
authToken: string,
): Promise<WalletStatsWithName[]> => {
const result = await cached_getWalletConnections(
normalizedParams(params),
authToken,
);
const modifiedResult = await Promise.all(
result.map(async (w) => {
if (
w.walletType === "inApp" ||
w.walletType === "inAppWallet" ||
w.walletType === "smart" ||
w.walletType === "smartWallet"
) {
return {
...w,
walletName: "User wallet",
};
}
const wallet = await getWalletInfo(w.walletType as WalletId).catch(
() => undefined,
);
if (!wallet) {
return {
...w,
walletName: w.walletType,
};
}
return {
...w,
walletName: wallet.name,
};
}),
);
return modifiedResult;
},
["getEOAAndInAppWalletConnections"],
{
revalidate: 60 * 60, // 1 hour
},
);
export async function getEOAAndInAppWalletConnections(
params: AnalyticsQueryParams,
authToken: string,
): Promise<WalletStatsWithName[]> {
return cache_getEOAAndInAppWalletConnections(
normalizedParams(params),
authToken,
);
}
const cached_getInAppWalletUsage = unstable_cache(
@@ -184,7 +286,7 @@ const cached_getInAppWalletUsage = unstable_cache(
if (res?.status !== 200) {
const reason = await res?.text();
console.error(
`Failed to fetch in-app wallet usage, ${res?.status} - ${res.statusText} - ${reason}`,
`Failed to fetch user wallets usage, ${res?.status} - ${res.statusText} - ${reason}`,
);
return [];
}
@@ -289,8 +391,11 @@ const cached_getAggregateUserOpUsage = unstable_cache(
]);
// Aggregate stats across wallet types
return userOpStats.reduce(
(acc, curr) => {
const aggregated = userOpStats.reduce(
(
acc: UserOpStats & { gasPriceSum: number; gasPriceCount: number },
curr,
) => {
// Skip testnets from the aggregated stats
if (curr.chainId) {
const chain = chains.data.find(
@@ -304,6 +409,10 @@ const cached_getAggregateUserOpUsage = unstable_cache(
acc.successful += curr.successful;
acc.failed += curr.failed;
acc.sponsoredUsd += curr.sponsoredUsd;
acc.gasUnits += curr.gasUnits;
// For avgGasPrice, we'll track sum and count for proper averaging
acc.gasPriceSum += curr.avgGasPrice * curr.successful;
acc.gasPriceCount += curr.successful;
return acc;
},
{
@@ -311,8 +420,28 @@ const cached_getAggregateUserOpUsage = unstable_cache(
failed: 0,
sponsoredUsd: 0,
successful: 0,
gasUnits: 0,
avgGasPrice: 0,
gasPriceSum: 0,
gasPriceCount: 0,
},
);
// Calculate the proper average gas price
aggregated.avgGasPrice =
aggregated.gasPriceCount > 0
? aggregated.gasPriceSum / aggregated.gasPriceCount
: 0;
// Return only the UserOpStats fields
return {
date: aggregated.date,
failed: aggregated.failed,
sponsoredUsd: aggregated.sponsoredUsd,
successful: aggregated.successful,
gasUnits: aggregated.gasUnits,
avgGasPrice: aggregated.avgGasPrice,
};
},
["getAggregateUserOpUsage"],
{
@@ -662,41 +791,6 @@ export function getUniversalBridgeWalletUsage(
);
}
const cached_getEngineCloudMethodUsage = unstable_cache(
async (
params: AnalyticsQueryParams,
authToken: string,
): Promise<EngineCloudStats[]> => {
const searchParams = buildSearchParams(params);
const res = await fetchAnalytics({
authToken,
url: `v2/engine-cloud/requests?${searchParams.toString()}`,
init: {
method: "GET",
},
});
if (res?.status !== 200) {
console.error("Failed to fetch Engine Cloud method usage");
return [];
}
const json = await res.json();
return json.data as EngineCloudStats[];
},
["getEngineCloudMethodUsage"],
{
revalidate: 60 * 60, // 1 hour
},
);
export function getEngineCloudMethodUsage(
params: AnalyticsQueryParams,
authToken: string,
) {
return cached_getEngineCloudMethodUsage(normalizedParams(params), authToken);
}
const _cached_getWebhookSummary = unstable_cache(
async (
params: AnalyticsQueryParams & { webhookId: string },
@@ -906,3 +1000,43 @@ export function getInsightUsage(
) {
return cached_getInsightUsage(normalizedParams(params), authToken);
}
const cached_getX402Settlements = unstable_cache(
async (
params: X402QueryParams,
authToken: string,
): Promise<X402SettlementStats[]> => {
const searchParams = buildSearchParams(params);
if (params.groupBy) {
searchParams.append("groupBy", params.groupBy);
}
const res = await fetchAnalytics({
authToken,
url: `v2/x402/settlements?${searchParams.toString()}`,
init: {
method: "GET",
},
});
if (res?.status !== 200) {
const reason = await res?.text();
console.error(
`Failed to fetch x402 settlements: ${res?.status} - ${res.statusText} - ${reason}`,
);
return [];
}
const json = await res.json();
return json.data as X402SettlementStats[];
},
["getX402Settlements"],
{
revalidate: 60 * 60, // 1 hour
},
);
export function getX402Settlements(params: X402QueryParams, authToken: string) {
return cached_getX402Settlements(normalizedParams(params), authToken);
}
@@ -0,0 +1,6 @@
export function projectContractsCacheTag(params: {
teamId: string;
projectId: string;
}) {
return `${params.teamId}/${params.projectId}/project-contracts`;
}
@@ -1,58 +1,111 @@
import { unstable_cache } from "next/cache";
import pLimit from "p-limit";
import { defineChain } from "thirdweb";
import { getContract } from "thirdweb/contract";
import {
getProjectContracts,
type ProjectContract,
} from "@/api/project/getProjectContracts";
import { fetchChainWithLocalOverrides } from "@/utils/fetchChainWithLocalOverrides";
import { supportedERCs } from "@/utils/supportedERCs";
import { serverThirdwebClient } from "../../constants/thirdweb-client.server";
import { resolveFunctionSelectors } from "../../lib/selectors";
import { projectContractsCacheTag } from "./cache-tag";
export async function getSortedDeployedContracts(params: {
onlyMainnet?: boolean;
type GetFilteredProjectContractsParams = {
teamId: string;
projectId: string;
authToken: string;
deploymentType: string | undefined;
}) {
};
type ProjectContractWithIsToken = ProjectContract & { isToken: boolean };
async function getProjectContractsWithIsTokenInfo(
params: GetFilteredProjectContractsParams,
): Promise<ProjectContractWithIsToken[]> {
const contracts = await getProjectContracts({
authToken: params.authToken,
deploymentType: params.deploymentType,
deploymentType: undefined, // don't pass it to api to fetch all contracts, we'll filter/merge manually here
projectId: params.projectId,
teamId: params.teamId,
});
const chainIds = Array.from(new Set(contracts.map((c) => c.chainId)));
const chains = (
await Promise.allSettled(
chainIds.map((chainId) =>
fetchChainWithLocalOverrides(chainId.toString()),
),
)
)
.filter((c) => c.status === "fulfilled")
.map((c) => c.value)
.filter((c) => c !== null);
const assetContracts = contracts.filter((c) => c.deploymentType === "asset");
const otherContracts = contracts.filter((c) => c.deploymentType !== "asset");
const mainnetContracts: ProjectContract[] = [];
const testnetContracts: ProjectContract[] = [];
// "asset" deployment type contract is assumed to be a "token" type contract already
// for others, check if its a "token" type contract (isERC20, isERC721 or isERC1155)
for (const contract of contracts) {
const chain = chains.find(
(chain) => contract.chainId === chain.chainId.toString(),
const limit = pLimit(20);
const othersContractWithIsTokenInfo: ProjectContractWithIsToken[] =
await Promise.all(
otherContracts.map(async (c) => {
return limit(async () => {
try {
const contract = getContract({
address: c.contractAddress,
// eslint-disable-next-line no-restricted-syntax
chain: defineChain(Number(c.chainId)),
client: serverThirdwebClient,
});
const functionSelectors = await resolveFunctionSelectors(contract);
const ercs = supportedERCs(functionSelectors);
if (ercs.isERC20 || ercs.isERC721 || ercs.isERC1155) {
return {
...c,
isToken: true,
};
}
return {
...c,
isToken: false,
};
} catch {
return {
...c,
isToken: false,
};
}
});
}),
);
if (chain && chain.status !== "deprecated") {
if (chain.testnet) {
testnetContracts.push(contract);
} else {
mainnetContracts.push(contract);
}
for (const c of othersContractWithIsTokenInfo) {
if (c.deploymentType === "asset") {
assetContracts.push(c);
} else {
otherContracts.push(c);
}
}
mainnetContracts.sort((a, b) => Number(a.chainId) - Number(b.chainId));
if (params.onlyMainnet) {
return mainnetContracts;
}
testnetContracts.sort((a, b) => Number(a.chainId) - Number(b.chainId));
return [...mainnetContracts, ...testnetContracts];
return [
...assetContracts.map(
(c) => ({ ...c, isToken: true }) as ProjectContractWithIsToken,
),
...othersContractWithIsTokenInfo,
];
}
export async function getFilteredProjectContracts(
params: GetFilteredProjectContractsParams & {
type: "token-contracts" | "non-token-contracts";
},
) {
const cached_getProjectContractsWithIsTokenInfo = unstable_cache(
getProjectContractsWithIsTokenInfo,
["get-project-contracts-with-is-token-info"],
{
revalidate: 3600, // 1 hour
tags: [projectContractsCacheTag(params)],
},
);
const contracts = await cached_getProjectContractsWithIsTokenInfo(params);
if (params.type === "token-contracts") {
return contracts.filter((c) => c.isToken);
} else {
return contracts.filter((c) => !c.isToken);
}
}
@@ -105,6 +105,7 @@ type PartnerPermission = "PROMPT_USER_V1" | "FULL_CONTROL_V1";
export type Partner = {
id: string;
name: string;
imageUrl?: string;
allowlistedDomains: string[];
allowlistedBundleIds: string[];
permissions: [PartnerPermission];
+4 -1
View File
@@ -8,7 +8,10 @@ import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { API_SERVER_SECRET } from "@/constants/server-envs";
import { getMemberByAccountId } from "./team-members";
export type Team = TeamResponse & { stripeCustomerId: string | null };
export type Team = TeamResponse & {
stripeCustomerId: string | null;
isLegacyPlan: boolean;
};
export async function getTeamBySlug(slug: string) {
const token = await getAuthToken();
+27
View File
@@ -0,0 +1,27 @@
import type { Project } from "@/api/project/projects";
type X402Fee = {
feeRecipient: string;
feeBps: number;
};
/**
* Extract x402 fee configuration from project's engineCloud service
*/
export function getX402Fees(project: Project): X402Fee {
const engineCloudService = project.services.find(
(service) => service.name === "engineCloud",
);
if (!engineCloudService) {
return {
feeRecipient: "",
feeBps: 0,
};
}
return {
feeRecipient: engineCloudService.x402FeeRecipient || "",
feeBps: engineCloudService.x402FeeBPS || 0,
};
}
@@ -1,42 +1,23 @@
"use client";
import { useId, useMemo } from "react";
import { Area, AreaChart, Bar, BarChart } from "recharts";
import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
import { ArrowUpRightIcon, XIcon } from "lucide-react";
import Link from "next/link";
import { LoadingDots } from "@/components/ui/LoadingDots";
import { cn } from "@/lib/utils";
import { Button } from "../ui/button";
type FakeCartData = {
value: number;
};
function generateRandomData(): FakeCartData[] {
return Array.from({ length: 30 }, () => ({
value: Math.floor(Math.random() * 100 + 30),
}));
}
const skeletonChartConfig = {
value: {
color: "hsl(var(--muted)/90%)",
label: "Value",
},
} satisfies ChartConfig;
export function EmptyChartState({
children,
type,
}: {
children?: React.ReactNode;
type: "bar" | "area";
}) {
const barChartData = useMemo(() => generateRandomData(), []);
export function EmptyChartState({ content }: { content?: React.ReactNode }) {
return (
<div className="relative z-0 h-full w-full">
<div className="absolute inset-0 z-[1] flex flex-col items-center justify-center font-medium text-base text-muted-foreground">
{children ?? "No data available"}
<div className="absolute inset-0 z-[1] flex flex-col items-center justify-center text-base text-muted-foreground">
{content || (
<div className="flex items-center gap-3 flex-col">
<div className="rounded-full border p-2 bg-background">
<XIcon className="size-4" />
</div>
<p className="text-sm"> No data available </p>
</div>
)}
</div>
<SkeletonBarChart data={barChartData} type={type} />
</div>
);
}
@@ -45,7 +26,7 @@ export function LoadingChartState({ className }: { className?: string }) {
return (
<div
className={cn(
"pointer-events-none flex h-full w-full items-center justify-center rounded-lg bg-muted/30",
"pointer-events-none flex h-full w-full items-center justify-center bg-card rounded-lg",
className,
)}
>
@@ -54,51 +35,42 @@ export function LoadingChartState({ className }: { className?: string }) {
);
}
function SkeletonBarChart(props: {
data: FakeCartData[];
type: "bar" | "area";
export function EmptyChartStateGetStartedCTA(props: {
link?: {
label: string;
href: string;
};
title: string;
description?: string;
}) {
const fillAreaSkeletonId = useId();
return (
<ChartContainer
className="pointer-events-none h-full w-full blur-[5px]"
config={skeletonChartConfig}
>
{props.type === "bar" ? (
<BarChart
data={props.data}
margin={{
top: 20,
}}
<div className="flex items-center flex-col">
<div className="rounded-full border p-2 mb-4">
<XIcon className="size-4 text-foreground" />
</div>
<div className="mb-5 space-y-1">
<h3 className="text-base text-foreground text-center font-medium">
{props.title}
</h3>
{props.description && (
<p className="text-center text-sm text-muted-foreground">
{props.description}
</p>
)}
</div>
{props.link && (
<Button
asChild
className="rounded-full gap-2"
variant="default"
size="sm"
>
<Bar dataKey="value" fill="var(--color-value)" radius={8} />
</BarChart>
) : (
<AreaChart
data={props.data}
margin={{
top: 20,
}}
>
<defs>
<linearGradient id={fillAreaSkeletonId} x1="0" x2="0" y1="0" y2="1">
<stop
offset="5%"
stopColor={"hsl(var(--muted-foreground)/0.5)"}
stopOpacity={0.8}
/>
<stop offset="95%" stopColor="transparent" stopOpacity={0.1} />
</linearGradient>
</defs>
<Area
dataKey="value"
fill={`url(#${fillAreaSkeletonId})`}
radius={8}
stroke="hsl(var(--muted-foreground))"
type="monotone"
/>
</AreaChart>
<Link href={props.link.href}>
{props.link.label} <ArrowUpRightIcon className="size-4" />
</Link>
</Button>
)}
</ChartContainer>
</div>
);
}
@@ -6,13 +6,16 @@ export const StatCard: React.FC<{
icon: React.FC<{ className?: string }>;
formatter?: (value: number) => string;
isPending: boolean;
}> = ({ label, value, formatter, icon: Icon, isPending }) => {
emptyText?: string;
}> = ({ label, value, formatter, icon: Icon, isPending, emptyText }) => {
return (
<dl className="flex items-center justify-between gap-4 rounded-lg border border-border bg-card p-4 pr-6">
<div>
<dd className="mb-0.5 font-semibold text-2xl tracking-tight">
{isPending ? (
<Skeleton className="h-8 w-20" />
) : emptyText ? (
<span className="text-muted-foreground">{emptyText}</span>
) : value !== undefined && formatter ? (
formatter(value)
) : (
@@ -2,9 +2,8 @@
"use client";
import { useTheme } from "next-themes";
import { useEffect, useMemo, useRef, useState } from "react";
import { defineChain } from "thirdweb";
import { BuyWidget, SwapWidget } from "thirdweb/react";
import { useEffect, useMemo, useRef } from "react";
import { BridgeWidget, type SupportedFiatCurrency } from "thirdweb/react";
import type { Wallet } from "thirdweb/wallets";
import {
reportAssetBuyCancelled,
@@ -18,21 +17,21 @@ import {
reportTokenSwapFailed,
reportTokenSwapSuccessful,
} from "@/analytics/report";
import { Button } from "@/components/ui/button";
import {
NEXT_PUBLIC_ASSET_PAGE_CLIENT_ID,
NEXT_PUBLIC_BRIDGE_IFRAME_CLIENT_ID,
NEXT_PUBLIC_BRIDGE_PAGE_CLIENT_ID,
NEXT_PUBLIC_CHAIN_PAGE_CLIENT_ID,
} from "@/constants/public-envs";
import { cn } from "@/lib/utils";
import { parseError } from "@/utils/errorParser";
import { getSDKTheme } from "@/utils/sdk-component-theme";
import { appMetadata } from "../../constants/connect";
import { getConfiguredThirdwebClient } from "../../constants/thirdweb.server";
type PageType = "asset" | "bridge" | "chain";
type PageType = "asset" | "bridge" | "chain" | "bridge-iframe";
export type BuyAndSwapEmbedProps = {
persistTokenSelections?: boolean;
buyTab:
| {
buyToken:
@@ -64,11 +63,12 @@ export type BuyAndSwapEmbedProps = {
| undefined;
pageType: PageType;
wallets?: Wallet[];
currency?: SupportedFiatCurrency;
showThirdwebBranding?: boolean;
};
export function BuyAndSwapEmbed(props: BuyAndSwapEmbedProps) {
const { theme } = useTheme();
const [tab, setTab] = useState<"buy" | "swap">("swap");
const themeObj = getSDKTheme(theme === "light" ? "light" : "dark");
const isMounted = useRef(false);
@@ -92,215 +92,170 @@ export function BuyAndSwapEmbed(props: BuyAndSwapEmbedProps) {
? NEXT_PUBLIC_BRIDGE_PAGE_CLIENT_ID
: props.pageType === "chain"
? NEXT_PUBLIC_CHAIN_PAGE_CLIENT_ID
: undefined,
: props.pageType === "bridge-iframe"
? NEXT_PUBLIC_BRIDGE_IFRAME_CLIENT_ID
: undefined,
secretKey: undefined,
teamId: undefined,
});
}, [props.pageType]);
return (
<div className="bg-card rounded-2xl border overflow-hidden flex flex-col relative z-10 shadow-xl min-w-0">
<div className="flex gap-2.5 p-4 border-b border-dashed">
<TabButton
label="Swap"
onClick={() => setTab("swap")}
isActive={tab === "swap"}
/>
<TabButton
label="Buy"
onClick={() => setTab("buy")}
isActive={tab === "buy"}
/>
</div>
<BridgeWidget
client={client}
theme={themeObj}
className="z-10 min-w-0 shadow-xl"
connectOptions={{
autoConnect: false,
wallets: props.wallets,
appMetadata: appMetadata,
}}
buy={
props.buyTab
? {
amount: props.buyTab.buyToken?.amount,
chainId: props.buyTab.buyToken?.chainId,
tokenAddress: props.buyTab.buyToken?.tokenAddress,
onError: (e, quote) => {
const errorMessage = parseError(e);
{tab === "buy" && (
<BuyWidget
amount={props.buyTab?.buyToken?.amount || "1"}
chain={
props.buyTab?.buyToken?.chainId
? defineChain(props.buyTab.buyToken.chainId)
: undefined
}
tokenAddress={
props.buyTab?.buyToken?.tokenAddress as `0x${string}` | undefined
}
className="!rounded-2xl !border-none"
title=""
client={client}
connectOptions={{
autoConnect: false,
wallets: props.wallets,
appMetadata: appMetadata,
}}
onError={(e, quote) => {
const errorMessage = parseError(e);
const buyChainId =
quote?.type === "buy"
? quote.intent.destinationChainId
: quote?.type === "onramp"
? quote.intent.chainId
: undefined;
const buyChainId =
quote?.type === "buy"
? quote.intent.destinationChainId
: quote?.type === "onramp"
? quote.intent.chainId
: undefined;
if (!buyChainId) {
return;
}
if (!buyChainId) {
return;
reportTokenBuyFailed({
buyTokenChainId: buyChainId,
buyTokenAddress:
quote?.type === "buy"
? quote.intent.destinationTokenAddress
: quote?.type === "onramp"
? quote.intent.tokenAddress
: undefined,
pageType: props.pageType,
});
if (props.pageType === "asset") {
reportAssetBuyFailed({
assetType: "coin",
chainId: buyChainId,
error: errorMessage,
contractType: undefined,
is_testnet: false,
});
}
},
onCancel: (quote) => {
const buyChainId =
quote?.type === "buy"
? quote.intent.destinationChainId
: quote?.type === "onramp"
? quote.intent.chainId
: undefined;
if (!buyChainId) {
return;
}
reportTokenBuyCancelled({
buyTokenChainId: buyChainId,
buyTokenAddress:
quote?.type === "buy"
? quote.intent.destinationTokenAddress
: quote?.type === "onramp"
? quote.intent.tokenAddress
: undefined,
pageType: props.pageType,
});
if (props.pageType === "asset") {
reportAssetBuyCancelled({
assetType: "coin",
chainId: buyChainId,
contractType: undefined,
is_testnet: false,
});
}
},
onSuccess: ({ quote }) => {
const buyChainId =
quote?.type === "buy"
? quote.intent.destinationChainId
: quote?.type === "onramp"
? quote.intent.chainId
: undefined;
if (!buyChainId) {
return;
}
reportTokenBuySuccessful({
buyTokenChainId: buyChainId,
buyTokenAddress:
quote?.type === "buy"
? quote.intent.destinationTokenAddress
: quote?.type === "onramp"
? quote.intent.tokenAddress
: undefined,
pageType: props.pageType,
});
if (props.pageType === "asset") {
reportAssetBuySuccessful({
assetType: "coin",
chainId: buyChainId,
contractType: undefined,
is_testnet: false,
});
}
},
}
reportTokenBuyFailed({
buyTokenChainId: buyChainId,
buyTokenAddress:
quote?.type === "buy"
? quote.intent.destinationTokenAddress
: quote?.type === "onramp"
? quote.intent.tokenAddress
: undefined,
pageType: props.pageType,
});
if (props.pageType === "asset") {
reportAssetBuyFailed({
assetType: "coin",
chainId: buyChainId,
error: errorMessage,
contractType: undefined,
is_testnet: false,
});
}
}}
onCancel={(quote) => {
const buyChainId =
quote?.type === "buy"
? quote.intent.destinationChainId
: quote?.type === "onramp"
? quote.intent.chainId
: undefined;
if (!buyChainId) {
return;
}
reportTokenBuyCancelled({
buyTokenChainId: buyChainId,
buyTokenAddress:
quote?.type === "buy"
? quote.intent.destinationTokenAddress
: quote?.type === "onramp"
? quote.intent.tokenAddress
: undefined,
pageType: props.pageType,
});
if (props.pageType === "asset") {
reportAssetBuyCancelled({
assetType: "coin",
chainId: buyChainId,
contractType: undefined,
is_testnet: false,
});
}
}}
onSuccess={({ quote }) => {
const buyChainId =
quote?.type === "buy"
? quote.intent.destinationChainId
: quote?.type === "onramp"
? quote.intent.chainId
: undefined;
if (!buyChainId) {
return;
}
reportTokenBuySuccessful({
buyTokenChainId: buyChainId,
buyTokenAddress:
quote?.type === "buy"
? quote.intent.destinationTokenAddress
: quote?.type === "onramp"
? quote.intent.tokenAddress
: undefined,
pageType: props.pageType,
});
if (props.pageType === "asset") {
reportAssetBuySuccessful({
assetType: "coin",
chainId: buyChainId,
contractType: undefined,
is_testnet: false,
});
}
}}
theme={themeObj}
paymentMethods={["card"]}
/>
)}
{tab === "swap" && (
<SwapWidget
client={client}
theme={themeObj}
className="!rounded-2xl !border-none"
connectOptions={{
autoConnect: false,
wallets: props.wallets,
appMetadata: appMetadata,
}}
prefill={{
buyToken: props.swapTab?.buyToken,
sellToken: props.swapTab?.sellToken,
}}
onError={(error, quote) => {
const errorMessage = parseError(error);
reportTokenSwapFailed({
errorMessage: errorMessage,
buyTokenChainId: quote.intent.destinationChainId,
buyTokenAddress: quote.intent.destinationTokenAddress,
sellTokenChainId: quote.intent.originChainId,
sellTokenAddress: quote.intent.originTokenAddress,
pageType: props.pageType,
});
}}
onSuccess={({ quote }) => {
reportTokenSwapSuccessful({
buyTokenChainId: quote.intent.destinationChainId,
buyTokenAddress: quote.intent.destinationTokenAddress,
sellTokenChainId: quote.intent.originChainId,
sellTokenAddress: quote.intent.originTokenAddress,
pageType: props.pageType,
});
}}
onCancel={(quote) => {
reportTokenSwapCancelled({
buyTokenChainId: quote.intent.destinationChainId,
buyTokenAddress: quote.intent.destinationTokenAddress,
sellTokenChainId: quote.intent.originChainId,
sellTokenAddress: quote.intent.originTokenAddress,
pageType: props.pageType,
});
}}
/>
)}
</div>
);
}
function TabButton(props: {
label: string;
onClick: () => void;
isActive: boolean;
}) {
return (
<Button
onClick={props.onClick}
size="sm"
className={cn(
"rounded-full text-muted-foreground px-4 bg-accent",
props.isActive && "text-foreground border-foreground/50",
)}
variant="outline"
>
{props.label}
</Button>
: undefined
}
swap={{
persistTokenSelections: props.persistTokenSelections,
prefill: {
buyToken: props.swapTab?.buyToken,
sellToken: props.swapTab?.sellToken,
},
onError: (error, quote) => {
const errorMessage = parseError(error);
reportTokenSwapFailed({
errorMessage: errorMessage,
buyTokenChainId: quote.intent.destinationChainId,
buyTokenAddress: quote.intent.destinationTokenAddress,
sellTokenChainId: quote.intent.originChainId,
sellTokenAddress: quote.intent.originTokenAddress,
pageType: props.pageType,
});
},
onSuccess: ({ quote }) => {
reportTokenSwapSuccessful({
buyTokenChainId: quote.intent.destinationChainId,
buyTokenAddress: quote.intent.destinationTokenAddress,
sellTokenChainId: quote.intent.originChainId,
sellTokenAddress: quote.intent.originTokenAddress,
pageType: props.pageType,
});
},
onCancel: (quote) => {
reportTokenSwapCancelled({
buyTokenChainId: quote.intent.destinationChainId,
buyTokenAddress: quote.intent.destinationTokenAddress,
sellTokenChainId: quote.intent.originChainId,
sellTokenAddress: quote.intent.originTokenAddress,
pageType: props.pageType,
});
},
}}
currency={props.currency}
showThirdwebBranding={props.showThirdwebBranding}
/>
);
}
@@ -34,7 +34,8 @@ export function ExportToCSVButton(props: {
return (
<Button
className={cn("flex items-center gap-2 border text-xs", props.className)}
className={cn("gap-2 rounded-full", props.className)}
size="sm"
disabled={props.disabled || exportMutation.isPending}
onClick={async () => {
exportMutation.mutate();
@@ -42,16 +43,11 @@ export function ExportToCSVButton(props: {
variant="outline"
>
{exportMutation.isPending ? (
<>
Downloading
<Spinner className="size-3" />
</>
<Spinner className="size-3.5 text-muted-foreground" />
) : (
<>
<DownloadIcon className="size-3" />
Export as CSV
</>
<DownloadIcon className="size-3.5 text-muted-foreground" />
)}
Export
</Button>
);
}
@@ -70,6 +70,7 @@ function Variants() {
key={currentPlan}
requiredPlan={requiredPlan}
teamSlug="foo"
isLegacyPlan={false}
/>
</BadgeContainer>
))}
@@ -14,6 +14,7 @@ type GatedSwitchProps = {
trackingLabel?: string;
currentPlan: Team["billingPlan"];
requiredPlan: Team["billingPlan"];
isLegacyPlan: boolean;
teamSlug: string;
switchProps?: SwitchProps;
};
@@ -33,7 +34,7 @@ export const GatedSwitch: React.FC<GatedSwitchProps> = (
<div className="w-full min-w-[280px]">
<h3 className="font-medium text-base">
<span className="capitalize">
{getTeamPlanBadgeLabel(props.requiredPlan)}+
{getTeamPlanBadgeLabel(props.requiredPlan, false)}+
</span>{" "}
plan required
</h3>
@@ -48,7 +49,8 @@ export const GatedSwitch: React.FC<GatedSwitchProps> = (
rel="noopener noreferrer"
target="_blank"
>
Upgrade to {getTeamPlanBadgeLabel(props.requiredPlan)} plan
Upgrade to {getTeamPlanBadgeLabel(props.requiredPlan, false)}{" "}
plan
<ExternalLinkIcon className="size-4" />
</Link>
</Button>
@@ -61,6 +63,7 @@ export const GatedSwitch: React.FC<GatedSwitchProps> = (
{isUpgradeRequired && (
<TeamPlanBadge
plan={props.requiredPlan}
isLegacyPlan={props.isLegacyPlan}
postfix="+"
teamSlug={props.teamSlug}
/>
@@ -23,28 +23,42 @@ const teamPlanToBadgeVariant: Record<
starter: "warning",
};
export function getTeamPlanBadgeLabel(plan: Team["billingPlan"]) {
export function getTeamPlanBadgeLabel(
plan: Team["billingPlan"],
isLegacyPlan: boolean,
) {
if (plan === "growth_legacy") {
return "Growth - Legacy";
}
if (isLegacyPlan) {
return `${plan} - Legacy`;
}
return plan;
}
export function TeamPlanBadge(props: {
teamSlug: string;
plan: Team["billingPlan"];
isLegacyPlan: boolean;
className?: string;
postfix?: string;
}) {
const router = useDashboardRouter();
function handleNavigateToBilling(e: React.MouseEvent | React.KeyboardEvent) {
e.stopPropagation();
e.preventDefault();
if (props.isLegacyPlan) {
router.push(`/team/${props.teamSlug}/~/billing`);
return;
}
if (props.plan !== "free") {
return;
}
e.stopPropagation();
e.preventDefault();
router.push(`/team/${props.teamSlug}/~/billing?showPlans=true`);
}
@@ -57,11 +71,13 @@ export function TeamPlanBadge(props: {
handleNavigateToBilling(e);
}
}}
role={props.plan === "free" ? "button" : undefined}
tabIndex={props.plan === "free" ? 0 : undefined}
variant={teamPlanToBadgeVariant[props.plan]}
role={props.plan === "free" || props.isLegacyPlan ? "button" : undefined}
tabIndex={props.plan === "free" || props.isLegacyPlan ? 0 : undefined}
variant={
props.isLegacyPlan ? "warning" : teamPlanToBadgeVariant[props.plan]
}
>
{`${getTeamPlanBadgeLabel(props.plan)}${props.postfix || ""}`}
{`${getTeamPlanBadgeLabel(props.plan, props.isLegacyPlan)}${props.postfix || ""}`}
</Badge>
);
}
@@ -40,7 +40,7 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
yAxis?: boolean;
xAxis?: {
sameDay?: boolean;
showHour?: boolean;
};
variant?: "stacked" | "individual";
@@ -90,9 +90,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
{props.isPending ? (
<LoadingChartState />
) : props.data.length === 0 ? (
<EmptyChartState type="area">
{props.emptyChartState}
</EmptyChartState>
<EmptyChartState content={props.emptyChartState} />
) : (
<AreaChart
accessibilityLayer
@@ -112,7 +110,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
tickFormatter={(value) =>
format(
new Date(value),
props.xAxis?.sameDay ? "MMM dd, HH:mm" : "MMM dd",
props.xAxis?.showHour ? "MMM dd, HH:mm" : "MMM dd",
)
}
tickLine={false}
@@ -1,12 +1,11 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import type { InAppWalletAuth } from "thirdweb/wallets";
import { BadgeContainer } from "@/storybook/utils";
import type { InAppWalletStats } from "@/types/analytics";
import { InAppWalletUsersChartCardUI } from "./InAppWalletUsersChartCard";
import { AutoMergeBarChart, type StatData } from "./automerge-barchart";
const meta = {
component: Component,
title: "Charts/InAppWallets",
title: "Charts/AutoMergeBarChart",
} satisfies Meta<typeof Component>;
export default meta;
@@ -19,60 +18,109 @@ export const Variants: Story = {
function Component() {
const title = "This is Title";
const description =
"This is an example of a description about in-app wallet usage chart";
"This is an example of a description about user wallet usage chart";
return (
<div className="container flex max-w-6xl flex-col gap-10 py-10">
<BadgeContainer label="30 days">
<InAppWalletUsersChartCardUI
<BadgeContainer label="10 days, view more variant">
<AutoMergeBarChart
viewMoreLink={"#"}
description={description}
inAppWalletStats={createInAppWalletStatsStub(30)}
stats={createStatsStub(10)}
isPending={false}
title={title}
maxLabelsToShow={5}
emptyChartState={undefined}
exportButton={undefined}
/>
</BadgeContainer>
<BadgeContainer label="30 days">
<AutoMergeBarChart
viewMoreLink={undefined}
description={description}
stats={createStatsStub(30)}
isPending={false}
title={title}
maxLabelsToShow={5}
emptyChartState={undefined}
exportButton={{
fileName: "in-app-wallets",
}}
/>
</BadgeContainer>
<BadgeContainer label="60 days">
<InAppWalletUsersChartCardUI
<AutoMergeBarChart
viewMoreLink={undefined}
description={description}
inAppWalletStats={createInAppWalletStatsStub(60)}
stats={createStatsStub(60)}
isPending={false}
title={title}
maxLabelsToShow={5}
emptyChartState={undefined}
exportButton={{
fileName: "in-app-wallets",
}}
/>
</BadgeContainer>
<BadgeContainer label="120 days">
<InAppWalletUsersChartCardUI
<AutoMergeBarChart
viewMoreLink={undefined}
description={description}
inAppWalletStats={createInAppWalletStatsStub(120)}
stats={createStatsStub(120)}
isPending={false}
title={title}
maxLabelsToShow={5}
emptyChartState={undefined}
exportButton={{
fileName: "in-app-wallets",
}}
/>
</BadgeContainer>
<BadgeContainer label="10 days">
<InAppWalletUsersChartCardUI
<AutoMergeBarChart
viewMoreLink={undefined}
description={description}
inAppWalletStats={createInAppWalletStatsStub(10)}
stats={createStatsStub(10)}
isPending={false}
title={title}
maxLabelsToShow={5}
emptyChartState={undefined}
exportButton={{
fileName: "in-app-wallets",
}}
/>
</BadgeContainer>
<BadgeContainer label="Empty List">
<InAppWalletUsersChartCardUI
<AutoMergeBarChart
viewMoreLink={undefined}
description={description}
inAppWalletStats={createInAppWalletStatsStub(0)}
stats={createStatsStub(0)}
isPending={false}
title={title}
maxLabelsToShow={5}
emptyChartState={undefined}
exportButton={{
fileName: "in-app-wallets",
}}
/>
</BadgeContainer>
<BadgeContainer label="Pending">
<InAppWalletUsersChartCardUI
<AutoMergeBarChart
viewMoreLink={undefined}
description={description}
inAppWalletStats={[]}
stats={[]}
isPending={true}
title={title}
maxLabelsToShow={5}
emptyChartState={undefined}
exportButton={{
fileName: "in-app-wallets",
}}
/>
</BadgeContainer>
</div>
@@ -111,17 +159,16 @@ const pickRandomAuthMethod = () => {
return capitalized;
};
function createInAppWalletStatsStub(days: number): InAppWalletStats[] {
const stubbedData: InAppWalletStats[] = [];
function createStatsStub(days: number): StatData[] {
const stubbedData: StatData[] = [];
let d = days;
while (d !== 0) {
const uniqueWallets = Math.floor(Math.random() * 100);
stubbedData.push({
authenticationMethod: pickRandomAuthMethod(),
label: pickRandomAuthMethod(),
date: new Date(2024, 11, d).toLocaleString(),
uniqueWalletsConnected: uniqueWallets,
newUsers: Math.floor(Math.random() * 100) + 1,
count: uniqueWallets,
});
if (Math.random() > 0.7) {
@@ -0,0 +1,173 @@
"use client";
import { format } from "date-fns";
import { useMemo } from "react";
import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart";
import { ExportToCSVButton } from "@/components/blocks/ExportToCSVButton";
import type { ChartConfig } from "@/components/ui/chart";
import { TotalValueChartHeader } from "./chart-header";
export type StatData = {
date: string;
count: number;
label: string;
};
type ChartData = Record<string, number> & {
time: string; // human readable date
};
const defaultLabel = "Unknown";
// merges the multiple stats in single bar if they share the same date
// shows the top `maxLabelsToShow` labels, merge the rest into "Others"
export function AutoMergeBarChart(props: {
stats: StatData[];
isPending: boolean;
title: string;
emptyChartState: React.ReactElement | undefined;
description: string | undefined;
maxLabelsToShow: number;
exportButton:
| {
fileName: string;
}
| undefined;
viewMoreLink: string | undefined;
}) {
const { stats, maxLabelsToShow } = props;
const { chartConfig, chartData } = useMemo(() => {
const _chartConfig: ChartConfig = {};
const _chartDataMap: Map<string, ChartData> = new Map();
const labelToCountMap: Map<string, number> = new Map();
// for each stat, add it in _chartDataMap
for (const stat of stats) {
const chartData = _chartDataMap.get(stat.date);
// if no data for current day - create new entry
if (!chartData && stat.count > 0) {
_chartDataMap.set(stat.date, {
time: stat.date,
[stat.label || defaultLabel]: stat.count,
} as ChartData);
} else if (chartData) {
chartData[stat.label || defaultLabel] =
(chartData[stat.label || defaultLabel] || 0) + stat.count;
}
labelToCountMap.set(
stat.label || defaultLabel,
stat.count + (labelToCountMap.get(stat.label || defaultLabel) || 0),
);
}
const labelsSorted = Array.from(labelToCountMap.entries())
.sort((a, b) => b[1] - a[1])
.map((w) => w[0]);
const labelsToShow = labelsSorted.slice(0, maxLabelsToShow);
const labelsToShowAsOther = labelsSorted.slice(maxLabelsToShow);
// replace chainIdsToTagAsOther chainId with "other"
for (const data of _chartDataMap.values()) {
for (const dataKey in data) {
if (labelsToShowAsOther.includes(dataKey)) {
data.others = (data.others || 0) + (data[dataKey] || 0);
delete data[dataKey];
}
}
}
labelsToShow.forEach((walletType, i) => {
_chartConfig[walletType] = {
color: `hsl(var(--chart-${(i % 10) + 1}))`,
label: labelsToShow[i],
};
});
// Add Other
if (labelsToShow.length >= maxLabelsToShow) {
labelsToShow.push("others");
_chartConfig.others = {
color: "hsl(var(--muted-foreground))",
label: "Others",
};
}
return {
chartConfig: _chartConfig,
chartData: Array.from(_chartDataMap.values()).sort(
(a, b) => new Date(a.time).getTime() - new Date(b.time).getTime(),
),
};
}, [stats, maxLabelsToShow]);
const uniqueLabels = Object.keys(chartConfig);
const total = useMemo(() => {
return props.stats.reduce((acc, stat) => acc + stat.count, 0);
}, [props.stats]);
return (
<ThirdwebBarChart
chartClassName="h-[275px] w-full"
config={chartConfig}
customHeader={
props.viewMoreLink ? (
<TotalValueChartHeader
total={total}
title={props.title}
isPending={props.isPending}
viewMoreLink={props.viewMoreLink}
/>
) : (
<div className="relative px-6 pt-6">
<div className="space-y-1">
<h3 className="font-semibold text-lg tracking-tight leading-none">
{props.title}
</h3>
{props.description && (
<p className="text-muted-foreground text-sm">
{props.description}
</p>
)}
</div>
{props.exportButton && props.stats.length > 0 && (
<ExportToCSVButton
className="top-6 right-6 mb-4 w-full bg-background md:absolute md:mb-0 md:flex md:w-auto"
fileName={props.exportButton.fileName}
getData={async () => {
// Shows the number of each type of wallet connected on all dates
const header = ["Time", ...uniqueLabels];
const rows = chartData.map((data) => {
const { time, ...rest } = data;
return [
new Date(time).toISOString(),
...uniqueLabels.map((w) => (rest[w] || 0).toString()),
];
});
return { header, rows };
}}
/>
)}
</div>
)
}
data={chartData}
emptyChartState={props.emptyChartState}
hideLabel={false}
isPending={props.isPending}
showLegend
toolTipLabelFormatter={(_v, item) => {
if (Array.isArray(item)) {
const time = item[0].payload.time as number;
return format(new Date(time), "MMM d, yyyy");
}
return undefined;
}}
variant="stacked"
/>
);
}
@@ -44,6 +44,10 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
toolTipValueFormatter?: (value: unknown) => React.ReactNode;
hideLabel?: boolean;
emptyChartState?: React.ReactElement;
className?: string;
xAxis?: {
showHour?: boolean;
};
};
export function ThirdwebBarChart<TConfig extends ChartConfig>(
@@ -55,7 +59,7 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
props.variant || configKeys.length > 4 ? "stacked" : "grouped";
return (
<Card>
<Card className={cn("overflow-hidden", props.className)}>
{props.header && (
<CardHeader>
<CardTitle className={cn("mb-2", props.header.titleClassName)}>
@@ -74,16 +78,19 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
{props.isPending ? (
<LoadingChartState />
) : props.data.length === 0 ? (
<EmptyChartState type="bar">
{props.emptyChartState}
</EmptyChartState>
<EmptyChartState content={props.emptyChartState} />
) : (
<BarChart accessibilityLayer data={props.data}>
<CartesianGrid vertical={false} />
<XAxis
axisLine={false}
dataKey="time"
tickFormatter={(value) => format(new Date(value), "MMM d")}
tickFormatter={(value) =>
format(
new Date(value),
props.xAxis?.showHour ? "MMM dd, HH:mm" : "MMM dd",
)
}
tickLine={false}
tickMargin={10}
/>
@@ -0,0 +1,60 @@
import { ArrowUpRightIcon } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { SkeletonContainer } from "@/components/ui/skeleton";
export function TotalValueChartHeader(props: {
total: number;
title: string;
isUsd?: boolean;
isPending: boolean;
viewMoreLink: string | undefined;
}) {
return (
<div className="space-y-1 p-6 pb-0 flex justify-between items-start gap-3">
<div>
<SkeletonContainer
loadedData={!props.isPending ? props.total : undefined}
skeletonData={100}
render={(value) => {
return (
<p className="text-3xl font-semibold tracking-tight">
{props.isUsd
? compactUSDFormatter.format(value)
: compactNumberFormatter.format(value)}
</p>
);
}}
/>
<h3 className="text-muted-foreground">{props.title}</h3>
</div>
{props.viewMoreLink && (
<Button
asChild
size="sm"
variant="outline"
className="gap-2 rounded-full text-muted-foreground hover:text-foreground"
>
<Link href={props.viewMoreLink}>
<span>View More</span>
<ArrowUpRightIcon className="size-4" />
</Link>
</Button>
)}
</div>
);
}
const compactNumberFormatter = new Intl.NumberFormat("en-US", {
notation: "compact",
maximumFractionDigits: 2,
});
const compactUSDFormatter = new Intl.NumberFormat("en-US", {
notation: "compact",
maximumFractionDigits: 2,
style: "currency",
currency: "USD",
});
@@ -38,11 +38,11 @@ type ShadcnSidebarBaseLink = {
isActive?: (pathname: string) => boolean;
};
type ShadcnSidebarLink =
export type ShadcnSidebarLink =
| ShadcnSidebarBaseLink
| {
group: string;
links: ShadcnSidebarBaseLink[];
links: ShadcnSidebarLink[];
}
| {
separator: true;
@@ -67,7 +67,7 @@ export function FullWidthSidebarLayout(props: {
)}
>
{/* left - sidebar */}
<Sidebar className="pt-2" collapsible="icon" side="left">
<Sidebar collapsible="icon" side="left">
<SidebarContent className="p-2">
<RenderSidebarMenu links={contentSidebarLinks} />
</SidebarContent>
@@ -97,10 +97,7 @@ export function FullWidthSidebarLayout(props: {
function MobileSidebarTrigger(props: { links: ShadcnSidebarLink[] }) {
const activeLink = useActiveShadcnSidebarLink(props.links);
const parentSubNav = props.links.find(
(link) =>
"subMenu" in link && link.links.some((l) => l.href === activeLink?.href),
);
const parentSubNav = findParentSubmenu(props.links, activeLink?.href);
return (
<div className="flex items-center gap-3 border-b px-4 py-4 lg:hidden">
@@ -109,7 +106,7 @@ function MobileSidebarTrigger(props: { links: ShadcnSidebarLink[] }) {
className="h-4 bg-muted-foreground/50"
orientation="vertical"
/>
{parentSubNav && "subMenu" in parentSubNav && (
{parentSubNav && (
<>
<span className="text-sm">{parentSubNav.subMenu.label}</span>
<ChevronRightIcon className="size-4 text-muted-foreground/50 -mx-1.5" />
@@ -131,24 +128,65 @@ function useActiveShadcnSidebarLink(links: ShadcnSidebarLink[]) {
return pathname?.startsWith(link.href);
}
for (const link of links) {
if ("links" in link) {
for (const subLink of link.links) {
if (isActive(subLink)) {
return subLink;
function walk(
navLinks: ShadcnSidebarLink[],
): ShadcnSidebarBaseLink | undefined {
for (const link of navLinks) {
if ("subMenu" in link) {
for (const subLink of link.links) {
if (isActive(subLink)) {
return subLink;
}
}
} else if ("href" in link) {
if (isActive(link)) {
return link;
}
}
} else if ("href" in link) {
if (isActive(link)) {
return link;
if ("links" in link && !("subMenu" in link)) {
const nested = walk(link.links);
if (nested) {
return nested;
}
}
}
return undefined;
}
return walk(links);
}, [links, pathname]);
return activeLink;
}
function findParentSubmenu(
links: ShadcnSidebarLink[],
activeHref: string | undefined,
): Extract<ShadcnSidebarLink, { subMenu: unknown }> | undefined {
if (!activeHref) {
return undefined;
}
for (const link of links) {
if ("subMenu" in link) {
if (link.links.some((subLink) => subLink.href === activeHref)) {
return link;
}
}
if ("links" in link && !("subMenu" in link)) {
const nested = findParentSubmenu(link.links, activeHref);
if (nested) {
return nested;
}
}
}
return undefined;
}
function useIsSubnavActive(links: ShadcnSidebarBaseLink[]) {
const pathname = usePathname();
@@ -66,13 +66,16 @@ export type ProjectPageHeaderProps = {
href: string;
};
className?: string;
// TODO: add task card component
task?: never;
};
export function ProjectPageHeader(props: ProjectPageHeaderProps) {
return (
<header className="container max-w-7xl py-6 relative">
<header
className={cn("container max-w-7xl py-6 relative", props.className)}
>
{/* top row */}
<div className="flex justify-between items-start mb-4">
{/* left - icon */}
@@ -12,18 +12,20 @@ import {
type ProjectPageProps = {
header: ProjectPageHeaderProps;
footer?: ProjectPageFooterProps;
/**
* @deprecated only for legacy pages where we still need internal tabs for the moment, currently:
* - /webhooks
*/
tabs?: TabPathLink[];
containerClassName?: string;
};
export function ProjectPage(props: React.PropsWithChildren<ProjectPageProps>) {
return (
<div className="flex flex-col grow">
<div className={cn(!props.tabs && "border-b")}>
<ProjectPageHeader {...props.header} />
<ProjectPageHeader
{...props.header}
className={cn(
props.tabs && props.tabs.length > 0 ? "pt-8 pb-6" : "py-8",
)}
/>
{props.tabs && (
<TabPathLinks
scrollableClassName="container max-w-7xl"
@@ -32,7 +34,11 @@ export function ProjectPage(props: React.PropsWithChildren<ProjectPageProps>) {
)}
</div>
<main className="container max-w-7xl pt-6">{props.children}</main>
<main
className={cn("container max-w-7xl pt-6", props.containerClassName)}
>
{props.children}
</main>
<div className="h-20" />
{props.footer && (
<div className="border-t mt-auto">
@@ -125,7 +125,7 @@ export const SelectWithSearch = React.forwardRef<
? renderOption(selectedOption)
: selectedOption?.label || placeholder}
</span>
<ChevronDownIcon className="size-4 cursor-pointer text-muted-foreground" />
<ChevronDownIcon className="size-3.5 cursor-pointer text-muted-foreground shrink-0" />
</div>
</Button>
</PopoverTrigger>
@@ -22,6 +22,7 @@ interface UpsellWrapperProps {
isLocked?: boolean;
requiredPlan: Team["billingPlan"];
currentPlan?: Team["billingPlan"];
isLegacyPlan?: boolean;
featureName: string;
featureDescription: string;
benefits?: {
@@ -41,6 +42,7 @@ export function UpsellWrapper({
featureDescription,
benefits = [],
className,
isLegacyPlan = false,
}: UpsellWrapperProps) {
if (!isLocked) {
return <>{children}</>;
@@ -66,6 +68,7 @@ export function UpsellWrapper({
featureDescription={featureDescription}
featureName={featureName}
requiredPlan={requiredPlan}
isLegacyPlan={isLegacyPlan}
teamSlug={teamSlug}
/>
</div>
@@ -79,6 +82,7 @@ export function UpsellContent(props: {
featureDescription: string;
requiredPlan: Team["billingPlan"];
currentPlan: Team["billingPlan"];
isLegacyPlan: boolean;
benefits?: {
description: string;
status: "available" | "soon";
@@ -96,6 +100,7 @@ export function UpsellContent(props: {
plan={props.requiredPlan}
postfix=" Feature"
teamSlug={props.teamSlug}
isLegacyPlan={props.isLegacyPlan}
/>
<div className="space-y-1">
<CardTitle className="font-bold text-2xl text-foreground md:text-3xl">
@@ -62,6 +62,8 @@ function Story() {
client={storybookThirdwebClient}
contracts={[]}
pageSize={10}
projectId="project-id"
teamId="team-id"
projectSlug={projectSlug}
removeContractFromProject={removeContractStub}
teamSlug={teamSlug}
@@ -74,6 +76,8 @@ function Story() {
client={storybookThirdwebClient}
contracts={[popularPolygonNFTs[0] as ProjectContract]}
pageSize={10}
projectId="project-id"
teamId="team-id"
projectSlug={projectSlug}
removeContractFromProject={removeContractStub}
teamSlug={teamSlug}
@@ -86,6 +90,8 @@ function Story() {
client={storybookThirdwebClient}
contracts={popularPolygonNFTs}
pageSize={10}
projectId="project-id"
teamId="team-id"
projectSlug={projectSlug}
removeContractFromProject={removeContractStub}
teamSlug={teamSlug}
@@ -95,6 +101,8 @@ function Story() {
<BadgeContainer label="10 Contracts, 2 chains">
<ContractTableUI
projectId="project-id"
teamId="team-id"
client={storybookThirdwebClient}
contracts={[...popularPolygonNFTs, ...EthereumPopularNFTs]}
pageSize={10}
@@ -114,6 +122,8 @@ function Story() {
...basePopularTokens,
]}
pageSize={10}
projectId="project-id"
teamId="team-id"
projectSlug={projectSlug}
removeContractFromProject={removeContractStub}
teamSlug={teamSlug}
@@ -32,6 +32,8 @@ import { useAllChainsData } from "@/hooks/chains/allChains";
import { removeContractFromProject } from "@/hooks/project-contracts";
import { ChainIconClient } from "@/icons/ChainIcon";
import { cn } from "@/lib/utils";
import { revalidateCacheTagAction } from "../../../actions/revalidate";
import { projectContractsCacheTag } from "../../../api/project/cache-tag";
import {
ContractNameCell,
ContractTypeCell,
@@ -57,6 +59,8 @@ export function ContractTable(props: {
client={props.client}
contracts={props.contracts}
pageSize={props.pageSize}
projectId={props.projectId}
teamId={props.teamId}
projectSlug={props.projectSlug}
removeContractFromProject={async (contractId) => {
await removeContractFromProject({
@@ -77,6 +81,8 @@ export function ContractTableUI(props: {
removeContractFromProject: (contractId: string) => Promise<void>;
client: ThirdwebClient;
variant: "asset" | "contract";
teamId: string;
projectId: string;
teamSlug: string;
projectSlug: string;
}) {
@@ -243,6 +249,13 @@ export function ContractTableUI(props: {
<ContractActionsCell
contractId={contract.id}
onContractRemoved={() => {
revalidateCacheTagAction(
projectContractsCacheTag({
teamId: props.teamId,
projectId: props.projectId,
}),
);
setDeletedContractIds((v) => {
return [...v, contract.id];
});
@@ -69,7 +69,7 @@ export default function Component() {
// Get your contract
var contract = await ThirdwebManager.Instance.GetContract(
address: "{{contract_address}}",
address: "{{contract_address}}",
chainId: {{chainId}},
);
@@ -124,7 +124,7 @@ export default function Component() {
// Get your contract
var contract = await ThirdwebManager.Instance.GetContract(
address: "{{contract_address}}",
address: "{{contract_address}}",
chainId: {{chainId}}
);
@@ -214,7 +214,7 @@ function App() {
// Get your contract
var contract = await ThirdwebManager.Instance.GetContract(
address: "{{contract_address}}",
address: "{{contract_address}}",
chainId: {{chainId}}
);`,
dotnet: `using Thirdweb;
@@ -272,7 +272,7 @@ export default function Component() {
// Get your contract
var contract = await ThirdwebManager.Instance.GetContract(
address: "{{contract_address}}",
address: "{{contract_address}}",
chainId: {{chainId}}
);
@@ -1103,7 +1103,7 @@ function AccountFactorySection(props: {
<AlertDescription>
The recommended way to use account factories is to integrate the{" "}
<UnderlineLink
href="https://portal.thirdweb.com/transactions/sponsor"
href="https://portal.thirdweb.com/wallets/sponsor-gas"
rel="noopener noreferrer"
target="_blank"
>
@@ -178,7 +178,7 @@ export async function ContractCard({
{auditLink && !isBeta && (
<>
<Link
className="relative z-1 flex items-center gap-1 font-medium text-sm text-success-text hover:underline"
className="relative z-10 flex items-center gap-1 font-medium text-sm text-success-text hover:underline"
href={auditLink}
rel="noopener noreferrer"
target="_blank"
@@ -1,11 +1,23 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowDownToLineIcon, ExternalLinkIcon } from "lucide-react";
import { UnderlineLink } from "@workspace/ui/components/UnderlineLink";
import {
ArrowDownToLineIcon,
CircleAlertIcon,
ExternalLinkIcon,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { getAddress, isAddress, type ThirdwebClient } from "thirdweb";
import {
getAddress,
getContract,
isAddress,
type ThirdwebClient,
} from "thirdweb";
import { defineChain } from "thirdweb/chains";
import { useActiveWalletChain } from "thirdweb/react";
import { z } from "zod";
import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors";
@@ -31,6 +43,11 @@ import { Spinner } from "@/components/ui/Spinner";
import { useChainSlug } from "@/hooks/chains/chainSlug";
import { useAddContractToProject } from "@/hooks/project-contracts";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { revalidateCacheTagAction } from "../../../actions/revalidate";
import { projectContractsCacheTag } from "../../../api/project/cache-tag";
import { resolveFunctionSelectors } from "../../../lib/selectors";
import { supportedERCs } from "../../../utils/supportedERCs";
import { Alert, AlertDescription, AlertTitle } from "../../ui/alert";
type ImportModalProps = {
isOpen: boolean;
@@ -42,6 +59,7 @@ type ImportModalProps = {
client: ThirdwebClient;
type: "contract" | "asset";
onSuccess?: () => void;
allowedContractType: "token" | "non-token";
};
export const ImportModal: React.FC<ImportModalProps> = (props) => {
@@ -75,6 +93,7 @@ export const ImportModal: React.FC<ImportModalProps> = (props) => {
teamId={props.teamId}
teamSlug={props.teamSlug}
type={props.type}
allowedContractType={props.allowedContractType}
/>
</DialogContent>
</Dialog>
@@ -105,6 +124,7 @@ function ImportForm(props: {
client: ThirdwebClient;
type: "contract" | "asset";
onSuccess?: () => void;
allowedContractType: "token" | "non-token";
}) {
const router = useDashboardRouter();
const activeChainId = useActiveWalletChain()?.id;
@@ -119,9 +139,14 @@ function ImportForm(props: {
const chainSlug = useChainSlug(form.watch("chainId"));
const addContractToProject = useAddContractToProject();
const [notAllowedError, setNotAllowedError] = useState(false);
return (
<Form {...form}>
<form
onChange={() => {
setNotAllowedError(false);
}}
onSubmit={form.handleSubmit(async (data) => {
const { chainId } = data;
let contractAddress: string;
@@ -158,6 +183,27 @@ function ImportForm(props: {
return;
}
// check if the contract matches the requirement
const contract = getContract({
address: contractAddress,
// eslint-disable-next-line no-restricted-syntax
chain: defineChain(chainId),
client: props.client,
});
const functionSelectors = await resolveFunctionSelectors(contract);
const ercs = supportedERCs(functionSelectors);
const isToken = ercs.isERC20 || ercs.isERC721 || ercs.isERC1155;
if (
(props.allowedContractType === "token" && !isToken) ||
(props.allowedContractType === "non-token" && isToken)
) {
setNotAllowedError(true);
return;
}
addContractToProject.mutate(
{
chainId: chainId.toString(),
@@ -177,6 +223,12 @@ function ImportForm(props: {
}
},
onSuccess: () => {
revalidateCacheTagAction(
projectContractsCacheTag({
teamId: props.teamId,
projectId: props.projectId,
}),
);
router.refresh();
toast.success("Contract imported successfully");
props.onSuccess?.();
@@ -217,6 +269,34 @@ function ImportForm(props: {
</div>
</div>
{notAllowedError && (
<div className="px-6 pt-6 text-sm text-destructive-text">
<Alert variant="destructive">
<CircleAlertIcon className="size-5" />
<AlertTitle> Invalid Contract </AlertTitle>
<AlertDescription>
{props.allowedContractType === "token" && (
<span>
This contract is not a token contract. <br /> Only ERC20,
ERC721 and ERC1155 contracts can be imported as tokens.
</span>
)}
{props.allowedContractType === "non-token" && (
<span>
This contract is a token contract. <br /> Go to the{" "}
<UnderlineLink
href={`/team/${props.teamSlug}/${props.projectSlug}/tokens`}
>
Tokens
</UnderlineLink>{" "}
page to import in dashboard.
</span>
)}
</AlertDescription>
</Alert>
</div>
)}
<div className="mt-8 flex justify-end border-t bg-card p-6">
{addContractToProject.isSuccess &&
addContractToProject.data?.result ? (
@@ -1,371 +0,0 @@
"use client";
import { createColumnHelper } from "@tanstack/react-table";
import { format } from "date-fns";
import { ArrowLeftIcon, ArrowRightIcon, UserIcon } from "lucide-react";
import Papa from "papaparse";
import { useCallback, useMemo, useState } from "react";
import type { ThirdwebClient } from "thirdweb";
import type { WalletUser } from "thirdweb/wallets";
import { TWTable } from "@/components/blocks/TWTable";
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner";
import {
ToolTipLabel,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
useAllEmbeddedWallets,
useEmbeddedWallets,
} from "@/hooks/useEmbeddedWallets";
import { CopyTextButton } from "../ui/CopyTextButton";
import { AdvancedSearchInput } from "./AdvancedSearchInput";
import { SearchResults } from "./SearchResults";
import { searchUsers } from "./searchUsers";
import type { SearchType } from "./types";
const getAuthIdentifier = (accounts: WalletUser["linkedAccounts"]) => {
const mainDetail = accounts[0]?.details;
return (
mainDetail?.id ??
mainDetail?.email ??
mainDetail?.phone ??
mainDetail?.address
);
};
const getPrimaryEmail = (accounts: WalletUser["linkedAccounts"]) => {
const emailFromPrimary = accounts[0]?.details?.email;
if (emailFromPrimary) {
return emailFromPrimary;
}
const emailAccount = accounts.find((account) => {
return typeof account.details?.email === "string" && account.details.email;
});
if (emailAccount && typeof emailAccount.details.email === "string") {
return emailAccount.details.email;
}
return undefined;
};
const columnHelper = createColumnHelper<WalletUser>();
const truncateIdentifier = (value: string) => {
if (value.length <= 18) {
return value;
}
return `${value.slice(0, 8)}...${value.slice(-4)}`;
};
export function InAppWalletUsersPageContent(
props: {
authToken: string;
client: ThirdwebClient;
teamId: string;
} & (
| { projectClientId: string; ecosystemSlug?: never }
| { ecosystemSlug: string; projectClientId?: never }
),
) {
const columns = useMemo(() => {
return [
columnHelper.accessor("id", {
cell: (cell) => {
const userId = cell.getValue();
if (!userId) {
return "N/A";
}
return (
<CopyTextButton
textToShow={truncateIdentifier(userId)}
textToCopy={userId}
tooltip="Copy User Identifier"
copyIconPosition="left"
variant="ghost"
/>
);
},
header: "User Identifier",
id: "user_identifier",
}),
columnHelper.accessor("linkedAccounts", {
cell: (cell) => {
const identifier = getAuthIdentifier(cell.getValue());
if (!identifier) {
return "N/A";
}
return (
<CopyTextButton
textToShow={truncateIdentifier(identifier)}
textToCopy={identifier}
tooltip="Copy Auth Identifier"
copyIconPosition="left"
variant="ghost"
/>
);
},
enableColumnFilter: true,
header: "Auth Identifier",
id: "auth_identifier",
}),
columnHelper.accessor("wallets", {
cell: (cell) => {
const address = cell.getValue()[0]?.address;
return address ? (
<WalletAddress address={address} client={props.client} />
) : null;
},
header: "Address",
id: "address",
}),
columnHelper.accessor("linkedAccounts", {
cell: (cell) => {
const email = getPrimaryEmail(cell.getValue());
if (!email) {
return <span className="text-muted-foreground text-sm">N/A</span>;
}
return (
<CopyTextButton
textToShow={email}
textToCopy={email}
tooltip="Copy Email"
copyIconPosition="left"
variant="ghost"
/>
);
},
header: "Email",
id: "email",
}),
columnHelper.accessor("wallets", {
cell: (cell) => {
const value = cell.getValue()[0]?.createdAt;
if (!value) {
return;
}
return (
<ToolTipLabel
label={format(new Date(value), "MMM dd, yyyy 'at' h:mm:ss a zzz")}
hoverable
>
<span className="text-sm">
{format(new Date(value), "MMM dd, yyyy")}
</span>
</ToolTipLabel>
);
},
header: "Created",
id: "created_at",
}),
columnHelper.accessor("linkedAccounts", {
cell: (cell) => {
const value = cell.getValue();
const loginMethodsDisplay = value.reduce((acc, curr) => {
if (acc.length === 2) {
acc.push("...");
}
if (acc.length < 2) {
acc.push(curr.type);
}
return acc;
}, [] as string[]);
const loginMethods = value.map((v) => v.type).join(", ");
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-sm">
{loginMethodsDisplay.join(", ")}
</TooltipTrigger>
<TooltipContent>
<span className="text-sm">{loginMethods}</span>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
header: "Login Methods",
id: "login_methods",
}),
];
}, [props.client]);
const [activePage, setActivePage] = useState(1);
const [searchResults, setSearchResults] = useState<WalletUser[]>([]);
const [isSearching, setIsSearching] = useState(false);
const [hasSearchResults, setHasSearchResults] = useState(false);
const walletsQuery = useEmbeddedWallets({
authToken: props.authToken,
clientId: props.projectClientId,
ecosystemSlug: props.ecosystemSlug,
teamId: props.teamId,
page: activePage,
});
const wallets = walletsQuery?.data?.users || [];
const { mutateAsync: getAllEmbeddedWallets, isPending } =
useAllEmbeddedWallets({
authToken: props.authToken,
});
const handleSearch = async (searchType: SearchType, query: string) => {
setIsSearching(true);
try {
const results = await searchUsers(
props.authToken,
props.projectClientId,
props.ecosystemSlug,
props.teamId,
searchType,
query,
);
setSearchResults(results);
setHasSearchResults(true);
} catch (error) {
console.error("Search failed:", error);
setSearchResults([]);
setHasSearchResults(true);
} finally {
setIsSearching(false);
}
};
const handleClearSearch = () => {
setSearchResults([]);
setHasSearchResults(false);
};
const downloadCSV = useCallback(async () => {
if (wallets.length === 0 || !getAllEmbeddedWallets) {
return;
}
const usersWallets = await getAllEmbeddedWallets({
clientId: props.projectClientId,
ecosystemSlug: props.ecosystemSlug,
teamId: props.teamId,
});
const csv = Papa.unparse(
usersWallets.map((row) => {
const email = getPrimaryEmail(row.linkedAccounts);
return {
address: row.wallets[0]?.address || "Uninitialized",
created: row.wallets[0]?.createdAt
? new Date(row.wallets[0].createdAt).toISOString()
: "Wallet not created yet",
email: email || "N/A",
login_methods: row.linkedAccounts.map((acc) => acc.type).join(", "),
auth_identifier: getAuthIdentifier(row.linkedAccounts) || "N/A",
user_identifier: row.id || "N/A",
};
}),
);
const csvUrl = URL.createObjectURL(
new Blob([csv], { type: "text/csv;charset=utf-8;" }),
);
const tempLink = document.createElement("a");
tempLink.href = csvUrl;
tempLink.setAttribute("download", "download.csv");
tempLink.click();
}, [
wallets,
props.projectClientId,
getAllEmbeddedWallets,
props.teamId,
props.ecosystemSlug,
]);
return (
<div>
<div className="overflow-hidden rounded-xl border border-border bg-card">
<div className="flex flex-col lg:flex-row lg:justify-between p-4 lg:px-6 py-5 lg:items-center gap-5">
<div>
<div className="flex mb-3">
<div className="p-2 rounded-full bg-background border border-border">
<UserIcon className="size-5 text-muted-foreground" />
</div>
</div>
<h2 className="font-semibold text-2xl tracking-tight">
User Wallets
</h2>
<p className="text-muted-foreground text-sm">
View and manage your project's users
</p>
</div>
<div className="flex flex-col items-start lg:items-end gap-2.5 border-t lg:border-t-0 pt-5 lg:pt-0 border-dashed">
<div className="w-full lg:w-auto lg:min-w-[320px]">
<AdvancedSearchInput
onSearch={handleSearch}
onClear={handleClearSearch}
isLoading={isSearching}
hasResults={hasSearchResults}
/>
</div>
<Button
className="gap-2 bg-background rounded-full"
disabled={wallets.length === 0 || isPending}
onClick={downloadCSV}
variant="outline"
>
{isPending && <Spinner className="size-4" />}
Download as .csv
</Button>
</div>
</div>
<div>
{hasSearchResults ? (
<SearchResults results={searchResults} client={props.client} />
) : (
<>
<TWTable
columns={columns}
data={wallets}
isFetched={walletsQuery.isFetched}
isPending={walletsQuery.isPending}
tableContainerClassName="rounded-none border-x-0 border-b-0"
title="in-app wallets"
/>
<div className="flex justify-center gap-3 border-t bg-card p-6">
<Button
className="gap-2 bg-background"
disabled={activePage === 1 || walletsQuery.isPending}
onClick={() => setActivePage((p) => Math.max(1, p - 1))}
size="sm"
variant="outline"
>
<ArrowLeftIcon className="size-4" />
Previous
</Button>
<Button
className="gap-2 bg-background"
disabled={wallets.length === 0 || walletsQuery.isPending}
onClick={() => setActivePage((p) => p + 1)}
size="sm"
variant="outline"
>
Next
<ArrowRightIcon className="size-4" />
</Button>
</div>
</>
)}
</div>
</div>
</div>
);
}
@@ -0,0 +1,612 @@
"use client";
import { createColumnHelper } from "@tanstack/react-table";
import { format } from "date-fns";
import {
ArrowLeftIcon,
ArrowRightIcon,
DollarSignIcon,
RefreshCwIcon,
UserIcon,
WalletIcon,
} from "lucide-react";
import Papa from "papaparse";
import { useCallback, useMemo, useState } from "react";
import type { ThirdwebClient } from "thirdweb";
import type { WalletUser } from "thirdweb/wallets";
import { StatCard } from "@/components/analytics/stat";
import { MultiNetworkSelector } from "@/components/blocks/NetworkSelectors";
import { TWTable } from "@/components/blocks/TWTable";
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { Spinner } from "@/components/ui/Spinner";
import {
ToolTipLabel,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
useAllEmbeddedWallets,
useEmbeddedWallets,
} from "@/hooks/useEmbeddedWallets";
import {
useFetchAllPortfolios,
type WalletPortfolioData,
} from "@/hooks/useWalletPortfolio";
import { CopyTextButton } from "../ui/CopyTextButton";
import { AdvancedSearchInput } from "./AdvancedSearchInput";
import { SearchResults } from "./SearchResults";
import { searchUsers } from "./searchUsers";
import type { SearchType } from "./types";
const getAuthIdentifier = (accounts: WalletUser["linkedAccounts"]) => {
const mainDetail = accounts[0]?.details;
return (
mainDetail?.id ??
mainDetail?.email ??
mainDetail?.phone ??
mainDetail?.address
);
};
const getPrimaryEmail = (accounts: WalletUser["linkedAccounts"]) => {
const emailFromPrimary = accounts[0]?.details?.email;
if (emailFromPrimary) {
return emailFromPrimary;
}
const emailAccount = accounts.find((account) => {
return typeof account.details?.email === "string" && account.details.email;
});
if (emailAccount && typeof emailAccount.details.email === "string") {
return emailAccount.details.email;
}
return undefined;
};
const columnHelper = createColumnHelper<WalletUser>();
const truncateIdentifier = (value: string) => {
if (value.length <= 18) {
return value;
}
return `${value.slice(0, 8)}...${value.slice(-4)}`;
};
export function UserWalletsTable(
props: {
authToken: string;
client: ThirdwebClient;
teamId: string;
} & (
| { projectClientId: string; ecosystemSlug?: never }
| { ecosystemSlug: string; projectClientId?: never }
),
) {
const [activePage, setActivePage] = useState(1);
const [searchResults, setSearchResults] = useState<WalletUser[]>([]);
const [isSearching, setIsSearching] = useState(false);
const [hasSearchResults, setHasSearchResults] = useState(false);
// Portfolio state
const [selectedChains, setSelectedChains] = useState<number[]>([1]); // Default to Ethereum
const [portfolioMap, setPortfolioMap] = useState<
Map<string, WalletPortfolioData>
>(new Map());
const [portfolioLoaded, setPortfolioLoaded] = useState(false);
const [fetchProgress, setFetchProgress] = useState({
completed: 0,
total: 0,
});
const walletsQuery = useEmbeddedWallets({
authToken: props.authToken,
clientId: props.projectClientId,
ecosystemSlug: props.ecosystemSlug,
teamId: props.teamId,
page: activePage,
});
const wallets = walletsQuery?.data?.users || [];
const { mutateAsync: getAllEmbeddedWallets, isPending: isLoadingAllWallets } =
useAllEmbeddedWallets({
authToken: props.authToken,
});
const fetchPortfoliosMutation = useFetchAllPortfolios();
const handleFetchBalances = useCallback(async () => {
if (selectedChains.length === 0) return;
try {
// First get all wallets
const allWallets = await getAllEmbeddedWallets({
clientId: props.projectClientId,
ecosystemSlug: props.ecosystemSlug,
teamId: props.teamId,
});
const allAddresses = allWallets
.map((w) => w.wallets[0]?.address)
.filter((a): a is string => !!a);
if (allAddresses.length === 0) {
setPortfolioLoaded(true);
return;
}
setFetchProgress({ completed: 0, total: allAddresses.length });
const results = await fetchPortfoliosMutation.mutateAsync({
addresses: allAddresses,
chainIds: selectedChains,
authToken: props.authToken,
teamId: props.teamId,
clientId: props.projectClientId,
ecosystemSlug: props.ecosystemSlug,
onProgress: (completed, total) => {
setFetchProgress({ completed, total });
},
});
setPortfolioMap(results);
setPortfolioLoaded(true);
} catch (error) {
console.error("Failed to fetch balances:", error);
}
}, [
selectedChains,
getAllEmbeddedWallets,
props.projectClientId,
props.ecosystemSlug,
props.teamId,
props.authToken,
fetchPortfoliosMutation,
]);
const isFetchingBalances =
isLoadingAllWallets || fetchPortfoliosMutation.isPending;
const aggregatedStats = useMemo(() => {
let fundedWallets = 0;
let totalValue = 0;
portfolioMap.forEach((data) => {
if (data.totalUsdValue > 0) {
fundedWallets++;
totalValue += data.totalUsdValue;
}
});
return { fundedWallets, totalValue };
}, [portfolioMap]);
const columns = useMemo(() => {
return [
columnHelper.accessor("id", {
cell: (cell) => {
const userId = cell.getValue();
if (!userId) {
return "N/A";
}
return (
<CopyTextButton
textToShow={truncateIdentifier(userId)}
textToCopy={userId}
tooltip="Copy User Identifier"
copyIconPosition="left"
variant="ghost"
/>
);
},
header: "User Identifier",
id: "user_identifier",
}),
columnHelper.accessor("linkedAccounts", {
cell: (cell) => {
const identifier = getAuthIdentifier(cell.getValue());
if (!identifier) {
return "N/A";
}
return (
<CopyTextButton
textToShow={truncateIdentifier(identifier)}
textToCopy={identifier}
tooltip="Copy Auth Identifier"
copyIconPosition="left"
variant="ghost"
/>
);
},
enableColumnFilter: true,
header: "Auth Identifier",
id: "auth_identifier",
}),
columnHelper.accessor("wallets", {
cell: (cell) => {
const address = cell.getValue()[0]?.address;
return address ? (
<WalletAddress address={address} client={props.client} />
) : null;
},
header: "Address",
id: "address",
}),
columnHelper.accessor("wallets", {
id: "total_balance",
header: "Total Balance",
cell: (cell) => {
const address = cell.getValue()[0]?.address;
if (!address) return "N/A";
if (!portfolioLoaded) {
return <span className="text-muted-foreground text-sm">—</span>;
}
const data = portfolioMap.get(address);
if (!data) {
return <span className="text-muted-foreground text-sm">—</span>;
}
return (
<span className="text-sm">
{new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(data.totalUsdValue)}
</span>
);
},
}),
columnHelper.accessor("wallets", {
id: "tokens",
header: "Tokens",
cell: (cell) => {
const address = cell.getValue()[0]?.address;
if (!address) return "N/A";
if (!portfolioLoaded) {
return <span className="text-muted-foreground text-sm">—</span>;
}
const data = portfolioMap.get(address);
if (!data || data.tokens.length === 0) {
return <span className="text-muted-foreground text-sm">None</span>;
}
const topTokens = data.tokens
.sort((a, b) => (b.usdValue || 0) - (a.usdValue || 0))
.slice(0, 3)
.map((t) => t.symbol)
.join(", ");
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-sm">
{topTokens}
{data.tokens.length > 3 ? "..." : ""}
</TooltipTrigger>
<TooltipContent>
<div className="flex flex-col gap-1">
{data.tokens.map((t) => (
<div
key={`${t.tokenAddress}-${t.chainId}`}
className="flex justify-between gap-4 text-xs"
>
<span>{t.symbol}</span>
<span>
{new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(t.usdValue || 0)}
</span>
</div>
))}
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
}),
columnHelper.accessor("linkedAccounts", {
cell: (cell) => {
const email = getPrimaryEmail(cell.getValue());
if (!email) {
return <span className="text-muted-foreground text-sm">N/A</span>;
}
return (
<CopyTextButton
textToShow={email}
textToCopy={email}
tooltip="Copy Email"
copyIconPosition="left"
variant="ghost"
/>
);
},
header: "Email",
id: "email",
}),
columnHelper.accessor("wallets", {
cell: (cell) => {
const value = cell.getValue()[0]?.createdAt;
if (!value) {
return;
}
return (
<ToolTipLabel
label={format(new Date(value), "MMM dd, yyyy 'at' h:mm:ss a zzz")}
hoverable
>
<span className="text-sm">
{format(new Date(value), "MMM dd, yyyy")}
</span>
</ToolTipLabel>
);
},
header: "Created",
id: "created_at",
}),
columnHelper.accessor("linkedAccounts", {
cell: (cell) => {
const value = cell.getValue();
const loginMethodsDisplay = value.reduce((acc, curr) => {
if (acc.length === 2) {
acc.push("...");
}
if (acc.length < 2) {
acc.push(curr.type);
}
return acc;
}, [] as string[]);
const loginMethods = value.map((v) => v.type).join(", ");
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-sm">
{loginMethodsDisplay.join(", ")}
</TooltipTrigger>
<TooltipContent>
<span className="text-sm">{loginMethods}</span>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
header: "Login Methods",
id: "login_methods",
}),
];
}, [props.client, portfolioMap, portfolioLoaded]);
const handleSearch = async (searchType: SearchType, query: string) => {
setIsSearching(true);
try {
const results = await searchUsers(
props.authToken,
props.projectClientId,
props.ecosystemSlug,
props.teamId,
searchType,
query,
);
setSearchResults(results);
setHasSearchResults(true);
} catch (error) {
console.error("Search failed:", error);
setSearchResults([]);
setHasSearchResults(true);
} finally {
setIsSearching(false);
}
};
const handleClearSearch = () => {
setSearchResults([]);
setHasSearchResults(false);
};
const downloadCSV = useCallback(async () => {
if (wallets.length === 0 || !getAllEmbeddedWallets) {
return;
}
const usersWallets = await getAllEmbeddedWallets({
clientId: props.projectClientId,
ecosystemSlug: props.ecosystemSlug,
teamId: props.teamId,
});
const csv = Papa.unparse(
usersWallets.map((row) => {
const email = getPrimaryEmail(row.linkedAccounts);
return {
address: row.wallets[0]?.address || "Uninitialized",
created: row.wallets[0]?.createdAt
? new Date(row.wallets[0].createdAt).toISOString()
: "Wallet not created yet",
email: email || "N/A",
login_methods: row.linkedAccounts.map((acc) => acc.type).join(", "),
auth_identifier: getAuthIdentifier(row.linkedAccounts) || "N/A",
user_identifier: row.id || "N/A",
};
}),
);
const csvUrl = URL.createObjectURL(
new Blob([csv], { type: "text/csv;charset=utf-8;" }),
);
const tempLink = document.createElement("a");
tempLink.href = csvUrl;
tempLink.setAttribute("download", "download.csv");
tempLink.click();
}, [
wallets,
props.projectClientId,
getAllEmbeddedWallets,
props.teamId,
props.ecosystemSlug,
]);
return (
<div className="overflow-hidden rounded-xl border border-border bg-card">
<div className="flex flex-col lg:flex-row lg:justify-between p-4 lg:px-6 py-5 lg:items-center gap-5">
<div>
<div className="flex mb-3">
<div className="p-2 rounded-full bg-background border border-border">
<UserIcon className="size-5 text-muted-foreground" />
</div>
</div>
<h2 className="font-semibold text-2xl tracking-tight">
User Wallets
</h2>
<p className="text-muted-foreground text-sm">
View and manage your project's users
</p>
</div>
<div className="flex flex-col items-start lg:items-end gap-2.5 border-t lg:border-t-0 pt-5 lg:pt-0 border-dashed">
<div className="w-full lg:w-auto lg:min-w-[320px]">
<AdvancedSearchInput
onSearch={handleSearch}
onClear={handleClearSearch}
isLoading={isSearching}
hasResults={hasSearchResults}
/>
</div>
<Button
className="gap-2 bg-background rounded-full"
disabled={wallets.length === 0 || isLoadingAllWallets}
onClick={downloadCSV}
variant="outline"
>
{isLoadingAllWallets && <Spinner className="size-4" />}
Download as .csv
</Button>
</div>
</div>
<div>
{hasSearchResults ? (
<SearchResults results={searchResults} client={props.client} />
) : (
<>
{/* Chain Selector and Fetch Button */}
<div className="flex items-center gap-3 px-4 lg:px-6 pb-4 border-b border-border">
<div className="flex items-center gap-2">
<MultiNetworkSelector
client={props.client}
selectedChainIds={selectedChains}
onChange={setSelectedChains}
disableChainId
hideTestnets
popoverContentClassName="max-h-[300px]"
/>
<Button
onClick={() => handleFetchBalances()}
disabled={
isFetchingBalances ||
selectedChains.length === 0 ||
walletsQuery.isPending
}
className="gap-2"
>
{isFetchingBalances ? (
<Spinner className="size-4" />
) : (
<RefreshCwIcon className="size-4" />
)}
{isFetchingBalances
? `Fetching... ${fetchProgress.total > 0 ? Math.round((fetchProgress.completed / fetchProgress.total) * 100) : 0}%`
: "Fetch All Balances"}
</Button>
</div>
{isFetchingBalances && (
<div className="flex flex-col gap-1 flex-1 min-w-[150px]">
{fetchProgress.total > 0 && (
<Progress
value={
(fetchProgress.completed / fetchProgress.total) * 100
}
className="h-2"
/>
)}
<p className="text-xs text-muted-foreground">
This may take a few minutes
</p>
</div>
)}
{portfolioLoaded && !isFetchingBalances && (
<Badge variant="success" className="ml-auto">
Balances loaded for {portfolioMap.size} wallets
</Badge>
)}
</div>
{/* Stats Section */}
<div className="grid grid-cols-2 gap-4 px-4 lg:px-6 py-4">
<StatCard
label="Funded Wallets"
value={portfolioLoaded ? aggregatedStats.fundedWallets : 0}
icon={WalletIcon}
isPending={isFetchingBalances}
emptyText={!portfolioLoaded ? "—" : undefined}
/>
<StatCard
label="Total Value"
value={portfolioLoaded ? aggregatedStats.totalValue : 0}
icon={DollarSignIcon}
formatter={(value) =>
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(value)
}
isPending={isFetchingBalances}
emptyText={!portfolioLoaded ? "—" : undefined}
/>
</div>
<TWTable
columns={columns}
data={wallets}
isFetched={walletsQuery.isFetched}
isPending={walletsQuery.isPending}
tableContainerClassName="rounded-none border-x-0 border-b-0"
title="User wallets"
/>
<div className="flex justify-center gap-3 border-t bg-card p-6">
<Button
className="gap-2 bg-background"
disabled={activePage === 1 || walletsQuery.isPending}
onClick={() => setActivePage((p) => Math.max(1, p - 1))}
size="sm"
variant="outline"
>
<ArrowLeftIcon className="size-4" />
Previous
</Button>
<Button
className="gap-2 bg-background"
disabled={wallets.length === 0 || walletsQuery.isPending}
onClick={() => setActivePage((p) => p + 1)}
size="sm"
variant="outline"
>
Next
<ArrowRightIcon className="size-4" />
</Button>
</div>
</>
)}
</div>
</div>
);
}
@@ -5,7 +5,6 @@ import Link from "next/link";
import { Button } from "@/components/ui/button";
import { useLocalStorage } from "@/hooks/useLocalStorage";
// biome-ignore lint/correctness/noUnusedVariables: banner is toggled on-demand via API content changes
function AnnouncementBannerUI(props: {
href: string;
label: string;
@@ -45,12 +44,11 @@ function AnnouncementBannerUI(props: {
}
export function AnnouncementBanner() {
// return (
// <AnnouncementBannerUI
// href="https://blog.thirdweb.com/the-fastest-way-to-build-web3-applications/"
// label="We have re-branded our Engine, Payments, and Connect products. Please read the full blog post for details on changes"
// trackingLabel="product-rebrand"
// />
// );
return null;
return (
<AnnouncementBannerUI
href="https://blog.thirdweb.com/changelog/project-view-update-wallets/"
label="Transactions, Vault, and Account Abstraction are now part of Wallets in Project Dashboard. View changes"
trackingLabel="project-view-update-wallets"
/>
);
}
@@ -42,7 +42,7 @@ const DialogContent = React.forwardRef<
<DialogOverlay className={dialogOverlayClassName} />
<DialogPrimitive.Content
className={cn(
"fixed z-50 grid w-full gap-4 rounded-t-xl border border-border bg-background p-6 shadow-lg duration-300 md:max-w-lg md:rounded-lg",
"fixed z-50 grid w-full gap-4 rounded-t-xl border border-border bg-background p-6 shadow-lg duration-300 md:max-w-lg md:rounded-xl",
// on mobile - put the dialog at the bottom of the screen, animate - slide up and fade in
"right-0 bottom-0 left-0",
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
@@ -12,6 +12,7 @@ export function LinkWithCopyButton(props: {
textToCopy: string;
copyTooltip: string;
className?: string;
linkIconClassName?: string;
}) {
const [isCopied, setIsCopied] = useState(false);
const Icon = isCopied ? CheckIcon : CopyIcon;
@@ -37,8 +38,15 @@ export function LinkWithCopyButton(props: {
target="_blank"
className="text-sm text-muted-foreground hover:underline flex items-center gap-1 tabular-nums flex-1 truncate hover:text-foreground group"
>
<span className="max-w-full truncate">{props.textToShow}</span>
<ArrowUpRightIcon className="size-3.5 opacity-70 shrink-0 group-hover:opacity-100" />
<span className="max-w-full truncate tabular-nums">
{props.textToShow}
</span>
<ArrowUpRightIcon
className={cn(
"size-3.5 opacity-70 shrink-0 group-hover:opacity-100",
props.linkIconClassName,
)}
/>
</Link>
</div>
);
@@ -41,3 +41,15 @@ export const NEXT_PUBLIC_CHAIN_PAGE_CLIENT_ID =
export const NEXT_PUBLIC_ASSET_PAGE_CLIENT_ID =
process.env.NEXT_PUBLIC_ASSET_PAGE_CLIENT_ID;
export const NEXT_PUBLIC_BRIDGE_IFRAME_CLIENT_ID =
process.env.NEXT_PUBLIC_BRIDGE_IFRAME_CLIENT_ID;
export const NEXT_PUBLIC_CHECKOUT_IFRAME_CLIENT_ID =
process.env.NEXT_PUBLIC_CHECKOUT_IFRAME_CLIENT_ID;
export const NEXT_PUBLIC_SWAP_IFRAME_CLIENT_ID =
process.env.NEXT_PUBLIC_SWAP_IFRAME_CLIENT_ID;
export const NEXT_PUBLIC_BUY_IFRAME_CLIENT_ID =
process.env.NEXT_PUBLIC_BUY_IFRAME_CLIENT_ID;
@@ -1,5 +1,5 @@
export const siwaExamplePrompts = [
"How do I add in-app wallet with sign in with google to my react app?",
"How do I add user wallet with sign in with google to my react app?",
"How do I send a transaction in Unity?",
"What does this contract revert error mean?",
"I see thirdweb support id in my console log, can you help me?",
@@ -13,6 +13,7 @@ import {
import {
THIRDWEB_BRIDGE_URL,
THIRDWEB_BUNDLER_DOMAIN,
THIRDWEB_ENGINE_CLOUD_URL,
THIRDWEB_INAPP_WALLET_DOMAIN,
THIRDWEB_INSIGHT_API_DOMAIN,
THIRDWEB_PAY_DOMAIN,
@@ -38,6 +39,7 @@ export function getConfiguredThirdwebClient(options: {
rpc: THIRDWEB_RPC_DOMAIN,
social: THIRDWEB_SOCIAL_API_DOMAIN,
storage: THIRDWEB_STORAGE_DOMAIN,
engineCloud: new URL(THIRDWEB_ENGINE_CLOUD_URL).hostname,
});
}
+3
View File
@@ -25,3 +25,6 @@ export const THIRDWEB_INSIGHT_API_DOMAIN =
export const THIRDWEB_BRIDGE_URL =
process.env.NEXT_PUBLIC_BRIDGE_URL || "bridge.thirdweb-dev.com";
export const THIRDWEB_ENGINE_CLOUD_URL =
process.env.NEXT_PUBLIC_ENGINE_CLOUD_URL || "https://engine.thirdweb-dev.com";
+10 -14
View File
@@ -330,20 +330,16 @@ export async function rotateSecretKeyClient(params: { project: Project }) {
throw new Error(res.error);
}
try {
// if the project has an encrypted vault admin key, rotate it as well
const service = params.project.services.find(
(service) => service.name === "engineCloud",
);
if (service?.encryptedAdminKey) {
await rotateVaultAccountAndAccessToken({
project: params.project,
projectSecretKey: res.data.data.secret,
projectSecretHash: res.data.data.secretHash,
});
}
} catch (error) {
console.error("Failed to rotate vault admin key", error);
// if the project has an encrypted vault admin key, rotate it as well
const service = params.project.services.find(
(service) => service.name === "engineCloud",
);
if (service?.encryptedAdminKey) {
await rotateVaultAccountAndAccessToken({
project: params.project,
projectSecretKey: res.data.data.secret,
projectSecretHash: res.data.data.secretHash,
});
}
return res.data;
@@ -177,33 +177,63 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
}) => {
const responses: WalletUser[] = [];
let page = 1;
let consecutiveFailures = 0;
const maxConsecutiveFailures = 3;
while (true) {
const res = await queryClient.fetchQuery<{
users: WalletUser[];
hasMore: boolean;
}>({
queryFn: fetchAccountList({
clientId,
ecosystemSlug,
teamId,
jwt: authToken,
pageNumber: page,
}),
queryKey: embeddedWalletsKeys.embeddedWallets(
address || "",
clientId || ecosystemSlug || "",
page,
),
});
while (consecutiveFailures < maxConsecutiveFailures) {
try {
const res = await queryClient.fetchQuery<{
users: WalletUser[];
hasMore: boolean;
}>({
queryFn: fetchAccountList({
clientId,
ecosystemSlug,
teamId,
jwt: authToken,
pageNumber: page,
}),
queryKey: embeddedWalletsKeys.embeddedWallets(
address || "",
clientId || ecosystemSlug || "",
page,
),
retry: 3,
retryDelay: (attemptIndex) =>
Math.min(1000 * 2 ** attemptIndex, 10000),
});
responses.push(...res.users);
responses.push(...res.users);
consecutiveFailures = 0; // Reset on success
if (!res.hasMore) {
break;
if (!res.hasMore) {
break;
}
page++;
} catch (error) {
consecutiveFailures++;
console.warn(
`Failed to fetch page ${page}, attempt ${consecutiveFailures}/${maxConsecutiveFailures}:`,
error,
);
if (consecutiveFailures >= maxConsecutiveFailures) {
// If we have some data, return it instead of throwing
if (responses.length > 0) {
console.warn(
`Returning partial results (${responses.length} users) after ${maxConsecutiveFailures} consecutive failures`,
);
break;
}
throw error;
}
// Wait before retrying the same page
await new Promise((resolve) =>
setTimeout(resolve, 1000 * consecutiveFailures),
);
}
page++;
}
return responses;
@@ -0,0 +1,231 @@
import { useMutation } from "@tanstack/react-query";
import type { GetBalanceResult } from "thirdweb/extensions/erc20";
import { THIRDWEB_API_HOST } from "@/constants/urls";
type WalletPortfolioToken = GetBalanceResult & {
usdValue?: number;
priceUsd?: number;
};
export type WalletPortfolioData = {
totalUsdValue: number;
tokens: WalletPortfolioToken[];
};
// Retry helper with exponential backoff - returns null on failure instead of throwing
async function fetchWithRetry(
url: string,
options: RequestInit,
maxRetries = 3,
): Promise<Response | null> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
// Retry on rate limit (429) or server errors (5xx)
if (response.status === 429 || response.status >= 500) {
const delay = Math.min(1000 * 2 ** attempt, 10000); // Max 10s delay
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
return response;
} catch (_e) {
// Network error - retry with backoff
const delay = Math.min(1000 * 2 ** attempt, 10000);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
// All retries failed - return null instead of throwing
return null;
}
// Fetch tokens for a single address on a single chain
async function fetchTokensForChain(
address: string,
chainId: number,
authToken: string,
teamId: string,
clientId?: string,
ecosystemSlug?: string,
): Promise<WalletPortfolioToken[]> {
try {
const url = new URL(`${THIRDWEB_API_HOST}/v1/wallets/${address}/tokens`);
url.searchParams.set("chainId", chainId.toString());
url.searchParams.set("limit", "50");
url.searchParams.set("metadata", "true");
url.searchParams.set("includeSpam", "false");
url.searchParams.set("includeNative", "true");
url.searchParams.set("sortBy", "usd_value");
url.searchParams.set("sortOrder", "desc");
const response = await fetchWithRetry(url.toString(), {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-thirdweb-team-id": teamId,
...(clientId && { "x-client-id": clientId }),
...(ecosystemSlug && {
"x-ecosystem-id": `ecosystem.${ecosystemSlug}`,
}),
},
});
// If all retries failed or response not ok, return empty (balance = 0)
if (!response || !response.ok) {
return [];
}
const data = await response.json();
const rawTokens = data.result?.tokens || [];
return rawTokens.map(
(t: {
balance: string;
decimals: number;
name: string;
symbol: string;
token_address: string;
chain_id: number;
price_data?: { usd_value?: number; price_usd?: number };
}): WalletPortfolioToken => ({
value: BigInt(t.balance),
decimals: t.decimals,
displayValue: (Number(t.balance) / 10 ** t.decimals).toString(),
name: t.name,
symbol: t.symbol,
tokenAddress: t.token_address,
chainId: t.chain_id,
usdValue: t.price_data?.usd_value,
priceUsd: t.price_data?.price_usd,
}),
);
} catch (e) {
// Silent fail for individual chain - continue with others
console.warn(
`Failed to fetch tokens for ${address} on chain ${chainId}:`,
e,
);
return [];
}
}
// Fetch tokens for a single address on selected chains
async function fetchWalletPortfolio(
address: string,
chainIds: number[],
authToken: string,
teamId: string,
clientId?: string,
ecosystemSlug?: string,
): Promise<WalletPortfolioData> {
// Fetch all chains concurrently for this address
const results = await Promise.all(
chainIds.map((chainId) =>
fetchTokensForChain(
address,
chainId,
authToken,
teamId,
clientId,
ecosystemSlug,
),
),
);
const allTokens = results.flat();
const totalUsdValue = allTokens.reduce(
(acc, token) => acc + (token.usdValue || 0),
0,
);
return {
totalUsdValue,
tokens: allTokens,
};
}
// Batch fetch portfolios for all addresses with progress callback
async function fetchAllPortfolios(
addresses: string[],
chainIds: number[],
authToken: string,
teamId: string,
clientId?: string,
ecosystemSlug?: string,
onProgress?: (completed: number, total: number) => void,
): Promise<Map<string, WalletPortfolioData>> {
const results = new Map<string, WalletPortfolioData>();
const concurrency = 50; // Process up to 50 addresses concurrently
let completed = 0;
let index = 0;
// Worker function that processes one address at a time
async function worker() {
while (index < addresses.length) {
const currentIndex = index++;
const address = addresses[currentIndex];
if (!address) continue;
try {
const data = await fetchWalletPortfolio(
address,
chainIds,
authToken,
teamId,
clientId,
ecosystemSlug,
);
results.set(address, data);
} catch (e) {
// Silent fail - continue with others
console.warn(`Failed to fetch portfolio for ${address}:`, e);
}
completed++;
onProgress?.(completed, addresses.length);
}
}
// Start concurrent workers
const workers = Array.from(
{ length: Math.min(concurrency, addresses.length) },
() => worker(),
);
await Promise.all(workers);
return results;
}
export function useFetchAllPortfolios() {
return useMutation({
mutationFn: async ({
addresses,
chainIds,
authToken,
teamId,
clientId,
ecosystemSlug,
onProgress,
}: {
addresses: string[];
chainIds: number[];
authToken: string;
teamId: string;
clientId?: string;
ecosystemSlug?: string;
onProgress?: (completed: number, total: number) => void;
}) => {
return fetchAllPortfolios(
addresses,
chainIds,
authToken,
teamId,
clientId,
ecosystemSlug,
onProgress,
);
},
});
}
@@ -1,21 +0,0 @@
export function ConnectSDKIcon(props: { className?: string }) {
return (
<svg
className={props.className}
fill="none"
height="24"
role="presentation"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16 8.00007V4.50073C16 3.66899 16 3.25312 15.8248 2.99755C15.6717 2.77425 15.4346 2.62257 15.1678 2.57715C14.8623 2.52517 14.4847 2.69944 13.7295 3.04799L4.85901 7.14206C4.18551 7.45291 3.84875 7.60834 3.60211 7.84939C3.38406 8.06249 3.21762 8.32262 3.1155 8.6099C3 8.93486 3 9.30575 3 10.0475V15.0001M16.5 14.5001H16.51M3 11.2001L3 17.8001C3 18.9202 3 19.4802 3.21799 19.9081C3.40973 20.2844 3.71569 20.5903 4.09202 20.7821C4.51984 21.0001 5.07989 21.0001 6.2 21.0001H17.8C18.9201 21.0001 19.4802 21.0001 19.908 20.7821C20.2843 20.5903 20.5903 20.2844 20.782 19.9081C21 19.4802 21 18.9202 21 17.8001V11.2001C21 10.08 21 9.51992 20.782 9.09209C20.5903 8.71577 20.2843 8.40981 19.908 8.21806C19.4802 8.00007 18.9201 8.00007 17.8 8.00007L6.2 8.00007C5.0799 8.00007 4.51984 8.00007 4.09202 8.21806C3.7157 8.40981 3.40973 8.71577 3.21799 9.09209C3 9.51991 3 10.08 3 11.2001ZM17 14.5001C17 14.7762 16.7761 15.0001 16.5 15.0001C16.2239 15.0001 16 14.7762 16 14.5001C16 14.2239 16.2239 14.0001 16.5 14.0001C16.7761 14.0001 17 14.2239 17 14.5001Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
}
-24
View File
@@ -1,24 +0,0 @@
export function NebulaIcon(props: { className?: string }) {
return (
<svg
className={props.className}
fill="none"
height="24"
role="presentation"
viewBox="0 0 153 153"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M76.3162 90.8232C82.4613 90.8232 87.4429 85.8416 87.4429 79.6965C87.4429 73.5514 82.4613 68.5698 76.3162 68.5698C70.171 68.5698 65.1895 73.5514 65.1895 79.6965C65.1895 85.8416 70.171 90.8232 76.3162 90.8232Z"
fill="currentColor"
/>
<path
clipRule="evenodd"
d="M91.9481 29.4892C78.7023 32.4916 63.6745 40.7425 50.4663 53.9508C46.781 57.636 43.4817 61.4629 40.5746 65.3532C44.5585 68.8492 47.0735 73.9786 47.0735 79.6955C47.0735 90.2299 38.5336 98.7698 27.9992 98.7698C27.1107 98.7698 26.2364 98.7091 25.3803 98.5915C23.3955 110.414 25.9579 119.384 31.3537 124.78C37.2202 130.646 47.3123 133.164 60.701 130.129C73.9468 127.126 88.9745 118.875 102.183 105.667C105.888 101.962 109.204 98.1131 112.122 94.2007C108.103 90.7037 105.562 85.551 105.562 79.8048C105.562 69.2704 114.102 60.7305 124.636 60.7305C125.536 60.7305 126.421 60.7928 127.288 60.9133C129.237 49.147 126.674 40.2167 121.295 34.8381C115.429 28.9716 105.337 26.4545 91.9481 29.4892ZM16.3791 94.8229C13.4047 109.493 15.9004 122.818 24.608 131.526C42.6147 149.532 80.3662 140.975 108.928 112.413C113.381 107.96 117.348 103.284 120.803 98.494C122.042 98.7466 123.324 98.8792 124.636 98.8792C135.171 98.8792 143.711 90.3393 143.711 79.8048C143.711 73.6582 140.803 68.1906 136.289 64.7023C139.234 50.0687 136.73 36.7817 128.041 28.0925C110.034 10.0859 72.2829 18.6429 43.7206 47.2051C39.2988 51.627 35.3564 56.2691 31.9179 61.024C30.6531 60.7599 29.3423 60.6211 27.9992 60.6211C17.4647 60.6211 8.9248 69.161 8.9248 79.6955C8.9248 85.8567 11.8461 91.3357 16.3791 94.8229ZM124.636 70.2677C119.369 70.2677 115.099 74.5376 115.099 79.8048C115.099 85.0721 119.369 89.342 124.636 89.342C129.904 89.342 134.174 85.0721 134.174 79.8048C134.174 74.5376 129.904 70.2677 124.636 70.2677ZM18.462 79.6955C18.462 74.4282 22.7319 70.1583 27.9992 70.1583C33.2664 70.1583 37.5363 74.4282 37.5363 79.6955C37.5363 84.9627 33.2664 89.2326 27.9992 89.2326C22.7319 89.2326 18.462 84.9627 18.462 79.6955Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
}
@@ -0,0 +1,21 @@
export function ExpoIcon(props: { className?: string }) {
return (
<svg
width="800px"
height="800px"
viewBox="0 -12.5 256 256"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
preserveAspectRatio="xMidYMid"
className={props.className}
aria-hidden="true"
>
<g>
<path
d="M121.309004,84.6732585 C123.402504,81.5874152 125.694292,81.1950171 127.553451,81.1950171 C129.41261,81.1950171 132.509843,81.5874152 134.604162,84.6732585 C151.106348,107.339593 178.345607,152.492 198.439108,185.798721 C211.542532,207.519499 221.6069,224.201947 223.671721,226.324944 C231.422996,234.294992 242.053551,229.327949 248.230809,220.287799 C254.312201,211.387762 256.000111,205.138399 256.000111,198.471155 C256.000111,193.930186 167.895315,30.0714244 159.022317,16.4322117 C150.48936,3.31359639 147.710044,0 133.105527,0 L122.176721,0 C107.615631,0 105.511479,3.31359639 96.9777022,16.4322117 C88.1055238,30.0714244 0.0001105152,193.930186 0.0001105152,198.471155 C0.0001105152,205.138399 1.68839227,211.387762 7.76991495,220.287799 C13.9471241,229.327949 24.5775965,234.294992 32.3286259,226.324944 C34.3936934,224.201947 44.4580605,207.519499 57.5616485,185.798721 C77.654822,152.492 104.806818,107.339593 121.309004,84.6732585 Z"
fill="currentColor"
/>
</g>
</svg>
);
}
@@ -0,0 +1,59 @@
export function NextjsIcon(props: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
data-testid="geist-icon"
strokeLinejoin="round"
style={{
color: "currentColor",
}}
viewBox="3 3 10 10"
aria-hidden="true"
className={props.className}
>
<g clipPath="url(#clip0_53_108)">
<path
d="M10.63 11V5"
stroke="url(#paint0_linear_53_108vsxrmxu21)"
strokeWidth={1.25}
strokeMiterlimit={Math.SQRT2}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.995 5.00087V5H4.745V11H5.995V6.96798L12.3615 14.7076C12.712 14.4793 13.0434 14.2242 13.353 13.9453L5.99527 5.00065L5.995 5.00087Z"
fill="url(#paint1_linear_53_108vsxrmxu21)"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_53_108vsxrmxu21"
x1={11.13}
y1={5}
x2={11.13}
y2={11}
gradientUnits="userSpaceOnUse"
>
<stop stopColor="currentColor" />
<stop offset={0.609375} stopColor="currentColor" stopOpacity={0.57} />
<stop offset={0.796875} stopColor="currentColor" stopOpacity={0} />
<stop offset={1} stopColor="currentColor" stopOpacity={0} />
</linearGradient>
<linearGradient
id="paint1_linear_53_108vsxrmxu21"
x1={9.9375}
y1={9.0625}
x2={13.5574}
y2={13.3992}
gradientUnits="userSpaceOnUse"
>
<stop stopColor="currentColor" />
<stop offset={1} stopColor="currentColor" stopOpacity={0} />
</linearGradient>
<clipPath id="clip0_53_108">
<rect width={16} height={16} fill="red" />
</clipPath>
</defs>
</svg>
);
}
@@ -0,0 +1,20 @@
export function NodeJSIcon(props: { className?: string }) {
return (
<svg
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
className={props.className}
>
<path
d="M17.1725 2.29872C16.4627 1.89953 15.5373 1.90132 14.8269 2.29872C11.2689 4.26227 7.71082 6.22641 4.15216 8.18906C3.45969 8.55335 2.99264 9.29698 3.00009 10.0688V21.9328C2.99509 22.7197 3.48622 23.4705 4.19655 23.8298C5.21871 24.3736 6.2118 24.9726 7.25244 25.4802C8.45451 26.0709 9.95843 26.2015 11.1752 25.5855C12.1629 25.075 12.6016 23.9395 12.6003 22.896C12.6083 18.9806 12.6016 15.0651 12.6034 11.1496C12.6269 10.9756 12.4962 10.7896 12.3064 10.7938C11.8517 10.7866 11.3964 10.7896 10.9417 10.7926C10.7699 10.7764 10.6022 10.9191 10.6152 11.0918C10.6091 14.982 10.6164 18.8734 10.6115 22.7642C10.6214 23.3024 10.2578 23.8196 9.73913 24.0014C8.5412 24.4213 5.12198 22.2012 5.12198 22.2012C4.9965 22.1431 4.91682 22.007 4.92912 21.8718C4.92912 17.9576 4.92973 14.0433 4.92912 10.1297C4.91187 9.97191 5.00912 9.8298 5.15402 9.76538C8.70033 7.8134 12.2448 5.85654 15.7911 3.90336C15.9143 3.82115 16.086 3.8214 16.2089 3.90396C19.7552 5.85654 23.3003 7.81161 26.8472 9.76368C26.9926 9.828 27.0857 9.9725 27.0709 10.1297C27.0703 14.0433 27.0721 17.9576 27.0697 21.8713C27.0802 22.0098 27.0086 22.144 26.8793 22.2048C23.3661 24.1462 19.8129 26.025 16.3315 28.0228C16.1796 28.1099 16.0075 28.2086 15.8373 28.1126C14.9218 27.6062 14.0174 27.0801 13.1049 26.5688C13.0057 26.5069 12.8794 26.4803 12.7759 26.5496C12.3668 26.7652 11.982 26.9398 11.5122 27.1258C10.8524 27.387 10.9578 27.4938 11.5529 27.8405C12.62 28.4444 13.6889 29.0459 14.756 29.6504C15.4585 30.0888 16.4024 30.12 17.1275 29.7149C20.6861 27.7538 24.2436 25.7904 27.8029 23.8293C28.5113 23.468 29.0049 22.7202 28.9999 21.9327V10.0688C29.0068 9.31264 28.5576 8.58227 27.886 8.21259C24.3156 6.23947 20.7435 4.27064 17.1725 2.29872Z"
fill="currentColor"
/>
<path
d="M22.5419 11.2062C21.1452 10.459 19.4836 10.4192 17.9315 10.5169C16.8102 10.6277 15.6309 10.9371 14.814 11.7409C13.9761 12.5489 13.7937 13.8537 14.1917 14.9085C14.4769 15.6539 15.1948 16.1386 15.9372 16.395C16.8935 16.7326 17.8979 16.837 18.9026 16.9414C19.819 17.0366 20.7357 17.1319 21.6165 17.4042C21.9763 17.5234 22.3953 17.7058 22.5055 18.0973C22.6073 18.5609 22.4957 19.0998 22.1193 19.4219C20.9237 20.3682 17.5979 20.2232 16.4166 19.4784C15.939 19.1611 15.7332 18.5994 15.6495 18.0641C15.6402 17.8973 15.5059 17.7443 15.3248 17.757C14.8713 17.7516 14.4178 17.7528 13.9643 17.7564C13.8061 17.7431 13.6416 17.8557 13.6329 18.0172C13.5397 20.4689 15.7914 21.5377 17.9039 21.773C19.1108 21.888 20.3442 21.8814 21.5327 21.6224C22.4261 21.419 23.3219 21.0444 23.9369 20.3563C24.6953 19.52 24.8444 18.2749 24.5043 17.2332C24.2443 16.4559 23.5012 15.9573 22.7416 15.7008C21.7086 15.3466 20.4844 15.1562 19.5488 15.0671C18.1889 14.9376 16.5729 14.9905 16.188 14.0969C16.0345 13.629 16.1651 13.048 16.5951 12.7602C17.7328 11.9885 20.0483 12.091 21.2265 12.6675C21.7675 12.9384 22.081 13.4948 22.2104 14.0565C22.2344 14.2215 22.3454 14.3937 22.5364 14.3865C22.9868 14.3955 23.4372 14.3889 23.8875 14.3895C24.0422 14.4003 24.2116 14.313 24.2418 14.1546C24.2227 12.9806 23.6232 11.7788 22.5419 11.2062Z"
fill="currentColor"
/>
</svg>
);
}
@@ -0,0 +1,23 @@
export function ViteIcon(props: { className?: string }) {
return (
<svg
viewBox="-0.5 0 257 257"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
preserveAspectRatio="xMidYMid"
aria-hidden="true"
className={props.className}
>
<g>
<path
d="M255.152904,37.937763 L134.896865,252.97646 C132.413943,257.416178 126.035075,257.442321 123.5149,253.02417 L0.87443175,37.9584812 C-1.87111536,33.1438084 2.24595371,27.3119153 7.70191187,28.2871109 L128.086639,49.8052023 C128.854587,49.9424525 129.640835,49.9411454 130.408783,49.8012155 L248.276014,28.3179595 C253.713738,27.3268821 257.850198,33.1136134 255.152904,37.937763 Z"
fill="currentColor"
/>
<path
d="M185.432401,0.0631038902 L96.4393502,17.500942 C94.9766549,17.7875335 93.8936852,19.0270992 93.8054529,20.5146956 L88.3311293,112.971419 C88.2023755,115.149123 90.2023075,116.839261 92.3277254,116.349082 L117.10466,110.630976 C119.422882,110.096354 121.517582,112.138114 121.041128,114.469407 L113.67994,150.515893 C113.184532,152.941955 115.462232,155.016394 117.831433,154.29681 L133.134834,149.647295 C135.507302,148.927059 137.786963,151.00738 137.285019,153.435402 L125.586724,210.056351 C124.854723,213.598061 129.565674,215.529368 131.530313,212.49287 L132.842687,210.464834 L205.359174,65.745575 C206.573511,63.3224548 204.479465,60.5594769 201.818118,61.0730542 L176.31441,65.9952397 C173.91776,66.4573155 171.878614,64.2253653 172.555061,61.8805431 L189.2009,4.17570253 C189.878001,1.82692623 187.831665,-0.406957894 185.432401,0.0631038902 Z"
fill="hsl(var(--background))"
/>
</g>
</svg>
);
}
+2 -1
View File
@@ -30,6 +30,7 @@ export function teamStub(id: string, billingPlan: Team["billingPlan"]): Team {
billingPlan: billingPlan,
billingStatus: "validPayment",
canCreatePublicChains: null,
isLegacyPlan: false,
capabilities: {
bundler: {
enabled: true,
@@ -248,7 +249,7 @@ export function teamSubscriptionsStub(
// In-App Wallets
{
amount: usage.inAppWalletAmount?.amount || 0,
description: `${usage.inAppWalletAmount?.quantity || 0} x In-App Wallets (Tier 1 at $0.00 / month)`,
description: `${usage.inAppWalletAmount?.quantity || 0} x User Wallets (Tier 1 at $0.00 / month)`,
thirdwebSku: "usage:in_app_wallet",
},
// AA Sponsorship
+49 -7
View File
@@ -7,6 +7,10 @@ export interface WalletStats {
walletType: string;
}
export type WalletStatsWithName = WalletStats & {
walletName: string;
};
export interface InAppWalletStats {
date: string;
authenticationMethod: string;
@@ -23,6 +27,8 @@ export interface UserOpStats {
successful: number;
failed: number;
sponsoredUsd: number;
gasUnits: number;
avgGasPrice: number;
chainId?: string;
}
@@ -33,13 +39,6 @@ export interface TransactionStats {
count: number;
}
export interface EngineCloudStats {
date: string;
chainId: string;
pathname: string;
totalRequests: number;
}
export interface UniversalBridgeStats {
date: string;
chainId: number;
@@ -94,3 +93,46 @@ export interface AnalyticsQueryParams {
period?: "day" | "week" | "month" | "year" | "all";
limit?: number;
}
export interface X402SettlementsOverall {
date: string;
totalRequests: number;
totalValue: number;
totalValueUSD: number;
}
export interface X402SettlementsByChainId extends X402SettlementsOverall {
chainId: string;
}
export interface X402SettlementsByPayer extends X402SettlementsOverall {
payer: string;
}
interface X402SettlementsByReceiver extends X402SettlementsOverall {
receiver: string;
}
export interface X402SettlementsByResource extends X402SettlementsOverall {
resource: string;
}
interface X402SettlementsByAsset {
date: string;
asset: string;
totalRequests: number;
totalValue: number;
totalValueUSD: number;
}
export type X402SettlementStats =
| X402SettlementsOverall
| X402SettlementsByChainId
| X402SettlementsByPayer
| X402SettlementsByReceiver
| X402SettlementsByResource
| X402SettlementsByAsset;
export interface X402QueryParams extends AnalyticsQueryParams {
groupBy?: "overall" | "chainId" | "payer" | "resource" | "asset" | "receiver";
}
+7
View File
@@ -13,9 +13,16 @@ export type ProductSKU =
| "usage:in_app_wallet"
| "usage:aa_sponsorship"
| "usage:aa_sponsorship_op_grant"
// dedicated relayer SKUs
| DedicatedRelayerSKU
| null;
export type ChainInfraSKU =
| "chain:infra:rpc"
| "chain:infra:insight"
| "chain:infra:account_abstraction";
export type DedicatedRelayerSKU =
| "product:dedicated_relayer_standard"
| "product:dedicated_relayer_premium"
| "product:dedicated_relayer_enterprise";
@@ -1,5 +1,6 @@
const compactNumberFormatter = new Intl.NumberFormat("en-US", {
notation: "compact",
maximumFractionDigits: 2,
});
export const formatTickerNumber = (value: number) => {
+5 -5
View File
@@ -1,10 +1,10 @@
const usdCurrencyFormatter = new Intl.NumberFormat("en-US", {
currency: "USD",
maximumFractionDigits: 2, // prefix with $
minimumFractionDigits: 0, // don't show decimal places if value is a whole number
notation: "compact", // at max 2 decimal places
roundingMode: "halfEven", // round to nearest even number, standard practice for financial calculations
style: "currency", // shows 1.2M instead of 1,200,000, 1.2B instead of 1,200,000,000
maximumFractionDigits: 2,
minimumFractionDigits: 0,
notation: "compact",
roundingMode: "halfEven",
style: "currency",
});
export const toUSD = (value: number) => {
+1 -1
View File
@@ -23,7 +23,7 @@ export const TEAM_PLANS: Record<
features: [
"Email Support",
"48hr Guaranteed Response",
"Custom In-App Wallet Auth",
"Custom User Wallet Auth",
],
price: 99,
subTitle: "Everything in Starter, plus:",
@@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { Turnstile } from "@marsidev/react-turnstile";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { CanClaimResponseType } from "app/(app)/api/testnet-faucet/can-claim/CanClaimResponseType";
import { ArrowUpRightIcon } from "lucide-react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useForm } from "react-hook-form";
@@ -192,6 +193,20 @@ export function FaucetButton({
// Can not claim
if (canClaimFaucetQuery.data && canClaimFaucetQuery.data.canClaim === false) {
if (canClaimFaucetQuery.data.type === "paid-plan-required") {
return (
<Button
asChild
className="w-full gap-2 whitespace-pre-wrap h-auto min-h-10 text-center bg-background"
variant="outline"
>
<Link href="/team/~/~/billing">
Faucet is not available on Free plan. Upgrade your plan to continue
<ArrowUpRightIcon className="size-4 shrink-0 text-muted-foreground" />
</Link>
</Button>
);
}
return (
<Button className="!opacity-100 w-full " disabled variant="outline">
{canClaimFaucetQuery.data.type === "throttle" && (
@@ -203,10 +218,6 @@ export function FaucetButton({
{canClaimFaucetQuery.data.type === "unsupported-chain" &&
"Faucet is empty right now"}
{/* TODO: add an upsell path here to subscribe to one of these plans */}
{canClaimFaucetQuery.data.type === "paid-plan-required" &&
"Faucet is only available on Starter, Growth, Scale and Pro plans."}
</Button>
);
}
@@ -1,55 +1,112 @@
import { BotIcon, CodeIcon, CoinsIcon } from "lucide-react";
import Link from "next/link";
import type { ChainMetadataWithServices } from "@/types/chain";
import { products } from "../../../../components/server/products";
import { useMemo } from "react";
import { BridgeIcon } from "@/icons/BridgeIcon";
import { PayIcon } from "@/icons/PayIcon";
import { WalletProductIcon } from "@/icons/WalletProductIcon";
import type {
ChainMetadataWithServices,
ChainSupportedService,
} from "@/types/chain";
import { SectionTitle } from "./SectionTitle";
export function SupportedProductsSection(props: {
services: ChainMetadataWithServices["services"];
}) {
const enabledProducts = products.filter((product) => {
return props.services.find(
(service) => service.service === product.id && service.enabled,
const enabledServices = useMemo(() => {
return props.services.reduce(
(acc, service) => {
acc[service.service] = service.enabled;
return acc;
},
{} as Record<ChainSupportedService, boolean>,
);
});
if (enabledProducts.length === 0) {
return null;
}
}, [props.services]);
return (
<section>
<SectionTitle title="thirdweb Products" />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
{enabledProducts.map((product) => {
return (
<div
className="relative rounded-xl border bg-card p-4 hover:border-active-border"
key={product.id}
>
<div className="flex mb-4">
<div className="p-2 rounded-full border bg-background">
<product.icon className="size-4 text-muted-foreground" />
</div>
</div>
<div>
<h3 className="mb-1">
<Link
className="before:absolute before:inset-0 text-base font-medium"
href={product.link}
rel="noopener noreferrer"
target="_blank"
>
{product.name}
</Link>
</h3>
<p className="text-muted-foreground text-sm">
{product.description}
</p>
</div>
</div>
);
})}
{enabledServices["connect-sdk"] && (
<ProductCard
icon={WalletProductIcon}
name="Wallets"
description="Create wallets to read and transact"
link="https://thirdweb.com/wallets"
/>
)}
{enabledServices["account-abstraction"] && (
<ProductCard
icon={PayIcon}
name="x402"
description="Create internet native payments with x402"
link="https://thirdweb.com/x402"
/>
)}
{enabledServices.pay && (
<ProductCard
icon={BridgeIcon}
name="Bridge"
description="Bridge and swap tokens across chains"
link="https://thirdweb.com/monetize/bridge"
/>
)}
{enabledServices.contracts && (
<ProductCard
icon={CoinsIcon}
name="Tokens"
description="Launch tokens and markets"
link="https://thirdweb.com/token"
/>
)}
<ProductCard
icon={BotIcon}
name="AI"
description="Read and write onchain via natural language"
link="https://thirdweb.com/ai"
/>
<ProductCard
icon={CodeIcon}
name="HTTP API"
description="Build products with our HTTP API"
link="https://portal.thirdweb.com/reference"
/>
</div>
</section>
);
}
function ProductCard(props: {
icon: React.FC<{ className?: string }>;
link: string;
name: string;
description: string;
}) {
return (
<div className="relative rounded-xl border bg-card p-4 hover:border-active-border">
<div className="flex mb-4">
<div className="p-2 rounded-full border bg-background">
<props.icon className="size-4 text-muted-foreground" />
</div>
</div>
<div>
<h3 className="mb-1">
<Link
className="before:absolute before:inset-0 text-base font-medium"
href={props.link}
rel="noopener noreferrer"
target="_blank"
>
{props.name}
</Link>
</h3>
<p className="text-muted-foreground text-sm">{props.description}</p>
</div>
</div>
);
}
@@ -2,6 +2,7 @@ import type { ThirdwebContract } from "thirdweb";
import { contractType as getContractType } from "thirdweb/extensions/thirdweb";
import { getDeployedEntrypointERC20 } from "thirdweb/tokens";
import { resolveFunctionSelectors } from "@/lib/selectors";
import { supportedERCs } from "@/utils/supportedERCs";
import { getValidReward } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/rewards";
import {
isERC20ClaimConditionsSupported,
@@ -21,7 +22,6 @@ import {
isPermissionsEnumerableSupported,
isPermissionsSupported,
} from "./detectedFeatures/permissions";
import { supportedERCs } from "./detectedFeatures/supportedERCs";
import { type EmbedTypeToShow, getEmbedTypeToShow } from "./getEmbedTypeToShow";
type ContractPageMetadata = {
@@ -1,9 +1,9 @@
import { supportedERCs } from "@/utils/supportedERCs";
import { isClaimConditionsSupported } from "./detectedFeatures/claimConditions";
import {
isDirectListingSupported,
isEnglishAuctionSupported,
} from "./detectedFeatures/marketplace";
import { supportedERCs } from "./detectedFeatures/supportedERCs";
export type EmbedTypeToShow =
| "marketplace-v3"
@@ -2,7 +2,7 @@ import type { ThirdwebContract } from "thirdweb";
import { isGetNFTsSupported as ERC721_isGetNFTsSupported } from "thirdweb/extensions/erc721";
import { isGetNFTsSupported as ERC1155_isGetNFTsSupported } from "thirdweb/extensions/erc1155";
import { resolveFunctionSelectors } from "@/lib/selectors";
import { supportedERCs } from "./detectedFeatures/supportedERCs";
import { supportedERCs } from "@/utils/supportedERCs";
export type NewPublicPageType = "erc20" | "erc1155" | "erc721";
@@ -22,7 +22,7 @@ export function AccountsPage(props: {
View list of smart accounts that have been created for this
contract.{" "}
<UnderlineLink
href="https://portal.thirdweb.com/transactions/sponsor"
href="https://portal.thirdweb.com/wallets/sponsor-gas"
target="_blank"
rel="noopener noreferrer"
>
@@ -283,7 +283,7 @@ function TokenInfoSection(props: {
<dt className="text-xs font-medium text-muted-foreground tracking-wider uppercase">
{props.label}
</dt>
<dd className="text-3xl font-bold text-foreground tracking-tight">
<dd className="text-3xl font-semibold text-foreground tracking-tight">
{props.value}
</dd>
</dl>
@@ -7,6 +7,7 @@ import { getTeams } from "@/api/team/get-team";
import type { MinimalTeamsAndProjects } from "@/components/contracts/import-contract/types";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { resolveFunctionSelectors } from "@/lib/selectors";
import { supportedERCs } from "@/utils/supportedERCs";
import { shortenIfAddress } from "@/utils/usedapp-external";
import type { ProjectMeta } from "../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
import { TeamHeader } from "../../../../team/components/TeamHeader/team-header";
@@ -14,7 +15,6 @@ import { ConfigureCustomChain } from "./_layout/ConfigureCustomChain";
import { getContractMetadataHeaderData } from "./_layout/contract-metadata";
import { ContractPageLayout } from "./_layout/contract-page-layout";
import { ContractPageLayoutClient } from "./_layout/contract-page-layout.client";
import { supportedERCs } from "./_utils/detectedFeatures/supportedERCs";
import { getContractPageParamsInfo } from "./_utils/getContractFromParams";
import { getContractPageMetadata } from "./_utils/getContractPageMetadata";
import { getContractPageSidebarLinks } from "./_utils/getContractPageSidebarLinks";
@@ -15,7 +15,7 @@ import {
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Separator } from "@/components/ui/separator";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { products } from "../../../components/server/products";
import { services } from "../../../components/server/products";
function cleanUrl(url: string) {
if (url.endsWith("?")) {
@@ -345,7 +345,7 @@ export const ChainServiceFilter: React.FC<ChainServiceFilterProps> = ({
const section = (
<FilterSection title="Services">
{products.map((product) => (
{services.map((product) => (
<div className="group flex items-center gap-2" key={product.id}>
<Checkbox
checked={isServiceActive(mutableSearchParams, product.id)}
@@ -393,7 +393,7 @@ export const ChainServiceFilter: React.FC<ChainServiceFilterProps> = ({
}
const firstFilter = allFilters[0];
const name = products.find((p) => p.id === firstFilter)?.name;
const name = services.find((p) => p.id === firstFilter)?.name;
const plus = allFilters.length > 1 ? ` +${allFilters.length - 1}` : "";
return [`${name}${plus}`, true];
@@ -12,7 +12,7 @@ import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type { ChainSupportedService } from "@/types/chain";
import { ChainIcon } from "../../../components/server/chain-icon";
import { products } from "../../../components/server/products";
import { services } from "../../../components/server/products";
import { getCustomChainMetadata } from "../../../utils";
type ChainListRowProps = {
@@ -83,7 +83,7 @@ export function ChainListRow({
<TableCell>
<div className="flex w-[520px] flex-row items-center gap-14 ">
<div className="z-10 flex items-center gap-4">
{products.map((p) => {
{services.map((p) => {
return (
<ProductIcon
href={p.link}
@@ -1,74 +1,69 @@
import { ConnectSDKIcon } from "@/icons/ConnectSDKIcon";
import { ContractIcon } from "@/icons/ContractIcon";
import { EngineIcon } from "@/icons/EngineIcon";
import { BotIcon, CoinsIcon, FuelIcon, HardDriveIcon } from "lucide-react";
import { BridgeIcon } from "@/icons/BridgeIcon";
import { InsightIcon } from "@/icons/InsightIcon";
import { NebulaIcon } from "@/icons/NebulaIcon";
import { PayIcon } from "@/icons/PayIcon";
import { RPCIcon } from "@/icons/RPCIcon";
import { SmartAccountIcon } from "@/icons/SmartAccountIcon";
import { WalletProductIcon } from "@/icons/WalletProductIcon";
import type { ChainSupportedService } from "@/types/chain";
export const products = [
export const services = [
{
description: "Create, deploy and manage smart contracts",
icon: ContractIcon,
icon: CoinsIcon,
id: "contracts",
link: "https://thirdweb.com/explore",
name: "Contracts",
link: "https://thirdweb.com/token",
name: "Tokens",
},
{
description: "Create and manage crypto wallets",
icon: ConnectSDKIcon,
icon: WalletProductIcon,
id: "connect-sdk",
link: "https://thirdweb.com/connect",
name: "Wallets",
link: "https://thirdweb.com/wallets",
name: "User Wallets",
},
{
description: "Performant and scalable RPC service",
icon: RPCIcon,
id: "rpc-edge",
link: "https://portal.thirdweb.com/infrastructure/rpc-edge/overview",
name: "RPC Edge",
link: "https://thirdweb.com/rpc",
name: "RPC",
},
{
description: "Backend server that reads, writes, and deploys contracts",
icon: EngineIcon,
icon: HardDriveIcon,
id: "engine",
link: "https://thirdweb.com/engine",
name: "Transactions",
link: "https://thirdweb.com/wallets",
name: "Server Wallets",
},
{
description: "Enable gas sponsorship for seamless transactions",
icon: SmartAccountIcon,
icon: FuelIcon,
id: "account-abstraction",
link: "https://portal.thirdweb.com/wallets/sponsor-gas",
name: "Account Abstraction",
link: "https://thirdweb.com/wallets",
name: "Gas Sponsorship",
},
{
description: "Enable payments in any token on any chain",
icon: PayIcon,
icon: BridgeIcon,
id: "pay",
link: "https://portal.thirdweb.com/payments",
name: "Payments",
link: "https://thirdweb.com/monetize/bridge",
name: "Bridge",
},
{
description: "Query and analyze blockchain data",
icon: InsightIcon,
id: "insight",
link: "https://thirdweb.com/insight",
name: "Insight",
},
{
description: "The most powerful AI for interacting with the blockchain",
icon: NebulaIcon,
icon: BotIcon,
id: "nebula",
link: "https://thirdweb.com/ai",
name: "thirdweb AI",
name: "AI",
},
{
icon: PayIcon,
id: "account-abstraction",
link: "https://thirdweb.com/x402",
name: "x402",
},
{
icon: InsightIcon,
id: "insight",
link: "https://thirdweb.com/gateway",
name: "Indexer",
},
] satisfies Array<{
name: string;
id: ChainSupportedService;
icon: React.FC<{ className?: string }>;
description: string;
link: string;
}>;
@@ -285,6 +285,11 @@ const chainMetaRecord = {
"Lisk is focused on serving builders in high-growth markets like Africa and Southeast Asia. When you build on Lisk, you become part of the strongest Ethereum collective, alongside Coinbase, Sony, World, Kraken and Uniswap -- the OP Superchain.",
headerImgUrl: liskBanner.src,
},
//Sei Mainnet
1329: {
about:
"Sei is a blockchain designed for fast, cheap financial transactions, combining the network effects of Ethereum with the performance of Solana. Sei has processed more than four billion transactions across more than 80 million wallets and has become the #1 EVM chain by number of active users. The team is backed by top investors such as Multicoin, Jump, Coinbase Ventures, and Circle Ventures – and includes talent from companies like Robinhood, Google, Coinbase, Databricks, Uber, and Goldman Sachs.",
},
//Reactive Mainnet
1597: {
about:
@@ -72,7 +72,6 @@ export const DecodedInputSet: React.FC<DecodedInputSetProps> = ({
<div className="mt-6 flex justify-start">
<Button
className="gap-2"
disabled={param.type === "bytes" && fields.length >= 1}
onClick={() =>
append({
defaultValue: "",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

@@ -1,130 +0,0 @@
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { Suspense } from "react";
import {
ContractCard,
ContractCardSkeleton,
} from "@/components/contracts/contract-card/contract-card";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { DeployUpsellCard } from "../components/upsells/deploy-your-own";
import { getCategory } from "../data";
type ExploreCategoryPageProps = {
params: Promise<{
category: string;
}>;
};
export async function generateMetadata(
props: ExploreCategoryPageProps,
): Promise<Metadata> {
const params = await props.params;
const category = getCategory(params.category);
if (!category) {
notFound();
}
return {
description: `${category.description} Deploy with one click to Ethereum, Polygon, Optimism, and other EVM blockchains with thirdweb.`,
title: `${category.name} Smart Contracts | Explore`,
};
}
export default async function ExploreCategoryPage(
props: ExploreCategoryPageProps,
) {
const params = await props.params;
const category = getCategory(params.category);
if (!category) {
notFound();
}
return (
<div className="flex flex-col">
<Breadcrumb className="border-border border-b px-6 py-4">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href="/explore">Explore</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>
{category.displayName || category.name}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="container flex flex-col gap-4 py-8">
<h1 className="mb-3 font-bold text-3xl tracking-tighter lg:text-5xl">
{category.displayName || category.name}
</h1>
<p className="max-w-screen-md text-base text-muted-foreground lg:text-lg">
{category.description}{" "}
{category.learnMore && (
<Link href={category.learnMore}>Learn more</Link>
)}
</p>
<div className="h-10" />
<div className="grid grid-cols-1 gap-5 md:grid-cols-3">
{category.contracts.map((publishedContractId) => {
const publisher: string | undefined = Array.isArray(
publishedContractId,
)
? publishedContractId[0].split("/")[0]
: publishedContractId.split("/")[0];
const contractId: string | undefined = Array.isArray(
publishedContractId,
)
? publishedContractId[0].split("/")[1]
: publishedContractId.split("/")[1];
const modules = Array.isArray(publishedContractId)
? publishedContractId[1]
: undefined;
const overrides = Array.isArray(publishedContractId)
? publishedContractId[2]
: undefined;
if (!publisher || !contractId) {
return null;
}
return (
<Suspense
fallback={<ContractCardSkeleton />}
key={publisher + contractId + overrides?.title}
>
<ContractCard
contractId={contractId}
descriptionOverride={overrides?.description}
isBeta={category.isBeta}
modules={
modules?.length
? modules.map((m) => ({
moduleId: m.split("/")[1] || "",
publisher: m.split("/")[0] || "",
}))
: undefined
}
publisher={publisher}
titleOverride={overrides?.title}
/>
</Suspense>
);
})}
</div>
<div className="h-16" />
<DeployUpsellCard />
</div>
</div>
);
}
@@ -1,4 +1,3 @@
import { ArrowRightIcon } from "lucide-react";
import Link from "next/link";
import { Suspense } from "react";
import {
@@ -7,48 +6,45 @@ import {
} from "@/components/contracts/contract-card/contract-card";
import type { ExploreCategory } from "../../data";
export function ContractRow({ category }: { category: ExploreCategory }) {
export function ContractRow({
category,
prefixCard,
}: {
category: ExploreCategory;
prefixCard?: React.ReactNode;
}) {
return (
<section>
{/* Title, Description + View all link */}
<div className="flex items-center justify-between gap-4">
<header className="flex flex-col gap-1.5">
<Link href={`/explore/${category.id}`}>
<h2 className="font-semibold text-2xl tracking-tight">
{category.displayName || category.name}
</h2>
</Link>
<header className="flex flex-col gap-1">
<h2 className="font-semibold text-2xl tracking-tight">
{category.displayName || category.name}
</h2>
<p className="text-muted-foreground">
{category.description}{" "}
{category.learnMore && (
<Link
className="inline text-link-foreground"
href={category.learnMore}
rel="noopener noreferrer"
target="_blank"
>
Learn more
</Link>
)}
</p>
{(category.description || category.learnMore) && (
<p className="text-muted-foreground">
{category.description}{" "}
{category.learnMore && (
<Link
className="inline text-link-foreground"
href={category.learnMore}
rel="noopener noreferrer"
target="_blank"
>
Learn more
</Link>
)}
</p>
)}
</header>
{category.contracts.length > 6 && (
<Link
className="flex shrink-0 items-center gap-1 text-base text-link-foreground hover:text-foreground"
href={`/explore/${category.id}`}
>
View all
<ArrowRightIcon className="size-4" />
</Link>
)}
</div>
<div className="h-5" />
<div className="relative z-0 grid grid-cols-1 gap-4 md:grid-cols-3">
{category.contracts.slice(0, 6).map((publishedContractId) => {
{prefixCard}
{category.contracts.map((publishedContractId) => {
const publisher: string | undefined = Array.isArray(
publishedContractId,
)
@@ -0,0 +1,103 @@
"use client";
import { RocketIcon, ShieldCheckIcon } from "lucide-react";
import Link from "next/link";
import { reportTokenUpsellClicked } from "@/analytics/report";
import { Img } from "@/components/blocks/Img";
import { Button } from "@/components/ui/button";
const thirdwebEns = {
name: "thirdweb.eth",
image: "https://euc.li/thirdweb.eth",
};
function TokenContractCard(props: {
name: string;
description: string;
link: string;
trackingType: "nft" | "coin";
}) {
return (
<article className="relative flex min-h-[220px] flex-col rounded-lg border border-border bg-card p-4 hover:border-active-border">
<div className="flex items-center gap-1.5">
<div className="flex items-center gap-1 font-medium text-sm text-success-text">
<ShieldCheckIcon className="size-4" />
Audited
</div>
</div>
<div className="h-3.5" />
{/* Title */}
<h3 className="font-semibold text-lg tracking-tight">
<Link
className="cursor-pointer before:absolute before:inset-0 before:z-0"
href={props.link}
onClick={() => {
reportTokenUpsellClicked({
assetType: props.trackingType,
pageType: "explore",
});
}}
>
{props.name}
</Link>
</h3>
<p className="mt-1 text-muted-foreground text-sm leading-5">
{props.description}
</p>
<div className="mt-auto relative z-1 flex justify-between gap-2 pt-3">
<Link
className="flex shrink-0 items-center gap-1.5 hover:underline"
href={`/${thirdwebEns.name}`}
target="_blank"
>
<Img
className="size-5 rounded-full object-cover"
src={thirdwebEns.image}
/>
<span className="text-xs"> {thirdwebEns.name}</span>
</Link>
<div className="flex items-center justify-between">
<Button
asChild
className="relative z-10 h-auto gap-1.5 px-2.5 py-1.5 text-xs hover:bg-inverted hover:text-inverted-foreground"
size="sm"
variant="outline"
>
<Link href={props.link}>
<RocketIcon className="size-3" />
Deploy
</Link>
</Button>
</div>
</div>
</article>
);
}
export function NFTCollectionCard() {
return (
<TokenContractCard
name="NFT Collection"
description="Launch an ERC-721 or ERC-1155 NFT collection"
link="/team/~/~project/tokens/create/nft"
trackingType="nft"
/>
);
}
export function CoinCard() {
return (
<TokenContractCard
name="Coin"
description="Launch an ERC-20 token to create a cryptocurrency"
link="/team/~/~project/tokens/create/token"
trackingType="coin"
/>
);
}
@@ -5,31 +5,12 @@ import { PlainTextCodeBlock } from "@/components/ui/code/plaintext-code";
export const DeployUpsellCard: React.FC = () => {
return (
<section className="rounded-xl border border-border px-4 py-6 shadow-lg md:px-8 md:py-14">
<h3 className="font-bold text-2xl tracking-tight">
<h3 className="font-semibold text-2xl tracking-tight">
Didn't find what you're looking for? Build your own!
</h3>
<div className="h-8" />
<p className="text-muted-foreground ">
Create a contract with a single command.{" "}
<Link
className="inline-flex items-center gap-1.5 text-link-foreground"
href="https://portal.thirdweb.com/contracts/build/overview"
rel="noopener noreferrer"
target="_blank"
>
Learn more about the Solidity SDK
<ExternalLinkIcon className="size-4" />
</Link>
</p>
<div className="h-2" />
<PlainTextCodeBlock code="npx thirdweb create contract" />
<div className="h-8" />
<p className="text-muted-foreground ">
Deploy a contract with a single command.{" "}
<Link
@@ -1,70 +0,0 @@
import Image from "next/image";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import exploreFeatureImage from "../../../../../../../public/assets/landingpage/explore-featured.png";
import heroIcon1 from "../../../../../../../public/assets/product-pages/publish/hero-icon-1.png";
import heroIcon2 from "../../../../../../../public/assets/product-pages/publish/hero-icon-2.png";
export const PublishUpsellCard: React.FC = () => {
return (
<div className="flex gap-10 rounded-xl border border-border bg-card p-8 shadow-lg md:p-10">
<div className="flex flex-col gap-6">
<h2 className="font-bold text-3xl tracking-tighter">
Accelerate your protocol's growth
</h2>
<p className="text-muted-foreground">
Publishing your contract is the best way to get your contracts in
front of our 70k+ community of web3 developers.
</p>
<div className="flex gap-2">
<Image alt="" className="hidden size-6 md:block" src={heroIcon1} />
<p className="text-muted-foreground">
<span className="font-semibold text-foreground">
Save development time.{" "}
</span>
Focus on protocol development and save time by not having to build
middleware layer yourself.
</p>
</div>
<div className="flex gap-2">
<Image alt="" className="hidden size-6 md:block" src={heroIcon2} />
<p className="text-muted-foreground">
<span className="font-semibold text-foreground">
Shareable landing page.{" "}
</span>
By publishing your contract, your contracts become easily shareable
with a landing page for your contract.
</p>
</div>
<div className="mt-auto flex gap-2 pt-4">
<Button asChild>
<Link href="/contact-us" rel="noopener noreferrer" target="_blank">
Get In Touch
</Link>
</Button>
<Button asChild variant="outline">
<Link
href="https://portal.thirdweb.com/contracts/publish/overview"
rel="noopener noreferrer"
target="_blank"
>
Learn More
</Link>
</Button>
</div>
</div>
<Image
alt=""
className="hidden w-[40%] grayscale invert md:block dark:filter-none"
draggable={false}
src={exploreFeatureImage}
/>
</div>
);
};
@@ -1,151 +1,55 @@
type PublishedContractID = `${string}/${string}`;
export interface ExploreCategory {
id: string;
name: string;
displayName?: string;
description: string;
description: string | undefined;
learnMore?: string;
contracts:
| Array<PublishedContractID>
| Array<
[
PublishedContractID,
Array<PublishedContractID>,
{ title: string; description: string },
]
>;
showInExplore?: boolean;
contracts: Array<
| PublishedContractID
| [
PublishedContractID,
Array<PublishedContractID>,
{ title: string; description: string },
]
>;
isBeta?: boolean;
}
const POPULAR = {
export const NFTS = {
contracts: [
"thirdweb.eth/DropERC721",
"thirdweb.eth/MarketplaceV3",
"thirdweb.eth/AccountFactory",
"0xaF3202F6bAEbA50d37e0d4B0b870455EDF198D7c/BurnMintERC677",
"thirdweb.eth/DropERC1155",
"thirdweb.eth/TokenERC20",
"thirdweb.eth/NFTStake",
"unlock-protocol.eth/PublicLock",
],
description: "A collection of our most deployed contracts.",
id: "popular",
name: "Popular",
} satisfies ExploreCategory;
const NFTS = {
contracts: [
"thirdweb.eth/DropERC721",
"thirdweb.eth/OpenEditionERC721",
"thirdweb.eth/TokenERC721",
"thirdweb.eth/DropERC1155",
"thirdweb.eth/TokenERC1155",
"flairsdk.eth/ERC721CommunityStream",
"thirdweb.eth/LoyaltyCard",
"doubledev.eth/ERC4907",
"thirdweb.eth/Multiwrap",
"kronickatz.eth/ERC721NESDrop",
],
description:
"NFT Collections, Editions, Drops and everything else NFT-related.",
displayName: "NFTs",
id: "nft",
name: "NFT",
} satisfies ExploreCategory;
const GOVERNANCE = {
contracts: [
"thirdweb.eth/VoteERC20",
"thirdweb.eth/TokenERC20",
"thirdweb.eth/Split",
],
export const GOVERNANCE = {
contracts: ["thirdweb.eth/VoteERC20", "thirdweb.eth/Split"],
description: "Create your own DAO, vote on proposals, and manage a treasury.",
id: "daos-governance",
name: "DAOs & Governance",
} satisfies ExploreCategory;
const DROPS = {
contracts: [
"thirdweb.eth/DropERC721",
"thirdweb.eth/DropERC1155",
"thirdweb.eth/DropERC20",
],
description: "Release NFTs and Tokens based on preset Claim Conditions.",
displayName: "Drops",
id: "drops",
name: "Drop",
} satisfies ExploreCategory;
const MARKETS = {
contracts: [
"thirdweb.eth/MarketplaceV3",
"thirdweb.eth/TokenERC20",
"thirdweb.eth/Split",
],
export const MARKETS = {
contracts: ["thirdweb.eth/MarketplaceV3", "thirdweb.eth/Split"],
description: "Quickly spin up your own on-chain marketplace for NFTs.",
displayName: "Marketplaces",
id: "marketplace",
name: "Marketplace",
} satisfies ExploreCategory;
const AIRDROP = {
export const AIRDROP = {
contracts: ["thirdweb.eth/Airdrop"],
description:
"Efficiently transfer large numbers of on-chain assets to a large number of recipients.",
displayName: "Airdrops",
id: "airdrop",
name: "Airdrop",
} satisfies ExploreCategory;
const GAMING = {
contracts: [
"thirdweb.eth/MarketplaceV3",
"thirdweb.eth/DropERC721",
"thirdweb.eth/TokenERC20",
"thirdweb.eth/TokenERC1155",
"thirdweb.eth/Multiwrap",
"thirdweb.eth/NFTStake",
],
description:
"A collection of contracts that are popular for building play-to-earn and free-to-own web3 games.",
displayName: "Gaming",
id: "gaming",
name: "Gaming",
showInExplore: false,
} satisfies ExploreCategory;
const LOYALTY = {
contracts: [
"thirdweb.eth/LoyaltyCard",
"thirdweb.eth/MarketplaceV3",
"thirdweb.eth/TokenERC20",
],
description:
"A collection of contracts that are popular for building loyalty programs.",
displayName: "Loyalty",
id: "loyalty",
name: "Loyalty",
showInExplore: false,
} satisfies ExploreCategory;
const COMMERCE = {
contracts: [
"thirdweb.eth/TokenERC721",
"thirdweb.eth/TokenERC1155",
"thirdweb.eth/TokenERC20",
"thirdweb.eth/Split",
],
description:
"Most popular contracts for building web3 commerce apps. Reward loyal customers and sell NFTs through your storefront.",
displayName: "Commerce",
id: "commerce",
name: "Commerce",
showInExplore: false,
} satisfies ExploreCategory;
const STAKING = {
export const STAKING = {
contracts: [
"thirdweb.eth/NFTStake",
"thirdweb.eth/EditionStake",
@@ -153,12 +57,10 @@ const STAKING = {
],
description: "Stake your NFTs or tokens to earn ERC20 tokens in return.",
displayName: "Staking",
id: "staking",
name: "Staking",
showInExplore: true,
} satisfies ExploreCategory;
const SMART_WALLET = {
export const SMART_WALLET = {
contracts: [
"thirdweb.eth/AccountFactory",
"thirdweb.eth/ManagedAccountFactory",
@@ -168,30 +70,16 @@ const SMART_WALLET = {
description:
"Account factories that let you spin up Account Abstraction (ERC-4337) wallets for your users. Not sure which factory is right for you?",
displayName: "Account Abstraction",
id: "smart-wallet",
learnMore:
"https://portal.thirdweb.com/wallets/smart-wallet/get-started#1-deploy-a-smart-wallet-factory-contract",
name: "Account Abstraction",
showInExplore: true,
} satisfies ExploreCategory;
const STYLUS = {
export const STYLUS = {
contracts: [
"0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusAirdropERC1155",
"0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusAirdropERC721",
"0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusAirdropERC20",
],
description:
"Airdrop your NFTs or tokens to a large number of recipients. Built with Arbitrum Stylus.",
displayName: "Airdrop - Arbitrum Stylus",
id: "stylus",
name: "Stylus",
showInExplore: true,
isBeta: true,
} satisfies ExploreCategory;
const MODULAR_CONTRACTS_STYLUS = {
contracts: [
// erc721 token
[
"thirdweb.eth/ERC721CoreInitializable",
@@ -232,44 +120,8 @@ const MODULAR_CONTRACTS_STYLUS = {
},
],
],
description:
"Collection of highly customizable and upgradeable smart contracts built with the modular contracts framework.",
displayName: "Modular Contracts - Arbitrum Stylus",
id: "modular-contracts",
name: "modular",
showInExplore: true,
description: undefined,
displayName: "Arbitrum Stylus",
name: "Stylus",
isBeta: true,
} satisfies ExploreCategory;
const CATEGORIES: Record<string, ExploreCategory> = {
[POPULAR.id]: POPULAR,
[NFTS.id]: NFTS,
[MARKETS.id]: MARKETS,
[STYLUS.id]: STYLUS,
[DROPS.id]: DROPS,
[SMART_WALLET.id]: SMART_WALLET,
[AIRDROP.id]: AIRDROP,
[GAMING.id]: GAMING,
[LOYALTY.id]: LOYALTY,
[COMMERCE.id]: COMMERCE,
[STAKING.id]: STAKING,
[GOVERNANCE.id]: GOVERNANCE,
[MODULAR_CONTRACTS_STYLUS.id]: MODULAR_CONTRACTS_STYLUS,
};
export function getCategory(id: string) {
if (isExploreCategory(id)) {
return CATEGORIES[id];
}
return null;
}
type ExploreCategoryName = keyof typeof CATEGORIES;
function isExploreCategory(category: string): category is ExploreCategoryName {
return category in CATEGORIES;
}
export const EXPLORE_PAGE_DATA = Object.values(CATEGORIES).filter((v) =>
"showInExplore" in v ? v.showInExplore !== false : true,
);
@@ -1,14 +1,20 @@
import type { Metadata } from "next";
import { Fragment } from "react";
import { ContractRow } from "./components/contract-row";
import { CoinCard, NFTCollectionCard } from "./components/token-cards";
import { DeployUpsellCard } from "./components/upsells/deploy-your-own";
import { PublishUpsellCard } from "./components/upsells/publish-submit";
import { EXPLORE_PAGE_DATA } from "./data";
import { TokensSection } from "./tokens-section";
import {
AIRDROP,
GOVERNANCE,
MARKETS,
NFTS,
SMART_WALLET,
STAKING,
STYLUS,
} from "./data";
const title = "List of smart contracts for EVM Developers";
const description =
"A list of Ethereum smart contract templates for web3 developers, including the most popular evm smart contracts for dapps, NFTs and more.";
"A list of smart contract templates for web3 developers, including the most popular evm smart contracts for dapps, NFTs and more.";
export const metadata: Metadata = {
description,
@@ -22,9 +28,9 @@ export const metadata: Metadata = {
export default async function ExplorePage() {
return (
<div className="flex flex-col">
<div className="border-b py-10">
<div className="border-b py-12">
<div className="container max-w-7xl">
<h1 className="mb-2 font-bold text-4xl lg:text-5xl tracking-tight">
<h1 className="mb-2 font-semibold text-4xl lg:text-5xl tracking-tight">
Explore
</h1>
<p className="max-w-screen-sm text-sm lg:text-base text-muted-foreground">
@@ -35,24 +41,34 @@ export default async function ExplorePage() {
</div>
</div>
<div className="container max-w-7xl pt-8 pb-4">
<TokensSection />
</div>
<div className="container max-w-7xl flex flex-col gap-8 py-10">
<div className="container max-w-7xl flex flex-col py-10">
<div className="flex flex-col gap-14">
{EXPLORE_PAGE_DATA.map((category, idx) => (
<Fragment key={category.id}>
{Math.floor(EXPLORE_PAGE_DATA.length / 2) === idx && (
<PublishUpsellCard />
)}
<ContractRow category={category} />
</Fragment>
))}
<PopularSection />
<ContractRow category={MARKETS} />
<ContractRow category={NFTS} prefixCard={<NFTCollectionCard />} />
<ContractRow category={SMART_WALLET} />
<ContractRow category={AIRDROP} />
<ContractRow category={STAKING} />
<ContractRow category={GOVERNANCE} />
<ContractRow category={STYLUS} />
<DeployUpsellCard />
</div>
<DeployUpsellCard />
</div>
</div>
);
}
function PopularSection() {
return (
<section>
<h2 className="font-semibold text-2xl tracking-tight mb-1">Popular</h2>
<p className="text-muted-foreground mb-4">
A collection of our most deployed contracts.
</p>
<div className="relative z-0 grid grid-cols-1 gap-4 md:grid-cols-3">
<CoinCard />
<NFTCollectionCard />
</div>
</section>
);
}
@@ -1,111 +0,0 @@
"use client";
import { ZapIcon } from "lucide-react";
import Link from "next/link";
import { reportTokenUpsellClicked } from "@/analytics/report";
import { GridPattern } from "@/components/ui/background-patterns";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
export function TokensSection() {
return (
<div className="bg-card p-4 lg:py-8 lg:px-6 border rounded-xl relative w-full overflow-hidden">
<GridPattern
width={30}
height={30}
strokeDasharray={"4 2"}
className="text-border dark:text-border/70 hidden lg:block translate-x-5"
style={{
maskImage:
"linear-gradient(to bottom left,white,transparent,transparent)",
}}
/>
<Badge
variant="outline"
className="text-sm bg-background h-auto px-3 py-1 gap-2 mb-4"
>
<div className="rounded-full bg-primary size-2" />
New
</Badge>
<h2 className="font-semibold text-2xl tracking-tight mb-1">
Launch Your Tokens Effortlessly
</h2>
<p className="text-muted-foreground mb-6 text-sm lg:text-base">
Deploy contract and configure all settings you need to launch your token
in one seamless flow
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-4xl">
<CardLink
assetType="coin"
title="Launch Coin"
description="Launch an ERC-20 token to create a cryptocurrency"
href="/team/~/~project/tokens/create/token"
bullets={[
"Deploy Contract",
"Configure Price and Supply",
"Airdrop Tokens",
]}
/>
<CardLink
assetType="nft"
title="Launch NFT Collection"
description="Launch an ERC-721 or ERC-1155 NFT collection"
href="/team/~/~project/tokens/create/nft"
bullets={[
"Deploy Contract",
"Upload NFTs",
"Configure Price and Supply",
]}
/>
</div>
</div>
);
}
function CardLink(props: {
title: string;
description: string;
href: string;
assetType: "nft" | "coin";
bullets: string[];
}) {
return (
<div
className={cn(
"relative flex flex-col rounded-lg border p-4 cursor-pointer hover:border-active-border bg-background",
)}
>
<div className="mb-3 flex">
<div className="flex items-center justify-center rounded-full border p-2 bg-card">
<ZapIcon className="size-4 text-muted-foreground" />
</div>
</div>
<h3 className="mb-0.5 font-semibold text-lg tracking-tight">
<Link
className="before:absolute before:inset-0"
href={props.href}
onClick={() => {
reportTokenUpsellClicked({
assetType: props.assetType,
pageType: "explore",
});
}}
>
{props.title}
</Link>
</h3>
<p className="text-muted-foreground text-sm">{props.description}</p>
<ul className="mt-4 space-y-1 text-sm list-disc list-inside text-muted-foreground">
{props.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</div>
);
}
@@ -3,7 +3,7 @@ import type { Metadata } from "next";
import { unstable_cache } from "next/cache";
import { Bridge } from "thirdweb";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { bridgeStats } from "../../../bridge/data";
import { bridgeStats } from "../../../bridge/(general)/data";
import { PageHeader } from "./components/header";
import { TokenPage } from "./components/token-page";
@@ -17,15 +17,21 @@ export default async function CheckoutPage(props: {
searchParams: Promise<{
amount?: string;
invoice_id?: string;
project_id?: string;
chain_id?: string | string[];
}>;
}) {
const params = await props.params;
const [params, searchParams] = await Promise.all([
props.params,
props.searchParams,
]);
console.log("params", params);
console.log("searchParams", searchParams);
switch (params.sku) {
case "topup": {
const amountUSD = Number.parseInt(
(await props.searchParams).amount || "10",
);
const amountUSD = Number.parseInt(searchParams.amount || "10");
if (Number.isNaN(amountUSD)) {
return <StripeRedirectErrorPage errorMessage="Invalid amount" />;
}
@@ -43,7 +49,7 @@ export default async function CheckoutPage(props: {
break;
}
case "invoice": {
const invoiceId = (await props.searchParams).invoice_id;
const invoiceId = searchParams.invoice_id;
if (!invoiceId) {
return <StripeRedirectErrorPage errorMessage="Invalid invoice ID" />;
}
@@ -59,10 +65,12 @@ export default async function CheckoutPage(props: {
redirect(invoice);
break;
}
default: {
const response = await getBillingCheckoutUrl({
sku: decodeURIComponent(params.sku) as Exclude<ProductSKU, null>,
teamSlug: params.team_slug,
params: searchParams,
});
if (response.status === "error") {
@@ -7,6 +7,10 @@ import { getAbsoluteUrl } from "@/utils/vercel";
export async function getBillingCheckoutUrl(options: {
teamSlug: string;
sku: Exclude<ProductSKU, null>;
params: {
project_id?: string;
chain_id?: string | string[];
};
}) {
const token = await getAuthToken();
@@ -17,13 +21,26 @@ export async function getBillingCheckoutUrl(options: {
} as const;
}
const chainIds = options.params.chain_id
? (Array.isArray(options.params.chain_id)
? options.params.chain_id
: [options.params.chain_id]
)
.map((value) => Number(value))
.filter((value) => Number.isFinite(value))
: undefined;
const body = {
redirectTo: getAbsoluteStripeRedirectUrl(),
sku: options.sku,
projectId: options.params.project_id,
chainIds: chainIds,
};
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamSlug}/checkout/create-link`,
{
body: JSON.stringify({
redirectTo: getAbsoluteStripeRedirectUrl(),
sku: options.sku,
}),
body: JSON.stringify(body),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
@@ -58,6 +75,18 @@ export async function getBillingCheckoutUrl(options: {
}
const json = await res.json();
if (
"error" in json &&
"message" in json.error &&
typeof json.error.message === "string"
) {
return {
error: json.error.message,
status: "error",
} as const;
}
if (!json.result) {
return {
error: "An unknown error occurred, please try again later.",
@@ -18,7 +18,6 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { getValidTeamPlan } from "@/utils/getValidTeamPlan";
import { SearchInput } from "../components/SearchInput";
export function AccountTeamsUI(props: {
@@ -117,8 +116,6 @@ function TeamRow(props: {
role: TeamAccountRole;
client: ThirdwebClient;
}) {
const plan = getValidTeamPlan(props.team);
return (
<div className="flex items-center justify-between gap-2">
{/* start */}
@@ -133,7 +130,11 @@ function TeamRow(props: {
<div>
<div className="flex items-center gap-3">
<p className="font-semibold text-sm">{props.team.name}</p>
<TeamPlanBadge plan={plan} teamSlug={props.team.slug} />
<TeamPlanBadge
plan={props.team.billingPlan}
teamSlug={props.team.slug}
isLegacyPlan={props.team.isLegacyPlan}
/>
</div>
<p className="text-muted-foreground text-sm capitalize">
{props.role.toLowerCase()}
@@ -312,7 +312,7 @@ function CreateProjectForm(props: {
type="submit"
>
{createProject.isPending && <Spinner className="size-4" />}
Create Projecct
Create Project
</Button>
</footer>
</form>

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