Compare commits

...
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
c37af4f191 Version Packages (#8389)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-10 14:31:30 -08:00
Joaquim VergesandGitHub f964b8aaff [SDK] Loosen peer dependency requirements for React Native (#8390) 2025-11-10 10:47:57 -08:00
YashandGitHub 424e6b8f9b Stylus modules (#8383) 2025-11-10 10:33:56 -08:00
MananTank 763d3de5a8 [MNY-303] Various UI/UX Improvements in Bridge components (#8388)
<!--

## 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 various UI/UX improvements across multiple components in the `thirdweb` package, particularly enhancing visual consistency and user interaction within the Bridge and Token Selection components.

### Detailed summary
- Adjusted font weights in several components from `600` to `500`.
- Updated `Spacer` component usage for consistent spacing.
- Enhanced `TokenSelection` UI with tabbed navigation for "All Tokens" and "Your Tokens".
- Added fallback images for icons in `TokenAndChain` and `ChainButton`.
- Refactored `CopyIcon` to use a `Button` instead of a `div` for better accessibility.
- Improved loading states and skeletons in `TokenSelectionScreen`.
- Simplified conditional rendering for transaction details in `PaymentReceipt`.

> ✨ 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**
  * Token selection: separate "All Tokens" and "Your Tokens" tabs with per-tab paging and "Load More"
  * Payment receipts: per-transaction explorer links and copy-to-clipboard controls
  * Enhanced success/details views showing expanded per-transaction status entries

* **UI/UX Improvements**
  * Refined typography weights across Bridge screens for clearer hierarchy
  * Adjusted icon sizing and added visual fallbacks for missing chain/token icons
  * Improved spacing, alignment, scrolling and containment in Bridge layouts
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-10 18:11:43 +00:00
Joaquim VergesandGitHub f6f138ccbc [Portal] Move x402 from payments to top-level section (#8386) 2025-11-09 18:09:15 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f5d5e73e57 Version Packages (#8385)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-09 15:17:10 -08:00
Joaquim VergesandGitHub 673e51b11c [SDK] Replace Celo Alfajores with Celo Sepolia Testnet (#8384) 2025-11-09 14:46:12 -08:00
MananTank 282edf7ccb [PRO-73] Portal: Update hero images (#8381)
<!--

## 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 making stylistic updates to the `Hero` component in the `page.tsx` file, including adjustments to padding and text size in the layout.

### Detailed summary
- Changed padding in the `section` from `py-14` to `pt-14 pb-6`.
- Modified paragraph class from `text-lg` to `text-base` for smaller text size on smaller screens while keeping `lg:text-lg`.

> ✨ 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**
  * Adjusted Hero section vertical spacing for improved layout balance.
  * Enhanced introductory text with responsive font sizing and refined spacing across different screen sizes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 20:40:50 +00:00
MananTank a87e91b99c [MNY-305] Dashboard: Checksum addresses in tx page (#8380)
<!--

## 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 address handling in the `GeneericTxDetails` and `TokenInfo` components by introducing the `getAddress` function from `thirdweb`, ensuring that addresses are consistently formatted.

### Detailed summary
- Added `getAddress` import in `page.tsx` and `bridge-status.tsx`.
- Replaced direct usage of `transaction.from` and `transaction.to` with `fromAddress` and `toAddress` in `GeneericTxDetails`.
- Updated `textToCopy` and `textToShow` in `CopyTextButton` components to use `fromAddress` and `toAddress`.
- Updated `TokenInfo` to use `getAddress` for `props.token.address` and `props.walletAddress`.
- Changed `textToCopy` and `textToShow` in `TokenInfo` to use `tokenAddress`.

> ✨ 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 address normalization in transaction and bridge status displays to ensure consistent formatting across the dashboard, preventing potential issues with address validation and comparison.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 20:28:47 +00:00
MananTank 0e630c4550 [MNY-304] Move engine tx summary request to client side to fix page crash (#8379)
<!--

## 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 transaction analytics feature by updating data fetching methods, improving UI components, and enhancing error handling for transaction summaries and charts.

### Detailed summary
- Removed outdated files: `utils.ts` and `filter.tsx`.
- Updated import paths for `TransactionSummaryData`.
- Changed UI text in `tx-chart-ui.tsx` for clarity.
- Enhanced error handling in data fetching functions.
- Introduced `getTransactionsChartData` and `getTransactionAnalyticsSummary` for improved data management.
- Refactored `TransactionsAnalyticsPageContent` to streamline data flow and UI rendering.
- Added new components for date and interval selection in transaction charts.

> ✨ 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**
  * Analytics now load client-side with a spinner and a streamlined layout; analytics sections only show when transactions exist.

* **New Features**
  * Direct date-range and interval controls in the analytics view.
  * Unified analytics summary and charts presented above the transactions table.
  * Chart header updated to "Transactions" with revised description.

* **Chores**
  * Removed URL-synced filter behavior; filters are local.
  * Empty-chart messaging changed to "No transactions found."
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 20:13:34 +00:00
Joaquim VergesandGitHub 11c7bf0955 [Playground] Remove hardcoded limit from wrapFetchWithPayment (#8376) 2025-11-07 02:38:54 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
c9e430d092 Version Packages (#8375)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-07 17:09:03 +07:00
FirekeeperandGitHub 1f440d0c97 Add Epic Games as new auth provider (#8343) 2025-11-07 15:59:08 +07:00
Yash094 3a0aac4393 Change TransactionWidget fiat decimals to two (#8366)
<!--

## [SDK] Fix: Format TransactionWidget fiat amounts to 2 decimals

## Notes for the reviewer

This PR updates the `formatMoney` function to display fiat amounts with a maximum of 2 decimal places instead of 5. This standardizes currency formatting for improved readability within the TransactionWidget.

## How to test

- Verify fiat amounts in the TransactionWidget (e.g., payment screens, token selection) are displayed with a maximum of 2 decimal places.
- Unit tests passed.

-->

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

<a href="https://cursor.com/background-agent?bcId=bc-65f07aee-1cee-4915-9656-8e4d9904ae3c"><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-65f07aee-1cee-4915-9656-8e4d9904ae3c"><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 focuses on modifying the formatting of token balances in the `formatTokenBalance.ts` file to limit the maximum number of fraction digits displayed.

### Detailed summary
- Changed `maximumFractionDigits` from `5` to `2` in the currency formatting 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

* **Bug Fixes**
  * Updated currency formatting to display a maximum of 2 decimal places instead of 5, improving readability and consistency of financial amounts throughout the app while maintaining appropriate minimum precision.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 07:36:09 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
7b13015053 Version Packages (#8374)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-06 16:29:15 -08:00
Joaquim VergesandGitHub 7f081618e1 [SDK] Expose useInvalidateBalances() (#8373) 2025-11-06 16:21:38 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
881c7a96f7 Version Packages (#8372)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-07 06:04:42 +07:00
FirekeeperandGitHub ad68969559 Refactor Solana sign transaction API for flexible inputs (#8371) 2025-11-07 05:56:11 +07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
915d955da1 Version Packages (#8370)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-06 13:58:49 -08:00
Joaquim VergesandGitHub 7da16cff2f [SDK] Make maxAmount optional in wrapFetchWithPayment (#8369) 2025-11-06 13:54:29 -08:00
Yash094 f0edc0f253 Update vault documentation text and faq (#8367)
```
[Portal] Fix: Update Vault open-source status and add FAQ

## Notes for the reviewer

This PR updates the Vault portal documentation to accurately reflect its open-source status.

*   Removed "open-source" from the main description and metadata on the Vault overview page.
*   Added a new FAQ entry under `/vault/faqs` clarifying that Vault is not yet open source.

## How to test

1.  Navigate to <https://portal.thirdweb.com/vault>
    *   Verify the main description no longer includes "open-source".
    *   (Optional) Inspect page metadata to confirm "open-source" is removed from the description.
2.  Navigate to <https://portal.thirdweb.com/vault/faqs>
    *   Verify the new FAQ "Is Vault open source?" is present with the answer "Vault is not yet open source, but we plan to release it soon."
```

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

<a href="https://cursor.com/background-agent?bcId=bc-ab8c2b24-6a74-4e0c-b302-d84d69ac5953"><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-ab8c2b24-6a74-4e0c-b302-d84d69ac5953"><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 focuses on updating the description of the `Vault` service to remove references to it being open source and refining the language for clarity.

### Detailed summary
- Added a new `Details` section in `apps/portal/src/app/vault/faqs/page.mdx` about Vault's open-source status.
- Updated the `description` in `apps/portal/src/app/vault/layout.tsx` to remove "open-source."
- Revised the `description` in `apps/portal/src/app/vault/page.mdx` to remove "open-source."
- Modified the main description of `Vault` in `apps/portal/src/app/vault/page.mdx` for 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

## Documentation
* Added new FAQ entry addressing open-source availability with information on future release plans
* Updated Vault service descriptions emphasizing non-custodial key management design and TEE security architecture
* Refined documentation metadata to provide clearer communication of Vault's core features, security model, and service capabilities

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-06 17:58:37 +00:00
Yash094 0a2d269782 Dashboard: Update faucet amount for injective testnet (#8368)
<!--

## 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 entry for the `Injective testnet` in the `faucet.ts` file, indicating a change in the configuration for test networks.

### Detailed summary
- Added a new entry for `Injective testnet` with a value of `1` in the configuration.
- The existing entry for `Arc testnet` remains unchanged.

> ✨ 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 Injective testnet, enabling faucet claims on this network with customized claim amount configuration.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-06 16:07:33 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
3d996d1d23 Version Packages (#8364)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-05 16:06:25 -08:00
Joaquim VergesandGitHub 40781b8170 [SDK] Allow custom chain for SIWE authentication (#8365) 2025-11-05 15:47:54 -08:00
MananTank d907017217 [MNY-295] Fix BuyWidget autoconnect not working when receiverAddress prop is set (#8363)
<!--

## 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 fixing the `BuyWidget` component's autoconnect functionality when the `receiverAddress` prop is set, and it introduces a new story variant to demonstrate this behavior.

### Detailed summary
- Updated `BuyWidget` to conditionally render the `AutoConnect` component based on `connectOptions.autoConnect`.
- Introduced a new story `NoAutoConnect` in `BuyWidget.stories.tsx` to showcase the widget with `autoConnect` set to false.
- Modified `FundWallet.tsx` to ensure proper handling of the `autoConnect` prop.

> ✨ 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 auto-connect functionality that was not working correctly when the receiver address property is set on the buy widget.

* **New Features**
  * Added configurable auto-connect option to the buy widget, enabling users to control whether auto-connection is enabled or disabled based on their needs.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-05 21:10:50 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
cf53c2d7f1 Version Packages (#8362)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-05 12:10:38 -08:00
Joaquim VergesandGitHub 207cb9dbeb [SDK] Enhance isMobile() detection with additional mobile signals (#8361) 2025-11-05 11:48:57 -08:00
Jonas DanielsandGitHub b4d365bdc0 Move x402 route from /payments/x402 to /x402 (#8360) 2025-11-04 16:30:08 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ea4e2fe376 Version Packages (#8357)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-04 14:20:21 -08:00
Joaquim VergesandGitHub 1d54cff7b4 [SDK] Add support for extra metadata in x402 payment requests (#8358) 2025-11-04 14:13:48 -08:00
YashandGitHub fd04eefea9 Update implementations, fix deployment (#8356) 2025-11-05 00:10:12 +07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
4cc68310dc Version Packages (#8355)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-04 00:47:18 -08:00
Joaquim VergesandGitHub ccdce496e0 [SDK] Fix hiddenWallets prop not applying to all wallets screen (#8354) 2025-11-04 00:39:11 -08:00
Joaquim VergesandGitHub 2725f337f8 [Dashboard] Fix active users aggregation in project highlights card (#8346) 2025-11-02 18:23:41 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2c6ea3e6dd Version Packages (#8340)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-31 18:48:20 -07:00
Joaquim VergesandGitHub 219fc69d72 [SDK] Update x402 flows to use remote /accepts endpoint (#8345) 2025-10-31 18:40:39 -07:00
Joaquim VergesandGitHub 8d17685683 [SDK] Refactor payment processing to use facilitator.accepts (#8344) 2025-10-31 18:38:51 -07:00
Jonas DanielsandGitHub 8ef418c543 Move success URL handling from PayPage to StyledBuyWidget component (#8342) 2025-10-30 14:35:32 -07:00
jnsdls b88f75b494 Add successUrl support to payment page (#8341)
<!--

## 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 various configuration files, updates the `PayPage` component with URL validation, and enhances the fetch functionality with OAuth support. It also includes the addition of testing HTML and size limits for the package.

### Detailed summary
- Added `wrap` export in `nexus-fetch.ts`.
- Introduced `biome.json`, `.size-limit.json`, `tsconfig.json`, `tsconfig.build.json`, and `knip.json` for configuration.
- Enhanced `PayPage` with `onlyUrl` validation and success URL handling.
- Added testing HTML file for `nexus-fetch`.
- Updated `package.json` with author and dependencies.
- Improved fetch functionality with OAuth and error handling.

> ✨ 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**
  - Payment page now supports redirecting to a provided success URL after payment completes.
  - New nexus-fetch library adds automatic OAuth token management, PKCE-backed sign-in flow, automatic token refresh, and integrated handling of proxy payment flows for web requests.

- **Chores**
  - Added build/configuration and size-limit files for the new nexus-fetch package.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-30 20:40:41 +00:00
MananTank c83f1c6f7a [MNY-298] SDK: Remove Bridge.routes, Update Docs (#8335)
<!--

## 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 removing the `Bridge.routes` functionality and the `useBridgeRoutes` hook from the codebase, along with related references and documentation updates in various files.

### Detailed summary

- Deleted `Routes.ts`, `Routes.test.ts`, and `useBridgeRoutes.ts` files.
- Removed references to `Bridge.routes` in multiple `.mdx` files and sidebar components.
- Updated `index.ts` to stop exporting `routes`.
- Adjusted `useSendTransactionCore` to use `Bridge.tokens` instead of `Bridge.routes`.
- Removed the `PriceComparator` class and associated methods for route comparison.

> ✨ 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**
  * Removed the Bridge.routes API, which previously supported route discovery with filtering by chains and tokens, pagination, and sorting
  * Removed the useBridgeRoutes React hook

* **Documentation**
  * Removed documentation pages covering bridge route discovery and filtering functionality
  * Removed related sidebar and guide navigation links

* **Tests**
  * Removed test suites for removed APIs

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-30 14:46:34 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
bc0295487b Version Packages (#8339)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-30 07:07:28 +07:00
FirekeeperandGitHub 90b779107d Add Solana wallet balance endpoint and UI integration (#8338) 2025-10-30 06:56:00 +07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
afe187de95 Version Packages (#8336)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-29 16:10:10 -07:00
759b93beff [SDK] Fix: move @storybook/react package to be a dev dependency (#8306)
Co-authored-by: Jonas Daniels <[email protected]>
2025-10-29 15:58:10 -07:00
Jonas DanielsandGitHub 8e357b3cb3 setup @thirdweb-dev/nexus package (#8332) 2025-10-29 15:55:12 -07:00
kumaryash90 6c318f83d6 Update implementations (#8334)
<!--

## 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 `MarketplaceV3` contract addresses and the `ZKSYNC_WETH` token addresses in the `implementations.ts` file.

### Detailed summary
- Updated `MarketplaceV3` address for network `232` from `0x9742f5ac11958cFAd151eBF0Fc31302fA409036E` to `0xB7387cF425eC8Da1b04b8E34De86245cb32bCA6D`.
- Updated `MarketplaceV3` address for network `37111` from `0x56Abb6a3f25DCcdaDa106191053b1CC54C196DEE` to `0xF8fd1016420c2B7832714241d36Efe55D41df126`.
- Updated `ZKSYNC_WETH` address for network `232` from `0xE5ecd226b3032910CEaa43ba92EE8232f8237553` to `0x6bDc36E20D267Ff0dd6097799f82e78907105e2F`.
- Updated `ZKSYNC_WETH` address for network `37111` from `0xaA91D645D7a6C1aeaa5988e0547267B77d33fe16` to `0xeee5a340Cdc9c179Db25dea45AcfD5FE8d4d3eB8`.

> ✨ 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 contract addresses for zkSync networks: MarketplaceV3 and WETH addresses replaced for chain 232 and chain 37111.
  * Added a changeset marking a patch release (release metadata).
  * No public API or exported signatures changed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-29 21:14:05 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f790c9fae9 Version Packages (#8328)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-29 02:14:23 -07:00
Joaquim VergesandGitHub bcbb3f540a [X402] Simplify FacilitatorNetworkSchema to accept any string (#8331) 2025-10-29 02:11:13 -07:00
Joaquim VergesandGitHub 931c730a08 [Paywall] Support string price format when tokenAddress is not provided (#8330) 2025-10-28 12:09:59 -07:00
YashandGitHub e0c83121c2 add arc testnet to chains package (#8325) 2025-10-28 12:09:02 -07:00
MananTank a82a31e5ad Dashboard: Use monotone curve type for AreaChart to avoid clipping (#8329)
<!--

## 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 `type` property for charts from `natural` to `monotone` in various components to improve the visual representation of the data.

### Detailed summary

- In `empty-chart-state.tsx`, changed `type` from `natural` to `monotone`.
- In `area-chart.tsx`, changed `type` from `natural` to `monotone`.
- In `ErrorRate.tsx`, changed `type` from `natural` to `monotone`.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Style**
  * Updated chart line rendering across multiple dashboard components for improved visual consistency. Area and bar charts now use refined curve interpolation for smoother data visualization.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 18:14:30 +00:00
MananTank 3d235e218a Dashboard: Update faucet amount for arc testnet (#8326)
<!--

## 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 entry for the `Arc testnet` in the `faucet.ts` file, enhancing the functionality to support an additional test network alongside the existing `Sophon testnet`.

### Detailed summary

- Added a comment for `Arc testnet`.
- Introduced a new entry `5042002: 1` for the `Arc testnet`.

> ✨ 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 faucet support for the Arc testnet, allowing users connected to this network to claim test funds.
  - The claim amount on Arc is set to 1, ensuring consistent and predictable claims on that network.
  - Existing networks and the default claim behavior remain unchanged, preserving current user flows while expanding testnet coverage.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 17:53:36 +00:00
MananTank c08fb1356e SDK: Fix Safe wallet connection issues with WalletConnect (#8327)
<!--

## 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 issues with the Safe wallet connection when using `WalletConnect`, ensuring better handling of account retrieval.

### Detailed summary

- Updated the comment for `account` retrieval to clarify that it grabs the address from mainnet if available.
- Modified the `firstAccountOn` function to return the first account from the session if no matching account is found.

> ✨ 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 Safe wallet connection issues with WalletConnect

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 17:19:37 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
68cca6f992 Version Packages (#8318)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-27 10:12:02 -07:00
Joaquim VergesandGitHub e0abf2b273 [SDK] Update x402 dependency and export additional types (#8319) 2025-10-27 10:02:00 -07:00
FirekeeperandGitHub abf0bbaf35 Update x402 default payment validity window (#8317) 2025-10-27 09:56:05 -07:00
MananTank 23cb3bd98c Dashboard: /pay/<id> page UI improvements, SDK: CheckoutWidget minor UI tweaks (#8310)
<!--

## 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 enhancements and minor adjustments in the `CheckoutWidget` and related components of the payment dashboard, improving styling, layout, and functionality.

### Detailed summary
- Minor UI adjustments in `CheckoutWidget`.
- Updated `formatTokenBalance.ts` to allow more flexible currency formatting.
- Added `trackingTight` prop to `Text` in `FiatValue.tsx`.
- Enhanced `PayPageWidget` with new class names for styling.
- Adjusted color values in SDK theme definitions.
- Improved layout and spacing in `WithHeader.tsx`.
- Modified text properties in `DirectPayment.tsx` for better readability.
- Created a new `PayIdPageHeader` component for improved header structure.
- Replaced complex JSX in `PayPage` with cleaner component structure, including `DotsBackgroundPattern`.

> ✨ 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**
  * Theme toggle added to Pay page headers; streamlined Pay page header and layout with decorative background.

* **Style**
  * Refined secondary/tertiary button and background colors, typography weights/letter spacing, spacing and divider styles.
  * Adjusted price and label styling, currency formatting precision, and visual spacing around branding.
  * Pay widget now uses a fallback image when no project image is provided.

* **Chores**
  * Added changelog entry documenting a patch release.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-27 16:11:49 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
dcef3f4fe7 Version Packages (#8315)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-26 01:25:18 -07:00
Joaquim VergesandGitHub bf46520a6b [SDK] Enhance compatibility with third party x402 libs (#8314) 2025-10-26 01:21:40 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
7defce7264 Version Packages (#8303)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-26 00:46:21 -07:00
Joaquim VergesandGitHub ae3a122cf1 [X402] Fix network comparison in decodePaymentRequest (#8313) 2025-10-26 00:44:27 -07:00
Jonas DanielsandGitHub 2412a6958c Handle null metadata in ERC721 and ERC1155 getNFT functions (#8312) 2025-10-24 15:22:14 -07:00
Jonas DanielsandGitHub bfd3be764c Filter out null-ish values from ERC721 and ERC1155 getNFTs() arrays (#8311) 2025-10-24 14:39:31 -07:00
Joaquim VergesandGitHub 46b9d2dd82 [Playground] Use production API URL (#8307) 2025-10-24 11:38:11 -07:00
MananTank 3e9eb49787 [MNY-289] SDK: Fix token selection UI loading state when wallet is connected to a chain that is not supported by tw bridge (#8304)
<!--

## 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 `Select Token` UI being stuck in a loading state when the wallet is connected to an unsupported chain by the `thirdweb Bridge`. It modifies the logic for selecting the default chain in various widgets.

### Detailed summary
- Updated the function from `getDefaultSelectedChain` to `findChain` for clarity.
- Improved logic to handle cases where `activeChainId` is undefined.
- Adjusted the selection of `selectedChain` to use the new `findChain` function, ensuring proper fallback 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

## Bug Fixes
* Fixed Select Token components from remaining in loading state when the connected wallet chain is not supported by thirdweb Bridge. This affects BuyWidget, SwapWidget, and BridgeWidget.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-23 23:39:57 +00:00
MananTank 630edadcec Dashboard: Fix horizontal overflow in /bridge page (#8302)
<!--

## 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 styling of the `bridge-page` component by adding an `overflow-hidden` class to a `div`, which likely improves the layout and prevents overflow issues.

### Detailed summary

- Added `overflow-hidden` class to the `div` in the `bridge-page` component to manage overflow behavior.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Style**
  * Updated the bridge page container to refine content display behavior and ensure consistent visual presentation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-23 23:30:50 +00:00
MananTank f757e60846 [MNY-237] SDK: Update theme colors, minor UI tweaks (#8305)
<!--

## 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 theme colors and making minor UI tweaks across various React components in the `thirdweb` package.

### Detailed summary

- Updated `weight` from `600` to `500` in multiple components' `Text`.
- Adjusted `border` styles in `Container` components.
- Modified padding and margin in several layout elements.
- Enhanced UI elements with consistent `letterSpacing` and `fontSize`.
- Added new theme stories for `darkThemeObj` and `lightThemeObj`.

> ✨ 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 Storybook theme showcase displaying color tokens and design system palette
- **Bug Fixes**
    - Improved loading skeleton UI with better scrollable layout and visual consistency
- **Style**
    - Refactored theme system with explicit color objects
    - Updated button styling with adjusted border radius and padding
    - Refined typography (font weights) across UI components
    - Adjusted layout spacing and padding throughout Bridge and wallet components
    - Increased QR code size for improved visibility
- **Chores**
    - Added changeset documentation for patch release

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-23 23:21:57 +00:00
kumaryash90 d5d593956e Update factory (#8301)
<!--

## 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 patch for the `thirdweb` package, adding a new factory address for the `create2` deployment on the Neura testnet.

### Detailed summary
- Added a new factory address for `267`: `0x4e59b44847b379578588920cA78FbF26c0B4956C` to the `FACTORIES` record in `create-2-factory.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

* **New Features**
  * Added support for Neura testnet in Create2 factory deployments.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-23 17:05:22 +00:00
205 changed files with 7217 additions and 4601 deletions
+9 -2
View File
@@ -188,9 +188,16 @@ jobs:
- name: Build Packages
run: pnpm build
- name: Report bundle size
- name: Report bundle size (thirdweb)
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
package_manager: pnpm
directory: packages/thirdweb
directory: packages/thirdweb
- name: Report bundle size (nexus)
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
package_manager: pnpm
directory: packages/nexus
+1 -1
View File
@@ -10,7 +10,7 @@ module.exports = [
"/wallets",
"/account-abstraction",
"/payments",
"/payments/x402",
"/x402",
"/nexus",
"/auth",
"/in-app-wallets",
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

+5
View File
@@ -479,6 +479,11 @@ async function redirects() {
destination: "/tokens",
permanent: false,
},
{
source: "/payments/x402",
destination: "/x402",
permanent: false,
},
];
}
+2 -1
View File
@@ -21,7 +21,8 @@ export type AuthOption =
| "steam"
| "apple"
| "coinbase"
| "line";
| "line"
| "epic";
export type Ecosystem = {
name: string;
@@ -95,7 +95,7 @@ function SkeletonBarChart(props: {
fill={`url(#${fillAreaSkeletonId})`}
radius={8}
stroke="hsl(var(--muted-foreground))"
type="natural"
type="monotone"
/>
</AreaChart>
)}
@@ -172,7 +172,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
key={key}
stackId={props.variant !== "stacked" ? undefined : "a"}
stroke={`var(--color-${key})`}
type="natural"
type="monotone"
/>
),
)}
+4
View File
@@ -5,6 +5,10 @@ const customClaimAmounts: Record<number, number> = {
631571: 0.1,
// Sophon testnet
531050104: 5,
// Arc testnet
5042002: 1,
// Injective testnet
1439: 1,
};
const defaultClaimAmount = 0.01;
@@ -21,8 +21,8 @@ export function getSDKTheme(theme: "light" | "dark"): Theme {
primaryButtonText: "hsl(var(--inverted-foreground))",
primaryText: "hsl(var(--foreground))",
scrollbarBg: "hsl(var(--muted))",
secondaryButtonBg: "hsl(var(--secondary))",
secondaryButtonHoverBg: "hsl(var(--secondary)/80%)",
secondaryButtonBg: "hsl(var(--secondary)/70%)",
secondaryButtonHoverBg: "hsl(var(--secondary))",
secondaryButtonText: "hsl(var(--secondary-foreground))",
secondaryIconColor: "hsl(var(--secondary-foreground))",
secondaryIconHoverBg: "hsl(var(--accent))",
@@ -33,7 +33,7 @@ export function getSDKTheme(theme: "light" | "dark"): Theme {
separatorLine: "hsl(var(--border))",
skeletonBg: "hsl(var(--secondary-foreground)/15%)",
success: "hsl(var(--success-text))",
tertiaryBg: "hsl(var(--muted)/30%)",
tertiaryBg: "hsl(var(--muted)/50%)",
tooltipBg: "hsl(var(--popover))",
tooltipText: "hsl(var(--popover-foreground))",
},
@@ -225,6 +225,10 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => {
moduleInfo: {
bytecodeUri: selectedModule.metadata.bytecodeUri,
},
isStylus:
(selectedModule.metadata.compilers?.stylus &&
selectedModule.metadata.compilers?.stylus?.length > 0) ||
false,
});
},
queryKey: [
@@ -432,6 +436,7 @@ async function isModuleCompatible(options: {
moduleInfo: {
bytecodeUri: string;
};
isStylus: boolean;
client: ThirdwebClient;
}) {
// 1. get module's bytecode
@@ -444,15 +449,17 @@ async function isModuleCompatible(options: {
// 2. check compatibility with core and installed modules
try {
const isCompatible = await checkModulesCompatibility({
chain: options.contractInfo.chain,
client: options.client,
coreBytecode: options.contractInfo.bytecode,
moduleBytecodes: [
moduleBytecode,
...options.contractInfo.installedModuleBytecodes,
],
});
const isCompatible =
options.isStylus ||
(await checkModulesCompatibility({
chain: options.contractInfo.chain,
client: options.client,
coreBytecode: options.contractInfo.bytecode,
moduleBytecodes: [
moduleBytecode,
...options.contractInfo.installedModuleBytecodes,
],
}));
return isCompatible;
} catch (e) {
@@ -10,7 +10,11 @@ import {
CircleXIcon,
} from "lucide-react";
import Link from "next/link";
import { NATIVE_TOKEN_ADDRESS, type ThirdwebClient } from "thirdweb";
import {
getAddress,
NATIVE_TOKEN_ADDRESS,
type ThirdwebClient,
} from "thirdweb";
import type { Status, Token } from "thirdweb/bridge";
import { status } from "thirdweb/bridge";
import { toTokens } from "thirdweb/utils";
@@ -123,6 +127,8 @@ function TokenInfo(props: {
const isNativeToken =
props.token.address.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase();
const tokenAddress = getAddress(props.token.address);
return (
<div className="flex-1 pt-10 pb-9 px-6 lg:px-8">
<div className="flex justify-between items-center">
@@ -206,7 +212,7 @@ function TokenInfo(props: {
<div className="space-y-1">
<p className="text-sm text-foreground ">{props.addressLabel}</p>
<WalletAddress
address={props.walletAddress}
address={getAddress(props.walletAddress)}
client={props.client}
className="py-0.5 h-auto text-sm [&>*]:!font-sans tabular-nums [&>*]:font-normal text-muted-foreground hover:text-foreground"
iconClassName="size-3"
@@ -222,8 +228,8 @@ function TokenInfo(props: {
? `/${chainQuery.data?.slug || props.token.chainId}`
: `/${chainQuery.data?.slug || props.token.chainId}/${props.token.address}`
}
textToShow={props.token.address}
textToCopy={props.token.address}
textToShow={tokenAddress}
textToCopy={tokenAddress}
copyTooltip="Copy Token Address"
className="-translate-x-1"
/>
@@ -8,7 +8,7 @@ import {
Clock4Icon,
InfoIcon,
} from "lucide-react";
import { toTokens } from "thirdweb";
import { getAddress, toTokens } from "thirdweb";
import { status } from "thirdweb/bridge";
import type { ChainMetadata } from "thirdweb/chains";
import {
@@ -153,6 +153,9 @@ function GeneericTxDetails(props: {
const timestamp = getDatefromTimestamp(block.timestamp);
const fromAddress = getAddress(transaction.from);
const toAddress = transaction.to ? getAddress(transaction.to) : undefined;
return (
<div className="border rounded-xl p-4 lg:p-6 bg-card">
{/* section 1 */}
@@ -216,8 +219,8 @@ function GeneericTxDetails(props: {
<section className="border-b py-6 space-y-5 lg:space-y-3">
<GridItem label="From" tooltip="The sending party of the transaction.">
<CopyTextButton
textToCopy={transaction.from}
textToShow={transaction.from}
textToCopy={fromAddress}
textToShow={fromAddress}
tooltip="Copy from address"
variant="ghost"
copyIconPosition="right"
@@ -225,14 +228,14 @@ function GeneericTxDetails(props: {
/>
</GridItem>
{transaction.to && (
{toAddress && (
<GridItem
label="Interacted with (To)"
tooltip="The receiving party of the transaction (could be a contract address)."
>
<CopyTextButton
textToCopy={transaction.to}
textToShow={transaction.to}
textToCopy={toAddress}
textToShow={toAddress}
tooltip="Copy to address"
variant="ghost"
copyIconPosition="right"
@@ -183,13 +183,64 @@ const STYLUS = {
],
description:
"Airdrop your NFTs or tokens to a large number of recipients. Built with Arbitrum Stylus.",
displayName: "Arbitrum Stylus Contracts",
displayName: "Airdrop - Arbitrum Stylus",
id: "stylus",
name: "Stylus",
showInExplore: true,
isBeta: true,
} satisfies ExploreCategory;
const MODULAR_CONTRACTS_STYLUS = {
contracts: [
// erc721 token
[
"thirdweb.eth/ERC721CoreInitializable",
[
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusMintableERC721",
"deployer.thirdweb.eth/BatchMetadataERC721",
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusTransferableERC721",
],
{
description: "ERC721 NFTs that only owners can mint.",
title: "Modular NFT Collection",
},
],
// erc1155 token
[
"thirdweb.eth/ERC1155CoreInitializable",
[
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusMintableERC1155",
"deployer.thirdweb.eth/BatchMetadataERC1155",
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusTransferableERC1155",
"deployer.thirdweb.eth/SequentialTokenIdERC1155",
],
{
description: "ERC1155 NFTs that only owners can mint.",
title: "Modular Edition",
},
],
// erc20 token
[
"thirdweb.eth/ERC20CoreInitializable",
[
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusMintableERC20",
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusTransferableERC20",
],
{
description: "ERC20 Tokens that only owners can mint.",
title: "Modular Token",
},
],
],
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,
isBeta: true,
} satisfies ExploreCategory;
const CATEGORIES: Record<string, ExploreCategory> = {
[POPULAR.id]: POPULAR,
[NFTS.id]: NFTS,
@@ -203,6 +254,7 @@ const CATEGORIES: Record<string, ExploreCategory> = {
[COMMERCE.id]: COMMERCE,
[STAKING.id]: STAKING,
[GOVERNANCE.id]: GOVERNANCE,
[MODULAR_CONTRACTS_STYLUS.id]: MODULAR_CONTRACTS_STYLUS,
};
export function getCategory(id: string) {
@@ -58,6 +58,7 @@ const authOptions = [
"apple",
"coinbase",
"line",
"epic",
] as const satisfies AuthOption[];
type AuthOptionsFormData = {
@@ -59,7 +59,6 @@ export function ErrorRate({ datapoints }: { datapoints: ResultItem[] }) {
key={tag}
stackId="a"
stroke={`var(--color-${tag})`}
type="natural"
/>,
);
}
@@ -14,6 +14,7 @@ type AggregatedMetrics = {
export function ProjectHighlightsCard(props: {
selectedChart: string | undefined;
aggregatedUserStats: InAppWalletStats[];
userStats: InAppWalletStats[];
volumeStats: UniversalBridgeStats[];
teamSlug: string;
@@ -22,6 +23,7 @@ export function ProjectHighlightsCard(props: {
}) {
const {
selectedChart,
aggregatedUserStats,
userStats,
volumeStats,
teamSlug,
@@ -70,6 +72,12 @@ export function ProjectHighlightsCard(props: {
: "activeUsers"
}
aggregateFn={(_data, key) => {
if (key === "activeUsers") {
return aggregatedUserStats.reduce(
(acc, curr) => acc + curr.uniqueWalletsConnected,
0,
);
}
return timeSeriesData.reduce((acc, curr) => acc + curr[key], 0);
}}
chartConfig={chartConfig}
@@ -378,8 +378,18 @@ async function AsyncAppHighlightsCard(props: {
params: PageParams;
authToken: string;
}) {
const [walletUserStatsTimeSeries, universalBridgeUsage] =
const [aggregatedUserStats, walletUserStatsTimeSeries, universalBridgeUsage] =
await Promise.allSettled([
getInAppWalletUsage(
{
from: props.range.from,
period: "all",
projectId: props.project.id,
teamId: props.project.teamId,
to: props.range.to,
},
props.authToken,
),
getInAppWalletUsage(
{
from: props.range.from,
@@ -408,6 +418,11 @@ async function AsyncAppHighlightsCard(props: {
) {
return (
<ProjectHighlightsCard
aggregatedUserStats={
aggregatedUserStats.status === "fulfilled"
? aggregatedUserStats.value
: []
}
selectedChart={props.selectedChart}
selectedChartQueryParam={props.selectedChartQueryParam}
teamSlug={props.params.team_slug}
@@ -1,45 +1,95 @@
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
"use client";
import { useQuery } from "@tanstack/react-query";
import { Spinner } from "@workspace/ui/components/spinner";
import type { ThirdwebClient } from "thirdweb";
import type { Project } from "@/api/project/projects";
import { UnifiedTransactionsTable } from "../components/transactions-table.client";
import { getTransactionAnalyticsSummary } from "../lib/analytics-summary.client";
import type { Wallet } from "../server-wallets/wallet-table/types";
import { TransactionAnalyticsFilter } from "./filter";
import { TransactionsChartCard } from "./tx-chart/tx-chart";
import type { SolanaWallet } from "../solana-wallets/wallet-table/types";
import { EngineChecklist } from "./ftux.client";
import { TransactionAnalyticsSummary } from "./summary";
import { TransactionsAnalytics } from "./tx-chart/tx-chart";
export function TransactionsAnalyticsPageContent(props: {
searchParams: {
from?: string | undefined | string[];
to?: string | undefined | string[];
interval?: string | undefined | string[];
};
project: Project;
showAnalytics: boolean;
wallets?: Wallet[];
wallets: Wallet[];
teamSlug: string;
client: ThirdwebClient;
authToken: string;
teamId: string;
isManagedVault: boolean;
testTxWithWallet: string | undefined;
testSolanaTxWithWallet: string | undefined;
solanaWallets: SolanaWallet[];
}) {
return (
<ResponsiveSearchParamsProvider value={props.searchParams}>
<div className="flex grow flex-col gap-6">
{props.showAnalytics && (
<>
<div className="flex justify-end">
<TransactionAnalyticsFilter />
</div>
<TransactionsChartCard
project={props.project}
searchParams={props.searchParams}
teamSlug={props.teamSlug}
wallets={props.wallets ?? []}
/>
</>
)}
<UnifiedTransactionsTable
client={props.client}
project={props.project}
teamSlug={props.teamSlug}
/>
const engineTxSummaryQuery = useQuery({
queryKey: [
"engine-tx-analytics-summary",
props.teamId,
props.project.publishableKey,
props.authToken,
],
queryFn: async () => {
const data = await getTransactionAnalyticsSummary({
clientId: props.project.publishableKey,
teamId: props.teamId,
authToken: props.authToken,
});
return data;
},
refetchOnWindowFocus: false,
refetchOnMount: false,
});
if (engineTxSummaryQuery.isPending) {
return (
<div className="flex h-[642px] grow items-center justify-center bg-card rounded-xl border">
<Spinner className="size-10" />
</div>
</ResponsiveSearchParamsProvider>
);
}
const hasTransactions = engineTxSummaryQuery.data
? engineTxSummaryQuery.data.totalCount > 0
: false;
return (
<div className="flex grow flex-col gap-10">
<EngineChecklist
isManagedVault={props.isManagedVault}
client={props.client}
hasTransactions={hasTransactions}
project={props.project}
teamSlug={props.teamSlug}
testTxWithWallet={props.testTxWithWallet}
testSolanaTxWithWallet={props.testSolanaTxWithWallet}
wallets={props.wallets}
solanaWallets={props.solanaWallets}
/>
{props.showAnalytics && hasTransactions && (
<div className="flex flex-col gap-6">
<TransactionAnalyticsSummary
clientId={props.project.publishableKey}
teamId={props.project.teamId}
initialData={engineTxSummaryQuery.data}
/>
<TransactionsAnalytics
project={props.project}
authToken={props.authToken}
teamSlug={props.teamSlug}
wallets={props.wallets ?? []}
/>
</div>
)}
<UnifiedTransactionsTable
client={props.client}
project={props.project}
teamSlug={props.teamSlug}
/>
</div>
);
}
@@ -1,51 +0,0 @@
"use client";
import {
useResponsiveSearchParams,
useSetResponsiveSearchParams,
} from "responsive-rsc";
import { DateRangeSelector } from "@/components/analytics/date-range-selector";
import { IntervalSelector } from "@/components/analytics/interval-selector";
import { normalizeTimeISOString } from "@/lib/time";
import { getTxAnalyticsFiltersFromSearchParams } from "../lib/utils";
export function TransactionAnalyticsFilter() {
const responsiveSearchParams = useResponsiveSearchParams();
const setResponsiveSearchParams = useSetResponsiveSearchParams();
const { range, interval } = getTxAnalyticsFiltersFromSearchParams({
from: responsiveSearchParams.from,
interval: responsiveSearchParams.interval,
to: responsiveSearchParams.to,
});
return (
<div className="no-scrollbar flex items-center gap-3 max-sm:overflow-auto">
<DateRangeSelector
popoverAlign="end"
range={range}
setRange={(newRange) => {
setResponsiveSearchParams((v) => {
return {
...v,
from: normalizeTimeISOString(newRange.from),
to: normalizeTimeISOString(newRange.to),
};
});
}}
/>
<IntervalSelector
intervalType={interval}
setIntervalType={(newInterval) => {
setResponsiveSearchParams((v) => {
return {
...v,
interval: newInterval,
};
});
}}
/>
</div>
);
}
@@ -1,7 +1,7 @@
import { ActivityIcon, CoinsIcon } from "lucide-react";
import { toEther } from "thirdweb/utils";
import { StatCard } from "@/components/analytics/stat"; // Assuming correct path
import type { TransactionSummaryData } from "../lib/analytics";
import type { TransactionSummaryData } from "../lib/analytics-summary.client";
// Renders the UI based on fetched data or pending state
function TransactionAnalyticsSummaryUI(props: {
@@ -0,0 +1,75 @@
"use client";
import { NEXT_PUBLIC_ENGINE_CLOUD_URL } from "@/constants/public-envs";
import type { TransactionStats } from "@/types/analytics";
export async function getTransactionsChartData({
teamId,
clientId,
from,
to,
interval,
authToken,
}: {
teamId: string;
clientId: string;
from: string;
to: string;
interval: "day" | "week";
authToken: string;
}): Promise<TransactionStats[]> {
const filters = {
endDate: to,
resolution: interval,
startDate: from,
};
const response = await fetch(
`${NEXT_PUBLIC_ENGINE_CLOUD_URL}/v1/transactions/analytics`,
{
body: JSON.stringify(filters),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": clientId,
"x-team-id": teamId,
},
method: "POST",
},
);
if (!response.ok) {
if (response.status === 401 || response.status === 400) {
return [];
}
// TODO - need to handle this error state, like we do with the connect charts
throw new Error(
`Error fetching transactions chart data: ${response.status} ${
response.statusText
} - ${await response.text().catch(() => "Unknown error")}`,
);
}
type TransactionsChartResponse = {
result: {
analytics: Array<{
timeBucket: string;
chainId: string;
count: number;
}>;
metadata: {
resolution: string;
startDate: string;
endDate: string;
};
};
};
const data = (await response.json()) as TransactionsChartResponse;
return data.result.analytics.map((stat) => ({
chainId: Number(stat.chainId),
count: stat.count,
date: stat.timeBucket,
}));
}
@@ -108,10 +108,10 @@ export function TransactionsChartCardUI(props: {
customHeader={
<div className="relative px-6 pt-6">
<h3 className="mb-0.5 font-semibold text-xl tracking-tight">
Daily Transactions
Transactions
</h3>
<p className="text-muted-foreground text-sm">
Amount of daily transactions by chain.
transactions broken down by chain
</p>
<div className="top-6 right-6 mb-8 grid grid-cols-2 items-center gap-2 md:absolute md:mb-0 md:flex">
@@ -194,13 +194,7 @@ function EmptyChartContent(props: {
</div>
</>
) : (
<p className="flex items-center gap-2 rounded-full border bg-background px-3.5 py-1.5 text-sm">
<span className="!pointer-events-auto relative flex size-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-sky-400 opacity-75" />
<span className="relative inline-flex size-2 rounded-full bg-primary" />
</span>
Waiting for transactions...
</p>
<p className="text-sm text-muted-foreground">No transactions found</p>
)}
</div>
);
@@ -1,73 +1,68 @@
import { ResponsiveSuspense } from "responsive-rsc";
"use client";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import type { Project } from "@/api/project/projects";
import { getTransactionsChart } from "../../lib/analytics";
import { getTxAnalyticsFiltersFromSearchParams } from "../../lib/utils";
import {
DateRangeSelector,
getLastNDaysRange,
} from "@/components/analytics/date-range-selector";
import { IntervalSelector } from "@/components/analytics/interval-selector";
import { normalizeTimeISOString } from "@/lib/time";
import type { Wallet } from "../../server-wallets/wallet-table/types";
import { getTransactionsChartData } from "./data";
import { TransactionsChartCardUI } from "./tx-chart-ui";
async function AsyncTransactionsChartCard(props: {
from: string;
to: string;
interval: "day" | "week";
export function TransactionsAnalytics(props: {
project: Project;
wallets: Wallet[];
authToken: string;
teamSlug: string;
}) {
const data = await getTransactionsChart({
const [range, setRange] = useState(() => getLastNDaysRange("last-30"));
const [interval, setInterval] = useState<"day" | "week">("day");
const params = {
clientId: props.project.publishableKey,
from: props.from,
interval: props.interval,
from: normalizeTimeISOString(range.from),
interval: interval,
teamId: props.project.teamId,
to: props.to,
to: normalizeTimeISOString(range.to),
authToken: props.authToken,
};
const engineTxAnalytics = useQuery({
queryKey: ["engine-tx-analytics", params],
queryFn: async () => {
const data = await getTransactionsChartData(params);
return data;
},
refetchOnWindowFocus: false,
refetchOnMount: false,
});
return (
<TransactionsChartCardUI
isPending={false}
project={props.project}
teamSlug={props.teamSlug}
userOpStats={data}
wallets={props.wallets}
/>
);
}
<div className="flex flex-col gap-6">
<div className="flex justify-end">
<div className="no-scrollbar flex items-center gap-3 max-sm:overflow-auto">
<DateRangeSelector
popoverAlign="end"
range={range}
setRange={setRange}
/>
export function TransactionsChartCard(props: {
searchParams: {
from?: string | undefined | string[];
to?: string | undefined | string[];
interval?: string | undefined | string[];
};
project: Project;
wallets: Wallet[];
teamSlug: string;
}) {
const { range, interval } = getTxAnalyticsFiltersFromSearchParams(
props.searchParams,
);
return (
<ResponsiveSuspense
// TODO - change this if this component does not end up using these params
fallback={
<TransactionsChartCardUI
isPending={true}
project={props.project}
teamSlug={props.teamSlug}
userOpStats={[]}
wallets={[]}
/>
}
searchParamsUsed={["from", "to", "interval"]}
>
<AsyncTransactionsChartCard
from={range.from.toISOString()}
interval={interval}
<IntervalSelector
intervalType={interval}
setIntervalType={setInterval}
/>
</div>
</div>
<TransactionsChartCardUI
isPending={engineTxAnalytics.isPending}
project={props.project}
teamSlug={props.teamSlug}
to={range.to.toISOString()}
userOpStats={engineTxAnalytics.data ?? []}
wallets={props.wallets}
/>
</ResponsiveSuspense>
</div>
);
}
@@ -40,6 +40,13 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import {
@@ -57,6 +64,7 @@ import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
import { WalletProductIcon } from "@/icons/WalletProductIcon";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { cn } from "@/lib/utils";
import { fetchSolanaBalance } from "../lib/getSolanaBalance";
import { updateDefaultProjectWallet } from "../lib/vault.client";
import { CreateServerWallet } from "../server-wallets/components/create-server-wallet.client";
import type { Wallet as EVMWallet } from "../server-wallets/wallet-table/types";
@@ -79,6 +87,7 @@ interface ServerWalletsTableProps {
teamSlug: string;
client: ThirdwebClient;
solanaPermissionError?: boolean;
authToken: string;
}
export function ServerWalletsTable(props: ServerWalletsTableProps) {
@@ -95,10 +104,14 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
solanaTotalPages,
client,
solanaPermissionError,
authToken,
} = props;
const [activeChain, setActiveChain] = useState<WalletChain>("evm");
const [selectedChainId, setSelectedChainId] = useState<number>(1);
const [selectedSolanaChain, setSelectedSolanaChain] = useState<
"solana:mainnet" | "solana:devnet"
>("solana:mainnet");
const [showSmartAccount, setShowSmartAccount] = useState(false);
const queryClient = useQueryClient();
@@ -145,11 +158,33 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
</>
)}
{activeChain === "solana" && (
<CreateSolanaWallet
project={project}
teamSlug={teamSlug}
disabled={solanaPermissionError}
/>
<>
<CreateSolanaWallet
project={project}
teamSlug={teamSlug}
disabled={solanaPermissionError}
/>
<Select
value={selectedSolanaChain}
onValueChange={(value) =>
setSelectedSolanaChain(
value as "solana:mainnet" | "solana:devnet",
)
}
>
<SelectTrigger className="w-fit min-w-[180px] rounded-full bg-background hover:bg-accent/50">
<SelectValue placeholder="Select network" />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="solana:mainnet" className="rounded-lg">
Solana Mainnet
</SelectItem>
<SelectItem value="solana:devnet" className="rounded-lg">
Solana Devnet
</SelectItem>
</SelectContent>
</Select>
</>
)}
</div>
@@ -278,6 +313,8 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
project={project}
teamSlug={teamSlug}
client={client}
authToken={authToken}
chainId={selectedSolanaChain}
/>
))}
</TableBody>
@@ -507,11 +544,15 @@ function SolanaWalletRow({
project,
teamSlug,
client,
authToken,
chainId,
}: {
wallet: SolanaWallet;
project: Project;
teamSlug: string;
client: ThirdwebClient;
authToken: string;
chainId: "solana:mainnet" | "solana:devnet";
}) {
const engineService = project.services.find(
(s) => s.name === "engineCloud",
@@ -547,7 +588,12 @@ function SolanaWalletRow({
</TableCell>
<TableCell>
<SolanaWalletBalance publicKey={wallet.publicKey} />
<SolanaWalletBalance
publicKey={wallet.publicKey}
authToken={authToken}
clientId={project.publishableKey}
chainId={chainId}
/>
</TableCell>
<TableCell>
@@ -739,16 +785,27 @@ function WalletBalance({
);
}
function SolanaWalletBalance({ publicKey }: { publicKey: string }) {
function SolanaWalletBalance({
publicKey,
authToken,
clientId,
chainId,
}: {
publicKey: string;
authToken: string;
clientId: string;
chainId: "solana:mainnet" | "solana:devnet";
}) {
const balance = useQuery({
queryFn: async () => {
// TODO: Implement actual Solana balance fetching
return {
displayValue: "0",
symbol: "SOL",
};
return await fetchSolanaBalance({
publicKey,
authToken,
clientId,
chainId,
});
},
queryKey: ["solanaWalletBalance", publicKey],
queryKey: ["solanaWalletBalance", publicKey, chainId],
});
if (balance.isFetching) {
@@ -0,0 +1,75 @@
"use client";
import { NEXT_PUBLIC_ENGINE_CLOUD_URL } from "@/constants/public-envs";
export type TransactionSummaryData = {
totalCount: number;
totalGasCostWei: string;
totalGasUnitsUsed: string; // Keep fetched data structure
};
type AnalyticsSummaryApiResponse = {
result: {
summary: {
totalCount: number;
totalGasCostWei: string;
totalGasUnitsUsed: string;
};
metadata: {
startDate?: string;
endDate?: string;
};
};
};
// Fetches data from the /analytics-summary endpoint
export async function getTransactionAnalyticsSummary(props: {
teamId: string;
clientId: string;
authToken: string;
}): Promise<TransactionSummaryData> {
const body = {};
const defaultData: TransactionSummaryData = {
totalCount: 0,
totalGasCostWei: "0",
totalGasUnitsUsed: "0",
};
try {
const response = await fetch(
`${NEXT_PUBLIC_ENGINE_CLOUD_URL}/v1/transactions/analytics-summary`,
{
body: JSON.stringify(body),
headers: {
Authorization: `Bearer ${props.authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
},
method: "POST",
},
);
if (!response.ok) {
if (response.status === 401 || response.status === 400) {
console.error("Unauthorized fetching transaction summary");
return defaultData;
}
const errorText = await response.text();
throw new Error(
`Error fetching transaction summary: ${response.status} ${response.statusText} - ${errorText}`,
);
}
const data = (await response.json()) as AnalyticsSummaryApiResponse;
return {
totalCount: data.result.summary.totalCount ?? 0,
totalGasCostWei: data.result.summary.totalGasCostWei ?? "0",
totalGasUnitsUsed: data.result.summary.totalGasUnitsUsed ?? "0",
};
} catch (error) {
console.error("Failed to fetch transaction summary:", error);
return defaultData;
}
}
@@ -1,157 +1,10 @@
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_ENGINE_CLOUD_URL } from "@/constants/public-envs";
import type { TransactionStats } from "@/types/analytics";
import type {
Transaction,
TransactionsResponse,
} from "../analytics/tx-table/types";
// Define the structure of the data we expect back from our fetch function
export type TransactionSummaryData = {
totalCount: number;
totalGasCostWei: string;
totalGasUnitsUsed: string; // Keep fetched data structure
};
// Define the structure of the API response
type AnalyticsSummaryApiResponse = {
result: {
summary: {
totalCount: number;
totalGasCostWei: string;
totalGasUnitsUsed: string;
};
metadata: {
startDate?: string;
endDate?: string;
};
};
};
// Fetches data from the /analytics-summary endpoint
export async function getTransactionAnalyticsSummary(props: {
teamId: string;
clientId: string;
}): Promise<TransactionSummaryData> {
const authToken = await getAuthToken();
const body = {};
const defaultData: TransactionSummaryData = {
totalCount: 0,
totalGasCostWei: "0",
totalGasUnitsUsed: "0",
};
try {
const response = await fetch(
`${NEXT_PUBLIC_ENGINE_CLOUD_URL}/v1/transactions/analytics-summary`,
{
body: JSON.stringify(body),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
},
method: "POST",
},
);
if (!response.ok) {
if (response.status === 401 || response.status === 400) {
console.error("Unauthorized fetching transaction summary");
return defaultData;
}
const errorText = await response.text();
throw new Error(
`Error fetching transaction summary: ${response.status} ${response.statusText} - ${errorText}`,
);
}
const data = (await response.json()) as AnalyticsSummaryApiResponse;
return {
totalCount: data.result.summary.totalCount ?? 0,
totalGasCostWei: data.result.summary.totalGasCostWei ?? "0",
totalGasUnitsUsed: data.result.summary.totalGasUnitsUsed ?? "0",
};
} catch (error) {
console.error("Failed to fetch transaction summary:", error);
return defaultData;
}
}
export async function getTransactionsChart({
teamId,
clientId,
from,
to,
interval,
}: {
teamId: string;
clientId: string;
from: string;
to: string;
interval: "day" | "week";
}): Promise<TransactionStats[]> {
const authToken = await getAuthToken();
const filters = {
endDate: to,
resolution: interval,
startDate: from,
};
const response = await fetch(
`${NEXT_PUBLIC_ENGINE_CLOUD_URL}/v1/transactions/analytics`,
{
body: JSON.stringify(filters),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": clientId,
"x-team-id": teamId,
},
method: "POST",
},
);
if (!response.ok) {
if (response.status === 401 || response.status === 400) {
return [];
}
// TODO - need to handle this error state, like we do with the connect charts
throw new Error(
`Error fetching transactions chart data: ${response.status} ${
response.statusText
} - ${await response.text().catch(() => "Unknown error")}`,
);
}
type TransactionsChartResponse = {
result: {
analytics: Array<{
timeBucket: string;
chainId: string;
count: number;
}>;
metadata: {
resolution: string;
startDate: string;
endDate: string;
};
};
};
const data = (await response.json()) as TransactionsChartResponse;
return data.result.analytics.map((stat) => ({
chainId: Number(stat.chainId),
count: stat.count,
date: stat.timeBucket,
}));
}
export async function getSingleTransaction({
teamId,
clientId,
@@ -0,0 +1,60 @@
import { configure, getSolanaWalletBalance } from "@thirdweb-dev/api";
import { THIRDWEB_API_HOST } from "@/constants/urls";
// Configure the API client to use the correct base URL
configure({
override: {
baseUrl: THIRDWEB_API_HOST,
},
});
export async function fetchSolanaBalance({
publicKey,
authToken,
clientId,
chainId = "solana:mainnet",
}: {
publicKey: string;
authToken: string;
clientId: string;
chainId?: "solana:mainnet" | "solana:devnet";
}): Promise<{
displayValue: string;
symbol: string;
value: string;
decimals: number;
} | null> {
try {
const response = await getSolanaWalletBalance({
path: {
address: publicKey,
},
query: {
chainId,
},
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": clientId,
},
});
if (response.error || !response.data) {
console.error(
"Error fetching Solana balance:",
response.error || "No data returned",
);
return null;
}
return {
displayValue: response.data.result.displayValue,
symbol: "SOL",
value: response.data.result.value,
decimals: response.data.result.decimals,
};
} catch (error) {
console.error("Error fetching Solana balance:", error);
return null;
}
}
@@ -1,14 +0,0 @@
import { getFiltersFromSearchParams } from "@/lib/time";
export function getTxAnalyticsFiltersFromSearchParams(params: {
from?: string | undefined | string[];
to?: string | undefined | string[];
interval?: string | undefined | string[];
}) {
return getFiltersFromSearchParams({
defaultRange: "last-30",
from: params.from,
interval: params.interval,
to: params.to,
});
}
@@ -7,10 +7,7 @@ import { ProjectPage } from "@/components/blocks/project-page/project-page";
import { NEXT_PUBLIC_THIRDWEB_VAULT_URL } from "@/constants/public-envs";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { TransactionsAnalyticsPageContent } from "./analytics/analytics-page";
import { EngineChecklist } from "./analytics/ftux.client";
import { TransactionAnalyticsSummary } from "./analytics/summary";
import { ServerWalletsTable } from "./components/server-wallets-table.client";
import { getTransactionAnalyticsSummary } from "./lib/analytics";
import type { Wallet } from "./server-wallets/wallet-table/types";
import { listSolanaAccounts } from "./solana-wallets/lib/vault.client";
import type { SolanaWallet } from "./solana-wallets/wallet-table/types";
@@ -110,12 +107,6 @@ export default async function TransactionsAnalyticsPage(props: {
const isSolanaPermissionError =
solanaAccounts.error?.message.includes("AUTH_INSUFFICIENT_SCOPE") ?? false;
const initialData = await getTransactionAnalyticsSummary({
clientId: project.publishableKey,
teamId: project.teamId,
}).catch(() => undefined);
const hasTransactions = initialData ? initialData.totalCount > 0 : false;
const client = getClientThirdwebClient({
jwt: authToken,
teamId: project.teamId,
@@ -152,41 +143,30 @@ export default async function TransactionsAnalyticsPage(props: {
}}
>
<div className="flex flex-col gap-10">
<EngineChecklist
isManagedVault={isManagedVault}
client={client}
hasTransactions={hasTransactions}
project={project}
teamSlug={params.team_slug}
testTxWithWallet={searchParams.testTxWithWallet as string | undefined}
testSolanaTxWithWallet={
searchParams.testSolanaTxWithWallet as string | undefined
}
wallets={wallets ?? []}
solanaWallets={solanaAccounts.data.items}
/>
{hasTransactions &&
!searchParams.testTxWithWallet &&
!searchParams.testSolanaTxWithWallet && (
<TransactionAnalyticsSummary
clientId={project.publishableKey}
initialData={initialData}
teamId={project.teamId}
/>
)}
{/* transactions */}
<TransactionsAnalyticsPageContent
client={client}
authToken={authToken}
project={project}
searchParams={searchParams}
showAnalytics={
hasTransactions &&
!searchParams.testTxWithWallet &&
!searchParams.testSolanaTxWithWallet
}
teamSlug={params.team_slug}
wallets={wallets}
wallets={wallets ?? []}
solanaWallets={solanaAccounts.data.items}
teamId={project.teamId}
isManagedVault={isManagedVault}
testTxWithWallet={
typeof searchParams.testTxWithWallet === "string"
? searchParams.testTxWithWallet
: undefined
}
testSolanaTxWithWallet={
typeof searchParams.testSolanaTxWithWallet === "string"
? searchParams.testSolanaTxWithWallet
: undefined
}
/>
{/* Server Wallets (EVM + Solana) */}
@@ -215,6 +195,7 @@ export default async function TransactionsAnalyticsPage(props: {
solanaWallets={solanaAccounts.data.items}
teamSlug={params.team_slug}
solanaPermissionError={isSolanaPermissionError}
authToken={authToken}
/>
)}
</div>
@@ -70,8 +70,8 @@ export function CreateSolanaWallet(props: {
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button
variant="default"
className="gap-2 rounded-full bg-foreground hover:bg-foreground/90"
variant="outline"
className="gap-1.5 rounded-full bg-background text-foreground"
disabled={props.disabled}
>
<PlusIcon className="size-4" />
@@ -87,6 +87,7 @@ const authMethodsToPickFrom: InAppWalletAuth[] = [
"line",
"x",
"tiktok",
"epic",
"coinbase",
"farcaster",
"telegram",
@@ -202,6 +202,7 @@ export default async function Page(props: {
solanaWallets={solanaAccounts.data.items}
teamSlug={params.team_slug}
solanaPermissionError={isSolanaPermissionError || false}
authToken={authToken}
/>
)}
</div>
@@ -20,7 +20,7 @@ export function BridgePageUI(props: {
<div className="grow flex flex-col">
<BridgePageHeader />
<div className="flex grow items-center justify-center px-4 relative pt-12 pb-20 lg:py-28 min-h-[calc(100dvh-60px)]">
<div className="flex grow items-center justify-center px-4 relative pt-12 pb-20 lg:py-28 min-h-[calc(100dvh-60px)] overflow-hidden">
<DotsBackgroundPattern />
<UniversalBridgeEmbed buyTab={props.buyTab} swapTab={props.swapTab} />
</div>
@@ -0,0 +1,72 @@
"use client";
import { Img } from "@workspace/ui/components/img";
import { MoonIcon, SunIcon } from "lucide-react";
import { useTheme } from "next-themes";
import { ClientOnly } from "@/components/blocks/client-only";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { payAppThirdwebClient } from "../constants";
export function PayIdPageHeader(props: {
projectName: string;
projectIcon: string | undefined;
}) {
return (
<div className="border-b border-border/70">
<header className="container flex max-w-7xl justify-between py-4">
<div className="flex items-center gap-3">
{props.projectIcon && (
<Img
src={
resolveSchemeWithErrorHandler({
uri: props.projectIcon,
client: payAppThirdwebClient,
}) || ""
}
alt=""
className="rounded-full size-6 object-cover"
/>
)}
<h2 className="text-xl font-semibold tracking-tight">
{props.projectName}
</h2>
</div>
<div className="flex items-center gap-3 lg:gap-5">
<ToggleThemeButton />
</div>
</header>
</div>
);
}
function ToggleThemeButton(props: { className?: string }) {
const { setTheme, theme } = useTheme();
return (
<ClientOnly
ssr={<Skeleton className="size-[36px] rounded-full border bg-accent" />}
>
<Button
aria-label="Toggle theme"
className={cn(
"h-auto w-auto rounded-full p-2 text-muted-foreground hover:text-foreground",
props.className,
)}
onClick={() => {
setTheme(theme === "dark" ? "light" : "dark");
}}
variant="ghost"
>
{theme === "light" ? (
<SunIcon className="size-5 " />
) : (
<MoonIcon className="size-5 " />
)}
</Button>
</ClientOnly>
);
}
+46 -161
View File
@@ -1,21 +1,16 @@
import { ShieldCheckIcon } from "lucide-react";
import { cn } from "@workspace/ui/lib/utils";
import type { Metadata } from "next";
import { ThemeProvider } from "next-themes";
import { Bridge, defineChain, toTokens } from "thirdweb";
import { getChainMetadata } from "thirdweb/chains";
import { shortenAddress } from "thirdweb/utils";
import { Bridge } from "thirdweb";
import { getPaymentLink } from "@/api/universal-bridge/links";
import { Badge } from "@/components/ui/badge";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import {
API_SERVER_SECRET,
DASHBOARD_THIRDWEB_SECRET_KEY,
} from "@/constants/server-envs";
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
import { resolveEns } from "@/lib/ens";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { PayPageWidget } from "../components/client/PayPageWidget.client";
import { payAppThirdwebClient } from "../constants";
import { PayIdPageHeader } from "./header";
const title = "thirdweb Pay";
const description = "Fast, secure, and simple payments.";
@@ -54,26 +49,10 @@ export default async function PayPage({
tokenAddress: paymentLink.destinationToken.address,
});
const chainPromise = getChainMetadata(
// eslint-disable-next-line no-restricted-syntax
defineChain(Number(paymentLink.destinationToken.chainId)),
);
const recipientPromise = resolveEns(
paymentLink.receiver,
getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
}),
);
const [tokens, projectMetadata, chain, recipientEnsOrAddress] =
await Promise.all([
tokensPromise,
projectMetadataPromise,
chainPromise,
recipientPromise,
]);
const [tokens, projectMetadata] = await Promise.all([
tokensPromise,
projectMetadataPromise,
]);
const token = tokens[0];
if (!token) {
@@ -81,144 +60,33 @@ export default async function PayPage({
}
return (
<div className="relative flex h-dvh w-full items-center justify-center">
<div className="relative flex flex-col min-h-dvh w-full">
<ThemeProvider
forcedTheme={theme === "light" ? "light" : "dark"}
defaultTheme={theme === "light" ? "light" : "dark"}
attribute="class"
disableTransitionOnChange
enableSystem={false}
>
<div className="flex z-10 flex-col lg:flex-row h-full w-full">
<header className="min-w-full lg:min-w-[500px] border-b lg:border-r lg:h-full bg-card flex flex-col gap-4 items-start p-4 lg:p-8">
<div>
<div className="flex flex-row items-center justify-start gap-4">
{projectMetadata.image && (
<img
src={
resolveSchemeWithErrorHandler({
uri: projectMetadata.image,
client: payAppThirdwebClient,
}) || ""
}
alt={projectMetadata.name}
width={25}
height={25}
className="rounded-full overflow-hidden"
/>
)}
<h2 className="text-xl font-bold">{projectMetadata.name}</h2>
</div>
{projectMetadata.description && (
<p className="mt-2 text-sm text-muted-foreground">
{projectMetadata.description}
</p>
)}
</div>
<PayIdPageHeader
projectIcon={projectMetadata.image || undefined}
projectName={projectMetadata.name}
/>
<div className="hidden lg:block my-4 w-full">
{paymentLink.amount && (
<div className="flex flex-col gap-1 w-full my-4">
<span className="text-muted-foreground text-xs">Details</span>
<div className="font-medium flex-row flex justify-between items-center w-full">
<div className="flex flex-row items-center gap-2">
{token.iconUri && (
<img
src={resolveSchemeWithErrorHandler({
uri: token.iconUri,
client: getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
}),
})}
alt={token.name}
width={25}
height={25}
className="size-5 rounded-full overflow-hidden"
/>
)}
{toTokens(BigInt(paymentLink.amount), token.decimals)}{" "}
{token.symbol}
</div>
{token.prices.USD && (
<span>
$
{(
Number(token.prices.USD) *
Number(
toTokens(
BigInt(paymentLink.amount),
token.decimals,
),
)
).toFixed(2)}
</span>
)}
</div>
</div>
)}
{chain && (
<div className="flex flex-col gap-1 w-full my-4">
<span className="text-muted-foreground text-xs">Network</span>
<div className="font-medium flex-row flex justify-between items-center w-full">
<div className="flex flex-row items-center gap-2">
{chain.icon?.url && (
<img
src={resolveSchemeWithErrorHandler({
uri: chain.icon.url,
client: getConfiguredThirdwebClient({
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
teamId: undefined,
}),
})}
alt={chain.name}
width={chain.icon.width}
height={chain.icon.height}
className="size-5 rounded-full overflow-hidden"
/>
)}
{chain.name}
</div>
</div>
</div>
)}
{recipientEnsOrAddress.ensName ||
(recipientEnsOrAddress.address && (
<div className="flex flex-col gap-1 w-full my-4">
<span className="text-muted-foreground text-xs">
Seller
</span>
<div className="font-medium flex-row flex justify-between items-center w-full">
{recipientEnsOrAddress.ensName ??
shortenAddress(recipientEnsOrAddress.address)}
</div>
</div>
))}
</div>
<div className="mt-auto hidden lg:block">
<Badge className="flex items-center gap-1.5 bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800">
<ShieldCheckIcon className="size-3" />
Secured by thirdweb
</Badge>
</div>
</header>
<main className="flex justify-center py-12 w-full items-center grow">
<PayPageWidget
amount={
paymentLink.amount ? BigInt(paymentLink.amount) : undefined
}
chainId={Number(paymentLink.destinationToken.chainId)}
clientId={undefined} // Payment links don't need to use the same client ID to be executed
image={paymentLink.imageUrl}
name={paymentLink.title}
paymentLinkId={id}
purchaseData={paymentLink.purchaseData}
recipientAddress={paymentLink.receiver}
redirectUri={redirectUri}
token={token}
/>
</main>
</div>
<main className="flex justify-center py-12 w-full items-center grow overflow-hidden relative">
<DotsBackgroundPattern />
<PayPageWidget
amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined}
chainId={Number(paymentLink.destinationToken.chainId)}
clientId={undefined} // Payment links don't need to use the same client ID to be executed
image={paymentLink.imageUrl}
name={paymentLink.title}
paymentLinkId={id}
purchaseData={paymentLink.purchaseData}
recipientAddress={paymentLink.receiver}
redirectUri={redirectUri}
token={token}
/>
</main>
</ThemeProvider>
</div>
);
@@ -237,7 +105,24 @@ async function getProjectMetadata(clientId: string) {
}
const { data } = (await response.json()) as {
data: { name: string; image: string | null; description: string | null };
data: { name: string; image: string | null };
};
return data;
}
function DotsBackgroundPattern(props: { className?: string }) {
return (
<div
className={cn(
"pointer-events-none absolute -inset-x-36 -inset-y-24 text-foreground/20 dark:text-muted-foreground/15 hidden lg:block",
props.className,
)}
style={{
backgroundImage: "radial-gradient(currentColor 1px, transparent 1px)",
backgroundSize: "24px 24px",
maskImage:
"radial-gradient(ellipse 100% 100% at 50% 50%, black 30%, transparent 50%)",
}}
/>
);
}
@@ -1,4 +1,5 @@
"use client";
import { cn } from "@workspace/ui/lib/utils";
import { payAppThirdwebClient } from "app/pay/constants";
import { useTheme } from "next-themes";
import { createThirdwebClient, NATIVE_TOKEN_ADDRESS, toTokens } from "thirdweb";
@@ -63,7 +64,12 @@ export function PayPageWidget({
client={
clientId ? createThirdwebClient({ clientId }) : payAppThirdwebClient
}
image={image}
className={cn(
"shadow-xl",
!image &&
"[&_.tw-header-image]:invert dark:[&_.tw-header-image]:invert-0",
)}
image={image || "/assets/pay/general-pay.png"}
name={name}
onSuccess={() => {
reportPaymentLinkBuySuccessful();
@@ -9,7 +9,12 @@ import { payLandingWallets } from "./wallets";
const client = getClientThirdwebClient();
export function StyledBuyWidget(
props: Omit<BuyWidgetProps, "client" | "theme">,
props: Omit<
BuyWidgetProps,
"client" | "theme" | "onSuccess" | "onError" | "onCancel"
> & {
successUrl?: string;
},
) {
const { theme } = useTheme();
@@ -22,6 +27,22 @@ export function StyledBuyWidget(
connectOptions={{
wallets: payLandingWallets,
}}
onSuccess={() => {
if (props.successUrl) {
try {
const url = new URL(props.successUrl);
url.searchParams.set("success", "true");
window.location.href = url.toString();
} catch (error) {
// Log URL construction error for debugging
console.error(
"Failed to construct redirect URL:",
props.successUrl,
error,
);
}
}
}}
/>
);
}
+30 -4
View File
@@ -21,19 +21,44 @@ type SearchParams = {
[key: string]: string | string[] | undefined;
};
const onlyAddress = (v: string) => (isAddress(v) ? v : undefined);
const onlyNumber = (v: string) =>
Number.isNaN(Number(v)) ? undefined : Number(v);
/**
* Validates and normalizes a URL string.
* Only allows http: and https: protocols with a valid hostname.
* @returns normalized URL string on success, undefined on failure
*/
const onlyUrl = (v: string): string | undefined => {
try {
const url = new URL(v);
// Only allow http or https protocols
if (url.protocol !== "http:" && url.protocol !== "https:") {
return undefined;
}
// Ensure hostname is non-empty
if (!url.hostname) {
return undefined;
}
// Return normalized URL
return url.toString();
} catch {
// Invalid URL format
return undefined;
}
};
export default async function PayPage(props: {
searchParams: Promise<SearchParams>;
}) {
const searchParams = await props.searchParams;
const onlyAddress = (v: string) => (isAddress(v) ? v : undefined);
const onlyNumber = (v: string) =>
Number.isNaN(Number(v)) ? undefined : Number(v);
const receiver = parse(searchParams.receiver, onlyAddress);
const token = parse(searchParams.token, onlyAddress);
const chain = parse(searchParams.chain, onlyNumber);
const amount = parse(searchParams.amount, onlyNumber);
const successUrl = parse(searchParams.successUrl, onlyUrl);
return (
<ThemeProvider
@@ -51,6 +76,7 @@ export default async function PayPage(props: {
tokenAddress={token}
receiverAddress={receiver}
amount={amount ? amount.toString() : undefined}
successUrl={successUrl}
/>
</div>
</div>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"dependencies": {
"@abstract-foundation/agw-react": "^1.6.4",
"@abstract-foundation/agw-react": "^1.10.0",
"@ai-sdk/react": "^2.0.25",
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-avatar": "^1.1.10",
@@ -18,8 +18,7 @@ export async function GET(request: NextRequest) {
// const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_SMART_WALLET as string;
const ENGINE_VAULT_ACCESS_TOKEN = process.env
.ENGINE_VAULT_ACCESS_TOKEN as string;
// const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
const API_URL = "http://localhost:3030";
const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
const twFacilitator = facilitator({
baseUrl: `${API_URL}/v1/payments/x402`,
@@ -42,7 +41,7 @@ export async function GET(request: NextRequest) {
const amount = queryParams.get("amount") || "0.01";
const payTo = queryParams.get("payTo") ?? undefined;
const tokenAddress = queryParams.get("tokenAddress") || token.address;
const tokenAddress = queryParams.get("tokenAddress");
const decimals = queryParams.get("decimals") || token.decimals.toString();
const waitUntil =
(queryParams.get("waitUntil") as "simulated" | "submitted" | "confirmed") ||
@@ -54,13 +53,15 @@ export async function GET(request: NextRequest) {
paymentData,
network: defineChain(Number(chainId)),
payTo,
price: {
amount: toUnits(amount, parseInt(decimals)).toString(),
asset: {
address: tokenAddress as `0x${string}`,
decimals: decimals ? parseInt(decimals) : token.decimals,
},
},
price: tokenAddress
? {
amount: toUnits(amount, parseInt(decimals)).toString(),
asset: {
address: tokenAddress as `0x${string}`,
decimals: decimals ? parseInt(decimals) : token.decimals,
},
}
: amount,
routeConfig: {
description: "Access to paid content",
},
@@ -43,7 +43,6 @@ export function X402RightSection(props: { options: X402PlaygroundOptions }) {
fetch,
THIRDWEB_CLIENT,
activeWallet,
BigInt(1 * 10 ** 18),
);
const searchParams = new URLSearchParams();
searchParams.set("chainId", props.options.chain.id.toString());
@@ -20,6 +20,7 @@ const allInAppWalletLoginMethods: InAppWalletAuth[] = [
"line",
"x",
"tiktok",
"epic",
"facebook",
"apple",
"coinbase",
+8
View File
@@ -742,6 +742,14 @@ const paymentRedirects = {
"/payments/swap": "/bridge/swap",
"/payments/tokens": "/bridge/tokens",
"/payments/routes": "/bridge/routes",
"/payments/x402": "/x402",
"/payments/x402/:path*": "/x402/:path*",
"/payments/fund": "/bridge/fund",
"/payments/products": "/bridge/products",
"/payments/transactions": "/bridge/transactions",
"/payments/send": "/bridge/send",
"/payments/webhooks": "/bridge/webhooks",
"/payments/custom-data": "/bridge/custom-data",
};
const contractRedirects = {
+2 -2
View File
@@ -40,8 +40,8 @@ const links = [
name: "Contracts",
},
{
href: "/payments",
name: "Payments",
href: "/x402",
name: "x402",
},
{
href: "/bridge",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 310 KiB

@@ -188,8 +188,8 @@ Any purchase data you provide to a payment will be included on all webhooks, sta
To connect with other auth strategies, use external wallets, or sponsor gas for users, check out the following guides:
- [Send a Payment](/payments/send)
- [Webhooks](/payments/webhooks)
- [Send a Payment](/bridge/send)
- [Webhooks](/bridge/webhooks)
## Explore Full API References
@@ -74,8 +74,8 @@ The BuyWidget handles the complete payment flow, supporting both crypto and fiat
## Going Further
- [Custom Payment Data](/payments/custom-data) - Attach metadata to payments
- [Webhooks](/payments/webhooks) - Get notified when payments complete
- [Custom Payment Data](/bridge/custom-data) - Attach metadata to payments
- [Webhooks](/bridge/webhooks) - Get notified when payments complete
## API Reference
+1 -1
View File
@@ -97,7 +97,7 @@ const preparedQuote = await Bridge.Buy.prepare({
The prepared quote will contain details about the payment, including the transactions needed to execute it.
You can execute the included transactions using the wallet of your choice. To learn how to send it using thirdweb Wallets, checkout the [Send a Payment](/payments/send) guide.
You can execute the included transactions using the wallet of your choice. To learn how to send it using thirdweb Wallets, checkout the [Send a Payment](/bridge/send) guide.
</TabsContent>
@@ -77,8 +77,8 @@ The CheckoutWidget handles the complete payment flow, supporting both crypto and
## Going Further
- [Custom Payment Data](/payments/custom-data) - Attach metadata to payments
- [Webhooks](/payments/webhooks) - Get notified when payments complete
- [Custom Payment Data](/bridge/custom-data) - Attach metadata to payments
- [Webhooks](/bridge/webhooks) - Get notified when payments complete
## API Reference
-172
View File
@@ -1,172 +0,0 @@
import {
Callout,
OpenSourceCard,
createMetadata,
InstallTabs,
SDKCard,
Grid,
ArticleIconCard,
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from "@doc";
import {
ReactIcon,
TypeScriptIcon,
UnityIcon,
DotNetIcon,
UnrealEngineIcon,
} from "@/icons";
import { ExternalLink } from "lucide-react";
export const metadata = createMetadata({
image: {
title: "Find Routes",
icon: "payments",
},
title: "Find Routes",
description: "Find available routes for bridging, swapping, and paying.",
});
# Find Routes
Use thirdweb's `Bridge.routes` utility to find all routes between tokens.
### Get All Routes
Retrieve all supported routes:
```typescript
import { Bridge } from "thirdweb";
const routes = await Bridge.routes({
client,
});
```
This returns an array of route objects with information about supported token pairs:
```typescript
[
{
destinationToken: {
address: "0x12c88a3C30A7AaBC1dd7f2c08a97145F5DCcD830",
chainId: 1,
decimals: 18,
iconUri: "https://assets.coingecko.com/coins/images/37207/standard/G.jpg",
name: "G7",
symbol: "G7",
},
originToken: {
address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
chainId: 480,
decimals: 18,
iconUri: "https://assets.relay.link/icons/1/light.png",
name: "Ether",
symbol: "ETH",
}
},
// ... more routes
]
```
### Filter Routes by Origin
Get all routes starting from a specific chain and token:
```typescript
import { Bridge } from "thirdweb";
// Get all routes starting from mainnet ETH
const routes = await Bridge.routes({
originChainId: 1,
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
client,
});
```
### Filter Routes by Destination
Find routes that end at a specific destination:
```typescript
import { Bridge } from "thirdweb";
// Get all routes ending at USDC on Optimism
const routes = await Bridge.routes({
destinationChainId: 10,
destinationTokenAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
client,
});
```
### Filter Routes by Both Origin and Destination
Find direct routes between specific token pairs:
```typescript
import { Bridge } from "thirdweb";
// Get routes from mainnet ETH to USDC on Optimism
const routes = await Bridge.routes({
originChainId: 1,
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
destinationChainId: 10,
destinationTokenAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
client,
});
```
### Pagination
For large result sets, use pagination to retrieve routes in chunks:
```typescript
import { Bridge } from "thirdweb";
// Get the first 10 routes starting from mainnet ETH
const routes = await Bridge.routes({
originChainId: 1,
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
limit: 10,
offset: 0,
client,
});
// Get the next 10 routes
const nextRoutes = await Bridge.routes({
originChainId: 1,
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
limit: 10,
offset: 10,
client,
});
```
### Limit Route Complexity
Control the maximum number of steps in the returned routes:
```typescript
import { Bridge } from "thirdweb";
// Get only direct routes (1 step)
const directRoutes = await Bridge.routes({
originChainId: 1,
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
maxSteps: 1,
client,
});
```
## Going further
To connect with other auth strategies, use external wallets, or sponsor gas for users, check out the following guides:
- [Send a Payment](/payments/send)
- [Get Token Prices](/payments/prices)
## Explore Full API References
- [Bridge.routes](/references/typescript/v5/routes)
+2 -3
View File
@@ -52,15 +52,14 @@ client,
});
```
You can execute this quote exactly like a buy. See the [Send a Payment](/payments/send) guide for more details on this process.
You can execute this quote exactly like a buy. See the [Send a Payment](/bridge/send) guide for more details on this process.
## Going further
To connect with other auth strategies, use external wallets, or sponsor gas for users, check out the following guides:
- [Send a Payment](/payments/send)
- [Get Routes](/payments/routes)
- [Send a Payment](/bridge/send)
## Explore Full API References
@@ -262,9 +262,8 @@ Once this code completes, your payment has been fully executed. Payments normall
## Going further
- [Sell a Product](/payments/products)
- [Token Prices](/payments/tokens)
- [Routes](/payments/routes)
- [Sell a Product](/bridge/products)
- [Token Prices](/bridge/tokens)
## Explore API References
+28 -8
View File
@@ -16,25 +16,45 @@ export const sidebar: SideBar = {
{
isCollapsible: false,
links: [
{
href: `${bridgeSlug}/sell`,
name: "Sell Tokens",
},
{
href: `${bridgeSlug}/swap`,
name: "Swap Tokens",
},
{
href: `${bridgeSlug}/bridge-widget-script`,
name: "BridgeWidget Script",
},
{
href: `${bridgeSlug}/fund`,
name: "Fund Wallets",
},
{
href: `${bridgeSlug}/products`,
name: "Sell a Product",
},
{
href: `${bridgeSlug}/transactions`,
name: "Pay for Transactions",
},
{
href: `${bridgeSlug}/send`,
name: "Send a Payment",
},
{
href: `${bridgeSlug}/sell`,
name: "Sell Tokens",
},
{
href: `${bridgeSlug}/tokens`,
name: "Get Token Prices",
},
{
href: `${bridgeSlug}/routes`,
name: "Get Routes",
href: `${bridgeSlug}/webhooks`,
name: "Webhooks",
},
{
href: `${bridgeSlug}/bridge-widget-script`,
name: "BridgeWidget Script",
href: `${bridgeSlug}/custom-data`,
name: "Custom Data",
},
],
name: "Guides",
+3 -110
View File
@@ -255,23 +255,6 @@ export class SwapManager {
throw new Error("Transaction timeout - please check status manually");
}
async getAvailableRoutes(params: {
originChainId?: number;
originTokenAddress?: string;
destinationChainId?: number;
destinationTokenAddress?: string;
limit?: number;
}) {
try {
return await Bridge.routes({
...params,
client: this.client,
});
} catch (error) {
throw new Error(`Failed to get routes: ${error.message}`);
}
}
}
```
@@ -319,92 +302,6 @@ async function swapEthToMatic(amount: string, userAccount: Account) {
}
```
### Advanced Price Comparison
Compare prices across multiple routes to find the best deal:
```typescript
interface RouteComparison {
route: any;
quote: SwapQuote;
priceImpact: number;
gasEstimate: bigint;
}
export class PriceComparator {
private swapManager: SwapManager;
constructor(swapManager: SwapManager) {
this.swapManager = swapManager;
}
async compareRoutes(
fromToken: { chainId: number; address: string },
amount: bigint,
userAddress: string
): Promise<RouteComparison[]> {
// Get available routes
const routes = await this.swapManager.getAvailableRoutes({
originChainId: fromToken.chainId,
originTokenAddress: fromToken.address,
limit: 10,
});
// Get quotes for each route
const comparisons: RouteComparison[] = [];
for (const route of routes) {
try {
const quote = await this.swapManager.getQuote({
fromChainId: route.originToken.chainId,
fromTokenAddress: route.originToken.address,
toChainId: route.destinationToken.chainId,
toTokenAddress: route.destinationToken.address,
amount,
userAddress,
client: this.swapManager.client,
});
const priceImpact = this.calculatePriceImpact(quote, amount);
const gasEstimate = this.estimateGasCosts(quote);
comparisons.push({
route,
quote,
priceImpact,
gasEstimate,
});
} catch (error) {
console.warn(`Failed to get quote for route ${route.originToken.symbol} -> ${route.destinationToken.symbol}:`, error);
}
}
// Sort by best output amount
return comparisons.sort((a, b) =>
Number(b.quote.destinationAmount) - Number(a.quote.destinationAmount)
);
}
private calculatePriceImpact(quote: SwapQuote, inputAmount: bigint): number {
// Simplified price impact calculation
const ratio = Number(quote.destinationAmount) / Number(quote.originAmount);
return Math.abs(1 - ratio) * 100; // Percentage
}
private estimateGasCosts(quote: SwapQuote): bigint {
// Sum up gas estimates from all transactions
return quote.steps.reduce((total, step) => {
return total + step.transactions.reduce((stepTotal: bigint, tx: any) => {
return stepTotal + (tx.gasLimit || 200000n); // Default estimate
}, 0n);
}, 0n);
}
}
```
<Callout variant="info">
**Gas Optimization Tip:** For frequently traded pairs, consider caching route data to reduce API calls and improve UI responsiveness.
</Callout>
</TabsContent>
</Tabs>
@@ -415,20 +312,16 @@ export class PriceComparator {
- **Cross-chain swaps** - Exchange tokens across 50+ supported chains
- **Real-time quotes** - Get up-to-date pricing and execution estimates
- **Route optimization** - Find the best path for any token pair
- **Price comparison** - Compare multiple routes to maximize output
- **Status tracking** - Monitor cross-chain transaction progress
## Going Further
- [Send a Payment](/payments/send) - Learn about peer-to-peer transfers
- [Get Routes](/payments/routes) - Explore route discovery APIs
- [Token Prices](/payments/tokens) - Access real-time token pricing
- [Webhooks](/payments/webhooks) - Get notified when swaps complete
- [Send a Payment](/bridge/send) - Learn about peer-to-peer transfers
- [Token Prices](/bridge/tokens) - Access real-time token pricing
- [Webhooks](/bridge/webhooks) - Get notified when swaps complete
## API Reference
- [Bridge.Buy.prepare](/references/typescript/v5/buy/prepare)
- [Bridge.routes](/references/typescript/v5/routes)
- [Bridge.status](/references/typescript/v5/status)
+12 -3
View File
@@ -59,7 +59,17 @@ This returns token information with current price:
symbol: "USDC",
name: "USD Coin",
iconUri: "https://assets.coingecko.com/coins/images/6319/large/USD_Coin_icon.png",
priceUsd: 1.00
prices: {
USD: 1.00,
EUR: 0.86,
GBP: 0.74,
JPY: 148.85,
KRW: 1393.14,
CNY: 7.18,
INR: 86.20,
NOK: 10.18,
SEK: 9.68,
}
}
]
```
@@ -118,8 +128,7 @@ const nextTokens = await Bridge.tokens({
To connect with other auth strategies, use external wallets, or sponsor gas for users, check out the following guides:
- [Send a Payment](/payments/send)
- [Get Routes](/payments/routes)
- [Send a Payment](/bridge/send)
## Explore Full API References
@@ -86,8 +86,8 @@ The TransactionWidget handles the complete payment flow, supporting both crypto
## Going Further
- [Custom Payment Data](/payments/custom-data) - Attach metadata to payments
- [Webhooks](/payments/webhooks) - Get notified when payments complete
- [Custom Payment Data](/bridge/custom-data) - Attach metadata to payments
- [Webhooks](/bridge/webhooks) - Get notified when payments complete
## API Reference
@@ -221,9 +221,8 @@ async function verifyWebhook(
To connect with other auth strategies, use external wallets, or sponsor gas for users, check out the following guides:
- [Send a Payment](/payments/send)
- [Find Routes](/payments/routes)
- [Get Token Prices](/payments/tokens)
- [Send a Payment](/bridge/send)
- [Get Token Prices](/bridge/tokens)
## Explore Full API References
@@ -0,0 +1,160 @@
import { Step, Steps, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "Stylus Minting Modules | thirdweb Documentation",
description:
"Customizable rust modules written for thirdweb's modular contracts",
image: {
title: "Stylus Minting Modules",
icon: "contracts",
},
});
# Stylus Minting Modules
You can now install rust based modules into a solidity based [modular contract](https://github.com/thirdweb-dev/modular-contracts/blob/dev/design-document.md).
This plug-n-play model also allows users to write their own custom minting logic in rust, taking advantage of Stylus' interoperability and gas savings. These modules work with existing router contracts and modules written in solidity.
## Deploy through dashboard
Deploying a Modular Contract and installing modules is easy through the thirdweb dashboard and ideal when you don't want to modify any code on the contract.
<Steps>
<Step title="Select Modular Contract for Stylus">
Navigate to the Modular Contracts section on Explore and select any token contract for your project.
</Step>
<Step title="Deploy Contract">
Select Arbitrum Sepolia or any other Stylus-supported network, then select Deploy. Once you have deployed one of these contracts, the modules come pre-installed, both solidity and rust ones.
</Step>
<Step title="Edit Modules">
If you wish to install a custom module, navigate to modules tab on your contract page. Enter the publisher address and select module from the dropdown, and click install.
You can uninstall a module from the same page.
Here's a list of prebuilt Rust / Stylus specific modules provided by thirdweb:
- [Mintable ERC20](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusMintableERC20)
- [Transferable ERC20](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusTransferableERC20)
- [Mintable ERC721](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusMintableERC721)
- [Transferable ERC721](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusTransferableERC721)
- [Mintable ERC1155](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusMintableERC1155)
- [Transferable ERC1155](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusTransferableERC1155)
You can customize these modules via thirdweb CLI as described below.
</Step>
</Steps>
## Build with CLI
If you want to modify the contract code or publish a custom module, you can use the thirdweb CLI.
<Steps>
<Step title="Create a new Stylus module">
In your CLI, run the following command to create a new directory with an template module. Select the module template from the list shown.
```bash
npx thirdweb create-stylus
```
</Step>
<Step title="Modify logic">
In the `src/lib.rs` file you can modify the contract logic such as adding fees, gating logic, analytics events, and more.
</Step>
<Step title="Build & Test">
To build your project, run the following command:
```bash
cargo stylus build
```
</Step>
<Step title="Deploy or Publish Your Module">
You can publish your module and install it to your modular contract. Publishing stores your module metadata in thirdweb’s on-chain registry so anyone (including you) can use that exact version later with a few clicks.
To publish your module, ensure you have your thirdweb secret key from your created project, then run the following command:
```bash
npx thirdweb publish-stylus -k YOUR_TW_SECRET_KEY
```
Once published, the module is now available to install via thirdweb dashboard from the modules tab of your contract.
</Step>
</Steps>
## Interacting with the Contract
Using the thirdweb SDKs, you can interact with your Stylus Modular contracts / modules to mint tokens, transfer ownership, and more.
The following describes how to interact with a ERC721 modular contract using thirdweb SDK. (Swap for ERC20 or ERC1155 variants as needed.)
### Upload metadata
```ts
import { BatchMetadataERC721 } from "thirdweb/modules";
const transaction = BatchMetadataERC721.uploadMetadata({
contract,
metadatas: [
{ name: "My NFT", description: "This is my NFT" },
],
});
await sendTransaction({
transaction,
account,
});
```
### Mint with role
```typescript
import { MintableERC721 } from "thirdweb/modules";
const transaction = MintableERC721.mintWithRole({
contract,
to: "0x...", // Address to mint tokens to
nfts: [
{
name: "My NFT",
description: "This is my NFT",
image: "ipfs://...",
},
],
});
// Send the transaction
await sendTransaction({ transaction, account });
```
### Set transfer rules
```ts
import { sendTransaction } from "thirdweb";
import { TransferableERC20 } from "thirdweb/modules";
const transaction = TransferableERC20.setTransferable({
contract,
enableTransfer: ...,
overrides: {
...
}
});
// Send the transaction
await sendTransaction({ transaction, account });
```
### Resources
- [Modular Contracts Design Doc](https://github.com/thirdweb-dev/modular-contracts/blob/dev/design-document.md)
@@ -0,0 +1,240 @@
import { Step, Steps, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "Stylus Zk Mint Contract | thirdweb Documentation",
description:
"Stylus Zk Minting contract and app template",
image: {
title: "Stylus Zk Mint",
icon: "contracts",
},
});
# ZK Minting with Stylus
You can now create and deploy ZK Proof based token contracts with Stylus. The templates provide you with Stylus contracts, ZK circuit files, and a ready-to-deploy Next.js app + API for proof generation and minting.
Follow the steps below to learn how to build a privacy-preserving ERC721 token system using Zero-Knowledge proofs on Arbitrum Stylus. Users can mint tokens by proving they own a minimum amount of ETH without revealing their exact balance. However, you can customize the circuits and the contracts to add your own checks for minting.
## What you'll build
- ZK Circuit: Proves token ownership without revealing exact balances
- Stylus Contract: Rust-based ERC721 contract that verifies ZK proofs
- Frontend: Next.js app for generating proofs and minting tokens
- Oracle System: Secure balance verification mechanism
## Prerequisites
- [Create a project on your thirdweb account](https://thirdweb.com/dashboard)
- Install thirdweb CLI by running `npm install -g thirdweb`
- Install Rust tool chain by running `curl https://sh.rustup.rs -sSf | sh` or visit rust-lang.org
- Install solc by running `npm install -g solc` or visit soliditylang.org
- Install Node version 18 or higher
<Steps>
<Step title="Project Setup">
In your CLI, run the following command to create a new directory with an template module. Select the module template from the list shown.
```bash
npx thirdweb create-stylus
```
Select **"Stylus ZK ERC721"** from the dropdown menu. This will:
- Clone the repository to your machine
- Set up the project structure
- Install basic dependencies
</Step>
<Step title="Install Dependencies">
Install dependencies for all components:
```bash
# Install root dependencies
pnpm install
# Install circuit dependencies
cd circuits
pnpm install
cd ..
# Install frontend dependencies
cd app
pnpm install
cd ..
```
</Step>
<Step title="Generate Cryptographic Keys">
Run the setup script to generate oracle keys and build the ZK circuit:
```bash
chmod +x setup.sh
./setup.sh
```
This script will:
- Generate a random oracle secret key
- Inject the secret into the ZK circuit
- Compile the circuit with circom
- Generate proving and verification keys
- Create the trusted setup for Groth16
**⚠️ Important**: The oracle secret is critical for security. Keep it private!
</Step>
<Step title="Deploy the Contract">
### Using thirdweb CLI
```bash
cd contracts
npx thirdweb@latest deploy-stylus -k <THIRDWEB_SECRET_KEY>
```
### Using Stylus CLI (Alternative)
```bash
cd contracts
cargo stylus deploy --endpoint arbitrum-sepolia
```
Copy the deployed contract address - you'll need it for the frontend.
</Step>
<Step title="Configure the Frontend">
Update the contract address in your frontend:
```bash
cd app
# Edit pages/index.tsx or lib/config.ts
# Update ZK_MINT_CONTRACT_ADDRESS with your deployed address
```
Create environment file:
```bash
# app/.env.local
RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
ORACLE_SECRET_KEY=<your_oracle_secret_from_setup>
```
</Step>
<Step title="Run the Application">
```bash
cd app
pnpm dev
# Visit http://localhost:3000
```
</Step>
<Step title="Test the System">
1. **Connect Wallet**: Connect to Arbitrum Sepolia testnet
2. **Generate Proof**: Click "Generate ZK Proof" - this proves you have sufficient balance
3. **Mint Tokens**: Use the proof to mint ERC721 tokens
</Step>
</Steps>
## Customizing the ZK Logic
The above described logic for ZK verification of user balance is provided as a template. You can modify the logic and write your own custom ZK verification code and circuits as described below.
### Understanding the Circuit
The core circuit (`circuits/token_ownership.circom`) has these components:
```txt
template TokenOwnership(oracle_secret) {
// Private inputs (hidden from public)
signal input actual_balance; // Real balance from oracle
signal input salt; // Randomness for uniqueness
// Public inputs (visible on-chain)
signal input min_required_balance; // Minimum threshold
signal input token_contract_hash; // Which token to check
signal input user_address_hash; // User's address hash
signal input timestamp; // When oracle signed data
signal input oracle_commitment; // Oracle's commitment
// Output
signal output nullifier; // Prevents replay attacks
}
```
### Customization Examples
#### 1. Change Balance Threshold Logic
Replace the balance check with custom logic:
```txt
// Original: Simple greater-than check
component gte = GreaterEqThan(64);
gte.in[0] <== actual_balance;
gte.in[1] <== min_required_balance;
gte.out === 1;
// Custom: Logarithmic scaling
component log_check = LogarithmicCheck();
log_check.balance <== actual_balance;
log_check.threshold <== min_required_balance;
log_check.out === 1;
```
#### 2. Add Multiple Token Support
Extend the circuit to verify multiple token balances:
```txt
template MultiTokenOwnership(oracle_secret, num_tokens) {
signal input actual_balances[num_tokens];
signal input min_required_balances[num_tokens];
signal input token_addresses[num_tokens];
// Verify each token separately
component checks[num_tokens];
for (var i = 0; i < num_tokens; i++) {
checks[i] = GreaterEqThan(64);
checks[i].in[0] <== actual_balances[i];
checks[i].in[1] <== min_required_balances[i];
checks[i].out === 1;
}
}
```
#### 3. Add Time-Based Constraints
Add expiration logic to proofs:
```txt
// Add time validation
component time_check = LessThan(64);
time_check.in[0] <== timestamp;
time_check.in[1] <== max_timestamp; // New public input
time_check.out === 1;
```
### Rebuilding After Changes
After modifying the circuit:
```bash
cd circuits
pnpm run build
# Re-inject verification keys
cd ..
node scripts/inject-vk.js
# Recompile contract
cd contracts
cargo build
```
### Resources
- [thirdweb Arbitrum Documentation](https://portal.thirdweb.com/contracts/arbitrum-stylus/stylus-contract)
- [Arbitrum Stylus Documentation](https://docs.arbitrum.io/stylus/concepts/how-it-works)
@@ -56,6 +56,14 @@ export const sidebar: SideBar = {
href: `${slug}/arbitrum-stylus/airdrop-contract`,
name: "Stylus Airdrop Contract",
},
{
href: `${slug}/arbitrum-stylus/zk-mint`,
name: "Stylus Zk Mint",
},
{
href: `${slug}/arbitrum-stylus/minting-modules`,
name: "Stylus Minting Modules",
},
],
name: "Arbitrum Stylus",
},
+6 -6
View File
@@ -41,14 +41,14 @@ export default function Page() {
function Hero() {
return (
<section className="grid gap-4 py-14 lg:grid-cols-[1fr_420px] lg:py-0">
<section className="grid gap-4 pt-14 pb-6 lg:grid-cols-[1fr_420px] lg:py-0">
{/* Left */}
<div className="flex flex-col justify-center">
<div>
<h1 className="mb-3 font-bold text-4xl tracking-tighter lg:text-6xl">
thirdweb Documentation
</h1>
<p className="mb-8 max-w-lg text-lg text-muted-foreground leading-normal text-pretty">
<p className="mb-6 max-w-lg text-base lg:text-lg text-muted-foreground leading-normal text-pretty">
Platform for building the next generation of internet products
</p>
<div className="flex gap-3">
@@ -178,10 +178,10 @@ function LearningResourcesSection() {
title="Wallets"
/>
<ArticleCardIndex
description="Create onchain payments and sell products"
href="/payments"
icon={PaymentsIcon}
title="Payments"
description="Instant payments for APIs, websites, and agents"
href="/x402"
icon={ZapIcon}
title="x402"
/>
<ArticleCardIndex
description="Bridge, swap, and onramp tokens on any chain"
-31
View File
@@ -49,10 +49,6 @@ export const sidebar: SideBar = {
href: `/bridge/tokens`,
name: "Get Token Prices",
},
{
href: `/bridge/routes`,
name: "Get Routes",
},
{
href: `${paymentsSlug}/webhooks`,
name: "Webhooks",
@@ -65,33 +61,6 @@ export const sidebar: SideBar = {
name: "Guides",
},
{ separator: true },
{
isCollapsible: false,
links: [
{
href: `${paymentsSlug}/x402`,
name: "Get Started",
},
{
href: `${paymentsSlug}/x402/client`,
name: "Client Side",
},
{
href: `${paymentsSlug}/x402/server`,
name: "Server Side",
},
{
href: `${paymentsSlug}/x402/agents`,
name: "Agents",
},
{
href: `${paymentsSlug}/x402/facilitator`,
name: "Facilitator",
},
],
name: "x402",
},
{ separator: true },
{
isCollapsible: false,
links: [
+4
View File
@@ -44,3 +44,7 @@ The admin key is temporarily stored in dashboard when performing actions that re
Please note, you should never share your admin key with third parties.
</Details>
<Details summary="Is Vault open source?">
Vault is not yet open source, but we plan to release it soon.
</Details>
+1 -1
View File
@@ -12,7 +12,7 @@ export default async function Layout(props: { children: React.ReactNode }) {
export const metadata = createMetadata({
description:
"Vault is an open-source non-custodial key management service, secured with TEE architecture (AWS Nitro Enclaves) and designed for blockchain applications.",
"Vault is a non-custodial key management service secured with TEE architecture (AWS Nitro Enclaves), designed for blockchain applications.",
title: "Vault",
image: {
icon: "vault",
+2 -2
View File
@@ -5,7 +5,7 @@ import { ArrowLeftRightIcon, UserLockIcon, UsersIcon, WalletIcon } from "lucide-
export const metadata = createMetadata({
title: "thirdweb Vault",
description: "An open-source, secure key management service for storing, managing, and protecting cryptographic keys and secrets.",
description: "A secure key management service for storing, managing, and protecting cryptographic keys and secrets.",
image: {
icon: "vault",
title: "thirdweb Vault",
@@ -14,7 +14,7 @@ export const metadata = createMetadata({
# Vault
Vault is an open-source non-custodial key management service, secured with TEE architecture (AWS Nitro Enclaves) and designed for blockchain applications.
Vault is a non-custodial key management service secured with TEE architecture (AWS Nitro Enclaves), designed for blockchain applications.
## Features
@@ -75,7 +75,7 @@ The client library wraps the native `fetch` API and handles:
</TabsContent>
<TabsContent value="http">
## Prepare Payment
## Fetch with Payment
Fetch any x402-compatible API and automatically handle payment flows when APIs return a `402 Payment Required` response using the authenticated wallet.
@@ -27,7 +27,9 @@ You can view all transactions processed by your facilitator in your project dash
## Chain and token support
Our facilitator supports payments on any EVM chain, as long as the payment token supports either:
The thirdweb x402 client/server stack supports payments on [170+ EVM chains](https://thirdweb.com/chainlist?service=account-abstraction).
The payment token must support either:
- ERC-2612 permit (most ERC20 tokens)
- ERC-3009 sign with authorization (USDC on all chains)
+21
View File
@@ -0,0 +1,21 @@
import { createMetadata } from "@doc";
import { DocLayout } from "@/components/Layouts/DocLayout";
import { sidebar } from "./sidebar";
export default async function Layout(props: { children: React.ReactNode }) {
return (
<DocLayout editPageButton={true} sideBar={sidebar}>
{props.children}
</DocLayout>
);
}
export const metadata = createMetadata({
description:
"Instant payments for your APIs, websites, and autonomous agents. x402 is an open-source protocol that turns the HTTP 402 status code into a fully-featured, on-chain payment layer.",
image: {
icon: "payments",
title: "x402 Payments Docs",
},
title: "x402 Payments Documentation",
});
@@ -12,9 +12,8 @@ export const metadata = createMetadata({
# x402 Payments
x402 is an open-source protocol that turns the dormant HTTP 402 Payment Required status code into a fully-featured, on-chain payment layer for APIs, websites, and autonomous agents.
[x402](https://x402.org) is an open-source protocol that turns the dormant HTTP 402 Payment Required status code into a fully-featured, on-chain payment layer for APIs, websites, and autonomous agents.
Learn more about the protocol at [x402.org](https://x402.org).
<ArticleIconCard
title="x402 Playground"
@@ -23,6 +22,15 @@ Learn more about the protocol at [x402.org](https://x402.org).
href="https://playground.thirdweb.com/payments/x402"
/>
## Chain and token support
The thirdweb x402 client/server stack supports payments on [170+ EVM chains](https://thirdweb.com/chainlist?service=account-abstraction).
The payment token must support either:
- ERC-2612 permit (most ERC20 tokens)
- ERC-3009 sign with authorization (USDC on all chains)
## Client Side
`wrapFetchWithPayment` wraps the native fetch API to automatically handle `402 Payment Required` responses from any x402-compatible API:
@@ -42,7 +50,7 @@ const fetchWithPay = wrapFetchWithPayment(fetch, client, wallet);
const response = await fetchWithPay('https://api.example.com/paid-endpoint');
```
You can also use the thirdweb API to fetch any x402 compatible endpoint and pay for it with the authenticated wallet. See the [client side docs](/payments/x402/client) for more details.
You can also use the thirdweb API to fetch any x402 compatible endpoint and pay for it with the authenticated wallet. See the [client side docs](/x402/client) for more details.
## Server Side
@@ -88,7 +96,7 @@ export async function GET(request: Request) {
}
```
You can also create middlewares to handle payment for multiple endpoints, see the [server side docs](/payments/x402/server) for more details. The [facilitator](/payments/x402/facilitator) handles settling the payment onchain using your own server wallet.
You can also create middlewares to handle payment for multiple endpoints, see the [server side docs](/x402/server) for more details. The [facilitator](/x402/facilitator) handles settling the payment onchain using your own server wallet.
## Going Further
@@ -97,18 +105,18 @@ You can also create middlewares to handle payment for multiple endpoints, see th
title="Client Side"
description="Learn how to interact with x402-compatible APIs from your client applications"
icon={TypeScriptIcon}
href="/payments/x402/client"
href="/x402/client"
/>
<ArticleIconCard
title="Server Side"
description="Accept x402 payments in your APIs with middleware or dedicated endpoints"
icon={EngineIcon}
href="/payments/x402/server"
href="/x402/server"
/>
<ArticleIconCard
title="Facilitator API"
description="Configure the facilitator service to verify and settle payments"
icon={EngineIcon}
href="/payments/x402/facilitator"
href="/x402/facilitator"
/>
</div>
@@ -421,23 +421,31 @@ Protect multiple endpoints with a shared middleware:
</TabsContent>
</Tabs>
## Price Configuration
## Price and Token Configuration
You can specify prices in multiple ways:
### USD String
This will default to using USDC on the specified network.
```typescript
price: "$0.10" // 10 cents in USD
network: polygon, // or any other EVM chain
price: "$0.10" // 10 cents in USDC
```
### ERC20 Token
You can use any ERC20 token that supports the ERC-2612 permit or ERC-3009 sign with authorization.
Simply pass the amount in base units and the token address.
```typescript
network: arbitrum,
price: {
amount: "100000", // Amount in base units (100000 = 0.1 USDC with 6 decimals)
amount: "1000000000000000", // Amount in base units (0.001 tokens with 18 decimals)
asset: {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // Token address
address: "0xf01E52B0BAC3E147f6CAf956a64586865A0aA928", // Token address
}
}
```

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 376 KiB

+77
View File
@@ -0,0 +1,77 @@
import { ExternalLinkIcon, ZapIcon } from "lucide-react";
import type { SideBar } from "@/components/Layouts/DocLayout";
import { EngineIcon, ReactIcon, TypeScriptIcon, UnityIcon } from "@/icons";
import { UnrealEngineIcon } from "../../icons/sdks/UnrealEngineIcon";
const x402Slug = "/x402";
export const sidebar: SideBar = {
links: [
{
href: x402Slug,
name: "Get Started",
icon: <ZapIcon />,
},
{
href: "https://playground.thirdweb.com/payments/x402",
icon: <ExternalLinkIcon />,
name: "Playground",
},
{ separator: true },
{
isCollapsible: false,
links: [
{
href: `${x402Slug}/client`,
name: "Client Side",
},
{
href: `${x402Slug}/server`,
name: "Server Side",
},
{
href: `${x402Slug}/agents`,
name: "Agents",
},
{
href: `${x402Slug}/facilitator`,
name: "Facilitator",
},
],
name: "Guides",
},
{ separator: true },
{
isCollapsible: false,
links: [
{
href: `https://payments.thirdweb.com/reference`,
icon: <EngineIcon />,
name: "REST API",
},
{
href: "/references/typescript/v5",
icon: <TypeScriptIcon />,
name: "TypeScript",
},
{
href: "/references/typescript/v5",
icon: <ReactIcon />,
name: "React",
},
{
href: "/unity",
icon: <UnityIcon />,
name: "Unity",
},
{
href: "/unreal-engine",
icon: <UnrealEngineIcon />,
name: "Unreal Engine",
},
],
name: "API References",
},
],
name: "x402",
};
@@ -15,6 +15,7 @@ const authOptions: InAppWalletAuth[] = [
"facebook",
"x",
"tiktok",
"epic",
"discord",
"telegram",
"twitch",
@@ -29,6 +29,9 @@ type AuthMethod =
| "telegram"
| "farcaster"
| "github"
| "line"
| "tiktok"
| "epic"
| "passkey"
| "wallet"
| "guest"
@@ -55,6 +58,9 @@ const authMethods: { id: AuthMethod; label: string; description: string }[] = [
{ id: "telegram", label: "Telegram", description: "Telegram OAuth" },
{ id: "farcaster", label: "Farcaster", description: "Farcaster OAuth" },
{ id: "github", label: "GitHub", description: "GitHub OAuth" },
{ id: "line", label: "Line", description: "Line OAuth" },
{ id: "tiktok", label: "TikTok", description: "TikTok OAuth" },
{ id: "epic", label: "Epic Games", description: "Epic Games OAuth" },
{ id: "passkey", label: "Passkey", description: "WebAuthn passkey" },
{
id: "wallet",
@@ -244,6 +250,9 @@ const account = await wallet.connect({
case "telegram":
case "farcaster":
case "github":
case "line":
case "tiktok":
case "epic":
return (
baseSetup +
`// ${authMethod.charAt(0).toUpperCase() + authMethod.slice(1)} OAuth
@@ -561,6 +570,9 @@ function PrebuiltUIExample() {
case "telegram":
case "farcaster":
case "github":
case "line":
case "tiktok":
case "epic":
return (
baseSetup +
` await wallet.connect({\n client,\n strategy: "${authMethod}",\n });\n` +
@@ -639,7 +651,10 @@ var address = await wallet.LoginWithOtp("123456");`
case "x":
case "telegram":
case "farcaster":
case "github": {
case "github":
case "line":
case "tiktok":
case "epic": {
const providerMap: Record<string, string> = {
google: "Google",
apple: "Apple",
@@ -649,6 +664,9 @@ var address = await wallet.LoginWithOtp("123456");`
telegram: "Telegram",
farcaster: "Farcaster",
github: "Github",
line: "Line",
tiktok: "TikTok",
epic: "Epic",
};
return (
baseSetup +
@@ -746,7 +764,10 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options);`
case "x":
case "telegram":
case "farcaster":
case "github": {
case "github":
case "line":
case "tiktok":
case "epic": {
const providerMap: Record<string, string> = {
google: "Google",
apple: "Apple",
@@ -756,6 +777,9 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options);`
telegram: "Telegram",
farcaster: "Farcaster",
github: "Github",
line: "Line",
tiktok: "TikTok",
epic: "Epic",
};
return (
baseSetup +
+128
View File
@@ -1,5 +1,133 @@
# wagmi-inapp
## 0.0.30
### Patch Changes
- Updated dependencies [[`f964b8a`](https://github.com/thirdweb-dev/js/commit/f964b8aaff22d4078ff4c4c5e09c6ebb5fcb182c), [`424e6b8`](https://github.com/thirdweb-dev/js/commit/424e6b8f9b071d3629ff0291ae6875fa4e4c882e), [`763d3de`](https://github.com/thirdweb-dev/js/commit/763d3de5a8db19a8022b2eda420f13a6787de100)]:
- thirdweb@5.111.10
- @thirdweb-dev/wagmi-adapter@0.2.185
## 0.0.29
### Patch Changes
- Updated dependencies [[`673e51b`](https://github.com/thirdweb-dev/js/commit/673e51b11cf2d06f00ab92bd4cbb45c51d95ec1f)]:
- thirdweb@5.111.9
- @thirdweb-dev/wagmi-adapter@0.2.184
## 0.0.28
### Patch Changes
- Updated dependencies [[`1f440d0`](https://github.com/thirdweb-dev/js/commit/1f440d0c97adf14d26d55666257b57541b2ef01a)]:
- thirdweb@5.111.8
- @thirdweb-dev/wagmi-adapter@0.2.183
## 0.0.27
### Patch Changes
- Updated dependencies [[`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8), [`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8)]:
- thirdweb@5.111.7
- @thirdweb-dev/wagmi-adapter@0.2.182
## 0.0.26
### Patch Changes
- Updated dependencies []:
- thirdweb@5.111.6
- @thirdweb-dev/wagmi-adapter@0.2.181
## 0.0.25
### Patch Changes
- Updated dependencies [[`7da16cf`](https://github.com/thirdweb-dev/js/commit/7da16cff2ff3aaa80c7fe5614639d950d88b5bf2)]:
- thirdweb@5.111.5
- @thirdweb-dev/wagmi-adapter@0.2.180
## 0.0.24
### Patch Changes
- Updated dependencies [[`d907017`](https://github.com/thirdweb-dev/js/commit/d907017217a0927e7f69eab0470b1fe120b4d1a1), [`40781b8`](https://github.com/thirdweb-dev/js/commit/40781b8170a1014ef76e438ba3c8244137dba7ea)]:
- thirdweb@5.111.4
- @thirdweb-dev/wagmi-adapter@0.2.179
## 0.0.23
### Patch Changes
- Updated dependencies [[`207cb9d`](https://github.com/thirdweb-dev/js/commit/207cb9dbeb82411809bafa2a8e833b1f10414b92)]:
- thirdweb@5.111.3
- @thirdweb-dev/wagmi-adapter@0.2.178
## 0.0.22
### Patch Changes
- Updated dependencies [[`1d54cff`](https://github.com/thirdweb-dev/js/commit/1d54cff7b442b8667ddd54bd783502c367ffc83e), [`fd04eef`](https://github.com/thirdweb-dev/js/commit/fd04eefea9a2a99eee3740d6746040c9f81cd43f)]:
- thirdweb@5.111.2
- @thirdweb-dev/wagmi-adapter@0.2.177
## 0.0.21
### Patch Changes
- Updated dependencies [[`ccdce49`](https://github.com/thirdweb-dev/js/commit/ccdce496e09bc9420b0e15854328651c01f36279)]:
- thirdweb@5.111.1
- @thirdweb-dev/wagmi-adapter@0.2.176
## 0.0.20
### Patch Changes
- Updated dependencies [[`c83f1c6`](https://github.com/thirdweb-dev/js/commit/c83f1c6f7a38c8a9f424798e6e726e1d037ed042), [`219fc69`](https://github.com/thirdweb-dev/js/commit/219fc69d72b1ca861bf0228589039788008ea0c0)]:
- thirdweb@5.111.0
- @thirdweb-dev/wagmi-adapter@0.2.175
## 0.0.19
### Patch Changes
- Updated dependencies [[`6c318f8`](https://github.com/thirdweb-dev/js/commit/6c318f83d63aa8acce46da666e43638e410f799c), [`759b93b`](https://github.com/thirdweb-dev/js/commit/759b93beff7ab10a3a2fa3b9dd25ca628efb9267), [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389)]:
- thirdweb@5.110.6
- @thirdweb-dev/wagmi-adapter@0.2.174
## 0.0.18
### Patch Changes
- Updated dependencies [[`c08fb13`](https://github.com/thirdweb-dev/js/commit/c08fb1356e77be9eeb0569905fcb2ab76a516dc3), [`bcbb3f5`](https://github.com/thirdweb-dev/js/commit/bcbb3f540a8eab2b02f9261b779bd2aad3464598), [`e0c8312`](https://github.com/thirdweb-dev/js/commit/e0c83121c201eb2dbdfa6a6b1bbb637f5ad2a21d)]:
- thirdweb@5.110.5
- @thirdweb-dev/wagmi-adapter@0.2.173
## 0.0.17
### Patch Changes
- Updated dependencies [[`23cb3bd`](https://github.com/thirdweb-dev/js/commit/23cb3bd98c5390e64f45da5a222042cdc76bd7f4), [`abf0bba`](https://github.com/thirdweb-dev/js/commit/abf0bbaf359c8de5b0ed0d481e3791c4b7cbf92b), [`e0abf2b`](https://github.com/thirdweb-dev/js/commit/e0abf2b2738661fda3c69551d78231cfac4b0767)]:
- thirdweb@5.110.4
- @thirdweb-dev/wagmi-adapter@0.2.172
## 0.0.16
### Patch Changes
- Updated dependencies [[`bf46520`](https://github.com/thirdweb-dev/js/commit/bf46520a6b183c4beb873de5e63853cd3a23e685)]:
- thirdweb@5.110.3
- @thirdweb-dev/wagmi-adapter@0.2.171
## 0.0.15
### Patch Changes
- Updated dependencies [[`3e9eb49`](https://github.com/thirdweb-dev/js/commit/3e9eb49787e00c09c3286aeb4ad73826a2856e65), [`bfd3be7`](https://github.com/thirdweb-dev/js/commit/bfd3be764c461c420f1f8520fe4d5d2cc83e42f7), [`ae3a122`](https://github.com/thirdweb-dev/js/commit/ae3a122cf1628f0fc997dd240d4b23842ac30f66), [`d5d5939`](https://github.com/thirdweb-dev/js/commit/d5d593956e12b2a15393ed32d1d742bc43575bdd), [`f757e60`](https://github.com/thirdweb-dev/js/commit/f757e6084648b6b6e5879abeab30954753a1020d)]:
- thirdweb@5.110.2
- @thirdweb-dev/wagmi-adapter@0.2.170
## 0.0.14
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "wagmi-inapp",
"private": true,
"version": "0.0.14",
"version": "0.0.30",
"type": "module",
"scripts": {
"dev": "vite",
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/api
## 0.1.2
### Patch Changes
- [#8338](https://github.com/thirdweb-dev/js/pull/8338) [`90b7791`](https://github.com/thirdweb-dev/js/commit/90b779107d3cb726adeca6a3f885129c12599c6a) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - added solana token balances endpoint
## 0.1.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/api",
"version": "0.1.1",
"version": "0.1.2",
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
+28
View File
@@ -66,6 +66,9 @@ import type {
GetSolanaTransactionData,
GetSolanaTransactionErrors,
GetSolanaTransactionResponses,
GetSolanaWalletBalanceData,
GetSolanaWalletBalanceErrors,
GetSolanaWalletBalanceResponses,
GetTokenOwnersData,
GetTokenOwnersErrors,
GetTokenOwnersResponses,
@@ -1590,6 +1593,31 @@ export const createSolanaWallet = <ThrowOnError extends boolean = false>(
});
};
/**
* Get Solana Wallet Balance
* Get the SOL or SPL token balance for a Solana wallet on a specific Solana network.
*
* **Authentication**: Pass `x-client-id` for frontend usage from allowlisted origins or `x-secret-key` for backend usage.
*/
export const getSolanaWalletBalance = <ThrowOnError extends boolean = false>(
options: Options<GetSolanaWalletBalanceData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).get<
GetSolanaWalletBalanceResponses,
GetSolanaWalletBalanceErrors,
ThrowOnError
>({
security: [
{
name: "x-client-id",
type: "apiKey",
},
],
url: "/v1/solana/wallets/{address}/balance",
...options,
});
};
/**
* Sign Solana Message
* Sign an arbitrary message with a Solana wallet. Supports both text and hexadecimal message formats with automatic format detection.
+92 -83
View File
@@ -2135,6 +2135,10 @@ export type GetWalletTransactionsData = {
address: string;
};
query: {
/**
* Chain ID(s) to request transaction data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137
*/
chainId: Array<number>;
/**
* Filter by block timestamp (Unix timestamp) greater than or equal to this value
*/
@@ -2171,10 +2175,6 @@ export type GetWalletTransactionsData = {
* Sort order: 'asc' for ascending, 'desc' for descending
*/
sortOrder?: "asc" | "desc";
/**
* Chain ID(s) to request transaction data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137
*/
chainId: Array<number>;
};
url: "/v1/wallets/{address}/transactions";
};
@@ -3234,11 +3234,11 @@ export type WriteContractErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -4314,11 +4314,11 @@ export type SendTransactionsErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -4725,11 +4725,11 @@ export type PaymentsPurchaseErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -4914,17 +4914,7 @@ export type VerifyX402PaymentData = {
paymentPayload: {
x402Version: number;
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
payload:
| {
signature: string;
@@ -4943,17 +4933,7 @@ export type VerifyX402PaymentData = {
};
paymentRequirements: {
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
maxAmountRequired: string;
resource: string;
description: string;
@@ -5015,7 +4995,6 @@ export type VerifyX402PaymentResponses = {
| "invalid_exact_svm_payload_transaction_instruction_not_spl_token_transfer_checked"
| "invalid_exact_svm_payload_transaction_instruction_not_token_2022_transfer_checked"
| "invalid_exact_svm_payload_transaction_not_a_transfer_instruction"
| "invalid_exact_svm_payload_transaction_cannot_derive_receiver_ata"
| "invalid_exact_svm_payload_transaction_receiver_ata_not_found"
| "invalid_exact_svm_payload_transaction_sender_ata_not_found"
| "invalid_exact_svm_payload_transaction_simulation_failed"
@@ -5049,17 +5028,7 @@ export type SettleX402PaymentData = {
paymentPayload: {
x402Version: number;
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
payload:
| {
signature: string;
@@ -5078,17 +5047,7 @@ export type SettleX402PaymentData = {
};
paymentRequirements: {
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
maxAmountRequired: string;
resource: string;
description: string;
@@ -5154,7 +5113,6 @@ export type SettleX402PaymentResponses = {
| "invalid_exact_svm_payload_transaction_instruction_not_spl_token_transfer_checked"
| "invalid_exact_svm_payload_transaction_instruction_not_token_2022_transfer_checked"
| "invalid_exact_svm_payload_transaction_not_a_transfer_instruction"
| "invalid_exact_svm_payload_transaction_cannot_derive_receiver_ata"
| "invalid_exact_svm_payload_transaction_receiver_ata_not_found"
| "invalid_exact_svm_payload_transaction_sender_ata_not_found"
| "invalid_exact_svm_payload_transaction_simulation_failed"
@@ -5183,7 +5141,10 @@ export type SettleX402PaymentResponses = {
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet";
| "sei-testnet"
| "polygon"
| "polygon-amoy"
| "peaq";
errorMessage?: string;
};
};
@@ -5226,17 +5187,7 @@ export type SupportedX402PaymentsResponses = {
kinds: Array<{
x402Version: 1;
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
extra?: {
defaultAsset?: {
/**
@@ -5340,11 +5291,11 @@ export type FetchWithPaymentErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -5552,17 +5503,7 @@ export type ListPayableServicesResponses = {
x402Version: number;
accepts: Array<{
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
maxAmountRequired: string;
resource: string;
description: string;
@@ -6298,11 +6239,11 @@ export type BridgeSwapErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -6640,6 +6581,74 @@ export type CreateSolanaWalletResponses = {
export type CreateSolanaWalletResponse =
CreateSolanaWalletResponses[keyof CreateSolanaWalletResponses];
export type GetSolanaWalletBalanceData = {
body?: never;
path: {
/**
* Public key of the Solana wallet.
*/
address: string;
};
query: {
/**
* Solana network to query. Choose either solana:mainnet or solana:devnet.
*/
chainId: "solana:mainnet" | "solana:devnet";
/**
* SPL token mint address. Omit to retrieve native SOL balance.
*/
tokenAddress?: string;
};
url: "/v1/solana/wallets/{address}/balance";
};
export type GetSolanaWalletBalanceErrors = {
/**
* Invalid request parameters. This occurs when the wallet address or token mint is invalid, or the request mixes a token mint with multiple networks.
*/
400: unknown;
/**
* Authentication required. Include `x-client-id` or `x-secret-key` headers.
*/
401: unknown;
/**
* Internal server error. This may occur due to RPC connectivity issues or unexpected failures.
*/
500: unknown;
};
export type GetSolanaWalletBalanceResponses = {
/**
* Wallet balance retrieved successfully for the requested Solana network.
*/
200: {
/**
* Balance data for the requested Solana network.
*/
result: {
/**
* Requested Solana network.
*/
chainId: "solana:mainnet" | "solana:devnet";
/**
* Number of decimals used by the token.
*/
decimals: number;
/**
* Human-readable balance formatted using token decimals.
*/
displayValue: string;
/**
* Raw balance value expressed in base units (lamports).
*/
value: string;
};
};
};
export type GetSolanaWalletBalanceResponse =
GetSolanaWalletBalanceResponses[keyof GetSolanaWalletBalanceResponses];
export type SignSolanaMessageData = {
/**
* Request payload for signing an arbitrary Solana message.
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/engine
## 3.4.0
### Minor Changes
- [#8371](https://github.com/thirdweb-dev/js/pull/8371) [`ad68969`](https://github.com/thirdweb-dev/js/commit/ad68969559a80a5f188a647ef0e1c04f0003a0ad) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - Reworked Solana Sign Transaction API that accepts more flexible inputs
## 3.3.0
### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/engine",
"version": "3.3.0",
"version": "3.4.0",
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
+27 -27
View File
@@ -274,6 +274,33 @@ export const sendSolanaTransaction = <ThrowOnError extends boolean = false>(
});
};
/**
* Sign Solana Transaction
* Sign a Solana transaction without broadcasting it
*/
export const signSolanaTransaction = <ThrowOnError extends boolean = false>(
options: Options<SignSolanaTransactionData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<
SignSolanaTransactionResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/sign/transaction",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Cancel Transaction
* Attempt to cancel a queued transaction. Transactions that have been sent and are waiting for mine cannot be cancelled.
@@ -424,33 +451,6 @@ export const signSolanaMessage = <ThrowOnError extends boolean = false>(
});
};
/**
* Sign Solana Transaction
* Sign a serialized Solana transaction.
*/
export const signSolanaTransaction = <ThrowOnError extends boolean = false>(
options?: Options<SignSolanaTransactionData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SignSolanaTransactionResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/sign-transaction",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Get Transactions
* Search transactions with various filters and pagination
+90 -48
View File
@@ -556,6 +556,10 @@ export type Eip7702OwnerExecution = {
* The delegated EOA address
*/
from: AddressDef;
/**
* Optional fleet ID to route the transaction to a specific bundler fleet
*/
fleetId?: string | null;
};
/**
@@ -570,6 +574,10 @@ export type Eip7702SessionKeyExecution = {
* The account address is the address of a delegated account you want to execute the transaction on. This account has granted a session key to the `session_key_address`
*/
accountAddress: AddressDef;
/**
* Optional fleet ID to route the transaction to a specific bundler fleet
*/
fleetId?: string | null;
};
export type EmptyIdempotencySetResponse = {
@@ -1000,15 +1008,11 @@ export type RpcErrorResponse = {
/**
* Request to send a Solana transaction
*/
export type SendSolanaTransactionRequest = {
export type SendSolanaTransactionRequest = SolanaTransactionInput & {
/**
* Idempotency key for this transaction (defaults to random UUID)
*/
idempotencyKey?: string;
/**
* List of Solana instructions to execute
*/
instructions: Array<SolanaInstructionData>;
/**
* Solana execution options
*/
@@ -1188,6 +1192,30 @@ export type SignResultData = {
signedData: string;
};
/**
* Request to sign a Solana transaction
*/
export type SignSolanaTransactionRequest = SolanaTransactionInput & {
/**
* Solana execution options
*/
executionOptions: SolanaExecutionOptions;
};
/**
* Data returned from successful signing
*/
export type SignSolanaTransactionResponse = {
/**
* The signature (base58-encoded)
*/
signature: string;
/**
* The signed serialized transaction (base64-encoded)
*/
signedTransaction: string;
};
/**
* Request to sign typed data
*/
@@ -1452,6 +1480,21 @@ export type SolanaRpcResponseErrorData =
type: "NODE_UNHEALTHY";
};
/**
* Input for Solana transaction - either build from instructions or use pre-built
*/
export type SolanaTransactionInput =
| SolanaTransactionInputInstructions
| SolanaTransactionInputSerialized;
export type SolanaTransactionInputInstructions = {
instructions: Array<SolanaInstructionData>;
};
export type SolanaTransactionInputSerialized = {
transaction: string;
};
/**
* Execution Option Variants
* All supported specific execution options are contained here
@@ -1503,6 +1546,22 @@ export type SuccessResponseQueuedTransactionsResponse = {
};
};
export type SuccessResponseSignSolanaTransactionResponse = {
/**
* Data returned from successful signing
*/
result: {
/**
* The signature (base58-encoded)
*/
signature: string;
/**
* The signed serialized transaction (base64-encoded)
*/
signedTransaction: string;
};
};
export type ThirdwebError =
| {
error: ThirdwebSerializationError;
@@ -1847,6 +1906,32 @@ export type SendSolanaTransactionResponses = {
export type SendSolanaTransactionResponse =
SendSolanaTransactionResponses[keyof SendSolanaTransactionResponses];
export type SignSolanaTransactionData = {
/**
* Sign Solana transaction request
*/
body: SignSolanaTransactionRequest;
headers?: {
/**
* Vault access token
*/
"x-vault-access-token"?: string | null;
};
path?: never;
query?: never;
url: "/v1/solana/sign/transaction";
};
export type SignSolanaTransactionResponses = {
/**
* Successfully signed Solana transaction
*/
200: SuccessResponseSignSolanaTransactionResponse;
};
export type SignSolanaTransactionResponse2 =
SignSolanaTransactionResponses[keyof SignSolanaTransactionResponses];
export type CancelTransactionData = {
body?: never;
path: {
@@ -2076,49 +2161,6 @@ export type SignSolanaMessageResponses = {
export type SignSolanaMessageResponse =
SignSolanaMessageResponses[keyof SignSolanaMessageResponses];
export type SignSolanaTransactionData = {
body?: {
/**
* Base58 encoded Solana public key
*/
from: string;
/**
* Base64 encoded Solana transaction
*/
transaction: string;
};
headers?: {
/**
* Vault Access Token used to access your EOA
*/
"x-vault-access-token"?: string;
};
path?: never;
query?: never;
url: "/v1/solana/sign-transaction";
};
export type SignSolanaTransactionResponses = {
/**
* Transaction signed
*/
200: {
result: {
/**
* Base58 encoded signature
*/
signature: string;
/**
* Base58 encoded Solana public key
*/
signerPublicKey: string;
};
};
};
export type SignSolanaTransactionResponse =
SignSolanaTransactionResponses[keyof SignSolanaTransactionResponses];
export type GetTransactionsData = {
body?: never;
path?: never;
+112
View File
@@ -1,5 +1,117 @@
# @thirdweb-dev/nebula
## 0.2.88
### Patch Changes
- Updated dependencies [[`f964b8a`](https://github.com/thirdweb-dev/js/commit/f964b8aaff22d4078ff4c4c5e09c6ebb5fcb182c), [`424e6b8`](https://github.com/thirdweb-dev/js/commit/424e6b8f9b071d3629ff0291ae6875fa4e4c882e), [`763d3de`](https://github.com/thirdweb-dev/js/commit/763d3de5a8db19a8022b2eda420f13a6787de100)]:
- thirdweb@5.111.10
## 0.2.87
### Patch Changes
- Updated dependencies [[`673e51b`](https://github.com/thirdweb-dev/js/commit/673e51b11cf2d06f00ab92bd4cbb45c51d95ec1f)]:
- thirdweb@5.111.9
## 0.2.86
### Patch Changes
- Updated dependencies [[`1f440d0`](https://github.com/thirdweb-dev/js/commit/1f440d0c97adf14d26d55666257b57541b2ef01a)]:
- thirdweb@5.111.8
## 0.2.85
### Patch Changes
- Updated dependencies [[`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8), [`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8)]:
- thirdweb@5.111.7
## 0.2.84
### Patch Changes
- Updated dependencies []:
- thirdweb@5.111.6
## 0.2.83
### Patch Changes
- Updated dependencies [[`7da16cf`](https://github.com/thirdweb-dev/js/commit/7da16cff2ff3aaa80c7fe5614639d950d88b5bf2)]:
- thirdweb@5.111.5
## 0.2.82
### Patch Changes
- Updated dependencies [[`d907017`](https://github.com/thirdweb-dev/js/commit/d907017217a0927e7f69eab0470b1fe120b4d1a1), [`40781b8`](https://github.com/thirdweb-dev/js/commit/40781b8170a1014ef76e438ba3c8244137dba7ea)]:
- thirdweb@5.111.4
## 0.2.81
### Patch Changes
- Updated dependencies [[`207cb9d`](https://github.com/thirdweb-dev/js/commit/207cb9dbeb82411809bafa2a8e833b1f10414b92)]:
- thirdweb@5.111.3
## 0.2.80
### Patch Changes
- Updated dependencies [[`1d54cff`](https://github.com/thirdweb-dev/js/commit/1d54cff7b442b8667ddd54bd783502c367ffc83e), [`fd04eef`](https://github.com/thirdweb-dev/js/commit/fd04eefea9a2a99eee3740d6746040c9f81cd43f)]:
- thirdweb@5.111.2
## 0.2.79
### Patch Changes
- Updated dependencies [[`ccdce49`](https://github.com/thirdweb-dev/js/commit/ccdce496e09bc9420b0e15854328651c01f36279)]:
- thirdweb@5.111.1
## 0.2.78
### Patch Changes
- Updated dependencies [[`c83f1c6`](https://github.com/thirdweb-dev/js/commit/c83f1c6f7a38c8a9f424798e6e726e1d037ed042), [`219fc69`](https://github.com/thirdweb-dev/js/commit/219fc69d72b1ca861bf0228589039788008ea0c0)]:
- thirdweb@5.111.0
## 0.2.77
### Patch Changes
- Updated dependencies [[`6c318f8`](https://github.com/thirdweb-dev/js/commit/6c318f83d63aa8acce46da666e43638e410f799c), [`759b93b`](https://github.com/thirdweb-dev/js/commit/759b93beff7ab10a3a2fa3b9dd25ca628efb9267), [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389)]:
- thirdweb@5.110.6
## 0.2.76
### Patch Changes
- Updated dependencies [[`c08fb13`](https://github.com/thirdweb-dev/js/commit/c08fb1356e77be9eeb0569905fcb2ab76a516dc3), [`bcbb3f5`](https://github.com/thirdweb-dev/js/commit/bcbb3f540a8eab2b02f9261b779bd2aad3464598), [`e0c8312`](https://github.com/thirdweb-dev/js/commit/e0c83121c201eb2dbdfa6a6b1bbb637f5ad2a21d)]:
- thirdweb@5.110.5
## 0.2.75
### Patch Changes
- Updated dependencies [[`23cb3bd`](https://github.com/thirdweb-dev/js/commit/23cb3bd98c5390e64f45da5a222042cdc76bd7f4), [`abf0bba`](https://github.com/thirdweb-dev/js/commit/abf0bbaf359c8de5b0ed0d481e3791c4b7cbf92b), [`e0abf2b`](https://github.com/thirdweb-dev/js/commit/e0abf2b2738661fda3c69551d78231cfac4b0767)]:
- thirdweb@5.110.4
## 0.2.74
### Patch Changes
- Updated dependencies [[`bf46520`](https://github.com/thirdweb-dev/js/commit/bf46520a6b183c4beb873de5e63853cd3a23e685)]:
- thirdweb@5.110.3
## 0.2.73
### Patch Changes
- Updated dependencies [[`3e9eb49`](https://github.com/thirdweb-dev/js/commit/3e9eb49787e00c09c3286aeb4ad73826a2856e65), [`bfd3be7`](https://github.com/thirdweb-dev/js/commit/bfd3be764c461c420f1f8520fe4d5d2cc83e42f7), [`ae3a122`](https://github.com/thirdweb-dev/js/commit/ae3a122cf1628f0fc997dd240d4b23842ac30f66), [`d5d5939`](https://github.com/thirdweb-dev/js/commit/d5d593956e12b2a15393ed32d1d742bc43575bdd), [`f757e60`](https://github.com/thirdweb-dev/js/commit/f757e6084648b6b6e5879abeab30954753a1020d)]:
- thirdweb@5.110.2
## 0.2.72
### Patch Changes
+1 -1
View File
@@ -57,5 +57,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.2.72"
"version": "0.2.88"
}
+13
View File
@@ -0,0 +1,13 @@
[
{
"import": "*",
"limit": "1 kB",
"name": "@thirdweb-dev/nexus (esm)",
"path": "./dist/esm/exports/nexus.js"
},
{
"limit": "1 kB",
"name": "@thirdweb-dev/nexus (cjs)",
"path": "./dist/cjs/exports/nexus.js"
}
]
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
"extends": "//",
"overrides": [
{
"assist": {
"actions": {
"source": {
"useSortedKeys": "off"
}
}
},
"includes": ["package.json"]
}
]
}
+12
View File
@@ -0,0 +1,12 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["src/exports/**"],
"ignore": ["src/**/__generated__/**", "**/*.bench.ts"],
"ignoreBinaries": ["printf"],
"ignoreDependencies": ["tslib"],
"project": ["src/**/*.{ts,tsx}"],
"rules": {
"enumMembers": "off",
"optionalPeerDependencies": "off"
}
}
+76
View File
@@ -0,0 +1,76 @@
{
"author": "thirdweb eng <[email protected]>",
"browser": {
"crypto": false
},
"bugs": {
"url": "https://github.com/thirdweb-dev/js/issues"
},
"devDependencies": {
"@biomejs/biome": "2.0.6",
"@size-limit/preset-small-lib": "11.2.0",
"knip": "5.60.2",
"rimraf": "6.0.1",
"size-limit": "11.2.0",
"typescript": "5.8.3"
},
"engines": {
"node": ">=22"
},
"exports": {
".": {
"types": "./dist/types/exports/nexus-fetch.d.ts",
"import": "./dist/esm/exports/nexus-fetch.js",
"default": "./dist/cjs/exports/nexus-fetch.js"
}
},
"files": [
"dist/*",
"src/*",
"!**/*.tsbuildinfo",
"!**/*.test.ts",
"!**/*.test.tsx",
"!**/*.test.ts.snap",
"!**/*.test-d.ts",
"!**/*.bench.ts",
"!tsconfig.build.json"
],
"license": "Apache-2.0",
"main": "./dist/cjs/exports/nexus-fetch.js",
"module": "./dist/esm/exports/nexus-fetch.js",
"name": "@thirdweb-dev/fetch",
"peerDependencies": {
"typescript": ">=5.0.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
},
"scripts": {
"build": "pnpm clean && pnpm build:types && pnpm build:cjs && pnpm build:esm",
"build:cjs": "tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json",
"build:esm": "tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json",
"build:types": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
"clean": "rimraf dist",
"dev": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --outDir ./dist/esm --watch",
"dev:cjs": "printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json && tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false --watch",
"dev:esm": "printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json && tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm --watch",
"fix": "biome check ./src --fix",
"format": "biome format ./src --write",
"knip": "knip",
"lint": "knip && biome check ./src && tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --noEmit",
"size": "size-limit",
"typecheck": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --noEmit",
"test:ui": "bun ./testing/test.html --watch"
},
"sideEffects": false,
"type": "module",
"types": "./dist/types/exports/nexus-fetch.d.ts",
"typings": "./dist/types/exports/nexus-fetch.d.ts",
"version": "0.0.0"
}
@@ -0,0 +1 @@
export { wrap } from "../fetch.js";
+459
View File
@@ -0,0 +1,459 @@
//---
// Constants
//---
const TOKEN_DATA_KEY = "tw:nexus:td";
const STATE_KEY = "tw:nexus:state";
const VERIFIER_KEY = "tw:nexus:verifier";
const NEXUS_API_BASE_URL = "https://nexus-api.thirdweb-dev.com";
const OAUTH_CLIENT_ID = "9ad56e84-5abe-4ea5-a6c2-9834b9d31245";
// Token expiry buffer: consider token expired 5 minutes before actual expiry
const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
//---
// Types
//---
type TokenData = {
accessToken: string;
refreshToken: string;
expiresAt: number;
};
type OAuthTokenResponse = {
access_token: string;
expires_in: number;
refresh_token: string;
token_type: "Bearer";
};
//---
// Token Encoding/Decoding
//---
function encodeTokenData(data: TokenData): string {
return btoa(JSON.stringify(data));
}
function decodeTokenData(encoded: string): TokenData | null {
try {
const decoded = atob(encoded);
return JSON.parse(decoded) as TokenData;
} catch {
return null;
}
}
//---
// Initialization
// Automatically wrap global fetch and complete OAuth flow if returning from authorization
//---
(() => {
if (hasWindow()) {
completeOauthFlow();
const originalFetch = globalThis.fetch;
try {
globalThis.fetch = wrap(originalFetch);
} catch (error) {
console.error("[thirdweb nexus] Error wrapping fetch", error);
globalThis.fetch = originalFetch;
}
}
})();
//---
// Main Fetch Wrapper
//---
/**
* Wraps a fetch function to automatically handle HTTP 402 (Payment Required) responses.
* When a 402 is encountered, it uses the Nexus API to process the payment and retry the request.
*
* @param fetchFn - The fetch function to wrap (typically globalThis.fetch)
* @param options - Optional configuration
* @param options.maxValue - Maximum payment value allowed in wei
* @returns A wrapped fetch function that handles 402 responses
*/
export function wrap(
fetchFn: typeof globalThis.fetch,
options?: {
maxValue?: bigint;
},
) {
return async (input: RequestInfo | URL, init?: RequestInit) => {
const response = await fetchFn(input, init);
if (response.status !== 402) {
return response;
}
const AT = await getAuthToken();
if (!AT) {
// open login page in a new tab or throw an error if opening fails
startOauthFlow();
return new Response("Payment required", { status: 402 });
}
// create the proxy request
const proxyUrl = new URL("/fetch", NEXUS_API_BASE_URL);
proxyUrl.searchParams.set(
"data",
btoa(
JSON.stringify({
// original request related information
request: {
url: new URL(
typeof input === "string"
? input
: input instanceof Request
? input.url
: input,
).toString(),
method:
(typeof input === "string"
? init?.method
: input instanceof Request
? input.method
: "GET") ?? "GET",
headers:
typeof input === "string"
? init?.headers
: input instanceof Request
? input.headers
: {},
},
// x402 options
x402Options: {
...(options?.maxValue
? { maxValue: options.maxValue.toString() }
: {}),
},
}),
),
);
let proxyRes = await fetchFn(proxyUrl, {
method: "POST",
body:
(typeof input === "string"
? init?.body
: input instanceof Request
? input.body
: init?.body) ?? undefined,
headers: {
Authorization: `Bearer ${AT}`,
},
});
// Handle 401 (unauthorized) - try to refresh token and retry
if (proxyRes.status === 401) {
const refreshedToken = await refreshAccessToken();
if (refreshedToken) {
// Retry with refreshed token
proxyRes = await fetchFn(proxyUrl, {
method: "POST",
body:
(typeof input === "string"
? init?.body
: input instanceof Request
? input.body
: init?.body) ?? undefined,
headers: {
Authorization: `Bearer ${refreshedToken}`,
},
});
} else {
// Refresh failed, start OAuth flow
startOauthFlow();
return new Response("Unauthorized", { status: 401 });
}
}
if (proxyRes.status === 402) {
// means the wallet doesn't have funds and needs to onramp, open the funding link
const data = (await proxyRes.json()) as {
result: { link: string; message: string };
};
// TODO: we can await this and then retry the call after by setting up
// window.postMessage communication and opening this in a new tab
const paymentUrl = new URL(data.result.link);
paymentUrl.searchParams.set("successUrl", window.location.href);
window.location.href = paymentUrl.toString();
return new Response("Payment required", { status: 402 });
}
return proxyRes;
};
}
//---
// Browser/Environment Utilities
//---
function hasWindow(): boolean {
return typeof window !== "undefined";
}
function getLocalStorage(): Storage | null {
try {
return hasWindow() ? window.localStorage : null;
} catch {
return null;
}
}
//---
// Token Data Management
//---
let inMemoryTokenData: TokenData | null = null;
function getTokenData(): TokenData | null {
if (inMemoryTokenData !== null) {
return inMemoryTokenData;
}
const encoded = getLocalStorage()?.getItem(TOKEN_DATA_KEY);
if (encoded) {
const data = decodeTokenData(encoded);
if (data) {
inMemoryTokenData = data;
return data;
}
}
return null;
}
function setTokenData(
accessToken: string,
refreshToken: string,
expiresIn: number,
): void {
if (!accessToken || accessToken === "undefined") {
return;
}
if (!refreshToken || refreshToken === "undefined") {
return;
}
// Calculate expiry timestamp (current time + expires_in seconds)
const expiresAt = Date.now() + expiresIn * 1000;
const tokenData: TokenData = { accessToken, refreshToken, expiresAt };
const encoded = encodeTokenData(tokenData);
getLocalStorage()?.setItem(TOKEN_DATA_KEY, encoded);
inMemoryTokenData = tokenData;
}
function deleteTokenData(): void {
getLocalStorage()?.removeItem(TOKEN_DATA_KEY);
inMemoryTokenData = null;
}
function isTokenExpired(tokenData: TokenData | null): boolean {
if (!tokenData) {
return true;
}
return Date.now() >= tokenData.expiresAt - TOKEN_EXPIRY_BUFFER_MS;
}
//---
// Token Validation & Refresh
//---
/**
* Gets a valid access token, refreshing it if necessary.
* @returns A valid access token or null if unavailable
*/
async function getAuthToken(): Promise<string | null> {
const tokenData = getTokenData();
// If we have a token but it's expired, try to refresh
if (tokenData && isTokenExpired(tokenData)) {
const refreshedToken = await refreshAccessToken();
return refreshedToken;
}
return tokenData?.accessToken ?? null;
}
/**
* Refreshes an expired access token using the refresh token.
* @returns A new access token or null if refresh fails
*/
async function refreshAccessToken(): Promise<string | null> {
const tokenData = getTokenData();
if (!tokenData) {
return null;
}
try {
const response = await fetch(`${NEXUS_API_BASE_URL}/oauth/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
grant_type: "refresh_token",
refresh_token: tokenData.refreshToken,
client_id: OAUTH_CLIENT_ID,
}),
});
if (!response.ok) {
// Refresh failed, clear all tokens
deleteTokenData();
return null;
}
const data = (await response.json()) as OAuthTokenResponse;
// Store new tokens
setTokenData(data.access_token, data.refresh_token, data.expires_in);
return data.access_token;
} catch (error) {
console.error("[thirdweb nexus] Error refreshing access token", error);
return null;
}
}
//---
// OAuth PKCE (Proof Key for Code Exchange) Utilities
//---
function storeState(code: string): void {
getLocalStorage()?.setItem(STATE_KEY, code);
}
function getState(): string | null | undefined {
return getLocalStorage()?.getItem(STATE_KEY);
}
function deleteState(): void {
getLocalStorage()?.removeItem(STATE_KEY);
}
function storeVerifier(verifier: string): void {
getLocalStorage()?.setItem(VERIFIER_KEY, verifier);
}
function getVerifier(): string | null | undefined {
return getLocalStorage()?.getItem(VERIFIER_KEY);
}
function deleteVerifier(): void {
getLocalStorage()?.removeItem(VERIFIER_KEY);
}
/**
* Generates a cryptographically random code verifier for PKCE.
*/
function generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}
/**
* Generates a code challenge from a verifier for PKCE using SHA-256.
*/
async function generateCodeChallenge(verifier: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest("SHA-256", data);
return base64UrlEncode(new Uint8Array(hash));
}
/**
* Base64 URL-safe encoding (without padding).
*/
function base64UrlEncode(array: Uint8Array): string {
const base64 = btoa(String.fromCharCode(...array));
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
//---
// OAuth Flow
//---
/**
* Initiates the OAuth authorization flow by redirecting to the Nexus authorization endpoint.
*/
async function startOauthFlow(): Promise<void> {
const state = crypto.randomUUID();
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);
const redirectUri = new URL(window.location.href);
const url = new URL("/oauth/authorize", NEXUS_API_BASE_URL);
url.searchParams.set("response_type", "code");
url.searchParams.set("client_id", OAUTH_CLIENT_ID);
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
url.searchParams.set("state", state);
url.searchParams.set(
"redirect_uri",
`${redirectUri.origin}${redirectUri.pathname}`,
);
storeState(state);
storeVerifier(verifier);
// just open the page directly
window.location.href = url.toString();
}
/**
* Completes the OAuth flow by exchanging the authorization code for tokens.
* Called automatically on page load if code parameter is present in URL.
*/
async function completeOauthFlow(): Promise<void> {
try {
const searchParams = new URL(window.location.href).searchParams;
const code = searchParams.get("code");
const state = searchParams.get("state");
const error = searchParams.get("error");
if (error) {
console.error("[thirdweb nexus] OAuth error:", error);
return;
}
if (!code || !state) {
// No code or state found, nothing to do
return;
}
if (state !== getState()) {
// State mismatch - possible CSRF attack
console.warn("[thirdweb nexus] OAuth state mismatch");
return;
}
const verifier = getVerifier();
if (!verifier) {
// No verifier found, nothing to do
return;
}
// Delete the state and verifier so they can't be reused
deleteState();
deleteVerifier();
const redirectUri = new URL(window.location.href);
const response = await fetch(`${NEXUS_API_BASE_URL}/oauth/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
code,
code_verifier: verifier,
grant_type: "authorization_code",
redirect_uri: `${redirectUri.origin}${redirectUri.pathname}`,
client_id: OAUTH_CLIENT_ID,
}),
});
if (!response.ok) {
console.error(
"[thirdweb nexus] Failed to exchange authorization code for tokens",
);
return;
}
const data = (await response.json()) as OAuthTokenResponse;
// Store all token data
setTokenData(data.access_token, data.refresh_token, data.expires_in);
} catch (error) {
console.error("[thirdweb nexus] Error completing OAuth flow:", error);
}
}
+21
View File
@@ -0,0 +1,21 @@
<html>
<head>
<title>Nexus Fetch Testing</title>
<script src="../src/exports/nexus-fetch.ts"></script>
</head>
<body>
<button id="test-button">Test</button>
<script>
const testButton = document.getElementById("test-button");
testButton.addEventListener("click", () => {
fetch("https://q1qkkomp.nx.link/kindle", {
method: "POST",
body: JSON.stringify({
recipient: "0x0890C23024089675D072E984f28A93bb391a35Ab",
})
});
});
testButton.click();
</script>
</body>
</html>
+48
View File
@@ -0,0 +1,48 @@
{
// This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config.
"compilerOptions": {
// Incremental builds
// NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes.
"allowJs": false,
"allowSyntheticDefaultImports": true,
"checkJs": false,
// Interop constraints
"esModuleInterop": false,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
// Incremental builds
// NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes.
"incremental": false,
// jsx for "/react" portion
"jsx": "react-jsx",
"lib": [
"ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances.
"DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped.
],
"module": "NodeNext",
// Language and environment
"moduleResolution": "NodeNext",
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
// Skip type checking for node modules
"skipLibCheck": true,
// Type checking
"strict": true,
"target": "ES2021",
"useDefineForClassFields": true,
"useUnknownInCatchVariables": true,
"verbatimModuleSyntax": true
},
// This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config.
"include": []
}
+16
View File
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"moduleResolution": "node",
"rootDir": "./src",
"sourceMap": true
},
"exclude": [
"src/**/*.test.ts",
"src/**/*.test.tsx",
"src/**/*.test-d.ts",
"src/**/*.bench.ts",
"src/**/*.macro.ts"
],
"extends": "./tsconfig.base.json",
"include": ["src"]
}
+13
View File
@@ -0,0 +1,13 @@
{
// This configuration is used for local development and type checking.
"compilerOptions": {
"baseUrl": ".",
"outDir": "./dist",
"paths": {
"~test/*": ["./test/src/*"]
}
},
"exclude": [],
"extends": "./tsconfig.base.json",
"include": ["src", "test"]
}
+13
View File
@@ -0,0 +1,13 @@
[
{
"import": "*",
"limit": "110 kB",
"name": "@thirdweb-dev/nexus (esm)",
"path": "./dist/esm/exports/nexus.js"
},
{
"limit": "350 kB",
"name": "@thirdweb-dev/nexus (cjs)",
"path": "./dist/cjs/exports/nexus.js"
}
]
+7
View File
@@ -0,0 +1,7 @@
# @thirdweb-dev/nexus
## 0.1.0
### Minor Changes
- [#8332](https://github.com/thirdweb-dev/js/pull/8332) [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389) Thanks [@jnsdls](https://github.com/jnsdls)! - initial release

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