Compare commits

...
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
7113f7c669 Version Packages (#7463)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-27 18:56:26 -07:00
f9d7935d84 Update engine API (#7427)
Signed-off-by: Prithvish Baidya <[email protected]>
Co-authored-by: Prithvish Baidya <[email protected]>
2025-06-27 18:41:37 -07:00
FirekeeperandGitHub acd5656d4d [SDK] EIP-7702 Session Keys (#7432) 2025-06-27 18:11:19 -07:00
gregandGitHub 6d1d344c48 Fix: Exclude IAW when smart wallets are used (#7454) 2025-06-27 13:36:11 -07:00
MananTank 26b8c320ae [TOOL-4899] Dashboard: fetch token icon from bridge api as fallback in coin asset page (#7460)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new utility function to fetch token information from a bridge and integrates it into the `ERC20PublicPage` component to enhance contract metadata with token icons.

### Detailed summary
- Added `fetchTokenInfoFromBridge` function in `fetch-coin-info.ts` to fetch token details from a bridge API.
- Integrated `fetchTokenInfoFromBridge` in the `ERC20PublicPage` component.
- Updated contract metadata to include the token icon if not already present.

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

<!-- end pr-codex -->

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

* **New Features**
  * Enhanced ERC20 public pages to automatically display token icons when available, enriching token metadata with external information.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 19:46:52 +00:00
Phillip HoandGitHub c7f7f48222 chore: change webhook back to object (#7461) 2025-06-28 02:18:40 +08:00
MananTank e814248d9c [TOOL-4898] Dashboard: Replace PayEmbed with BuyWidget in asset page, add tracking (#7459)
<!--

## 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 `PayEmbed` component to `BuyWidget` in the `BuyTokenEmbed` function, enhancing the functionality with error reporting and success tracking for asset purchases.

### Detailed summary
- Replaced `PayEmbed` with `BuyWidget` in `BuyTokenEmbed`.
- Added `amount`, `chain`, and `className` props to `BuyWidget`.
- Implemented `onError` and `onSuccess` callbacks for analytics reporting.
- Updated the token address prop to ensure correct type.
- Modified chart className in `contract-analytics.tsx`.

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

<!-- end pr-codex -->

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

* **New Features**
  * Updated the buy token section to use an improved widget with enhanced error and success handling, including dynamic titles and analytics reporting.

* **Style**
  * Adjusted the chart's aspect ratio for better display across different screen sizes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 16:43:21 +00:00
MananTank d7a0613067 [TOOL-4897] Dashboard: Add Contract creator badge in Coin and NFT asset page (#7458)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the `contractCreator` property to multiple components, allowing the display of the contract creator's address in the UI. It also adds a new function, `getContractCreator`, to retrieve the contract creator's information based on the contract's role.

### Detailed summary
- Added `contractCreator` prop to `NFTPublicPageLayout`, `ContractHeaderUI`, and `ContractCreatorBadge`.
- Implemented `getContractCreator` function to fetch the contract creator using `ThirdwebContract`.
- Updated `NFTPublicPage` and `ERC20PublicPage` to call `getContractCreator` and pass the result to the UI.
- Enhanced storybook stories to include `contractCreator` in various scenarios.

> ✨ 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 display of contract creator information on ERC20 and NFT public pages.
  * Introduced a badge showing the contract creator's address in the contract header section.

* **Enhancements**
  * Contract headers now conditionally show the creator badge if creator data is available.
  * Storybook stories for contract headers updated to include contract creator scenarios for improved UI previews.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 16:31:14 +00:00
MananTank dbc031664d [TOOL-4867] Dashboard: Fix Coin asset page crash on "unlimited" maxClaimableSupply (#7457)
<!--

## 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 handling of the `totalSupply` property in the `SupplyClaimedProgress` component and related files to support the string value `"unlimited"` instead of using a constant value from `thirdweb/utils`.

### Detailed summary
- Changed `totalSupply` type from `bigint` to `bigint | "unlimited"` in `SupplyClaimedProgress`.
- Updated the condition to check for `"unlimited"` instead of `maxUint256`.
- Modified the story for `SupplyClaimedProgress` to use `"unlimited"` instead of `maxUint256`.
- Adjusted `totalSupply` handling in `TokenDropClaim` to return `"unlimited"` if `maxClaimableSupply` equals `maxUint256`.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **New Features**
  * Updated the supply progress display to clearly indicate when the total supply is unlimited.
* **Refactor**
  * Improved internal handling and representation of unlimited supply for better clarity in the UI.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 16:20:18 +00:00
saminacodes 5640d48854 [Docs] Project Rebrand (#7455)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on renaming and rebranding various components and documentation from `Universal Bridge` and `Engine` to `Payments` and `Transactions`, respectively, to better reflect their functionality and purpose.

### Detailed summary
- Renamed `Universal Bridge` to `Payments` across multiple files.
- Renamed `Engine` to `Transactions` across various components and documentation.
- Updated metadata and titles in numerous `.mdx` files to reflect the new branding.
- Changed references in UI components and layout files to match new naming conventions.
- Adjusted descriptions and summaries in documentation to align with the new terms.
- Updated links and API references to point to the correct services.

> ✨ 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**
  * Expanded the learning resources section with updated and additional cards, including new topics like "Contracts" and "Vault".

* **Style**
  * Updated navigation and sidebar labels for improved clarity (e.g., "Connect" to "Wallets", "Bridge" to "Payments", "Engine" to "Transactions").
  * Swapped the order of the GitHub icon and theme switcher in the header for better usability.

* **Documentation**
  * Renamed all references from "Universal Bridge" to "Payments" and from "Engine" to "Transactions" across guides, FAQs, and documentation.
  * Updated instructions, headings, and content to reflect new product names and improved clarity.
  * Added a new FAQ entry explaining the difference between "Transactions Cloud" and "Dedicated" infrastructure.
  * Updated links and navigation paths to match new terminology.
  * Adjusted terminology in wallet-related documentation to replace "Connect" with "Wallets".
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 15:26:01 +00:00
GiselleNessi 9d6739ea59 updated text support page (#7447)
<!--

## 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 descriptions and titles of various features in the support section of the dashboard, as well as changing a header for improved clarity.

### Detailed summary
- Updated `description` and `title` for the wallets feature.
- Changed `description` and `title` for the universal bridge feature, along with its `viewAllUrl`.
- Modified `description` and `title` for the engine feature, including its `viewAllUrl`.
- Revised `description` for the insight feature.
- Changed the header from "Learning Resources" to "Support Articles".

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

<!-- end pr-codex -->

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

* **Refactor**
  * Updated product names and descriptions for greater clarity in the Support section.
  * Changed the heading above product cards from "Learning Resources" to "Support Articles."
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 10:52:59 +00:00
GiselleNessi dc49683357 removed next steps chainlist page (#7443)
<!--

## 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 unused components and updating product descriptions in the `page.tsx` and `products.ts` files.

### Detailed summary
- Deleted `NextSteps` and `ClaimChainSection` components from `page.tsx`.
- Removed related JSX comments in `page.tsx`.
- Updated product descriptions and names in `products.ts`:
  - Changed "Pre-built contracts & deployment tools" to "Create, deploy and manage smart contracts".
  - Changed "Client-side SDKs for wallets and blockchain interactions" to "Create and manage crypto wallets".
  - Changed "Engine" to "Transactions".
  - Changed "Account factory contracts, bundler & paymaster" to "Enable gas sponsorship for seamless transactions".
  - Changed "Point of sale solution for bridging, onramping & swapping" to "Enable payments in any token on any chain".
  - Changed "Query, transform and analyze blockchain data" to "Query and analyze blockchain data".

> ✨ 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**
  * Removed the "Next Steps" and "Claim Chain" sections from the chain page, including related external links and prompts.
* **Documentation**
  * Updated product names and descriptions to use clearer, more user-focused language for better understanding of features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 10:42:56 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
30496388c4 Version Packages (#7453)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-27 03:40:38 -07:00
Joaquim VergesandGitHub c2ef5eb51c [SDK] Update zod dependency to 3.25.67 (#7451) 2025-06-27 01:11:51 -07:00
gregandGitHub 277c2e2ed9 Feature: Add fallback when we cant fetch the token in new widgets (#7452) 2025-06-26 16:43:46 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
0ac5e59eb9 Version Packages (#7445)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-26 14:56:22 -07:00
gregfromstl fa5e13b5d3 Feature: Adds onramp webhook schema to sdk (#7450)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on adding onramp webhook parsing functionality to the `Universal Bridge` in the `thirdweb` package, along with updates to existing tests to accommodate new payload structures.

### Detailed summary
- Added onramp webhook parsing for `Universal Bridge`.
- Updated `chainId` and `transactionHash` values in tests.
- Modified the structure of `validWebhook` and `validPayload` in `Webhook.test.ts`.
- Changed `destinationAmount` and `originAmount` types to `bigint`.
- Introduced `onchainWebhookSchema` and `onrampWebhookSchema` in `Webhook.ts`.
- Updated `webhookSchema` to include `onchain` and `onramp` transaction types.
- Enhanced the `parse` function to return a `WebhookPayload` that satisfies the new schema.

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

<!-- end pr-codex -->

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

* **New Features**
  * Enhanced webhook parsing to support onramp webhooks for the Universal Bridge.

* **Refactor**
  * Improved validation and structure of webhook payloads for clearer distinction between onchain and onramp types.
  * Updated numeric fields to ensure correct data types and improved payload handling.

* **Documentation**
  * Added a changeset file describing the new onramp webhook parsing capability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-26 21:30:46 +00:00
gregandGitHub 45bb790dd1 Fix: Include correct client in payment link UI (#7449) 2025-06-26 11:21:52 -07:00
0xFirekeeper 102a8c8224 [Portal] EIP-7702 Session Key Docs (.NET) (#7448)
Closes TOOL-4880

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new features and updates for the `EcosystemWallet` in the Thirdweb .NET SDK, focusing on EIP-7702 functionalities, including session management and permissions.

### Detailed summary
- Added new sidebar links for EIP-7702 actions.
- Updated `CreateSessionKey` function to include EIP-7702 functionalities.
- Introduced detailed documentation for new methods: `SignerHasFullPermissions`, `GetCallPoliciesForSigner`, `GetTransferPoliciesForSigner`, `GetSessionExpirationForSigner`, and `GetSessionStateForSigner`.
- Each method includes usage examples, parameter details, return values, and exception 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**
  * Expanded sidebar navigation with new links for EIP-7702 session key actions under the wallet section.
  * Added documentation for EIP-7702 session key management, including creating session keys, checking signer permissions, retrieving call and transfer policies, session expiration, and session state.

* **Documentation**
  * Introduced detailed guides and usage examples for new EIP-7702 wallet session key features and related methods in the .NET SDK.
  * Clarified sidebar link names to distinguish between EIP-4337 and EIP-7702 session key creation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-26 16:19:05 +00:00
Phillip HoandGitHub 472e737616 chore: webhook payload must be an array (#7444) 2025-06-26 18:38:21 +08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
0345492e99 Version Packages (#7433)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-26 03:01:11 -07:00
gregandGitHub e650434061 [SDK] Fix: Chain name label (#7442) 2025-06-25 15:47:17 -07:00
gregandGitHub 6712911950 [SDK] Chore: Remove onrampTokenAddress set to ETH (#7441) 2025-06-25 14:22:20 -07:00
orbuloandGitHub 1387337955 [SDK] Feature: update hey-api version to 0.76.0 (#7431) 2025-06-25 12:30:12 -07:00
Phillip HoandGitHub a2b737e144 fix: typo on a field in webhook event helper (#7440) 2025-06-25 23:19:02 +08:00
Phillip HoandGitHub c5f9921dc9 feat: add webhook producer to service-utils (#7439) 2025-06-25 13:35:19 +08:00
Joaquim VergesandGitHub 830125c920 Add CLAUDE.md (#7438) 2025-06-24 19:11:06 -07:00
Joaquim VergesandGitHub af89836b6c [SDK] Handle sponsored gas on TransactionWidget (#7437) 2025-06-24 19:10:47 -07:00
MananTank d85ee5fac3 Dashboard: add "Contracts" link in contract layout header (#7436)
![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/yNOf1svJ8o3zjO7zQouZ/05f5e0d2-cfee-4604-8567-a630e13f8232.png)

<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new optional property, `currentProjectSubpath`, to enhance the `TeamHeader` components by allowing the display of additional project navigation details, including a label, href, and icon.

### Detailed summary
- Added `currentProjectSubpath` property to `TeamHeaderLoggedIn` and `TeamHeaderDesktopUI`.
- Updated `TeamHeaderUI` to render project subpath details if provided.
- Introduced a new story in `TeamHeaderUI.stories.tsx` to showcase the `currentProjectSubpath` feature.
- Integrated `currentProjectSubpath` into the `ContractLayout` component for enhanced navigation.

> ✨ 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 displaying an active project subpage link with an icon in the team header, enhancing breadcrumb navigation for projects.
  - Updated the team header to visually indicate the current subpage within a project.

- **Documentation**
  - Added a new Storybook variant to demonstrate the team header with an active subpage link.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-24 23:37:56 +00:00
MananTank 8bb4d4bc11 Dashboard: remove date selector from UB FTUX, improved skeletons (#7435)
<!--

## 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 `Page` component in the `loading.tsx` file and the `PayAnalytics` component in the `PayAnalytics.tsx` file, enhancing the structure and user experience by modifying the rendering of analytics and loading states.

### Detailed summary
- Removed `ResponsiveSuspense` and `PayAnalyticsFilter` from `loading.tsx`.
- Integrated `PayAnalytics` directly into the `Page` component.
- Added `ResponsiveSuspense` with a skeleton loader in `PayAnalytics`.
- Reorganized analytics display in `PayAnalytics`, including filtering and layout adjustments.

> ✨ 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**
  - Improved the loading experience in the PayAnalytics section with responsive skeleton placeholders during data loading.
  - Updated the layout to display analytics filters above the loading state for better usability.

- **Chores**
  - Removed unused loading and suspense logic from related pages to streamline the user interface.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-24 22:27:52 +00:00
MananTank 3c6668b6c8 [TOOL-4833] Add Manage Contract button in new public contract pages (#7434)
<!--

## 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 handling of `projectSlug` and `teamSlug` properties across various components in the application, improving navigation and data management related to teams and projects.

### Detailed summary
- Added `projectSlug` and `teamSlug` props in several components including `cards.tsx`, `DeployedContractsPage.tsx`, and `layout.tsx`.
- Updated links to include `teamSlug` and `projectSlug` for better navigation.
- Introduced new API function `getContractImportedProjects` to fetch projects with imported contracts.
- Created `TeamSelectorCard` and `ProjectAndTeamSelectorCard` components for selecting teams and projects.
- Improved UI elements such as buttons and tooltips in `ContractHeaderUI` and `ImportModal`.
- Enhanced type definitions for better type safety and clarity in components.

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

<!-- end pr-codex -->

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

- **New Features**
  - Introduced a team and project selector UI, enabling users to easily select teams or projects from visual cards.
  - Added a project and team selector for associating contracts, allowing users to view, select, or import contracts into projects across teams.
  - Enhanced contract management by providing direct navigation to contract pages based on project selection.
  - Improved contract view links to include team and project information in URLs for clearer navigation.
  - Added functionality to fetch projects that have imported specific contracts within teams.

- **UI Improvements**
  - Updated button styles with rounded edges for a polished look.
  - Added copy address and manage contract buttons with icons and tooltips for enhanced usability.

- **Storybook/Documentation**
  - Added Storybook stories to visually test and demonstrate the new team and project selector components under various scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-24 21:58:48 +00:00
gregandGitHub 6aa2765a34 Feature: UB Widget Tracking and Docs (#7426) 2025-06-24 13:19:01 -07:00
226 changed files with 11940 additions and 3426 deletions
+290
View File
@@ -0,0 +1,290 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
### Package Management
- Use `pnpm install` (or `pnpm install --ignore-scripts` on Windows)
- Monorepo managed with Turborepo and pnpm workspaces
### Building
```bash
# Build all packages
pnpm build
# Build specific packages with dependencies
turbo run build --filter=./packages/*
# Development with watch mode
pnpm dev # Core thirdweb package
pnpm dashboard # Run dashboard + dependencies
pnpm playground # Run playground + dependencies
pnpm portal # Run portal docs + dependencies
```
### Testing
```bash
# Run all tests
pnpm test
# Interactive testing (thirdweb package)
cd packages/thirdweb && pnpm test:devr
# Test specific file
pnpm test:dev <path-to-test-file>
# E2E testing (dashboard)
cd apps/dashboard && pnpm playwright
```
### Code Quality
```bash
# Lint all packages
pnpm lint
# Auto-fix linting issues
pnpm fix
# Format code
turbo run format
# Biome is the primary linter/formatter
```
### Development Workflow
```bash
# Start development server for dashboard
pnpm dashboard
# Start playground for SDK testing
pnpm playground
# Generate changeset for releases
pnpm changeset
# Version packages
pnpm version-packages
```
## Repository Architecture
### Monorepo Structure
This is a Turborepo monorepo with the main thirdweb v5 SDK consolidated into `/packages/thirdweb/`. Legacy packages are in `/legacy_packages/`.
### Core Package (`/packages/thirdweb/`)
**Main Modules:**
- `client/` - ThirdwebClient foundation
- `chains/` - 50+ supported blockchain definitions
- `contract/` - Contract interaction with automatic ABI resolution
- `transaction/` - Transaction management and execution
- `wallets/` - Comprehensive wallet integration system
- `extensions/` - Modular contract extensions (ERC20, ERC721, etc.)
- `auth/` - SIWE authentication and signature verification
- `pay/` - Fiat and crypto payment infrastructure
- `storage/` - IPFS integration for decentralized storage
- `rpc/` - Low-level blockchain communication
**Exports Structure:**
The SDK uses modular exports from `src/exports/` including:
- `thirdweb.ts` - Core client and utilities
- `chains.ts` - Chain definitions
- `wallets.ts` - Wallet connectors
- `react.ts` - React hooks and components
- `extensions/` - Contract standards and protocols
### Applications (`/apps/`)
- **dashboard** - Web-based developer console (Next.js, Chakra UI)
- **playground-web** - Interactive SDK testing environment
- **portal** - Documentation site with MDX
- **nebula** - Account abstraction and smart wallet management
- **wallet-ui** - Wallet interface and testing
### Key Packages (`/packages/`)
- **thirdweb** - Main SDK (TypeScript, React, React Native)
- **engine** - thirdweb Engine API client
- **insight** - Analytics and data APIs
- **nebula** - Account abstraction client
- **service-utils** - Shared utilities across services
## Development Practices
### GitHub Workflow & Pull Requests
- **PR titles**: Must start with affected workspace in brackets (e.g. `[SDK]`, `[Dashboard]`, `[Portal]`, `[Playground]`)
- **PR descriptions**: Begin with one-sentence summary, add checklist of changes, reference issues with `Fixes #123`
- **Commits**: Keep small and topical – one logical change per commit
- **Branch naming**: Use `area/brief-topic` format (e.g. `sdk/fix-gas-estimate`). Avoid personal names
- **Reviews**: Request at least one core maintainer review. Do not self-merge unless sole package owner
- **CI requirements**: All checks (type-check, Biome, tests) must pass before merging
### Code Quality & Formatting
- **Biome**: Primary linter/formatter (rules in `biome.json`)
- **Pre-commit**: Run `pnpm biome check --apply` before committing
- **Build verification**: Run `pnpm build` after each file change to ensure everything builds
- Avoid editor-specific configs; rely on shared settings
### TypeScript Guidelines
- **Style**: Write idiomatic TypeScript with explicit function declarations and return types
- **File structure**: Limit each file to one stateless, single-responsibility function for clarity
- **Types**: Re-use shared types from `@/types` or local `types.ts` barrels
- **Interfaces vs Types**: Prefer type aliases over interface except for nominal shapes
- **Type safety**: Avoid `any` and `unknown` unless unavoidable; narrow generics when possible
- **Architecture**: Choose composition over inheritance; leverage utility types (`Partial`, `Pick`, etc.)
### Testing Strategy
- **Co-location**: Place tests alongside code: `foo.ts` ↔ `foo.test.ts`
- **Test approach**: Use real function invocations with stub data; avoid brittle mocks
- **Network mocking**: Use Mock Service Worker (MSW) for fetch/HTTP call interception
- **Test quality**: Keep tests deterministic and side-effect free
- **Running tests**: `cd packages/thirdweb && pnpm test:dev <filename>`
- **Test accounts**: Predefined accounts in `test/src/test-wallets.ts`
- **Chain forking**: Use `FORKED_ETHEREUM_CHAIN` for mainnet interactions, `ANVIL_CHAIN` for isolated tests
### SDK Development (`packages/thirdweb`)
#### Public API Guidelines
- **Exports**: Export everything via `exports/` directory, grouped by feature
- **Documentation**: Every public symbol must have comprehensive TSDoc with:
- At least one `@example` block that compiles
- Custom annotation tags (`@beta`, `@internal`, `@experimental`)
- **Comments**: Comment only ambiguous logic; avoid restating TypeScript in prose
#### Performance Optimization
- **Lazy loading**: Load heavy dependencies inside async paths to keep initial bundle lean:
```typescript
const { jsPDF } = await import("jspdf");
```
- **Bundle budgets**: Track via `package.json#size-limit`
- **Dependencies**: De-duplicate across packages through pnpm workspace hoisting
### Dashboard & Playground Development
#### UI Component Standards
- **Core components**: Import primitives from `@/components/ui/*` (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator)
- **Navigation**: Use `NavLink` for internal navigation with automatic active states
- **Organization**: Group feature-specific components under `feature/components/*` with barrel `index.ts`
#### Styling Conventions
- **CSS framework**: Tailwind CSS only – no inline styles or CSS modules
- **Class merging**: Use `cn()` from `@/lib/utils` for conditional logic
- **Design tokens**: Use design system tokens (backgrounds: `bg-card`, borders: `border-border`, muted text: `text-muted-foreground`)
- **Component API**: Expose `className` prop on root element for overrides
#### Component Architecture
- **Server Components** (Node edge):
- Start files with `import "server-only";`
- Read cookies/headers with `next/headers`
- Access server-only environment variables
- Perform heavy data fetching
- Implement redirect logic with `redirect()` from `next/navigation`
- **Client Components** (browser):
- Begin files with `'use client';`
- Handle interactive UI with React hooks (`useState`, `useEffect`, React Query, wallet hooks)
- Access browser APIs (`localStorage`, `window`, `IntersectionObserver`)
- Support fast transitions with prefetched data
#### Data Fetching Patterns
- **Server Side**:
- Always call `getAuthToken()` to retrieve JWT from cookies
- Use `Authorization: Bearer` header – never embed tokens in URLs
- Return typed results (`Project[]`, `User[]`) – avoid `any`
- **Client Side**:
- Wrap calls in React Query (`@tanstack/react-query`)
- Use descriptive, stable `queryKeys` for cache hits
- Configure `staleTime`/`cacheTime` based on freshness (default ≥ 60s)
- Keep tokens secret via internal API routes or server actions
#### Analytics Event Guidelines
- **When to add**: Only create events that answer clear product/business questions
- **Check duplicates**: Review `src/@/analytics/report.ts` first
- **Naming**:
- Event name: human-readable `<subject> <verb>` (e.g. "contract deployed")
- Function: `report<Subject><Verb>` (PascalCase)
- **Template**:
```typescript
/**
* ### Why do we need to report this event?
* - Tracks number of contracts deployed
*
* ### Who is responsible for this event?
* @username
*/
export function reportContractDeployed(properties: {
address: string;
chainId: number;
}) {
posthog.capture("contract deployed", properties);
}
```
- **Client-side only**: Never import `posthog-js` in server components
- **Changes**: Inform **#eng-core-services** before renaming/removing events
### Extension Development
- Extensions follow modular pattern in `src/extensions/`
- Auto-generated contracts from ABI definitions
- Composable functions with TypeScript safety
- Support for read/write operations
### Wallet Architecture
- Unified `Wallet` and `Account` interfaces
- Support for in-app wallets (social/email login)
- Smart wallets with account abstraction
- EIP-1193, EIP-5792, EIP-7702 standard support
## Contribution Workflow
1. **Fork and Clone**: Create fork, clone, create feature branch
2. **Install**: `pnpm install` (use `--ignore-scripts` on Windows)
3. **Develop**: Use appropriate dev commands above
4. **Test**: Write unit tests, run linting, test on demo apps
5. **Changeset**: Run `pnpm changeset` for semantic versioning
6. **PR**: Submit pull request to main branch
### Release Testing
Comment `/release-pr` on PR to trigger dev release to npm for testing.
### Changeset Guidelines
Each change in `packages/*` should contain a changeset for the appropriate package with the appropriate version bump:
- **patch**: Changes that don't impact the public API
- **minor**: Any new/modified public API
- **major**: Breaking changes (surface prominently in PR descriptions)
### Documentation Standards
- For new UI components, add Storybook stories (`*.stories.tsx`) alongside the code
- Surface breaking changes prominently in PR descriptions
- Keep documentation focused on developer experience and practical usage
## Multi-Platform Support
The SDK supports:
- **Web**: React hooks and components
- **React Native**: Mobile-specific exports and components
- **Node.js**: Server-side functionality
- **Framework Adapters**: Wagmi, Ethers compatibility layers
Key files:
- `src/exports/react.native.ts` - React Native specific exports
- `packages/react-native-adapter/` - Mobile platform shims
- `packages/wagmi-adapter/` - Wagmi ecosystem integration
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+1 -1
View File
@@ -81,7 +81,7 @@
"tiny-invariant": "^1.3.3",
"use-debounce": "^10.0.5",
"vaul": "^1.1.2",
"zod": "3.25.62"
"zod": "3.25.67"
},
"devDependencies": {
"@biomejs/biome": "2.0.4",
@@ -1,4 +1,5 @@
import "server-only";
import { getAddress } from "thirdweb";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
export type ProjectContract = {
@@ -43,3 +44,43 @@ export async function getProjectContracts(options: {
return data.result;
}
export type PartialProject = {
id: string;
name: string;
slug: string;
image: string | null;
};
/**
* get a list of projects within a team that have a given contract imported
*/
export async function getContractImportedProjects(options: {
teamId: string;
authToken: string;
chainId: number;
contractAddress: string;
}) {
const url = new URL(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/projects/contracts/lookup?chainId=${options.chainId}&contractAddress=${getAddress(options.contractAddress)}`,
);
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${options.authToken}`,
},
});
if (!res.ok) {
const errorMessage = await res.text();
console.error("Error fetching: /projects/contracts/lookup");
console.error(errorMessage);
return [];
}
const data = (await res.json()) as {
result: PartialProject[];
};
return data.result;
}
@@ -37,6 +37,8 @@ type ImportModalProps = {
onClose: () => void;
teamId: string;
projectId: string;
projectSlug: string;
teamSlug: string;
client: ThirdwebClient;
type: "contract" | "asset";
onSuccess?: () => void;
@@ -69,7 +71,9 @@ export const ImportModal: React.FC<ImportModalProps> = (props) => {
client={props.client}
onSuccess={props.onSuccess}
projectId={props.projectId}
projectSlug={props.projectSlug}
teamId={props.teamId}
teamSlug={props.teamSlug}
type={props.type}
/>
</DialogContent>
@@ -96,6 +100,8 @@ const importFormSchema = z.object({
function ImportForm(props: {
teamId: string;
projectId: string;
teamSlug: string;
projectSlug: string;
client: ThirdwebClient;
type: "contract" | "asset";
onSuccess?: () => void;
@@ -216,7 +222,7 @@ function ImportForm(props: {
addContractToProject.data?.result ? (
<Button asChild className="gap-2">
<Link
href={`/${chainSlug}/${addContractToProject.data.result.contractAddress}`}
href={`/team/${props.teamSlug}/${props.projectSlug}/contract/${chainSlug}/${addContractToProject.data.result.contractAddress}`}
rel="noopener noreferrer"
target="_blank"
>
@@ -0,0 +1,20 @@
"use client";
import { defineChain, type ThirdwebClient } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { BuyWidget } from "thirdweb/react";
export function BuyFundsSection(props: {
chain: ChainMetadata;
client: ThirdwebClient;
}) {
return (
<section className="flex flex-col gap-4 items-center justify-center">
<BuyWidget
amount="0"
// eslint-disable-next-line no-restricted-syntax
chain={defineChain(props.chain.chainId)}
client={props.client}
/>
</section>
);
}
@@ -1,37 +0,0 @@
"use client";
import { FileTextIcon } from "lucide-react";
import Link from "next/link";
import type { ChainMetadata } from "thirdweb/chains";
import { SectionTitle } from "../server/SectionTitle";
export default function NextSteps(props: { chain: ChainMetadata }) {
const { chain } = props;
return (
<section>
<SectionTitle title="Next Steps" />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
<div className="relative flex gap-3 rounded-lg border bg-card p-4 pr-8 transition-colors hover:border-active-border">
<FileTextIcon className="mt-0.5 size-5 shrink-0" />
<div>
<h3 className="mb-1.5 font-medium">
<Link
className="before:absolute before:inset-0"
href={
"https://blog.thirdweb.com/supercharge-user-adoption-integrate-embedded-wallets-in-minutes/"
}
rel="noopener noreferrer"
target="_blank"
>
Create a login for {chain.name}
</Link>
</h3>
<p className="text-muted-foreground text-sm">
Supercharge User Adoption—Integrate In-App Wallets in Minutes
</p>
</div>
</div>
</div>
</section>
);
}
@@ -1,49 +0,0 @@
"use client";
import { useTheme } from "next-themes";
import type { ThirdwebClient } from "thirdweb";
import { PayEmbed } from "thirdweb/react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { defineDashboardChain } from "@/lib/defineDashboardChain";
import { getSDKTheme } from "../../../../../../../../@/utils/sdk-component-theme";
export function PayModalButton(props: {
chainId: number;
label: string;
client: ThirdwebClient;
}) {
const { theme } = useTheme();
return (
<Dialog>
<DialogTrigger asChild>
<Button className="w-full" variant="primary">
{props.label}
</Button>
</DialogTrigger>
<DialogContent
className="border-none bg-transparent p-0 md:max-w-[360px]"
dialogCloseClassName="focus:ring-0"
dialogOverlayClassName="backdrop-blur-lg"
>
<PayEmbed
className="!w-auto"
client={props.client}
payOptions={{
prefillBuy: {
allowEdits: {
amount: true,
chain: false,
token: true,
},
// Do not include local chain overrides for chain pages
// eslint-disable-next-line no-restricted-syntax
chain: defineDashboardChain(props.chainId, undefined),
},
}}
theme={getSDKTheme(theme === "dark" ? "dark" : "light")}
/>
</DialogContent>
</Dialog>
);
}
@@ -1,92 +0,0 @@
import { useId } from "react";
export function CreditCardIcon(props: { bg: string; className?: string }) {
const linearGradientId = useId();
const linearGradientId2 = useId();
const linearGradientId3 = useId();
return (
<svg
aria-hidden="true"
className={props.className}
fill="none"
height={48}
viewBox="0 0 48 48"
width={48}
xmlns="http://www.w3.org/2000/svg"
>
<rect fill={props.bg} height={48} rx={16} width={48} />
<rect
height={46}
rx={15}
stroke={`url(#${linearGradientId})`}
strokeOpacity="0.2"
strokeWidth={2}
width={46}
x={1}
y={1}
/>
<rect fill={props.bg} height={38} rx={11} width={38} x={5} y={5} />
<rect
height={38}
rx={11}
stroke={`url(#${linearGradientId2})`}
strokeWidth={2}
width={38}
x={5}
y={5}
/>
<path
d="M32.3337 22.3333H15.667M23.167 25.6666H19.0003M15.667 20.8333L15.667 27.1666C15.667 28.1 15.667 28.5668 15.8486 28.9233C16.0084 29.2369 16.2634 29.4918 16.577 29.6516C16.9335 29.8333 17.4002 29.8333 18.3337 29.8333L29.667 29.8333C30.6004 29.8333 31.0671 29.8333 31.4236 29.6516C31.7372 29.4918 31.9922 29.2369 32.152 28.9233C32.3337 28.5668 32.3337 28.1 32.3337 27.1666V20.8333C32.3337 19.8999 32.3337 19.4332 32.152 19.0766C31.9922 18.763 31.7372 18.5081 31.4236 18.3483C31.0671 18.1666 30.6004 18.1666 29.667 18.1666L18.3337 18.1666C17.4002 18.1666 16.9335 18.1666 16.577 18.3483C16.2634 18.5081 16.0084 18.763 15.8486 19.0766C15.667 19.4332 15.667 19.8999 15.667 20.8333Z"
stroke={`url(#${linearGradientId3})`}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id={linearGradientId}
x1="-3.26045"
x2="45.7807"
y1="15.4021"
y2="50.1547"
>
<stop stopColor="#F213A4" />
<stop offset="0.1517" stopColor="#E011A7" />
<stop offset="0.4554" stopColor="#B20DAF" />
<stop offset="0.8789" stopColor="#6806BB" />
<stop offset={1} stopColor="#5204BF" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id={linearGradientId2}
x1="1.28296"
x2="42.1506"
y1="16.835"
y2="45.7956"
>
<stop stopColor="#F213A4" />
<stop offset="0.1517" stopColor="#E011A7" />
<stop offset="0.4554" stopColor="#B20DAF" />
<stop offset="0.8789" stopColor="#6806BB" />
<stop offset={1} stopColor="#5204BF" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id={linearGradientId3}
x1="14.5349"
x2="27.1676"
y1="21.9102"
y2="34.6989"
>
<stop stopColor="#F213A4" />
<stop offset="0.1517" stopColor="#E011A7" />
<stop offset="0.4554" stopColor="#B20DAF" />
<stop offset="0.8789" stopColor="#6806BB" />
<stop offset={1} stopColor="#5204BF" />
</linearGradient>
</defs>
</svg>
);
}
@@ -1,68 +0,0 @@
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import type { ThirdwebClient } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { ChainIcon } from "../../../../components/server/chain-icon";
import { PayModalButton } from "../client/PayModal";
import { CreditCardIcon } from "../icons/CreditCardIcon";
import { SectionTitle } from "./SectionTitle";
export function BuyFundsSection(props: {
chain: ChainMetadata;
client: ThirdwebClient;
}) {
const sanitizedChainName = props.chain.name.replace("Mainnet", "").trim();
return (
<section>
<SectionTitle title="Bridge" />
<div className="flex justify-center rounded-lg border border-border bg-card px-4 py-10">
<div className="flex max-w-[520px] flex-col items-center ">
<div className="flex items-center">
<ChainIcon
className="-mr-2 size-12 rounded-full border p-1"
iconUrl={props.chain.icon?.url}
/>
<CreditCardIcon
bg="hsl(var(--background))"
className="-ml-2 size-12"
/>
</div>
<div className="h-6" />
<h2 className="px-4 text-center font-semibold text-lg tracking-tight">
Bridge Funds to {sanitizedChainName}
</h2>
<div className="h-2" />
<p className="max-w-[520px] px-4 text-center text-muted-foreground text-sm">
Get {props.chain.nativeCurrency.symbol} on {sanitizedChainName} with
fiat or any token on another chain.
</p>
<div className="h-8" />
<PayModalButton
chainId={props.chain.chainId}
client={props.client}
label={`Bridge to ${sanitizedChainName}`}
/>
<div className="h-4" />
<Link
className="inline-flex items-center gap-1.5 text-muted-foreground text-sm hover:text-foreground"
href="https://portal.thirdweb.com/connect/pay/overview"
rel="noopener noreferrer"
target="_blank"
>
Learn more about Universal Bridge
<ExternalLinkIcon className="size-3" />
</Link>
</div>
</div>
</section>
);
}
@@ -1,31 +0,0 @@
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { SectionTitle } from "./SectionTitle";
export function ClaimChainSection() {
return (
<section>
<SectionTitle title="Claim Chain" />
<div className="rounded-lg border bg-card p-4">
<p className="mb-1"> This chain page is unclaimed </p>
<p className="mb-5 text-muted-foreground text-sm">
Are you the owner of this ecosystem? Claim this page to add additional
information on this page to help your developers get started!
</p>
<Button asChild variant="outline">
<Link
className="gap-2"
href="https://share.hsforms.com/1qAViW3U5SoKbabNhRZy4Kgea58c"
rel="noopener noreferrer"
target="_blank"
>
<span>Claim Chain</span>
<ExternalLinkIcon className="-mt-[3px] size-4" />
</Link>
</Button>
</div>
</section>
);
}
@@ -2,10 +2,8 @@ import { CircleAlertIcon } from "lucide-react";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { getRawAccount } from "../../../../account/settings/getAccount";
import { getChain, getChainMetadata } from "../../utils";
import NextSteps from "./components/client/NextSteps";
import { BuyFundsSection } from "./components/server/BuyFundsSection";
import { BuyFundsSection } from "./components/client/BuyFundsSection";
import { ChainOverviewSection } from "./components/server/ChainOverviewSection";
import { ClaimChainSection } from "./components/server/ClaimChainSection";
import { ChainCTA } from "./components/server/cta-card";
import { ExplorersSection } from "./components/server/explorer-section";
import { FaucetSection } from "./components/server/FaucetSection";
@@ -60,11 +58,6 @@ export default async function Page(props: {
{chain.services.filter((s) => s.enabled).length > 0 && (
<SupportedProductsSection services={chain.services} />
)}
{/*Next Steps */}
<NextSteps chain={chain} />
{/* Claim Chain */}
{!chainMetadata && <ClaimChainSection />}
</div>
);
}
@@ -83,7 +83,7 @@ export const PrimaryDashboardButton: React.FC<AddToDashboardCardProps> = ({
// if user is on a project page
if (projectMeta) {
return (
<Button asChild variant="default">
<Button asChild className="rounded-full" variant="default">
<Link
className="gap-2"
href={`/${contractInfo.chainSlug}/${contractAddress}`}
@@ -0,0 +1,27 @@
import type { ThirdwebContract } from "thirdweb";
import { isOwnerSupported, owner } from "thirdweb/extensions/common";
import {
getRoleMember,
isGetRoleAdminSupported,
} from "thirdweb/extensions/permissions";
export async function getContractCreator(
contract: ThirdwebContract,
functionSelectors: string[],
) {
if (isOwnerSupported(functionSelectors)) {
return owner({
contract,
});
}
if (isGetRoleAdminSupported(functionSelectors)) {
return getRoleMember({
contract,
index: BigInt(0),
role: "admin",
});
}
return null;
}
@@ -1,6 +1,5 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { maxUint256 } from "thirdweb/utils";
import { BadgeContainer } from "../../../../../../../../@/storybook/utils";
import { BadgeContainer } from "@/storybook/utils";
import { SupplyClaimedProgress } from "./supply-claimed-progress";
const meta = {
@@ -19,7 +18,7 @@ function StoryVariants() {
return (
<div className="container max-w-md space-y-10 py-10">
<BadgeContainer label="10 / Unlimited Supply">
<SupplyClaimedProgress claimedSupply={10n} totalSupply={maxUint256} />
<SupplyClaimedProgress claimedSupply={10n} totalSupply="unlimited" />
</BadgeContainer>
<BadgeContainer label="500/1000 Supply">
@@ -1,14 +1,13 @@
import { InfinityIcon } from "lucide-react";
import { maxUint256 } from "thirdweb/utils";
import { Progress } from "@/components/ui/progress";
import { supplyFormatter } from "../nft/format";
export function SupplyClaimedProgress(props: {
claimedSupply: bigint;
totalSupply: bigint;
totalSupply: bigint | "unlimited";
}) {
// if total supply is unlimited
if (props.totalSupply === maxUint256) {
if (props.totalSupply === "unlimited") {
return (
<p className="flex items-center justify-between gap-2">
<span className="font-medium text-sm">Claimed Supply </span>
@@ -0,0 +1,19 @@
import type { ThirdwebContract } from "thirdweb";
import { WalletAddress } from "@/components/blocks/wallet-address";
export function ContractCreatorBadge(props: {
contractCreator: string;
clientContract: ThirdwebContract;
}) {
return (
<div className="flex items-center gap-1.5 bg-card rounded-full px-2.5 py-1.5 border hover:bg-accent">
<span className="text-xs text-foreground">By</span>
<WalletAddress
address={props.contractCreator}
className="py-0 text-xs h-auto !no-underline"
client={props.clientContract.client}
iconClassName="size-3.5 hidden"
/>
</div>
);
}
@@ -88,6 +88,25 @@ export const WithImageAndMultipleSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: mockTokenImage,
name: "Sample Token",
socialUrls: {
discord: mockSocialUrls.discord,
github: mockSocialUrls.github,
telegram: mockSocialUrls.telegram,
twitter: mockSocialUrls.twitter,
website: mockSocialUrls.website,
},
symbol: "SMPL",
},
};
export const WithContractCreator: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: "0x1234567890123456789012345678901234567890",
image: mockTokenImage,
name: "Sample Token",
socialUrls: {
@@ -105,6 +124,7 @@ export const WithBrokenImageAndSingleSocialUrl: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "broken-image.png",
name: "Sample Token",
socialUrls: {
@@ -118,6 +138,7 @@ export const WithoutImageAndNoSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: undefined,
name: "Sample Token",
socialUrls: {},
@@ -129,6 +150,7 @@ export const LongNameAndLotsOfSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "https://thirdweb.com/chain-icons/ethereum.svg",
name: "This is a very long token name that should wrap to multiple lines",
socialUrls: {
@@ -148,6 +170,7 @@ export const AllSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "https://thirdweb.com/chain-icons/ethereum.svg",
name: "Sample Token",
socialUrls: {
@@ -171,6 +194,7 @@ export const InvalidSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "https://thirdweb.com/chain-icons/ethereum.svg",
name: "Sample Token",
socialUrls: {
@@ -188,6 +212,7 @@ export const SomeSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "https://thirdweb.com/chain-icons/ethereum.svg",
name: "Sample Token",
socialUrls: {
@@ -1,4 +1,4 @@
import { ExternalLinkIcon, GlobeIcon } from "lucide-react";
import { ExternalLinkIcon, GlobeIcon, Settings2Icon } from "lucide-react";
import Link from "next/link";
import { useMemo } from "react";
import type { ThirdwebContract } from "thirdweb";
@@ -19,6 +19,7 @@ import { YoutubeIcon } from "@/icons/brand-icons/YoutubeIcon";
import { ChainIconClient } from "@/icons/ChainIcon";
import { cn } from "@/lib/utils";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { ContractCreatorBadge } from "./ContractCreatorBadge";
const platformToIcons: Record<string, React.FC<{ className?: string }>> = {
discord: DiscordIcon,
@@ -42,6 +43,7 @@ export function ContractHeaderUI(props: {
clientContract: ThirdwebContract;
socialUrls: object;
imageClassName?: string;
contractCreator: string | null;
}) {
const socialUrls = useMemo(() => {
const socialUrlsValue: { name: string; href: string }[] = [];
@@ -111,6 +113,13 @@ export function ContractHeaderUI(props: {
)}
</Link>
<CopyAddressButton
address={props.clientContract.address}
className="rounded-full bg-card w-[30px] h-[30px] p-0 [&>span]:hidden [&>svg]:text-foreground"
copyIconPosition="left"
variant="outline"
/>
{socialUrls
.toSorted((a, b) => {
const aIcon = platformToIcons[a.name.toLowerCase()];
@@ -140,12 +149,36 @@ export function ContractHeaderUI(props: {
{/* bottom row */}
<div className="flex flex-row flex-wrap items-center gap-2">
<CopyAddressButton
address={props.clientContract.address}
className="rounded-full bg-card px-2.5 py-1.5 text-xs"
copyIconPosition="left"
variant="outline"
/>
{props.contractCreator && (
<ContractCreatorBadge
clientContract={props.clientContract}
contractCreator={props.contractCreator}
/>
)}
<ToolTipLabel
contentClassName="max-w-[300px]"
label={
<>
View this contract in thirdweb dashboard to view contract
management interface
</>
}
>
<Button
asChild
className="rounded-full bg-card gap-1.5 text-xs py-1.5 px-2.5 h-auto"
size="sm"
variant="outline"
>
<Link
href={`/team/~/~/contract/${props.chainMetadata.slug}/${props.clientContract.address}`}
>
<Settings2Icon className="size-3.5 text-muted-foreground" />
Manage Contract
</Link>
</Button>
</ToolTipLabel>
{explorersToShow?.map((validBlockExplorer) => (
<BadgeLink
@@ -2,45 +2,47 @@
import { useTheme } from "next-themes";
import type { Chain, ThirdwebClient } from "thirdweb";
import { PayEmbed } from "thirdweb/react";
import { getSDKTheme } from "../../../../../../../../../@/utils/sdk-component-theme";
import { BuyWidget } from "thirdweb/react";
import {
reportAssetBuyFailed,
reportAssetBuySuccessful,
} from "@/analytics/report";
import { parseError } from "@/utils/errorParser";
import { getSDKTheme } from "@/utils/sdk-component-theme";
export function BuyTokenEmbed(props: {
client: ThirdwebClient;
chain: Chain;
tokenSymbol: string;
tokenName: string;
tokenAddress: string;
}) {
const { theme } = useTheme();
return (
<PayEmbed
<BuyWidget
amount="1"
chain={props.chain}
className="!rounded-xl !w-full"
client={props.client}
connectOptions={{
autoConnect: false,
}}
payOptions={{
metadata: {
name: `Buy ${props.tokenSymbol}`,
},
mode: "fund_wallet",
prefillBuy: {
allowEdits: {
amount: true,
chain: false,
token: false,
},
amount: "1",
chain: props.chain,
token: {
address: props.tokenAddress,
name: props.tokenName,
symbol: props.tokenSymbol,
},
},
onError={(e) => {
const errorMessage = parseError(e);
reportAssetBuyFailed({
assetType: "coin",
chainId: props.chain.id,
contractType: "DropERC20",
error: errorMessage,
});
}}
onSuccess={() => {
reportAssetBuySuccessful({
assetType: "coin",
chainId: props.chain.id,
contractType: "DropERC20",
});
}}
theme={getSDKTheme(theme === "light" ? "light" : "dark")}
tokenAddress={props.tokenAddress as `0x${string}`}
/>
);
}
@@ -26,7 +26,7 @@ import {
getApprovalForTransaction,
} from "thirdweb/extensions/erc20";
import { useActiveAccount, useSendTransaction } from "thirdweb/react";
import { getClaimParams } from "thirdweb/utils";
import { getClaimParams, maxUint256 } from "thirdweb/utils";
import {
reportAssetBuyFailed,
reportAssetBuySuccessful,
@@ -328,9 +328,16 @@ export function TokenDropClaim(props: {
claimedSupply={BigInt(
toTokens(props.claimCondition.supplyClaimed, props.decimals),
)}
totalSupply={BigInt(
toTokens(props.claimCondition.maxClaimableSupply, props.decimals),
)}
totalSupply={
props.claimCondition.maxClaimableSupply === maxUint256
? "unlimited"
: BigInt(
toTokens(
props.claimCondition.maxClaimableSupply,
props.decimals,
),
)
}
/>
<div className="h-4" />
@@ -39,7 +39,7 @@ export function ContractAnalyticsOverview(props: {
</div>
<ThirdwebAreaChart
cardContentClassName="p-0"
chartClassName="aspect-[1.5] lg:aspect-[3]"
chartClassName="aspect-[1.2] lg:aspect-[2.6]"
className="border-none bg-background p-0"
config={{
events: {
@@ -0,0 +1,32 @@
import { isProd } from "@/constants/env-utils";
export async function fetchTokenInfoFromBridge(params: {
chainId: number;
tokenAddress: string;
clientId: string;
}) {
try {
const res = await fetch(
`https://bridge.${isProd ? "thirdweb.com" : "thirdweb-dev.com"}/v1/tokens?chainId=${params.chainId}&tokenAddress=${params.tokenAddress}&clientId=${params.clientId}`,
);
if (!res.ok) {
return null;
}
const data = (await res.json()) as {
data: Array<{
iconUri: string;
address: string;
decimals: number;
name: string;
symbol: string;
priceUsd: number;
}>;
};
return data.data[0];
} catch {
return null;
}
}
@@ -2,6 +2,8 @@ import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { getContractMetadata } from "thirdweb/extensions/common";
import { decimals, getActiveClaimCondition } from "thirdweb/extensions/erc20";
import { resolveFunctionSelectors } from "@/lib/selectors";
import { getContractCreator } from "../_components/getContractCreator";
import { PageHeader } from "../_components/PageHeader";
import { ContractHeaderUI } from "./_components/ContractHeader";
import { TokenDropClaim } from "./_components/claim-tokens/claim-tokens-ui";
@@ -9,6 +11,7 @@ import { ContractAnalyticsOverview } from "./_components/contract-analytics/cont
import { BuyTokenEmbed } from "./_components/PayEmbedSection";
import { TokenStats } from "./_components/PriceChart";
import { RecentTransfers } from "./_components/RecentTransfers";
import { fetchTokenInfoFromBridge } from "./_utils/fetch-coin-info";
import { getCurrencyMeta } from "./_utils/getCurrencyMeta";
export async function ERC20PublicPage(props: {
@@ -16,25 +19,43 @@ export async function ERC20PublicPage(props: {
clientContract: ThirdwebContract;
chainMetadata: ChainMetadata;
}) {
const [contractMetadata, activeClaimCondition, tokenDecimals] =
await Promise.all([
getContractMetadata({
contract: props.serverContract,
}),
getActiveClaimConditionWithErrorHandler(props.serverContract),
decimals({
contract: props.serverContract,
}),
]);
const [
contractMetadata,
activeClaimCondition,
tokenDecimals,
tokenInfo,
functionSelectors,
] = await Promise.all([
getContractMetadata({
contract: props.serverContract,
}),
getActiveClaimConditionWithErrorHandler(props.serverContract),
decimals({
contract: props.serverContract,
}),
fetchTokenInfoFromBridge({
chainId: props.serverContract.chain.id,
clientId: props.clientContract.client.clientId,
tokenAddress: props.serverContract.address,
}),
resolveFunctionSelectors(props.serverContract),
]);
const claimConditionCurrencyMeta = activeClaimCondition
? await getCurrencyMeta({
chain: props.serverContract.chain,
chainMetadata: props.chainMetadata,
client: props.serverContract.client,
currencyAddress: activeClaimCondition.currency,
}).catch(() => undefined)
: undefined;
if (!contractMetadata.image && tokenInfo) {
contractMetadata.image = tokenInfo.iconUri;
}
const [contractCreator, claimConditionCurrencyMeta] = await Promise.all([
getContractCreator(props.serverContract, functionSelectors),
activeClaimCondition
? getCurrencyMeta({
chain: props.serverContract.chain,
chainMetadata: props.chainMetadata,
client: props.serverContract.client,
currencyAddress: activeClaimCondition.currency,
}).catch(() => undefined)
: undefined,
]);
const buyEmbed = (
<BuyEmbed
@@ -62,6 +83,7 @@ export async function ERC20PublicPage(props: {
<ContractHeaderUI
chainMetadata={props.chainMetadata}
clientContract={props.clientContract}
contractCreator={contractCreator}
image={contractMetadata.image}
name={contractMetadata.name}
socialUrls={
@@ -141,8 +163,6 @@ function BuyEmbed(props: {
chain={props.clientContract.chain}
client={props.clientContract.client}
tokenAddress={props.clientContract.address}
tokenName={props.tokenName}
tokenSymbol={props.tokenSymbol}
/>
);
}
@@ -12,6 +12,7 @@ export function NFTPublicPageLayout(props: {
[key: string]: unknown;
};
children: React.ReactNode;
contractCreator: string | null;
}) {
return (
<div className="flex grow flex-col">
@@ -20,6 +21,7 @@ export function NFTPublicPageLayout(props: {
<ContractHeaderUI
chainMetadata={props.chainMetadata}
clientContract={props.clientContract}
contractCreator={props.contractCreator}
image={
typeof props.contractMetadata.image === "string"
? props.contractMetadata.image
@@ -6,6 +6,7 @@ import { ResponsiveLayout } from "@/components/blocks/Responsive";
import { Skeleton } from "@/components/ui/skeleton";
import { resolveFunctionSelectors } from "@/lib/selectors";
import { cn } from "@/lib/utils";
import { getContractCreator } from "../_components/getContractCreator";
import { NFTPublicPageLayout } from "./nft-page-layout";
import {
BuyNFTDropCardServer,
@@ -35,15 +36,17 @@ export async function NFTPublicPage(props: {
}),
]);
const nftDropClaimParams =
const [contractCreator, nftDropClaimParams] = await Promise.all([
getContractCreator(props.serverContract, functionSelectors),
props.type === "erc721"
? await getNFTDropClaimParams({
? getNFTDropClaimParams({
chainMetadata: props.chainMetadata,
functionSelectors,
serverContract: props.serverContract,
totalNFTCount,
})
: undefined;
: undefined,
]);
const _isTokenByIndexSupported =
props.type === "erc721" && isTokenByIndexSupported(functionSelectors);
@@ -101,6 +104,7 @@ export async function NFTPublicPage(props: {
<NFTPublicPageLayout
chainMetadata={props.chainMetadata}
clientContract={props.clientContract}
contractCreator={contractCreator}
contractMetadata={contractMetadata}
>
<ResponsiveLayout
@@ -10,18 +10,18 @@ import type { ChainSupportedService } from "@/types/chain";
export const products = [
{
description: "Pre-built contracts & deployment tools",
description: "Create, deploy and manage smart contracts",
icon: ContractIcon,
id: "contracts",
link: "https://thirdweb.com/explore",
name: "Contracts",
},
{
description: "Client-side SDKs for wallets and blockchain interactions",
description: "Create and manage crypto wallets",
icon: ConnectSDKIcon,
id: "connect-sdk",
link: "https://thirdweb.com/connect",
name: "Connect SDK",
name: "Wallets",
},
{
description: "Performant and scalable RPC service",
@@ -35,24 +35,24 @@ export const products = [
icon: EngineIcon,
id: "engine",
link: "https://thirdweb.com/engine",
name: "Engine",
name: "Transactions",
},
{
description: "Account factory contracts, bundler & paymaster",
description: "Enable gas sponsorship for seamless transactions",
icon: SmartAccountIcon,
id: "account-abstraction",
link: "https://portal.thirdweb.com/connect/account-abstraction/overview",
name: "Account Abstraction",
},
{
description: "Point of sale solution for bridging, onramping & swapping",
description: "Enable payments in any token on any chain",
icon: PayIcon,
id: "pay",
link: "https://portal.thirdweb.com/connect/pay/overview",
name: "Universal Bridge",
name: "Payments",
},
{
description: "Query, transform and analyze blockchain data",
description: "Query and analyze blockchain data",
icon: InsightIcon,
id: "insight",
link: "https://thirdweb.com/insight",
@@ -33,25 +33,25 @@ const HELP_PRODUCTS = [
"https://playground.thirdweb.com/connect/sign-in/button?tab=code",
},
{
description: "Wallets, auth, and onchain interactions",
description: "Create and manage crypto wallets",
icon: WalletIcon,
title: "Connect",
title: "Wallets",
viewAllUrl: "https://portal.thirdweb.com/connect",
},
{
description: "Bridge and onramp tokens on any chain",
description: "Enable payments on any tokens on any chain",
icon: PayIcon,
title: "Universal Bridge",
viewAllUrl: "https://portal.thirdweb.com/pay",
title: "Payments",
viewAllUrl: "https://portal.thirdweb.com/pay/troubleshoot",
},
{
description: "Reliable transactions and monitoring",
description: "Perform read and write transactions onchain",
icon: EngineIcon,
title: "Engine",
viewAllUrl: "https://portal.thirdweb.com/engine/v3",
title: "Transactions",
viewAllUrl: "https://portal.thirdweb.com/engine/v3/troubleshoot",
},
{
description: "Blockchain data queries and transformations",
description: "Query and analyze blockchain data",
icon: InsightIcon,
title: "Insight",
viewAllUrl: "https://portal.thirdweb.com/insight",
@@ -117,7 +117,7 @@ export default async function SupportPage() {
</header>
</div>
<section className="container flex flex-col gap-6">
<h2 className="font-bold text-3xl">Learning Resources</h2>
<h2 className="font-bold text-3xl">Support Articles</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-3">
{HELP_PRODUCTS.map((product) => (
<Card className="relative col-span-1 bg-card" key={product.title}>
@@ -10,6 +10,8 @@ import { Button } from "@/components/ui/button";
export function DeployedContractsPageHeader(props: {
teamId: string;
projectId: string;
projectSlug: string;
teamSlug: string;
client: ThirdwebClient;
}) {
const [importModalOpen, setImportModalOpen] = useState(false);
@@ -23,7 +25,9 @@ export function DeployedContractsPageHeader(props: {
setImportModalOpen(false);
}}
projectId={props.projectId}
projectSlug={props.projectSlug}
teamId={props.teamId}
teamSlug={props.teamSlug}
type="contract"
/>
@@ -10,6 +10,8 @@ import { Button } from "@/components/ui/button";
export function DeployViaCLIOrImportCard(props: {
teamId: string;
projectId: string;
projectSlug: string;
teamSlug: string;
client: ThirdwebClient;
}) {
const [importModalOpen, setImportModalOpen] = useState(false);
@@ -23,7 +25,9 @@ export function DeployViaCLIOrImportCard(props: {
setImportModalOpen(false);
}}
projectId={props.projectId}
projectSlug={props.projectSlug}
teamId={props.teamId}
teamSlug={props.teamSlug}
type="contract"
/>
@@ -23,7 +23,9 @@ export function DeployedContractsPage(props: {
<DeployViaCLIOrImportCard
client={props.client}
projectId={props.projectId}
projectSlug={props.projectSlug}
teamId={props.teamId}
teamSlug={props.teamSlug}
/>
</div>
);
@@ -43,7 +43,9 @@ export default async function Layout(props: {
<DeployedContractsPageHeader
client={client}
projectId={project.id}
projectSlug={params.project_slug}
teamId={team.id}
teamSlug={params.team_slug}
/>
<TabPathLinks
links={[
@@ -33,7 +33,9 @@ export function Cards(props: {
reportAssetImportSuccessful();
}}
projectId={props.projectId}
projectSlug={props.projectSlug}
teamId={props.teamId}
teamSlug={props.teamSlug}
type="asset"
/>
@@ -34,7 +34,14 @@ type ExecutionResult4337Serialized =
}
| {
status: "FAILED";
error: string;
error: {
stage: string;
message: string;
errorCode: string;
inner_error: object;
nonce_used: string;
account_address: string;
};
}
| {
status: "SUBMITTED";
@@ -3,7 +3,7 @@
import { format, formatDistanceToNowStrict } from "date-fns";
import { ExternalLinkIcon, InfoIcon } from "lucide-react";
import Link from "next/link";
import { type ThirdwebClient, toEther } from "thirdweb";
import { hexToNumber, isHex, type ThirdwebClient, toEther } from "thirdweb";
import type { Project } from "@/api/projects";
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Badge } from "@/components/ui/badge";
@@ -43,10 +43,14 @@ export function TransactionDetailsUI({
const status = executionResult?.status as keyof typeof statusDetails;
const errorMessage =
executionResult && "error" in executionResult
? executionResult.error
? executionResult.error.message
: executionResult && "revertData" in executionResult
? executionResult.revertData?.revertReason
: null;
const errorDetails =
executionResult && "error" in executionResult
? executionResult.error
: undefined;
const chain = chainId ? idToChain.get(Number.parseInt(chainId)) : undefined;
const explorer = chain?.explorers?.[0];
@@ -64,7 +68,7 @@ export function TransactionDetailsUI({
// Gas information
const gasUsed =
executionResult && "actualGasUsed" in executionResult
? `${executionResult.actualGasUsed}`
? `${isHex(executionResult.actualGasUsed) ? hexToNumber(executionResult.actualGasUsed) : executionResult.actualGasUsed}`
: "N/A";
const gasCost =
@@ -244,9 +248,16 @@ export function TransactionDetailsUI({
</CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md bg-destructive/10 p-4 text-destructive">
{errorMessage}
</div>
{errorDetails ? (
<CodeClient
code={JSON.stringify(errorDetails, null, 2)}
lang="json"
/>
) : (
<div className="rounded-md bg-destructive/10 p-4 text-destructive">
{errorMessage}
</div>
)}
</CardContent>
</Card>
)}
@@ -328,7 +339,9 @@ export function TransactionDetailsUI({
Block Number
</div>
<div className="text-sm md:w-2/3">
{transaction.confirmedAtBlockNumber}
{isHex(transaction.confirmedAtBlockNumber)
? hexToNumber(transaction.confirmedAtBlockNumber)
: transaction.confirmedAtBlockNumber}
</div>
</div>
)}
@@ -1,3 +1,4 @@
import { ResponsiveSuspense } from "responsive-rsc";
import type { ThirdwebClient } from "thirdweb";
import {
getUniversalBridgeUsage,
@@ -5,7 +6,9 @@ import {
} from "@/api/analytics";
import type { Range } from "@/components/analytics/date-range-selector";
import { CodeServer } from "@/components/ui/code/code.server";
import { Skeleton } from "@/components/ui/skeleton";
import { apiCode, embedCode, sdkCode } from "./code-examples";
import { PayAnalyticsFilter } from "./PayAnalyticsFilter";
import { PayCustomersTable } from "./PayCustomersTable";
import { PayEmbedFTUX } from "./PayEmbedFTUX";
import { PaymentHistory } from "./PaymentHistory";
@@ -96,44 +99,70 @@ export async function PayAnalytics(props: {
}
return (
<div className="flex flex-col gap-10 lg:gap-6">
<GridWithSeparator>
<div className="flex items-center border-border border-b pb-6 xl:border-none xl:pb-0">
<TotalVolumePieChart
data={volumeData?.filter((x) => x.status === "completed") || []}
/>
</div>
<TotalPayVolume
data={volumeData?.filter((x) => x.status === "completed") || []}
dateFormat={dateFormat}
/>
</GridWithSeparator>
<div className="grid grid-cols-1 gap-6 xl:grid-cols-2">
<CardContainer>
<Payouts
data={volumeData?.filter((x) => x.status === "completed") || []}
dateFormat={dateFormat}
/>
</CardContainer>
<CardContainer>
<PaymentsSuccessRate data={volumeData || []} />
</CardContainer>
<div>
<div className="mb-4 flex justify-start">
<PayAnalyticsFilter />
</div>
<ResponsiveSuspense
fallback={
<div className="flex flex-col gap-6">
<Skeleton className="h-[350px] border rounded-xl" />
<div className="grid grid-cols-1 gap-6 xl:grid-cols-2">
<Skeleton className="h-[350px] border rounded-xl" />
<Skeleton className="h-[350px] border rounded-xl" />
</div>
<div className="grid grid-cols-1 gap-6 xl:grid-cols-2">
<Skeleton className="h-[350px] border rounded-xl" />
<Skeleton className="h-[350px] border rounded-xl" />
</div>
<Skeleton className="h-[500px] border rounded-xl" />
</div>
}
searchParamsUsed={["from", "to", "interval"]}
>
<div className="flex flex-col gap-10 lg:gap-6">
<GridWithSeparator>
<div className="flex items-center border-border border-b pb-6 xl:border-none xl:pb-0">
<TotalVolumePieChart
data={volumeData?.filter((x) => x.status === "completed") || []}
/>
</div>
<TotalPayVolume
data={volumeData?.filter((x) => x.status === "completed") || []}
dateFormat={dateFormat}
/>
</GridWithSeparator>
<GridWithSeparator>
<div className="border-border border-b pb-6 xl:border-none xl:pb-0">
<PayNewCustomers data={walletData || []} dateFormat={dateFormat} />
<div className="grid grid-cols-1 gap-6 xl:grid-cols-2">
<CardContainer>
<Payouts
data={volumeData?.filter((x) => x.status === "completed") || []}
dateFormat={dateFormat}
/>
</CardContainer>
<CardContainer>
<PaymentsSuccessRate data={volumeData || []} />
</CardContainer>
</div>
<GridWithSeparator>
<div className="border-border border-b pb-6 xl:border-none xl:pb-0">
<PayNewCustomers
data={walletData || []}
dateFormat={dateFormat}
/>
</div>
<PayCustomersTable client={props.client} data={walletData || []} />
</GridWithSeparator>
<CardContainer>
<PaymentHistory
client={props.client}
projectClientId={props.projectClientId}
teamId={props.teamId}
/>
</CardContainer>
</div>
<PayCustomersTable client={props.client} data={walletData || []} />
</GridWithSeparator>
<CardContainer>
<PaymentHistory
client={props.client}
projectClientId={props.projectClientId}
teamId={props.teamId}
/>
</CardContainer>
</ResponsiveSuspense>
</div>
);
}
@@ -1,3 +0,0 @@
"use client";
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
@@ -1,16 +1,11 @@
import { loginRedirect } from "@app/login/loginRedirect";
import { ArrowUpRightIcon } from "lucide-react";
import { redirect } from "next/navigation";
import {
ResponsiveSearchParamsProvider,
ResponsiveSuspense,
} from "responsive-rsc";
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
import { getAuthToken } from "@/api/auth-token";
import { getProject } from "@/api/projects";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { PayAnalytics } from "./components/PayAnalytics";
import { PayAnalyticsFilter } from "./components/PayAnalyticsFilter";
import { getUniversalBridgeFiltersFromSearchParams } from "./components/time";
export default async function Page(props: {
@@ -54,26 +49,14 @@ export default async function Page(props: {
return (
<ResponsiveSearchParamsProvider value={searchParams}>
<div>
<div className="mb-4 flex justify-start">
<PayAnalyticsFilter />
</div>
<ResponsiveSuspense
fallback={
<div className="flex w-full items-center justify-center py-24">
<Spinner className="size-8" />
</div>
}
searchParamsUsed={["from", "to", "interval"]}
>
<PayAnalytics
client={client}
interval={interval}
projectClientId={project.publishableKey}
projectId={project.id}
range={range}
teamId={project.teamId}
/>
</ResponsiveSuspense>
<PayAnalytics
client={client}
interval={interval}
projectClientId={project.publishableKey}
projectId={project.id}
range={range}
teamId={project.teamId}
/>
<div className="h-10" />
<div className="relative overflow-hidden rounded-lg border-2 border-green-500/20 bg-gradient-to-br from-card/80 to-card/50 p-4 shadow-[inset_0_1px_2px_0_rgba(0,0,0,0.02)]">
@@ -1,13 +1,11 @@
import { redirect } from "next/navigation";
import { getAuthToken, getAuthTokenWalletAddress } from "@/api/auth-token";
import { getProjects } from "@/api/projects";
import { getTeams } from "@/api/team";
import { AppFooter } from "@/components/footers/app-footer";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import {
getAuthToken,
getAuthTokenWalletAddress,
} from "../../../../../../../../@/api/auth-token";
import { ErrorProvider } from "../../../../../../../../@/contexts/error-handler";
import { ErrorProvider } from "@/contexts/error-handler";
import { ContractIcon } from "@/icons/ContractIcon";
import { SharedContractLayout } from "../../../../../../(dashboard)/(chain)/[chain_id]/[contractAddress]/shared-layout";
import { getValidAccount } from "../../../../../../account/settings/getAccount";
import { TeamHeaderLoggedIn } from "../../../../../components/TeamHeader/team-header-logged-in.client";
@@ -68,6 +66,11 @@ export default async function ContractLayout(props: {
accountAddress={accountAddress}
client={client}
currentProject={project}
currentProjectSubpath={{
href: `/team/${params.team_slug}/${params.project_slug}/contracts`,
icon: <ContractIcon />,
label: "Contracts",
}}
currentTeam={team}
teamsAndProjects={teamsAndProjects}
/>
@@ -1,4 +1,5 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { BoxIcon } from "lucide-react";
import { ThirdwebProvider } from "thirdweb/react";
import type { Project } from "@/api/projects";
import type { Team } from "@/api/team";
@@ -119,6 +120,19 @@ function Variants(props: { type: "mobile" | "desktop" }) {
type={props.type}
/>
</BadgeContainer>
<BadgeContainer label="Pro Plan - active subpage">
<Variant
currentProject={proTeam.projects[0]}
currentProjectSubpath={{
href: "/team/project/foo",
icon: <BoxIcon />,
label: "Foo",
}}
team={proTeam.team}
type={props.type}
/>
</BadgeContainer>
</div>
</ThirdwebProvider>
);
@@ -132,6 +146,11 @@ function Variant(props: {
team: Team;
type: "mobile" | "desktop";
currentProject?: Project;
currentProjectSubpath?: {
label: string;
href: string;
icon: React.ReactNode;
};
}) {
const Comp =
props.type === "mobile" ? TeamHeaderMobileUI : TeamHeaderDesktopUI;
@@ -149,6 +168,7 @@ function Variant(props: {
createProject={() => {}}
createTeam={() => {}}
currentProject={undefined}
currentProjectSubpath={props.currentProjectSubpath}
currentTeam={props.team}
logout={() => {}}
teamsAndProjects={teamsAndProjectsStub}
@@ -21,6 +21,13 @@ export type TeamHeaderCompProps = {
currentTeam: Team;
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
currentProject: Project | undefined;
currentProjectSubpath:
| {
label: string;
icon: React.ReactNode;
href: string;
}
| undefined;
className?: string;
account: Pick<Account, "email" | "id">;
logout: () => void;
@@ -107,6 +114,23 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) {
teamsAndProjects={props.teamsAndProjects}
/>
</div>
{props.currentProjectSubpath && (
<>
<SlashSeparator />
<div className="flex items-center gap-1.5">
<div className="flex items-center justify-center rounded-full border size-6 [&>svg]:size-[50%] [&>svg]:text-muted-foreground">
{props.currentProjectSubpath.icon}
</div>
<Link
className="flex flex-row items-center gap-2 font-semibold text-sm"
href={props.currentProjectSubpath.href}
>
{props.currentProjectSubpath.label}
</Link>
</div>
</>
)}
</>
)}
</div>
@@ -28,6 +28,11 @@ export function TeamHeaderLoggedIn(props: {
account: Pick<Account, "email" | "id">;
accountAddress: string;
client: ThirdwebClient;
currentProjectSubpath?: {
label: string;
href: string;
icon: React.ReactNode;
};
}) {
// identify the account
useIdentifyAccount({
@@ -89,6 +94,7 @@ export function TeamHeaderLoggedIn(props: {
);
},
currentProject: props.currentProject,
currentProjectSubpath: props.currentProjectSubpath,
currentTeam: props.currentTeam,
logout: logout,
teamsAndProjects: props.teamsAndProjects,
@@ -0,0 +1,84 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { teamStub } from "@/storybook/stubs";
import { storybookThirdwebClient } from "@/storybook/utils";
import { TeamSelectorCard } from "./team-selector";
const meta: Meta<typeof TeamSelectorCard> = {
component: TeamSelectorCard,
decorators: [
(Story) => (
<div className="py-20 flex justify-center items-center">
<Story />
</div>
),
],
parameters: {
nextjs: {
appDirectory: true,
},
},
title: "selectors/TeamSelectorCard",
};
export default meta;
type Story = StoryObj<typeof TeamSelectorCard>;
export const TwoTeams: Story = {
args: {
client: storybookThirdwebClient,
paths: undefined,
searchParams: "",
teams: [teamStub("1", "free"), teamStub("2", "starter")],
},
};
export const FiveTeams: Story = {
args: {
client: storybookThirdwebClient,
paths: undefined,
searchParams: "",
teams: [
teamStub("1", "free"),
teamStub("2", "starter"),
teamStub("3", "growth"),
teamStub("4", "pro"),
teamStub("5", "scale"),
],
},
};
export const WithSearchParams: Story = {
args: {
client: storybookThirdwebClient,
paths: undefined,
searchParams: "tab=overview&section=analytics",
teams: [
teamStub("1", "free"),
teamStub("2", "starter"),
teamStub("3", "growth"),
],
},
};
export const WithPaths: Story = {
args: {
client: storybookThirdwebClient,
paths: ["projects", "123", "settings"],
searchParams: "",
teams: [teamStub("1", "free"), teamStub("2", "starter")],
},
};
export const WithPathsAndSearchParams: Story = {
args: {
client: storybookThirdwebClient,
paths: ["projects", "123", "settings"],
searchParams: "tab=overview&section=analytics",
teams: [
teamStub("1", "free"),
teamStub("2", "starter"),
teamStub("3", "growth"),
teamStub("4", "pro"),
],
},
};
@@ -0,0 +1,73 @@
import { ChevronRightIcon, UsersIcon } from "lucide-react";
import Link from "next/link";
import type { ThirdwebClient } from "thirdweb";
import type { Team } from "@/api/team";
import { GradientAvatar } from "@/components/blocks/avatar/gradient-avatar";
import { TeamPlanBadge } from "@/components/blocks/TeamPlanBadge";
export function createTeamLink(params: {
team: Team;
paths: string[] | undefined;
searchParams: string | undefined;
}) {
const pathsSegment = params.paths?.length ? `/${params.paths.join("/")}` : "";
const searchParamsSegment = params.searchParams
? `?${params.searchParams}`
: "";
return `/team/${params.team.slug}${pathsSegment}${searchParamsSegment}`;
}
export function TeamSelectorCard(props: {
teams: Team[];
client: ThirdwebClient;
searchParams: string;
paths: string[] | undefined;
}) {
return (
<div className="w-full max-w-lg rounded-xl border bg-card shadow-2xl">
<div className="flex flex-col border-b p-4 lg:p-6">
<div className="mb-2 self-start rounded-full border p-2">
<UsersIcon className="size-5 text-muted-foreground" />
</div>
<h1 className="mb-0.5 font-semibold text-xl tracking-tight">
Select a team
</h1>
<p className="text-muted-foreground text-sm">
You are currently a member of multiple teams
<br />
Select a team to view this page
</p>
</div>
<div className="flex flex-col [&>*:not(:last-child)]:border-b">
{props.teams.map((team) => {
return (
<div
className="group relative flex items-center gap-3 px-4 py-4 hover:bg-accent/50 lg:px-6"
key={team.id}
>
<GradientAvatar
className="size-8 rounded-full border"
client={props.client}
id={team.id}
src={team.image || ""}
/>
<Link
className="before:absolute before:inset-0"
href={createTeamLink({
paths: props.paths,
searchParams: props.searchParams,
team,
})}
>
{team.name}
</Link>
<TeamPlanBadge plan={team.billingPlan} teamSlug={team.slug} />
<ChevronRightIcon className="ml-auto size-4 text-muted-foreground group-hover:text-foreground" />
</div>
);
})}
</div>
</div>
);
}
@@ -1,14 +1,11 @@
import { ChevronRightIcon, UsersIcon } from "lucide-react";
import Link from "next/link";
import { notFound, redirect } from "next/navigation";
import { getAuthToken } from "@/api/auth-token";
import { getTeams, type Team } from "@/api/team";
import { GradientAvatar } from "@/components/blocks/avatar/gradient-avatar";
import { TeamPlanBadge } from "@/components/blocks/TeamPlanBadge";
import { getTeams } from "@/api/team";
import { AppFooter } from "@/components/footers/app-footer";
import { DotsBackgroundPattern } from "@/components/ui/background-patterns";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { TeamHeader } from "../../components/TeamHeader/team-header";
import { createTeamLink, TeamSelectorCard } from "./components/team-selector";
export default async function Page(props: {
params: Promise<{
@@ -69,54 +66,12 @@ export default async function Page(props: {
<div className="relative flex grow flex-col overflow-hidden">
<DotsBackgroundPattern />
<div className="container z-10 flex grow flex-col items-center justify-center py-20">
<div className="w-full max-w-lg rounded-xl border bg-card shadow-2xl">
<div className="flex flex-col border-b p-4 lg:p-6">
<div className="mb-2 self-start rounded-full border p-2">
<UsersIcon className="size-5 text-muted-foreground" />
</div>
<h1 className="mb-0.5 font-semibold text-xl tracking-tight">
Select a team
</h1>
<p className="text-muted-foreground text-sm">
You are currently a member of multiple teams
<br />
Select a team to view this page
</p>
</div>
<div className="flex flex-col [&>*:not(:last-child)]:border-b">
{teams.map((team) => {
return (
<div
className="group relative flex items-center gap-3 px-4 py-4 hover:bg-accent/50 lg:px-6"
key={team.id}
>
<GradientAvatar
className="size-8 rounded-full border"
client={client}
id={team.id}
src={team.image || ""}
/>
<Link
className="before:absolute before:inset-0"
href={createTeamLink({
paths: params.paths,
searchParams: searchParamsString,
team,
})}
>
{team.name}
</Link>
<TeamPlanBadge
plan={team.billingPlan}
teamSlug={team.slug}
/>
<ChevronRightIcon className="ml-auto size-4 text-muted-foreground group-hover:text-foreground" />
</div>
);
})}
</div>
</div>
<TeamSelectorCard
client={client}
paths={params.paths}
searchParams={searchParamsString}
teams={teams}
/>
</div>
</div>
@@ -124,11 +79,3 @@ export default async function Page(props: {
</div>
);
}
function createTeamLink(params: {
team: Team;
paths: string[] | undefined;
searchParams: string | undefined;
}) {
return `/team/${params.team.slug}/${(params.paths || [])?.join("/") || ""}${params.searchParams ? `?${params.searchParams}` : ""}`;
}
@@ -0,0 +1,133 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import type { PartialProject } from "@/api/getProjectContracts";
import { projectStub, teamStub } from "@/storybook/stubs";
import { storybookLog, storybookThirdwebClient } from "@/storybook/utils";
import { ProjectAndTeamSelectorCard } from "./TeamAndProjectSelectorCard";
const meta = {
component: ProjectAndTeamSelectorCard,
decorators: [
(Story) => (
<div className="py-20 flex justify-center items-center">
<Story />
</div>
),
],
parameters: {
nextjs: {
appDirectory: true,
},
},
title: "selectors/TeamAndProjectSelectorCard",
} satisfies Meta<typeof ProjectAndTeamSelectorCard>;
export default meta;
type Story = StoryObj<typeof meta>;
// Helper function to create PartialProject from full Project
function createPartialProject(
project: ReturnType<typeof projectStub>,
): PartialProject {
return {
id: project.id,
image: project.image,
name: project.name,
slug: project.slug,
};
}
export const OneTeamFiveProjects: Story = {
args: {
client: storybookThirdwebClient,
description: "Select a project to continue",
onSelect: (selected) => {
console.log("Selected:", selected);
},
teamAndProjects: [
{
projects: [
createPartialProject(projectStub("1", "team-1")),
createPartialProject(projectStub("2", "team-1")),
createPartialProject(projectStub("3", "team-1")),
createPartialProject(projectStub("4", "team-1")),
createPartialProject(projectStub("5", "team-1")),
],
team: teamStub("1", "growth"),
},
],
},
};
export const TwoTeamsTwoProjectsEach: Story = {
args: {
client: storybookThirdwebClient,
description: "Select a project to continue",
onSelect: (selected) => {
storybookLog(selected);
},
teamAndProjects: [
{
projects: [
createPartialProject(projectStub("1", "team-1")),
createPartialProject(projectStub("2", "team-1")),
],
team: teamStub("1", "free"),
},
{
projects: [
createPartialProject(projectStub("3", "team-2")),
createPartialProject(projectStub("4", "team-2")),
],
team: teamStub("2", "starter"),
},
],
},
};
export const TwoTeamsOneWithThreeProjectsOtherWithZero: Story = {
args: {
client: storybookThirdwebClient,
description: "Select a project to continue",
onSelect: (selected) => {
storybookLog(selected);
},
teamAndProjects: [
{
projects: [
createPartialProject(projectStub("1", "team-1")),
createPartialProject(projectStub("2", "team-1")),
createPartialProject(projectStub("3", "team-1")),
],
team: teamStub("1", "growth"),
},
{
projects: [],
team: teamStub("2", "free"), // Empty projects array
},
],
},
};
export const TwoTeamsLotsOfProjects: Story = {
args: {
client: storybookThirdwebClient,
description: "Select a project to continue",
onSelect: (selected) => {
console.log("Selected:", selected);
},
teamAndProjects: [
{
projects: Array.from({ length: 100 }, (_, i) =>
createPartialProject(projectStub(`${i + 1}`, "team-1")),
),
team: teamStub("1", "growth"),
},
{
projects: Array.from({ length: 100 }, (_, i) =>
createPartialProject(projectStub(`${i + 1}`, "team-2")),
),
team: teamStub("2", "growth"),
},
],
},
};
@@ -0,0 +1,109 @@
"use client";
import { BoxIcon, ChevronRightIcon } from "lucide-react";
import type { ThirdwebClient } from "thirdweb";
import type { PartialProject } from "@/api/getProjectContracts";
import type { Team } from "@/api/team";
import { GradientAvatar } from "@/components/blocks/avatar/gradient-avatar";
import { ProjectAvatar } from "@/components/blocks/avatar/project-avatar";
import { TeamPlanBadge } from "@/components/blocks/TeamPlanBadge";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export function ProjectAndTeamSelectorCard(props: {
teamAndProjects: {
team: Team;
projects: PartialProject[];
}[];
client: ThirdwebClient;
description: React.ReactNode;
onSelect: (selected: { team: Team; project: PartialProject }) => void;
}) {
const teamAndProjects = props.teamAndProjects.filter(
({ projects }) => projects.length > 0,
);
const showTeamHeader = teamAndProjects.length > 1;
return (
<div className="w-full max-w-lg rounded-xl border bg-card shadow-2xl">
<div className="flex flex-col border-b p-4 lg:p-6">
<div className="mb-2 self-start rounded-full border p-2">
<BoxIcon className="size-5 text-muted-foreground" />
</div>
<h1 className="mb-0.5 font-semibold text-xl tracking-tight">
Select a project
</h1>
<p className="text-muted-foreground text-sm">{props.description}</p>
</div>
<div
className={cn("flex flex-col", showTeamHeader && "p-4 lg:p-6 gap-6")}
>
{teamAndProjects.map(({ team, projects }) => {
// If multiple teams, show team name and then projects
return (
<div key={team.id}>
<div
className={cn(
showTeamHeader &&
"border rounded-xl overflow-hidden bg-background",
)}
>
{showTeamHeader && (
<div className={cn("px-4 py-3 border-b bg-muted/50")}>
<div className="flex items-center gap-2">
<GradientAvatar
className="size-6 rounded-full border"
client={props.client}
id={team.id}
src={team.image || ""}
/>
<span className="font-medium text-sm">{team.name}</span>
<TeamPlanBadge
plan={team.billingPlan}
teamSlug={team.slug}
/>
</div>
</div>
)}
<div className="max-h-[320px] overflow-y-auto">
<div className="[&>*:not(:last-child)]:border-b">
{projects.map((project) => (
<Button
className={cn(
"h-auto group relative flex items-center gap-3 px-4 py-4 hover:bg-accent/50 lg:px-6 w-full rounded-none justify-start",
showTeamHeader && "lg:pl-5",
)}
key={project.slug}
onClick={() =>
props.onSelect({
project,
team,
})
}
variant="ghost"
>
<ProjectAvatar
className="size-6 rounded-full border"
client={props.client}
src={project.image || ""}
/>
<span className="text-sm font-medium">
{project.name}
</span>
<ChevronRightIcon className="ml-auto size-3.5 text-muted-foreground/50 group-hover:text-foreground" />
</Button>
))}
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
);
}
@@ -0,0 +1,80 @@
"use client";
import type { ThirdwebClient } from "thirdweb";
import type { PartialProject } from "@/api/getProjectContracts";
import type { Team } from "@/api/team";
import { useAddContractToProject } from "@/hooks/project-contracts";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { ProjectAndTeamSelectorCard } from "../../../../../components/TeamAndProjectSelectorCard";
export function SelectProjectForContract(props: {
chainSlug: string;
contractAddress: string;
client: ThirdwebClient;
teamAndProjects: {
team: Team;
projects: PartialProject[];
}[];
}) {
const router = useDashboardRouter();
return (
<ProjectAndTeamSelectorCard
client={props.client}
description={
<>
This contract is imported in multiple projects
<br />
Select a project to view this contract
</>
}
onSelect={(selection) => {
router.push(
`/team/${selection.team.slug}/${selection.project.slug}/contract/${props.chainSlug}/${props.contractAddress}`,
);
}}
teamAndProjects={props.teamAndProjects}
/>
);
}
export function ImportAndSelectProjectForContract(props: {
chainSlug: string;
chainId: number;
contractAddress: string;
client: ThirdwebClient;
teamAndProjects: {
team: Team;
projects: PartialProject[];
}[];
}) {
const router = useDashboardRouter();
const addToProject = useAddContractToProject();
return (
<ProjectAndTeamSelectorCard
client={props.client}
description={
<>
This contract is not imported in any projects
<br />
Select a project to import the contract and continue
</>
}
onSelect={(selection) => {
// do not await - send request and move to the contract page
addToProject.mutate({
chainId: props.chainId.toString(),
contractAddress: props.contractAddress,
contractType: undefined,
deploymentType: undefined,
projectId: selection.project.id,
teamId: selection.team.id,
});
router.push(
`/team/${selection.team.slug}/${selection.project.slug}/contract/${props.chainSlug}/${props.contractAddress}`,
);
}}
teamAndProjects={props.teamAndProjects}
/>
);
}
@@ -0,0 +1,147 @@
import { notFound, redirect } from "next/navigation";
import { getAddress } from "thirdweb";
import { getAuthToken } from "@/api/auth-token";
import {
getContractImportedProjects,
type PartialProject,
} from "@/api/getProjectContracts";
import { getProjects } from "@/api/projects";
import { getTeams, type Team } from "@/api/team";
import { AppFooter } from "@/components/footers/app-footer";
import { DotsBackgroundPattern } from "@/components/ui/background-patterns";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { fetchChain } from "@/utils/fetchChain";
import { getValidAccount } from "../../../../../../account/settings/getAccount";
import { loginRedirect } from "../../../../../../login/loginRedirect";
import { TeamHeader } from "../../../../../components/TeamHeader/team-header";
import {
ImportAndSelectProjectForContract,
SelectProjectForContract,
} from "./components/project-selector";
export default async function Page(props: {
params: Promise<{
chain: string;
contractAddress: string;
}>;
}) {
const params = await props.params;
const pagePath = `/team/~/~/contract/${params.chain}/${params.contractAddress}`;
const contractAddress = getAddress(params.contractAddress);
const [authToken, chainMetadata, account, teams] = await Promise.all([
getAuthToken(),
fetchChain(params.chain),
await getValidAccount(pagePath),
getTeams(),
]);
if (!chainMetadata) {
notFound();
}
if (!authToken || !account || !teams) {
loginRedirect(pagePath);
}
// get the list of projects in each team where this contract is imported
// filter out teams with no projects
const teamAndProjectWithContracts = (
await Promise.all(
teams.map(async (team) => {
return {
projects: await getContractImportedProjects({
authToken,
chainId: chainMetadata.chainId,
contractAddress,
teamId: team.id,
}).catch(() => []),
team,
};
}),
)
).filter((x) => x.projects.length > 0);
const projectImports: Array<{
team: Team;
project: PartialProject;
}> = [];
for (const teamAndProject of teamAndProjectWithContracts) {
for (const project of teamAndProject.projects) {
projectImports.push({
project,
team: teamAndProject.team,
});
}
}
// if contract imported in only one project, redirect to it directly
if (projectImports.length === 1 && projectImports[0]) {
redirect(
`/team/${projectImports[0].team.slug}/${projectImports[0].project.slug}/contract/${chainMetadata.slug}/${contractAddress}`,
);
}
const client = getClientThirdwebClient({
jwt: authToken,
teamId: undefined,
});
// if contract imported in multiple projects
// user needs to select one
if (projectImports.length > 1) {
return (
<ProjectSelectionLayout>
<SelectProjectForContract
chainSlug={chainMetadata.slug}
client={client}
contractAddress={contractAddress}
teamAndProjects={teamAndProjectWithContracts}
/>
</ProjectSelectionLayout>
);
}
// if contract not imported in any projects
// user needs to select one from all projects, import the contract in the selected project and redirect
const teamAndAllProjects = await Promise.all(
teams.map(async (team) => {
return {
projects: await getProjects(team.slug).catch(() => []),
team,
};
}),
);
return (
<ProjectSelectionLayout>
<ImportAndSelectProjectForContract
chainId={chainMetadata.chainId}
chainSlug={chainMetadata.slug}
client={client}
contractAddress={contractAddress}
teamAndProjects={teamAndAllProjects}
/>
</ProjectSelectionLayout>
);
}
function ProjectSelectionLayout(props: { children: React.ReactNode }) {
return (
<div className="relative flex min-h-dvh flex-col">
<div className="border-b bg-card">
<TeamHeader />
</div>
<div className="relative flex grow flex-col overflow-hidden">
<DotsBackgroundPattern />
<div className="container z-10 flex grow flex-col items-center justify-center py-20">
{props.children}
</div>
</div>
<AppFooter className="relative z-10" />
</div>
);
}
+3 -3
View File
@@ -4,7 +4,7 @@ import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
import { checksumAddress } from "thirdweb/utils";
import { getPaymentLink } from "@/api/universal-bridge/links";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { PayPageEmbed } from "../components/client/PayPageEmbed.client";
import { PayPageWidget } from "../components/client/PayPageWidget.client";
const title = "thirdweb Pay";
const description = "Fast, secure, and simple payments.";
@@ -54,10 +54,10 @@ export default async function PayPage({
};
return (
<PayPageEmbed
<PayPageWidget
amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined}
chainId={Number(paymentLink.destinationToken.chainId)}
clientId={paymentLink.clientId}
clientId={undefined} // Payment links don't need to use the same client ID to be executed
image={paymentLink.imageUrl}
name={paymentLink.title}
paymentLinkId={id}
@@ -1,98 +0,0 @@
"use client";
import { payAppThirdwebClient } from "app/pay/constants";
import { useTheme } from "next-themes";
import { useEffect } from "react";
import { NATIVE_TOKEN_ADDRESS, toTokens } from "thirdweb";
import { AutoConnect, PayEmbed } from "thirdweb/react";
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
export function PayPageEmbed({
chainId,
recipientAddress,
paymentLinkId,
amount,
token,
name,
image,
redirectUri,
theme,
purchaseData,
}: {
chainId: number;
recipientAddress: string;
paymentLinkId?: string;
amount?: bigint;
token: { name: string; symbol: string; address: string; decimals: number };
name?: string;
image?: string;
redirectUri?: string;
clientId: string;
theme?: "light" | "dark";
purchaseData: Record<string, unknown> | undefined;
}) {
const { theme: browserTheme, setTheme } = useTheme();
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
if (theme) {
setTheme(theme);
}
}, [theme, setTheme]);
const chain = useV5DashboardChain(chainId);
return (
<>
<AutoConnect client={payAppThirdwebClient} />
<PayEmbed
client={payAppThirdwebClient}
paymentLinkId={paymentLinkId}
payOptions={{
metadata: {
image,
name,
},
mode: "direct_payment",
onPurchaseSuccess: (result) => {
if (!redirectUri) return;
const url = new URL(redirectUri);
switch (result?.type) {
case "crypto": {
url.searchParams.set("status", result.status.status);
if (
"source" in result.status &&
result.status.source?.transactionHash
) {
url.searchParams.set(
"txHash",
result.status.source?.transactionHash,
);
}
break;
}
case "fiat": {
url.searchParams.set("status", result.status.status);
if ("intentId" in result.status) {
url.searchParams.set("intentId", result.status.intentId);
}
break;
}
case "transaction": {
url.searchParams.set("txHash", result.transactionHash);
break;
}
}
return window.open(url.toString());
},
paymentInfo: {
amount: amount ? toTokens(amount, token.decimals) : "0.01",
chain,
sellerAddress: recipientAddress,
token: token.address === NATIVE_TOKEN_ADDRESS ? undefined : token,
},
purchaseData,
}}
theme={theme ?? (browserTheme === "light" ? "light" : "dark")}
/>
</>
);
}
@@ -0,0 +1,77 @@
"use client";
import { payAppThirdwebClient } from "app/pay/constants";
import { useTheme } from "next-themes";
import { useEffect } from "react";
import { createThirdwebClient, NATIVE_TOKEN_ADDRESS, toTokens } from "thirdweb";
import { AutoConnect, CheckoutWidget } from "thirdweb/react";
import { checksumAddress } from "thirdweb/utils";
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
export function PayPageWidget({
chainId,
recipientAddress,
paymentLinkId,
amount,
token,
name,
image,
redirectUri,
theme,
purchaseData,
clientId,
}: {
chainId: number;
recipientAddress: string;
paymentLinkId?: string;
amount?: bigint;
token: { name: string; symbol: string; address: string; decimals: number };
name?: string;
image?: string;
redirectUri?: string;
clientId: string | undefined;
theme?: "light" | "dark";
purchaseData: Record<string, unknown> | undefined;
}) {
const { theme: browserTheme, setTheme } = useTheme();
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
if (theme) {
setTheme(theme);
}
}, [theme, setTheme]);
const chain = useV5DashboardChain(chainId);
return (
<>
<AutoConnect
client={
clientId ? createThirdwebClient({ clientId }) : payAppThirdwebClient
}
/>
<CheckoutWidget
amount={amount ? toTokens(amount, token.decimals) : "0"}
chain={chain}
client={
clientId ? createThirdwebClient({ clientId }) : payAppThirdwebClient
}
image={image}
name={name}
onSuccess={() => {
if (!redirectUri) return;
const url = new URL(redirectUri);
return window.open(url.toString());
}}
paymentLinkId={paymentLinkId}
purchaseData={purchaseData}
seller={checksumAddress(recipientAddress)}
theme={theme ?? (browserTheme === "light" ? "light" : "dark")}
tokenAddress={
token.address === NATIVE_TOKEN_ADDRESS
? undefined
: checksumAddress(token.address)
}
/>
</>
);
}
+2 -2
View File
@@ -3,7 +3,7 @@ import { createThirdwebClient, defineChain, getContract } from "thirdweb";
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
import { checksumAddress } from "thirdweb/utils";
import { PaymentLinkForm } from "./components/client/PaymentLinkForm.client";
import { PayPageEmbed } from "./components/client/PayPageEmbed.client";
import { PayPageWidget } from "./components/client/PayPageWidget.client";
import type { PayParams } from "./components/types";
import { payAppThirdwebClient } from "./constants";
@@ -84,7 +84,7 @@ export default async function PayPage({
};
return (
<PayPageEmbed
<PayPageWidget
amount={BigInt(params.amount)}
chainId={Number(params.chainId)}
clientId={client.clientId}
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+1 -1
View File
@@ -37,7 +37,7 @@
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"thirdweb": "workspace:*",
"zod": "3.25.62"
"zod": "3.25.67"
},
"devDependencies": {
"@biomejs/biome": "2.0.4",
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+1 -1
View File
@@ -32,7 +32,7 @@
"tailwind-merge": "^2.6.0",
"thirdweb": "workspace:*",
"use-debounce": "^10.0.5",
"zod": "3.25.62"
"zod": "3.25.67"
},
"devDependencies": {
"@biomejs/biome": "2.0.4",
@@ -42,7 +42,7 @@ function BuyMerch() {
client={THIRDWEB_CLIENT}
theme="light"
chain={base}
amount={toUnits("2", 6)}
amount={"2"}
tokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
seller="0xEb0effdFB4dC5b3d5d3aC6ce29F3ED213E95d675"
feePayer="seller"
@@ -51,6 +51,7 @@ function BuyOnchainAsset() {
return (
<TransactionWidget
amount={"0.001"}
client={THIRDWEB_CLIENT}
theme={theme === "light" ? "light" : "dark"}
transaction={claimTo({
@@ -41,7 +41,7 @@ export function PayTransactionPreview() {
return (
<TransactionWidget
amount={"0.1"}
amount={"0.001"}
client={THIRDWEB_CLIENT}
description={nft?.metadata?.description}
image={nft?.metadata?.image}
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+249 -119
View File
@@ -1603,164 +1603,287 @@ let returnType: Element;
# Buy Crypto
---
## PayEmbed
## BuyWidget
Embed a prebuilt UI for funding wallets, purchases or transactions with crypto or fiat.
Widget is a prebuilt UI for purchasing a specific token.
### Example
#### Default configuration
#### Basic usage
By default, the `PayEmbed` component will allows users to fund their wallets with crypto or fiat on any of the supported chains..
The `BuyWidget` component requires `client`, `chain`, and `amount` props to function.
```tsx
<PayEmbed client={client} />;
import { ethereum } from "thirdweb/chains";
<BuyWidget
client={client}
chain={ethereum}
amount="0.1"
/>
```
#### Top up wallets
#### Buy a specific token
You can set the `mode` option to `"fund_wallet"` to allow users to top up their wallets with crypto or fiat.
You can specify a token to purchase by passing the `tokenAddress` prop.
```tsx
<PayEmbed
<BuyWidget
client={client}
payOptions={{
mode: "fund_wallet",
metadata: {
name: "Get funds", // title of the payment modal
},
prefillBuy: {
chain: base, // chain to prefill the buy screen with
amount: "0.01", // amount to prefill the buy screen with
},
}}
/>;
```
#### Direct Payments
You can set the `mode` option to `"direct_payment"` to allow users to make a direct payment to a wallet address.
```tsx
<PayEmbed
client={client}
theme={"light"}
payOptions={{
mode: "direct_payment",
paymentInfo: {
amount: "35",
chain: base,
token: getDefaultToken(base, "USDC"),
sellerAddress: "0x...", // the wallet address of the seller
},
metadata: {
name: "Black Hoodie (Size L)",
image: "/drip-hoodie.png",
},
}}
/>;
```
#### Transactions
You can set the `mode` option to `"transaction"` to allow users to execute a transaction with a different wallet, chain or token.
```tsx
<PayEmbed
client={client}
payOptions={{
mode: "transaction",
// can be any transaction
transaction: claimTo({
contract: nftContract,
quantity: 1n,
tokenId: 0n,
to: "0x...",
}),
// this could be any metadata, including NFT metadata
metadata: {
name: "VIP Ticket",
image: "https://...",
},
}}
/>;
```
You can also handle ERC20 payments by passing `erc20value` to your transaction:
```tsx
<PayEmbed
client={client}
payOptions={{
mode: "transaction",
transaction: prepareContractCall({
contract: yourContract,
functionName: "purchase",
args: [arg1, arg2, ...],
erc20value: {
token: USDC_TOKEN_ADDRESS, // the erc20 token required to purchase
amount: toUnits("100", 6), // the amount of erc20 token required to purchase
},
}),
}}
/>
```
#### Enable/Disable payment methods
You can disable the use of crypto or fiat by setting the `buyWithCrypto` or `buyWithFiat` options to `false`.
```tsx
<PayEmbed
client={client}
payOptions={{
buyWithFiat: false,
}}
/>;
chain={ethereum}
amount="100"
tokenAddress="0xA0b86a33E6417E4df2057B2d3C6d9F7cc11b0a70"
/>
```
#### Customize the UI
You can customize the UI of the `PayEmbed` component by passing a custom theme object to the `theme` prop.
You can customize the UI of the `BuyWidget` component by passing a custom theme object to the `theme` prop.
```tsx
<PayEmbed
<BuyWidget
client={client}
chain={ethereum}
amount="0.1"
theme={darkTheme({
colors: {
modalBg: "red",
},
})}
/>;
/>
```
Refer to the [Theme](https://portal.thirdweb.com/references/typescript/v5/Theme) type for more details.
#### Configure the wallet connection
#### Update the Title
You can customize the wallet connection flow by passing a `connectOptions` object to the `PayEmbed` component.
You can update the title of the widget by passing a `title` prop to the `BuyWidget` component.
```tsx
<PayEmbed
<BuyWidget
client={client}
connectOptions={{
connectModal: {
size: "compact",
title: "Sign in",
},
}}
/>;
chain={ethereum}
amount="0.1"
title="Buy ETH"
/>
```
Refer to the [PayEmbedConnectOptions](https://portal.thirdweb.com/references/typescript/v5/PayEmbedConnectOptions) type for more details.
#### Configure the wallet connection
You can customize the wallet connection flow by passing a `connectOptions` object to the `BuyWidget` component.
```tsx
<BuyWidget
client={client}
chain={ethereum}
amount="0.1"
connectOptions={{
connectModal: {
size: 'compact',
title: "Sign in",
}
}}
/>
```
Refer to the [BuyWidgetConnectOptions](https://portal.thirdweb.com/references/typescript/v5/BuyWidgetConnectOptions) type for more details.
```ts
function PayEmbed(props: PayEmbedProps): Element;
function BuyWidget(props: BuyWidgetProps): Element;
```
### Parameters
Props of type [PayEmbedProps](https://portal.thirdweb.com/references/typescript/v5/PayEmbedProps) to configure the PayEmbed component.
Props of type [BuyWidgetProps](https://portal.thirdweb.com/references/typescript/v5/BuyWidgetProps) to configure the BuyWidget component.
#### Type
```ts
let props: {
activeWallet?: Wallet;
amount: string;
chain: Chain;
className?: string;
client: ThirdwebClient;
connectOptions?: BuyWidgetConnectOptions;
description?: string;
hiddenWallets?: Array<WalletId>;
image?: string;
locale?: LocaleId;
onCancel?: () => void;
onError?: (error: Error) => void;
onSuccess?: () => void;
paymentLinkId?: string;
presetOptions?: [number, number, number];
purchaseData?: Record<string, unknown>;
style?: React.CSSProperties;
supportedTokens?: SupportedTokens;
theme?: "light" | "dark" | Theme;
title?: string;
tokenAddress?: Address;
};
```
### Returns
```ts
let returnType: Element;
```
## CheckoutWidget
Widget is a prebuilt UI for making direct payments to a wallet address.
### Example
#### Basic usage
The `CheckoutWidget` component requires `client`, `chain`, `amount`, `tokenAddress`, and `sellerAddress` props to function.
```tsx
import { base } from "thirdweb/chains";
import { getDefaultToken } from "thirdweb/utils";
<CheckoutWidget
client={client}
chain={base}
amount="35"
tokenAddress={getDefaultToken(base, "USDC")}
sellerAddress="0x..."
/>
```
#### With metadata
You can add metadata to display product information.
```tsx
<CheckoutWidget
client={client}
chain={base}
amount="35"
tokenAddress={getDefaultToken(base, "USDC")}
sellerAddress="0x..."
title="Black Hoodie (Size L)"
description="Premium cotton hoodie"
image="/drip-hoodie.png"
/>
```
```ts
function CheckoutWidget(props: CheckoutWidgetProps): Element;
```
### Parameters
Props of type [CheckoutWidgetProps](https://portal.thirdweb.com/references/typescript/v5/CheckoutWidgetProps) to configure the CheckoutWidget component.
#### Type
```ts
let props: {
activeWallet?: Wallet;
amount: string;
chain: Chain;
className?: string;
client: ThirdwebClient;
connectOptions?: CheckoutWidgetConnectOptions;
description?: string;
hiddenWallets?: Array<WalletId>;
image?: string;
locale?: LocaleId;
onCancel?: () => void;
onError?: (error: Error) => void;
onSuccess?: () => void;
paymentLinkId?: string;
purchaseData?: Record<string, unknown>;
sellerAddress: Address;
style?: React.CSSProperties;
supportedTokens?: SupportedTokens;
theme?: "light" | "dark" | Theme;
title?: string;
tokenAddress: Address;
};
```
### Returns
```ts
let returnType: Element;
```
## TransactionWidget
Widget is a prebuilt UI for executing a transaction with a different wallet, chain or token.
### Example
#### Basic usage
The `TransactionWidget` component requires `client` and `transaction` props to function.
```tsx
import { claimTo } from "thirdweb/extensions/erc1155";
<TransactionWidget
client={client}
transaction={claimTo({
contract: nftContract,
quantity: 1n,
tokenId: 0n,
to: "0x...",
})}
/>
```
#### With metadata
You can add metadata to display transaction information.
```tsx
<TransactionWidget
client={client}
transaction={claimTo({
contract: nftContract,
quantity: 1n,
tokenId: 0n,
to: "0x...",
})}
title="VIP Ticket"
description="Exclusive NFT collection"
image="https://..."
/>
```
#### ERC20 payments
You can handle ERC20 payments by passing `erc20value` to your transaction:
```tsx
import { prepareContractCall } from "thirdweb";
import { toUnits } from "thirdweb/utils";
<TransactionWidget
client={client}
transaction={prepareContractCall({
contract: yourContract,
functionName: "purchase",
args: [arg1, arg2, ...],
erc20value: {
token: USDC_TOKEN_ADDRESS, // the erc20 token required to purchase
amount: toUnits("100", 6), // the amount of erc20 token required to purchase
},
})}
/>
```
```ts
function TransactionWidget(props: TransactionWidgetProps): Element;
```
### Parameters
Props of type [TransactionWidgetProps](https://portal.thirdweb.com/references/typescript/v5/TransactionWidgetProps) to configure the TransactionWidget component.
#### Type
@@ -1769,14 +1892,21 @@ let props: {
activeWallet?: Wallet;
className?: string;
client: ThirdwebClient;
connectOptions?: PayEmbedConnectOptions;
connectOptions?: TransactionWidgetConnectOptions;
description?: string;
hiddenWallets?: Array<WalletId>;
image?: string;
locale?: LocaleId;
onCancel?: () => void;
onError?: (error: Error) => void;
onSuccess?: () => void;
paymentLinkId?: string;
payOptions?: PayUIOptions;
purchaseData?: Record<string, unknown>;
style?: React.CSSProperties;
supportedTokens?: SupportedTokens;
theme?: "light" | "dark" | Theme;
title?: string;
transaction: PreparedTransaction;
};
```
+18 -18
View File
@@ -33,15 +33,15 @@ import { ThirdwebIcon } from "../icons/thirdweb";
const links = [
{
href: "/connect",
name: "Connect",
name: "Wallets",
},
{
href: "/pay",
name: "Bridge",
name: "Payments",
},
{
href: "/engine",
name: "Engine",
name: "Transactions",
},
{
href: "/contracts",
@@ -181,7 +181,7 @@ const sdkLinks = [
const supportLinks = [
{
href: "/knowledge-base",
name: "Get thirdweb support",
name: "Articles",
},
{
href: "/account",
@@ -216,6 +216,20 @@ export function Header() {
</div>
<div className="flex items-center gap-3">
<div className="hidden xl:block">
<Link
className="text-foreground"
href="https://github.com/thirdweb-dev"
target="_blank"
>
<GithubIcon className="mx-3 size-6" />
</Link>
</div>
<div className="hidden xl:block">
<ThemeSwitcher className="border-none bg-transparent" />
</div>
<div className="hidden xl:block">
<DocSearch variant="search" />
</div>
@@ -231,20 +245,6 @@ export function Header() {
</Button>
</div>
<div className="hidden xl:block">
<ThemeSwitcher className="border-none bg-transparent" />
</div>
<div className="hidden xl:block">
<Link
className="text-foreground"
href="https://github.com/thirdweb-dev"
target="_blank"
>
<GithubIcon className="mx-3 size-6" />
</Link>
</div>
<div className="flex items-center gap-1 xl:hidden">
<ThemeSwitcher className="border-none bg-transparent" />
<DocSearch variant="icon" />
+1 -1
View File
@@ -9,7 +9,7 @@ export default async function Layout(props: { children: React.ReactNode }) {
sideBar={sidebar}
sidebarHeader={
<div className="flex-col items-center gap-1">
<p className="py-5 font-semibold text-foreground text-lg">Connect</p>
<p className="py-5 font-semibold text-foreground text-lg">Wallets</p>
<PlatformSelector selected="Overview" />
</div>
}
+6 -6
View File
@@ -34,9 +34,9 @@ export const metadata = createMetadata({
},
});
# What is Connect?
# Wallets
Connect is the complete toolkit for connecting every user to your application. It features customizable onboarding flows, self-custodial in-app wallets, account abstraction, onramps, and a performant API to interact with the blockchain.
Wallets is the complete toolkit for connecting every user to your application. It features customizable onboarding flows, self-custodial in-app wallets, account abstraction, onramps, and a performant API to interact with the blockchain.
## Live Playground
@@ -55,7 +55,7 @@ You can interact with the React SDK in the [Live Playground](https://playground.
## Quick starts
Get started with Connect in your preferred language.
Get started with Wallets in your preferred language.
<Grid>
<SDKCard
@@ -78,7 +78,7 @@ Get started with Connect in your preferred language.
/>
</Grid>
## With Connect, you can
## With Wallets, you can
- **Connect to 500+ different wallet providers** with support for every EVM network
- **Log in and authenticate your users** with customizable and secure [email, phone, passkeys and social login](https://portal.thirdweb.com/connect/in-app-wallet/overview) flows.
@@ -90,7 +90,7 @@ Get started with Connect in your preferred language.
<Callout variant="info" title="Have you generated a client ID?">
<div className="flex flex-col gap-4 items-start">
<p>
You'll need a client ID to access Connect's free blockchain APIs, storage, and more.
You'll need a client ID to access the Wallet APIs, storage, and more.
</p>
<Button asChild variant='outline'>
@@ -102,7 +102,7 @@ Get started with Connect in your preferred language.
## Supported Chains
Connect is supported on every EVM compatible chain. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist).
Wallets are supported on every EVM compatible chain. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist).
<DocImage src={SupportedChains} />
@@ -43,7 +43,7 @@ Yes, you can easily use account abstraction with in-app wallets to enable sponso
Thirdweb wallets can be used as a signer to a smart contract account (account abstraction), but it can also be used as a standalone EOA.
### Is there a way to display the logged-in email address in the Connect component, similar to the wallet address?
### Is there a way to display the logged-in email address in the Wallet component, similar to the wallet address?
Yes, we show the email by default in the details modal after you connect. To display the email anywhere else using React or React Native, [check out the code snippets for all platforms](/connect/in-app-wallet/how-to/interact-with-wallets#get-the-user-email).
@@ -40,7 +40,7 @@ Your client ID authenticates your application with thirdweb's services and provi
## Live Playground
Before diving into the implementation, check out our playground to see all the various capabilities of Connect:
Before diving into the implementation, check out our playground to see all the various capabilities of Wallets:
<ArticleIconCard
title="Try the demo"
@@ -224,7 +224,7 @@ Before diving into the implementation, check out our playground to see all the v
}
```
### Connect Button Component
### Wallet Button Component
Use the pre-built ConnectButton for a complete wallet connection UI:
@@ -8,4 +8,4 @@ If you are thinking of migrating your users off another embedded wallet provider
<Callout variant="info" title="Learn more">Learn more about Migration from our [GitHub repository](https://github.com/thirdweb-example/migrate)</Callout>
If you’d prefer to use your existing solutions for now and take advantage of thirdweb for other parts of your application, we recommend using the [Connect SDK adapters](/typescript/v5/adapters).
If you’d prefer to use your existing solutions for now and take advantage of thirdweb for other parts of your application, we recommend using the [Wallet SDK adapters](/typescript/v5/adapters).
+25 -1
View File
@@ -104,6 +104,30 @@ const walletActions: SidebarLink = (() => {
href: `${parentSlug}/generateexternalloginlink`,
name: "GenerateExternalLoginLink",
},
{
href: `${parentSlug}/createsessionkey7702`,
name: "CreateSessionKey (EIP-7702)",
},
{
href: `${parentSlug}/signerhaspermissions7702`,
name: "SignerHasFullPermissions",
},
{
href: `${parentSlug}/getcallpolicies7702`,
name: "GetCallPoliciesForSigner",
},
{
href: `${parentSlug}/gettransferpolicies7702`,
name: "GetTransferPoliciesForSigner",
},
{
href: `${parentSlug}/getsessionexpiration7702`,
name: "GetSessionExpirationForSigner",
},
{
href: `${parentSlug}/getsessionstate7702`,
name: "GetSessionStateForSigner",
},
],
name: "InAppWallet & EcosystemWallet",
},
@@ -116,7 +140,7 @@ const walletActions: SidebarLink = (() => {
},
{
href: `${parentSlug}/createsessionkey`,
name: "CreateSessionKey",
name: "CreateSessionKey (EIP-4337)",
},
{
href: `${parentSlug}/addadmin`,
@@ -0,0 +1,441 @@
import { Details, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "EcosystemWallet.CreateSessionKey | Thirdweb .NET SDK",
description:
"Creates a session key for EIP-7702 accounts with configurable permissions and policies.",
});
# EcosystemWallet.CreateSessionKey
Creates a session key for the user wallet with EIP-7702 functionality. Session keys allow delegated access to the wallet with specific permissions and time constraints. This method is only supported for EIP7702 and EIP7702Sponsored execution modes.
**How it works**: The method creates a `SessionSpec` with your parameters, signs it using EIP-712, and calls `createSessionWithSig` on the EIP-7702 account contract to register the session key on-chain.
## Usage
```csharp
// Create a session key for 24 hours with full permissions
var receipt = await ecosystemWallet.CreateSessionKey(
chainId: 1,
signerAddress: await sessionSigner.GetAddress(),
durationInSeconds: 86400, // 24 hours
grantFullPermissions: true
);
// Create a session key with specific call and transfer policies
var callPolicies = new List<CallSpec>
{
new CallSpec
{
Target = "0x1234567890123456789012345678901234567890",
Selector = new byte[] { 0xa9, 0x05, 0x9c, 0xbb }, // transfer(address,uint256)
MaxValuePerUse = BigInteger.Parse("100000000000000000"), // 0.1 ETH max per call
ValueLimit = new UsageLimit
{
LimitType = 1, // Lifetime limit
Limit = BigInteger.Parse("1000000000000000000"), // 1 ETH total
Period = 86400 // 1 day period
},
Constraints = new List<Constraint>
{
new Constraint
{
Condition = 1, // Equal to (not 0)
Index = 0, // First parameter (to address)
RefValue = new byte[32], // Reference value for validation
Limit = new UsageLimit
{
LimitType = 2, // Allowance (not 0)
Limit = BigInteger.Parse("500000000000000000"), // 0.5 ETH per period
Period = 3600 // 1 hour
}
}
}
}
};
var transferPolicies = new List<TransferSpec>
{
new TransferSpec
{
Target = "0x0000000000000000000000000000000000000000", // ETH transfers
MaxValuePerUse = BigInteger.Parse("100000000000000000"), // 0.1 ETH max per transfer
ValueLimit = new UsageLimit
{
LimitType = 1, // Lifetime limit
Limit = BigInteger.Parse("1000000000000000000"), // 1 ETH total
Period = 86400 // 1 day
}
},
new TransferSpec
{
Target = "0xA0b86a33E6411a3bb4CC4C7b9C5C5C7C7C8C9C0c", // Specific token contract
MaxValuePerUse = BigInteger.Parse("1000000000000000000000"), // 1000 tokens max per transfer
ValueLimit = new UsageLimit
{
LimitType = 2, // Allowance (resets every period)
Limit = BigInteger.Parse("10000000000000000000000"), // 10,000 tokens per period
Period = 3600 // 1 hour period
}
}
};
var receipt = await ecosystemWallet.CreateSessionKey(
chainId: 1,
signerAddress: await sessionSigner.GetAddress(),
durationInSeconds: 3600, // 1 hour
grantFullPermissions: false,
callPolicies: callPolicies,
transferPolicies: transferPolicies,
uid: System.Text.Encoding.UTF8.GetBytes("unique-session-id") // Custom unique identifier
);
// Create a wildcard session key (works with any address)
var wildcardReceipt = await ecosystemWallet.CreateSessionKey(
chainId: 1,
signerAddress: "0x0000000000000000000000000000000000000000", // Wildcard address
durationInSeconds: 7200, // 2 hours
grantFullPermissions: true
);
```
## Common Use Cases
### Gaming Session Key
```csharp
// Allow spending game tokens with daily limits
var gameTokenPolicy = new List<CallSpec>
{
new CallSpec
{
Target = "0xGameTokenContract...",
Selector = new byte[] { 0xa9, 0x05, 0x9c, 0xbb }, // transfer(address,uint256)
MaxValuePerUse = BigInteger.Parse("1000000000000000000000"), // 1000 tokens per transaction
ValueLimit = new UsageLimit
{
LimitType = 2, // Allowance (resets daily)
Limit = BigInteger.Parse("10000000000000000000000"), // 10,000 tokens per day
Period = 86400 // 1 day
}
}
};
```
### DeFi Trading Session Key
```csharp
// Allow DEX trades with specific token constraints
var dexTradingPolicy = new List<CallSpec>
{
new CallSpec
{
Target = "0xUniswapV3Router...",
Selector = new byte[] { 0x41, 0x4b, 0xf3, 0x89 }, // exactInputSingle
MaxValuePerUse = BigInteger.Parse("100000000000000000"), // 0.1 ETH max per trade
ValueLimit = new UsageLimit
{
LimitType = 2, // Daily allowance
Limit = BigInteger.Parse("1000000000000000000"), // 1 ETH per day
Period = 86400
},
Constraints = new List<Constraint>
{
new Constraint
{
Condition = 1, // Equal - only allow specific token
Index = 0, // First parameter (tokenIn)
RefValue = new byte[32], // USDC token address (padded to 32 bytes)
Limit = new UsageLimit { LimitType = 0 } // Unlimited usage of this constraint
}
}
}
};
```
### Subscription Payment Session Key
```csharp
// Allow recurring payments to specific service
var subscriptionPolicy = new List<CallSpec>
{
new CallSpec
{
Target = "0xSubscriptionContract...",
Selector = new byte[] { 0x12, 0x34, 0x56, 0x78 }, // paySubscription()
MaxValuePerUse = BigInteger.Parse("50000000000000000000"), // $50 worth of tokens
ValueLimit = new UsageLimit
{
LimitType = 2, // Monthly allowance
Limit = BigInteger.Parse("50000000000000000000"), // $50 per month
Period = 2592000 // 30 days
},
Constraints = new List<Constraint>
{
new Constraint
{
Condition = 1, // Equal - only allow payments to specific service
Index = 0, // Service ID parameter
RefValue = BitConverter.GetBytes(12345).Concat(new byte[28]).ToArray(), // Service ID 12345
Limit = new UsageLimit { LimitType = 0 }
}
}
}
};
```
### Emergency Withdrawal Session Key
```csharp
// Allow emergency withdrawals with strict limits
var emergencyPolicy = new List<TransferSpec>
{
new TransferSpec
{
Target = "0x0000000000000000000000000000000000000000", // ETH
MaxValuePerUse = BigInteger.Parse("100000000000000000"), // 0.1 ETH per withdrawal
ValueLimit = new UsageLimit
{
LimitType = 1, // Lifetime limit
Limit = BigInteger.Parse("500000000000000000"), // 0.5 ETH total
Period = 0 // Not used for lifetime limits
}
}
};
var emergencyReceipt = await ecosystemWallet.CreateSessionKey(
chainId: 1,
signerAddress: await emergencyWallet.GetAddress(),
durationInSeconds: 604800, // 1 week
grantFullPermissions: false,
callPolicies: null,
transferPolicies: emergencyPolicy
);
```
<Details summary="Parameters">
### chainId
`BigInteger`: The chain ID for the session key.
### signerAddress
`string`: The address of the signer for the session key. Use `"0x0000000000000000000000000000000000000000"` for wildcard session keys that work with any address.
### durationInSeconds
`long`: Duration in seconds for which the session key will be valid.
### grantFullPermissions
`bool` (optional): Whether to grant full permissions to the session key. When `true`, creates a "wildcard" session that ignores call and transfer policies and allows any action. When `false`, only the specified call and transfer policies will be applied. Defaults to `true`.
**Important**: When `grantFullPermissions` is `true`, the `callPolicies` and `transferPolicies` parameters are ignored.
### callPolicies
`List<CallSpec>` (optional): List of call policies to apply to the session key. If `null`, no call policies will be applied. Only used when `grantFullPermissions` is `false`.
#### CallSpec Properties:
- `Target` (`string`): The contract address that can be called
- `Selector` (`byte[]`): The function selector (4 bytes) that can be called - use tools like [4byte.directory](https://www.4byte.directory/) to find selectors
- `MaxValuePerUse` (`BigInteger`): Maximum ETH value that can be sent per function call
- `ValueLimit` (`UsageLimit`): Overall ETH spending limits for this call policy
- `Constraints` (`List<Constraint>`): Additional parameter constraints for function calls
**Selector Examples:**
- `transfer(address,uint256)`: `0xa9059cbb`
- `approve(address,uint256)`: `0x095ea7b3`
- `swapExactETHForTokens(uint256,address[],address,uint256)`: `0x7ff36ab5`
### transferPolicies
`List<TransferSpec>` (optional): List of transfer policies to apply to the session key. If `null`, no transfer policies will be applied. Only used when `grantFullPermissions` is `false`.
#### TransferSpec Properties:
- `Target` (`string`): The token contract address for transfers
- Use `"0x0000000000000000000000000000000000000000"` for native ETH
- Use specific token contract addresses for ERC-20 tokens
- `MaxValuePerUse` (`BigInteger`): Maximum amount that can be transferred per transaction
- `ValueLimit` (`UsageLimit`): Overall usage limits for transfers of this token
**Common Token Examples:**
- ETH: `0x0000000000000000000000000000000000000000`
- USDC: `0xA0b86a33E6411a3bb4CC4C7b9C5C5C7C7C8C9C0c` (mainnet)
- USDT: `0xdAC17F958D2ee523a2206206994597C13D831ec7` (mainnet)
### uid
`byte[]` (optional): A unique identifier for the session key. If `null`, a new GUID will be generated automatically. This UID is used to identify and manage the session key on-chain.
</Details>
<Details summary="Related Methods">
### Managing Session Keys
Once a session key is created, you can use these methods to inspect and manage it:
```csharp
// Check if a signer has full permissions (wildcard access)
bool hasFullPermissions = await ecosystemWallet.SignerHasFullPermissions(
chainId: 1,
signerAddress: await sessionSigner.GetAddress()
);
// Get the call policies for a specific signer
var callPolicies = await ecosystemWallet.GetCallPoliciesForSigner(
chainId: 1,
signerAddress: await sessionSigner.GetAddress()
);
// Get the transfer policies for a specific signer
var transferPolicies = await ecosystemWallet.GetTransferPoliciesForSigner(
chainId: 1,
signerAddress: await sessionSigner.GetAddress()
);
// Get the session expiration timestamp
var expirationTimestamp = await ecosystemWallet.GetSessionExpirationForSigner(
chainId: 1,
signerAddress: await sessionSigner.GetAddress()
);
// Get complete session state including remaining limits
var sessionState = await ecosystemWallet.GetSessionStateForSigner(
chainId: 1,
signerAddress: await sessionSigner.GetAddress()
);
// Check remaining limits
foreach (var limit in sessionState.TransferValue)
{
Console.WriteLine($"Transfer limit remaining for {limit.Target}: {limit.Remaining}");
}
foreach (var limit in sessionState.CallValue)
{
Console.WriteLine($"Call value limit remaining for {limit.Target}.{limit.Selector.ToHex()}: {limit.Remaining}");
}
```
</Details>
<Details summary="Type Definitions">
### LimitType Enum
Controls how usage limits are enforced over time:
- `0` (Unlimited): No limits applied
- `1` (Lifetime): Total limit that never resets
- `2` (Allowance): Limit that resets every period (e.g., daily/hourly spending limits)
### Condition Enum
Defines how parameter constraints are evaluated:
- `0` (Unconstrained): No constraint applied
- `1` (Equal): Parameter must equal the reference value
- `2` (Greater): Parameter must be greater than reference value
- `3` (Less): Parameter must be less than reference value
- `4` (GreaterOrEqual): Parameter must be greater than or equal to reference value
- `5` (LessOrEqual): Parameter must be less than or equal to reference value
- `6` (NotEqual): Parameter must not equal the reference value
### UsageLimit
Defines spending or usage limits over time periods:
```csharp
public class UsageLimit
{
public byte LimitType { get; set; } // 0=Unlimited, 1=Lifetime, 2=Allowance
public BigInteger Limit { get; set; } // The limit amount (ignored if Unlimited)
public BigInteger Period { get; set; } // Time period in seconds (only used for Allowance)
}
```
**Examples:**
- **Unlimited**: `LimitType = 0` (Limit and Period ignored)
- **Lifetime limit**: `LimitType = 1, Limit = 1000` (max 1000 tokens ever)
- **Daily allowance**: `LimitType = 2, Limit = 100, Period = 86400` (100 tokens per day)
### Constraint
Defines parameter validation and usage limits for function calls:
```csharp
public class Constraint
{
public byte Condition { get; set; } // Condition type (0-6, see enum above)
public ulong Index { get; set; } // Parameter index to constrain (0-based)
public byte[] RefValue { get; set; } // Reference value for comparison (32 bytes)
public UsageLimit Limit { get; set; } // Usage limit for this parameter value
}
```
**Example Use Cases:**
- Limit transfers to specific addresses
- Restrict token amounts in function calls
- Control which contracts can be called
- Set spending limits per recipient
</Details>
<Details summary="Return Value">
### ThirdwebTransactionReceipt
The transaction receipt for the session key creation, as a `ThirdwebTransactionReceipt` object.
</Details>
<Details summary="Exceptions">
### InvalidOperationException
Thrown when the execution mode is not EIP7702 or EIP7702Sponsored.
### ArgumentException
Thrown when the signer address is null or empty, or when the duration is less than or equal to zero.
</Details>
<Details summary="Common Errors & Troubleshooting">
### Session Creation Errors
**"Execution mode not supported"**
- Ensure your EcosystemWallet is created with `ExecutionMode.EIP7702` or `ExecutionMode.EIP7702Sponsored`
**"Invalid signer address"**
- Verify the signer address is a valid Ethereum address
- Use `"0x0000000000000000000000000000000000000000"` for wildcard sessions
**"Duration too short"**
- Duration must be greater than 0 seconds
- Consider reasonable session lengths (1 hour to 30 days)
### Runtime Session Errors
**"MaxValueExceeded"**
- Transaction value exceeds `MaxValuePerUse` in CallSpec/TransferSpec
- Increase the limit or split into smaller transactions
**"LifetimeUsageExceeded"**
- Total usage has exceeded the lifetime limit
- Create a new session key or increase limits
**"AllowanceExceeded"**
- Period allowance has been exhausted
- Wait for the next period or create additional session keys
**"CallPolicyViolated"**
- Attempting to call a contract/function not in the CallSpec list
- Add the required contract and selector to callPolicies
**"ConditionFailed"**
- Function parameter doesn't meet constraint requirements
- Check parameter values match the expected constraints
</Details>
@@ -0,0 +1,66 @@
import { Details, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "EcosystemWallet.GetCallPoliciesForSigner | Thirdweb .NET SDK",
description:
"Gets the call policies for a specific signer on the EIP-7702 account.",
});
# EcosystemWallet.GetCallPoliciesForSigner
Gets the call policies for a specific signer on the EIP-7702 account. Returns a list of `CallSpec` objects that define which contracts and functions the signer can call.
## Usage
```csharp
// Get call policies for a signer
var callPolicies = await ecosystemWallet.GetCallPoliciesForSigner(
chainId: 1,
signerAddress: "0x1234567890123456789012345678901234567890"
);
// Examine the policies
foreach (var policy in callPolicies)
{
Console.WriteLine($"Can call {policy.Target} with selector {BitConverter.ToString(policy.Selector)}");
Console.WriteLine($"Max value per use: {policy.MaxValuePerUse}");
Console.WriteLine($"Constraints: {policy.Constraints.Count}");
}
```
<Details summary="Parameters">
### chainId
`BigInteger`: The chain ID of the EIP-7702 account.
### signerAddress
`string`: The address of the signer to get call policies for.
</Details>
<Details summary="Return Value">
### List&lt;CallSpec&gt;
A list of call policies for the signer. Each `CallSpec` contains:
- `Target`: Contract address that can be called
- `Selector`: Function selector (4 bytes) that can be called
- `MaxValuePerUse`: Maximum ETH value per function call
- `ValueLimit`: Overall spending limits
- `Constraints`: Parameter constraints for function calls
</Details>
<Details summary="Exceptions">
### InvalidOperationException
Thrown when the execution mode is not EIP7702 or EIP7702Sponsored.
### ArgumentException
Thrown when the signer address is null or empty.
</Details>
@@ -0,0 +1,68 @@
import { Details, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "EcosystemWallet.GetSessionExpirationForSigner | Thirdweb .NET SDK",
description:
"Gets the session expiration timestamp for a specific signer on the EIP-7702 account.",
});
# EcosystemWallet.GetSessionExpirationForSigner
Gets the session expiration timestamp for a specific signer on the EIP-7702 account. Returns the Unix timestamp when the session key expires.
## Usage
```csharp
// Get session expiration for a signer
var expirationTimestamp = await ecosystemWallet.GetSessionExpirationForSigner(
chainId: 1,
signerAddress: "0x1234567890123456789012345678901234567890"
);
// Convert to DateTime for readability
var expirationDateTime = DateTimeOffset.FromUnixTimeSeconds((long)expirationTimestamp);
Console.WriteLine($"Session expires at: {expirationDateTime}");
// Check if session is still valid
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
bool isValid = expirationTimestamp > currentTime;
Console.WriteLine($"Session is valid: {isValid}");
if (isValid)
{
var timeRemaining = expirationDateTime - DateTimeOffset.UtcNow;
Console.WriteLine($"Time remaining: {timeRemaining.TotalHours:F1} hours");
}
```
<Details summary="Parameters">
### chainId
`BigInteger`: The chain ID of the EIP-7702 account.
### signerAddress
`string`: The address of the signer to get session expiration for.
</Details>
<Details summary="Return Value">
### BigInteger
The Unix timestamp when the session expires. Use `DateTimeOffset.FromUnixTimeSeconds()` to convert to a readable date.
</Details>
<Details summary="Exceptions">
### InvalidOperationException
Thrown when the execution mode is not EIP7702 or EIP7702Sponsored.
### ArgumentException
Thrown when the signer address is null or empty.
</Details>
@@ -0,0 +1,95 @@
import { Details, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "EcosystemWallet.GetSessionStateForSigner | Thirdweb .NET SDK",
description:
"Gets the complete session state for a specific signer on the EIP-7702 account, including remaining limits and usage information.",
});
# EcosystemWallet.GetSessionStateForSigner
Gets the complete session state for a specific signer on the EIP-7702 account, including remaining limits and usage information. This provides a comprehensive view of what the signer can still do within their session limits.
## Usage
```csharp
// Get complete session state for a signer
var sessionState = await ecosystemWallet.GetSessionStateForSigner(
chainId: 1,
signerAddress: "0x1234567890123456789012345678901234567890"
);
// Check remaining transfer limits
Console.WriteLine("Transfer Limits:");
foreach (var limit in sessionState.TransferValue)
{
string tokenType = limit.Target == "0x0000000000000000000000000000000000000000"
? "ETH"
: $"Token {limit.Target}";
Console.WriteLine($" {tokenType}: {limit.Remaining} remaining");
}
// Check remaining call value limits
Console.WriteLine("\nCall Value Limits:");
foreach (var limit in sessionState.CallValue)
{
Console.WriteLine($" {limit.Target}.{BitConverter.ToString(limit.Selector)}: {limit.Remaining} ETH remaining");
}
// Check parameter constraint limits
Console.WriteLine("\nParameter Constraints:");
foreach (var limit in sessionState.CallParams)
{
Console.WriteLine($" {limit.Target}.{BitConverter.ToString(limit.Selector)} param[{limit.Index}]: {limit.Remaining} remaining");
}
```
<Details summary="Parameters">
### chainId
`BigInteger`: The chain ID of the EIP-7702 account.
### signerAddress
`string`: The address of the signer to get session state for.
</Details>
<Details summary="Return Value">
### SessionState
An object containing the current session state with three arrays:
#### TransferValue
- `LimitState[]`: Remaining limits for token transfers
- Each entry shows remaining transfer amounts for specific tokens
#### CallValue
- `LimitState[]`: Remaining ETH value limits for contract calls
- Each entry shows remaining ETH that can be sent with function calls
#### CallParams
- `LimitState[]`: Remaining limits for constrained function parameters
- Each entry shows remaining usage for specific parameter constraints
#### LimitState Properties
- `Remaining`: Amount remaining for this limit
- `Target`: Contract or token address
- `Selector`: Function selector (for calls only)
- `Index`: Parameter index (for parameter constraints only)
</Details>
<Details summary="Exceptions">
### InvalidOperationException
Thrown when the execution mode is not EIP7702 or EIP7702Sponsored.
### ArgumentException
Thrown when the signer address is null or empty.
</Details>
@@ -0,0 +1,68 @@
import { Details, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "EcosystemWallet.GetTransferPoliciesForSigner | Thirdweb .NET SDK",
description:
"Gets the transfer policies for a specific signer on the EIP-7702 account.",
});
# EcosystemWallet.GetTransferPoliciesForSigner
Gets the transfer policies for a specific signer on the EIP-7702 account. Returns a list of `TransferSpec` objects that define which tokens the signer can transfer and with what limits.
## Usage
```csharp
// Get transfer policies for a signer
var transferPolicies = await ecosystemWallet.GetTransferPoliciesForSigner(
chainId: 1,
signerAddress: "0x1234567890123456789012345678901234567890"
);
// Examine the policies
foreach (var policy in transferPolicies)
{
string tokenType = policy.Target == "0x0000000000000000000000000000000000000000"
? "ETH"
: $"Token {policy.Target}";
Console.WriteLine($"Can transfer {tokenType}");
Console.WriteLine($"Max per transfer: {policy.MaxValuePerUse}");
Console.WriteLine($"Limit type: {policy.ValueLimit.LimitType}");
}
```
<Details summary="Parameters">
### chainId
`BigInteger`: The chain ID of the EIP-7702 account.
### signerAddress
`string`: The address of the signer to get transfer policies for.
</Details>
<Details summary="Return Value">
### List&lt;TransferSpec&gt;
A list of transfer policies for the signer. Each `TransferSpec` contains:
- `Target`: Token contract address (`0x0000000000000000000000000000000000000000` for ETH)
- `MaxValuePerUse`: Maximum amount per transfer transaction
- `ValueLimit`: Overall usage limits with type and period
</Details>
<Details summary="Exceptions">
### InvalidOperationException
Thrown when the execution mode is not EIP7702 or EIP7702Sponsored.
### ArgumentException
Thrown when the signer address is null or empty.
</Details>
@@ -0,0 +1,62 @@
import { Details, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "EcosystemWallet.SignerHasFullPermissions | Thirdweb .NET SDK",
description:
"Checks if a signer has full permissions (wildcard access) on the EIP-7702 account.",
});
# EcosystemWallet.SignerHasFullPermissions
Checks if the signer has full permissions on the EIP-7702 account. Returns `true` if the signer is a wildcard signer with unrestricted access.
## Usage
```csharp
// Check if a signer has full permissions
bool hasFullPermissions = await ecosystemWallet.SignerHasFullPermissions(
chainId: 1,
signerAddress: "0x1234567890123456789012345678901234567890"
);
if (hasFullPermissions)
{
Console.WriteLine("Signer has unlimited access to the wallet");
}
else
{
Console.WriteLine("Signer has restricted access based on policies");
}
```
<Details summary="Parameters">
### chainId
`BigInteger`: The chain ID of the EIP-7702 account.
### signerAddress
`string`: The address of the signer to check permissions for.
</Details>
<Details summary="Return Value">
### bool
`true` if the signer has full permissions (wildcard access), `false` if the signer has restricted permissions or no permissions.
</Details>
<Details summary="Exceptions">
### InvalidOperationException
Thrown when the execution mode is not EIP7702 or EIP7702Sponsored.
### ArgumentException
Thrown when the signer address is null or empty.
</Details>
+1 -1
View File
@@ -163,5 +163,5 @@ export const sidebar: SideBar = {
name: "FAQ",
},
],
name: "Engine",
name: "Transactions",
};
@@ -4,13 +4,13 @@ Smart server wallets are smart accounts designed to perform blockchain operation
## Benefits
Smart server wallets are the recommended way to perform blockchain operations with Engine.
Smart server wallets are the recommended way to perform blockchain operations with thirdweb Transactions.
- **Managed Gas Fees**: Smart server wallets manage gas fees for you by automatically paying the fees using the payment method associated with your thirdweb account. You do not have to worry about topping up
gas funds on your server wallets.
- **Secure Key Management**: Smart server wallets are secured by Vault. Read more about Vault in the Security section below.
- **Minimal Setup:** Smart server wallets can be created with just a few clicks directly in the thirdweb dashboard. No need to manage private keys or seed phrases or secure with external key management systems.
- **Higher Throughput:** Smart server wallets can handle higher throughput by using multi-dimensional nonces. While Engine handles concurrent transactions through a queue system, smart wallets use parallelization to process multiple transactions at the same time, more efficiently than EOAs.
- **Higher Throughput:** Smart server wallets can handle higher throughput by using multi-dimensional nonces. While thirdweb Transactions handles concurrent transactions through a queue system, smart wallets use parallelization to process multiple transactions at the same time, more efficiently than EOAs.
## Security
@@ -22,4 +22,3 @@ Keys used to manage server wallets including the admin key and access keys can b
Smart server wallets cost a gas premium fee for each transaction completed on any mainnet as part of account abstraction fees. There are no gas charges for testnets.
[See pricing page for details](https://thirdweb.com/pricing).
@@ -1,8 +0,0 @@
import { Details } from "@doc";
# Server Wallets
Smart server wallets are the recommended way to perform blockchain operations with Engine.
You can create smart server wallets by navigating to Engine > Server Wallets in your project dashboard.
+14 -6
View File
@@ -1,20 +1,28 @@
import { Callout, Details } from "@doc";
# Engine FAQs
# Transactions FAQs
<Details summary="How is pricing calculated for Engine Cloud?">
Pricing is calculated through the number of write requests (ex: /v1/write/contract) and sign requests (ex: /v1/sign/transaction) made through the Engine API. For requests costs, [see the thirdweb pricing page](https://thirdweb.com/pricing). Read requests made through Engine API are free, within the RPC limits/plan.
<Details summary="How is pricing calculated?">
Pricing is calculated through the number of write requests (ex: /v1/write/contract) and sign requests (ex: /v1/sign/transaction) made through the Transactions API. For requests costs, [see the thirdweb pricing page](https://thirdweb.com/pricing). Read requests made through Transactions API are free, within the RPC limits/plan.
For transactions through server wallets paid through the user's thirdweb account, users will pay a 5% premium on the gas fee of each transaction completed on any mainnet as part of account abstraction fees. There are no gas charges for testnets.
For requests through server wallets paid through the user's thirdweb account, users will pay a 5% premium on the gas fee of each request completed on any mainnet as part of account abstraction fees. There are no gas charges for testnets.
**Please note: Legacy pricing plans may be subject to the 10% fee specified when signed up instead of 5%. [See pricing page for updated plans and benefits](https://thirdweb.com/pricing)**
The breakdown for usage and transaction fees can be found in your usage dashboard under the team overview.
</Details>
<Details summary="Which chains does Engine Cloud support?">
While in beta, Engine Cloud currently supports every non ZK EVM-compatible chain which includes support for Account Abstraction. See the current list of supported chains.
<Details summary="Which chains does Transactions Cloud support?">
While in beta, Transactions Cloud currently supports every non ZK EVM-compatible chain which includes support for Account Abstraction. See the current list of supported chains.
If your chain is not listed, please [contact us](https://thirdweb.com/contact-us) to prioritize enabling support.
</Details>
<Details summary="What is the difference between Transactions Cloud and Dedicated?">
Cloud uses shared infrastructure that allows you to get started quickly with Transactions. It is ideal for most use cases and is the recommended way to use Transactions.
Dedicated is a self-hosted version of Transactions that allows you to run Transactions on your own infrastructure.
It is ideal for high throughput applications, or applications that require complete control over their infrastructure.
</Details>
@@ -4,21 +4,21 @@ import SendTransaction from '../assets/send-transaction.png';
# Get started
Learn how to get started with thirdweb Engine. This guide will walk you through the steps to start building with thirdweb Engine by creating a Vault, server wallets, and integrating into your application.
Learn how to get started with thirdweb Transactions. This guide will walk you through the steps to start building with thirdweb Transactions by creating a Vault, server wallets, and integrating into your application.
## Prerequisites
- Create a thirdweb project. If you don't have a project yet, [learn how to create a project](/account/api-keys/create).
## Engine Setup
## Transactions Setup
<Steps>
<Step title="Navigate to Engine">
In your project dashboard, navigate to **Engine** to get started.
<Step title="Navigate to Transactions">
In your project dashboard, navigate to **Transactions** to get started.
</Step>
<Step title="Create Vault">
Create a vault to manage your Engine's server wallets. After setup, you can manage your Vault to create additional access tokens or rotate admin keys.
Create a vault to manage your Transactions's server wallets. After setup, you can manage your Vault to create additional access tokens or rotate admin keys.
<Callout variant="info" title="Vault">
Vault is thirdweb's key management service designed to store smart server wallets non-custodially. [Learn more about Vault.](/vault)
@@ -27,10 +27,10 @@ Learn how to get started with thirdweb Engine. This guide will walk you through
</Step>
<Step title="Create Server Wallet">
Create a server wallet to perform blockchain actions with Engine.
Create a server wallet to perform blockchain actions with Transactions.
<Callout variant="info" title="Server Wallet">
Server wallets are smart wallets Engine uses to perform blockchain actions. [Learn more about server wallets](/engine/v2/configure-wallets/server-wallets).
Server wallets are smart wallets Transactions uses to perform blockchain actions. [Learn more about server wallets](/Transactions/v2/configure-wallets/server-wallets).
</Callout>
</Step>
@@ -42,7 +42,7 @@ Learn how to get started with thirdweb Engine. This guide will walk you through
</Step>
<Step title="Integrate with your app">
Integrate Engine into your application using the thirdweb SDK or Engine API. [View full API reference.](https://client.scalar.com/workspace/default/request/yq_Wx56PL4Ur6jOZsAOpA)
Integrate Transactions into your application using the thirdweb SDK or Transactions API. [View full API reference.](https://client.scalar.com/workspace/default/request/yq_Wx56PL4Ur6jOZsAOpA)
<Tabs defaultValue="curl">
<TabsList>
@@ -255,7 +255,7 @@ Learn how to get started with thirdweb Engine. This guide will walk you through
- The full catalog of extension functions
- The prepareContractCall function to encode your transactions
- The full account interface, predefined chains, and more
The SDK handles encoding your transactions, signing them to Engine and polling for status.
The SDK handles encoding your transactions, signing them to Transactions and polling for status.
</Callout>
</Step>
</Steps>
+5 -3
View File
@@ -10,7 +10,9 @@ export default async function Layout(props: { children: React.ReactNode }) {
sideBar={sidebar}
sidebarHeader={
<div className="flex-col items-center gap-1">
<p className="py-5 font-semibold text-foreground text-lg">Engine</p>
<p className="py-5 font-semibold text-foreground text-lg">
Transactions
</p>
<EngineVersionSelector selected="v3" />
</div>
}
@@ -22,6 +24,6 @@ export default async function Layout(props: { children: React.ReactNode }) {
export const metadata = createMetadata({
description:
"Engine is a backend HTTP server that calls smart contracts using your managed backend wallets.",
title: "Engine V3",
"thirdweb Transactions is a backend HTTP server that calls smart contracts using your managed backend wallets.",
title: "Transactions",
});
@@ -1,8 +1,8 @@
import { Callout } from "@doc";
# Migrating from Engine v2 to v3
# Migrating from Transactions v2 to v3
Learn how to migrate your applications from Engine v2 to Engine v3. We'll cover common operations and highlight the differences in API requests and authentication.
Learn how to migrate your applications from v2 to v3. We'll cover common operations and highlight the differences in API requests and authentication.
<Callout variant="info" title="Migrating Wallets">
If you'd like to migrate your wallets from v2 to v3 to also be secured by [Vault](/vault) or thirdweb's key management service, please [contact us for assistance.](https://thirdweb.com/support)
@@ -14,7 +14,7 @@ Migrating your contract write operations from v2 to v3 involves changes to the e
#### OLD (v2)
The v2 approach used your dedicated engine url, with a specific endpoint for each chain and contract and backend wallet address in the header.
The v2 approach used your dedicated Transactions url, with a specific endpoint for each chain and contract and backend wallet address in the header.
```bash
curl -X POST "<engine_url>/contract/<chain>/<contract_address>/write" \
@@ -173,9 +173,9 @@ curl https://engine.thirdweb.com/v1/read/contract \
## Extension functions
Engine v2 has a large number of endpoints to do specific contract calls, like mintTo, claimTo, etc.
V2 has a large number of endpoints to do specific contract calls, like mintTo, claimTo, etc.
Engine v3 does not have these endpoints yet, but they are coming soon. In the meantime, if your backend is written in typescript, we recommend using the [thirdweb SDK](/references/typescript/v5/functions#extensions) to do these operations:
V3 does not have these endpoints yet, but they are coming soon. In the meantime, if your backend is written in typescript, we recommend using the [thirdweb SDK](/references/typescript/v5/functions#extensions) to do these operations:
##### OLD (v2)
@@ -194,7 +194,7 @@ const response = await fetch(
metadataWithSupply: {
metadata: {
name: "Acme Inc. Superfan",
description: "Created with thirdweb Engine",
description: "Created with thirdweb",
image:
"ipfs://QmciR3WLJsf2BgzTSjbG5zCxsrEQ8PqsHK7JWGWsDSNo46/nft.png",
},
@@ -233,17 +233,17 @@ const transaction = mintTo({
supply: 1n, // The quantity of NFTs to mint
nft: {
name: "Acme Inc. Superfan",
description: "Created with thirdweb Engine",
description: "Created with thirdweb",
image:
"ipfs://QmciR3WLJsf2BgzTSjbG5zCxsrEQ8PqsHK7JWGWsDSNo46/nft.png",
},
});
// Enqueue the transaction via Engine
// Enqueue the transaction
const { transactionId } =
await serverWallet.enqueueTransaction({
transaction,
});
```
The entire thirdweb SDK is available to use with engine v3 in this manner using the [`Engine` namespace](/references/typescript/v5/functions#engine). Check the [thirdweb SDK docs](/references/typescript/v5/functions#extensions) for all available functions.
The entire thirdweb SDK is available to use with Transactions v3 in this manner using the [`Transactions` namespace](/references/typescript/v5/functions#engine). Check the [thirdweb SDK docs](/references/typescript/v5/functions#extensions) for all available functions.
+8 -9
View File
@@ -5,19 +5,19 @@ import SupportedChains from "../../_images/supported-chains.png";
export const metadata = createMetadata({
title: "thirdweb Engine",
title: "thirdweb Transactions",
description: "An open-source, backend server that reads, writes, and deploys contracts at production scale.",
});
# Engine
# Transactions
Engine provides a way to read and write to the blockchain at scale through simple API endpoints.
Transactions provides a way to read and write to the blockchain at scale through simple API endpoints.
<OpenSourceCard title="Engine" href="https://github.com/thirdweb-dev/engine" />
<OpenSourceCard title="Transactions" href="https://github.com/thirdweb-dev/engine" />
<Callout variant="info" title="Looking for the v2 documentation?">
We just launched Engine Cloud in beta — we're actively adding new features, endpoints, and performance improvements.
Engine Standard and Premium have now been rebranded as Engine Dedicated V2, with Engine Dedicated V3 on the way.
We just launched Transactions Cloud in beta — we're actively adding new features, endpoints, and performance improvements.
Engine Standard and Premium have now been rebranded as Transactions Dedicated V2, with Transactions Dedicated V3 on the way.
Looking for the previous version? [Head back to the Engine V2 documentation.](/engine/v2)
</Callout>
@@ -74,10 +74,9 @@ Looking for the previous version? [Head back to the Engine V2 documentation.](/e
description="Debug transaction failures with detailed timelines, onchain errors, and gas details."
iconUrl={<ActivityIcon />}
/>
/>
</div>
## Engine Cloud vs. Dedicated
## Cloud vs. Dedicated
| Feature | Cloud ☁️ | Dedicated (Coming Soon) |
|--------------------|-------------------------------------------|------------------------------------------|
@@ -90,7 +89,7 @@ Looking for the previous version? [Head back to the Engine V2 documentation.](/e
## Supported Chains
Engine is supported on every EVM compatible chain and private subnets. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist?service=engine).
Transactions are supported on every EVM compatible chain and private subnets. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist?service=engine).
<DocImage src={SupportedChains} />
+2 -2
View File
@@ -45,7 +45,7 @@ export const sidebar: SideBar = {
icon: <WalletIcon />,
links: [
{
href: `${engineV3Slug}/configure-wallets/smart-server-wallet`,
href: `${engineV3Slug}/configure-wallets/server-wallets`,
name: "Smart Server Wallet",
},
],
@@ -82,5 +82,5 @@ export const sidebar: SideBar = {
name: "FAQ",
},
],
name: "Engine",
name: "Transactions",
};
@@ -1,8 +1,8 @@
import { Details } from "@doc";
# Engine Troubleshoot Guide
# Transactions Troubleshoot Guide
<Details summary="Transactions stuck in a pending state using Engine Cloud.">
<Details summary="Requests stuck in a pending state using Transactions Cloud.">
Ensure that Account Abstraction is enabled for your project by navigating to Project Settings > Enabled Services > Account Abstraction.
@@ -1,10 +1,10 @@
# Engine TypeScript SDK
# Transactions TypeScript SDK
You can use the full TypeScript thirdweb SDK in your backend, allowing you to use:
- The full catalog of extension functions
- The prepareContractCall function to encode your transactions
- The full account interface, predefined chains, and more
The SDK handles encoding your transactions, signing them to Engine and polling for status.
The SDK handles encoding your transactions, signing them to Transactions and polling for status.
## Example
@@ -20,7 +20,7 @@ import {
createThirdwebClient,
sendTransaction,
getContract,
Engine,
Transactions,
} from "thirdweb";
import { baseSepolia } from "thirdweb/chains";
import { claimTo } from "thirdweb/extensions/1155";
@@ -49,7 +49,7 @@ const transaction = claimTo({
quantity: 1n, // The quantity of NFTs to mint
});
// Send the transaction via Engine
// Send the transaction via Transactions
const result = await sendTransaction({
account: serverWallet,
transaction,
+17 -5
View File
@@ -163,24 +163,30 @@ function LearningResourcesSection() {
return (
<section>
<SectionTitle anchorId="learning" title="Learning Resources" />
<Grid className="md:grid-cols-1 lg:grid-cols-2">
<Grid className="md:grid-cols-1 lg:grid-cols-3">
<ArticleCardIndex
description="Wallets, auth, and onchain interactions"
description="Create and manage crypto wallets"
href="/connect"
icon={ConnectIcon}
title="Connect"
title="Wallets"
/>
<ArticleCardIndex
description="Bridge and onramp tokens on any chain"
href="/pay"
icon={BridgeIcon}
title="Universal Bridge"
title="Payments"
/>
<ArticleCardIndex
description="Reliable transactions and monitoring"
href="/engine"
icon={EngineIcon}
title="Engine"
title="Transactions"
/>
<ArticleCardIndex
description="Create, deploy, and manage smart contracts"
href="/contracts"
icon={EngineIcon}
title="Contracts"
/>
<ArticleCardIndex
description="Blockchain data queries and transformations"
@@ -194,6 +200,12 @@ function LearningResourcesSection() {
icon={NebulaIcon}
title="Nebula"
/>
<ArticleCardIndex
description="Non-custodial key management service"
href="/vault"
icon={NebulaIcon}
title="Vault"
/>
</Grid>
</section>
);
@@ -1,17 +1,17 @@
import { createMetadata, DocImage } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Customize ConnectButton",
title: "thirdweb Payments - Customize ConnectButton",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Customize ConnectButton - thirdweb",
title: "thirdweb Payments - Customize ConnectButton - thirdweb",
description:
"thirdweb Universal Bridge - Customizing ConnectButton onramps and crypto purchase flows",
"thirdweb Payments - Customizing ConnectButton onramps and crypto purchase flows",
});
# Customize ConnectButton
Learn how to customize Universal Bridge within the `ConnectButton` interface. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/ConnectButtonProps).
Learn how to customize Payments within the `ConnectButton` interface. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/ConnectButtonProps).
---
@@ -39,7 +39,7 @@ You can enable users to select your token on a given chain by passing an array o
## Prefill Destination Token
In the case you want your users to purchase your token by default in your application, you can choose to pre-fill the Universal Bridge purchase flow with `prefillBuy` .
In the case you want your users to purchase your token by default in your application, you can choose to pre-fill the Payments purchase flow with `prefillBuy` .
For example, if you wanted users to only purchase Ethereum on Base network, you could do as follows:
@@ -137,7 +137,7 @@ In some cases, you may only want to show users fiat or crypto payment options fo
## Theme
You can set the theme for the Universal Bridge component, which is set to `"dark"` by default. theme can be set to either `"dark"` , `"light"` or a custom theme object.
You can set the theme for the Payments component, which is set to `"dark"` by default. theme can be set to either `"dark"` , `"light"` or a custom theme object.
We have [`lightTheme`](/references/typescript/v5/lightTheme) or [`darkTheme`](/references/typescript/v5/darkTheme) providers that you can override to kickstart customization.
@@ -1,154 +0,0 @@
import { createMetadata, DocImage } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Customize PayEmbed",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Customize PayEmbed - thirdweb",
description:
"thirdweb Universal Bridge - Customizing embeddable onramps and crypto purchase flows",
});
# PayEmbed Customization
Learn how to customize the `PayEmbed`. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/PayEmbedProps).
---
## Customize Supported Tokens
You can enable users to select your token on a given chain by passing an array of `SupportedTokens`. Note that this array overrides all default tokens listed for that chain.
```tsx
<PayEmbed
client={client}
payOptions={{
supportedTokens: {
"1": [
{
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
name: "USD Coin",
symbol: "USDC",
icon: usdcIcon,
},
],
},
}}
/>
```
---
## Prefill Destination Token
In the case you want your users to purchase your token by default in your application, you can choose to pre-fill the Pay purchase flow with `prefillBuy` .
For example, if you wanted users to only purchase Ethereum on Base network, you could do as follows:
```tsx
import { base } from "thirdweb/chains";
<PayEmbed
client={client}
payOptions={{
prefillBuy: {
token: {
address: "0x866a087038f7C12cf33EF91aC5b1AcE6Ac1DA788",
name: "Base ETH",
symbol: "ETH",
icon: "...", // optional
},
chain: base,
allowEdits: {
amount: true, // allow editing buy amount
token: false, // disable selecting buy token
chain: false, // disable selecting buy chain
},
},
}}
/>;
```
If you'd like to prefill a purchase with a native token, you can set the chain without passing a token:
```tsx
<PayEmbed
client={client}
payOptions={{
prefillBuy: {
chain: base,
},
}}
/>
```
---
## Preferred Provider
You can specify which onramp provider to present to your users. By default, we choose a recommended provider based on the location of the user, KYC status, and currency. Please make sure your set provider is [available in your country](../onramp-providers).
```tsx
<PayEmbed
client={client}
payOptions={{
preferredProvider: "COINBASE" | "STRIPE" | "TRANSAK",
}}
/>
```
---
## Disable Payment Methods
In some cases, you may only want to show users fiat or crypto payment options for your onchain goods or services. You can do this by setting either `buyWithCrypto` or `buyWithFiat` to `false`.
#### Disable Buy With Crypto
```tsx
<PayEmbed
client={client}
payOptions={{
buyWithCrypto: false,
}}
/>
```
#### Disable Buy With Fiat
```tsx
<PayEmbed
client={client}
payOptions={{
buyWithFiat: false,
}}
/>
```
---
## Theme
You can set the theme for the component, which is set to `"dark"` by default. theme can be set to either `"dark"` , `"light"` or a custom theme object.
We have [`lightTheme`](/references/typescript/v5/lightTheme) or [`darkTheme`](/references/typescript/v5/darkTheme) providers that you can override to kickstart customization.
You can refer to our [`Theme`](/references/typescript/v5/Theme) page for a full view of customizable options if you’d prefer to create a custom theme from scratch.
#### Provided Themes
```tsx
<PayEmbed theme='light' />
<PayEmbed theme='dark' />
```
#### Custom Theme
```tsx
import { darkTheme } from 'thirdweb/react';
// Using custom theme
<PayEmbed theme={darkTheme({ ... })} />
```
@@ -1,17 +1,17 @@
import { createMetadata, DocImage } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Customize sendTransaction",
title: "thirdweb Payments - Customize sendTransaction",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Customize sendTransaction - thirdweb",
title: "thirdweb Payments - Customize sendTransaction - thirdweb",
description:
"thirdweb Universal Bridge- Customizing point of sale onramps and crypto purchase flows",
"thirdweb Payments- Customizing point of sale onramps and crypto purchase flows",
});
# useSendTransaction Customization
Learn how to customize the Universal Bridge interface when executing a transaction with `useSendTransaction`. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/SendTransactionConfig).
Learn how to customize the Payments interface when executing a transaction with `useSendTransaction`. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/SendTransactionConfig).
---
+18 -18
View File
@@ -2,11 +2,11 @@ import { createMetadata, Details } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - FAQs",
title: "thirdweb Payments - FAQs",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - FAQs - thirdweb",
description: "thirdweb Universal Bridge - Frequently Asked Questions",
title: "thirdweb Payments - FAQs - thirdweb",
description: "thirdweb Payments - Frequently Asked Questions",
});
# FAQs
@@ -16,35 +16,35 @@ thirdweb does not charge any fees for buy with fiat. Any fees on onramping are b
</Details>
<Details summary="What are the fees for Buy With Crypto?">
For buying with crypto or swap fees, thirdweb charges a 0.3% protocol fee. Developers may monetize their application by charging additional fees on swap transactions. To set a fee in your project page, navigate to Universal Bridge > Settings to set this fee.
For buying with crypto or swap fees, thirdweb charges a 0.3% protocol fee. Developers may monetize their application by charging additional fees on swap transactions. To set a fee in your project page, navigate to Payments > Settings to set this fee.
</Details>
<Details summary="What does the term “network fees” refer to?">
Network fees, also referred to as miner fees, are paid to the miner for processing crypto transactions and securing the respective network. These fees do not go to thirdweb.
</Details>
<Details summary="What does Universal Bridge use to operate under the hood?">
Universal Bridge is an asset router that combines thirdweb's own onchain infrastructure with existing bridging and swapping protocols to find the optimal route for any given transaction.
<Details summary="What does Payments use to operate under the hood?">
Payments is an asset router that combines thirdweb's own onchain infrastructure with existing bridging and swapping protocols to find the optimal route for any given transaction.
This can include intents, native bridges, third party bridges, cross-chain messaging protocols, thirdweb's custom bridges, and more. All routes are monitored and planned continuously for
health, price, and speed to guarantee the best path every time.
</Details>
<Details summary="How can I support a token on Universal Bridge?">
Universal Bridge is able to support most token that meet the following criteria:
<Details summary="How can I support a token on Payments?">
Payments is able to support most token that meet the following criteria:
- The token must have sufficient liquidity
- The token must be on a [supported Universal Bridge Chain](https://thirdweb.com/chainlist?service=pay).
- The token must be on a [supported Payments Chain](https://thirdweb.com/chainlist?service=pay).
To request support for a token, please visit the Universal Bridge tab in your project dashboard > Settings > "Don't see your token listed?" component and fill out the chain and token address information.
To request support for a token, please visit the Payments tab in your project dashboard > Settings > "Don't see your token listed?" component and fill out the chain and token address information.
Universal Bridge will automatically kick off the token route discovery process. Please check back the Universal Bridge modal after 20-40 minutes for the requested token.
Payments will automatically kick off the token route discovery process. Please check back the Payments modal after 20-40 minutes for the requested token.
</Details>
<Details summary="Am I able to use Universal Bridge with other wallet infrastructure providers?">
Yes, Universal Bridge is designed to be agnostic to wallet infrastructure. You can use it with any wallet service.
<Details summary="Am I able to use Payments with other wallet infrastructure providers?">
Yes, Payments is designed to be agnostic to wallet infrastructure. You can use it with any wallet service.
</Details>
<Details summary="Does Universal Bridge support Apple Pay or Google Pay?">
<Details summary="Does Payments support Apple Pay or Google Pay?">
Apple Pay and Google Pay are supported through the onramp providers depending on region. Coinbase, Stripe, and Transak currently support Apple Pay. Transak supports Google Pay. [Learn more about the supported onramp providers.](/connect/pay/onramp-providers).
</Details>
@@ -111,15 +111,15 @@ All other tokens will require an additional Buy With Crypto step.
Users will be required to complete KYC verification for their first purchase using 'Buy with Fiat.' After that, no further KYC will be needed for future purchases on the same platform.
</Details>
<Details summary="What cryptocurrencies does Universal Bridge support?">
Universal Bridge can support any currency on our [supported chains](https://thirdweb.com/chainlist?service=pay).
<Details summary="What cryptocurrencies does Payments support?">
Payments can support any currency on our [supported chains](https://thirdweb.com/chainlist?service=pay).
</Details>
<Details summary="How are cryptocurrency prices set in thirdweb Universal Bridge?">
<Details summary="How are cryptocurrency prices set in thirdweb Payments?">
Prices are set depending on the liquidity provider used to route your transaction.
</Details>
<Details summary="Does thirdweb Universal Bridge offer fiat payouts?">
<Details summary="Does thirdweb Payments offer fiat payouts?">
Fiat payouts are not currently available.
</Details>
+3 -3
View File
@@ -4,12 +4,12 @@ import FeeShare from './assets/fee-dash.png';
# Understanding fees
Understand how protocol, developer, and onramp fees work with Universal Bridge.
Understand how protocol, developer, and onramp fees work with Payments.
### Developer Fee
To monetize applications or games, you are able to set a developer fee on each crypto-to-crypto or swap transaction. You may set this percentage
through Project > Universal Bridge > Settings.
through Project > Payments > Settings.
<DocImage src={FeeShare} alt="Developer fee share" />
@@ -49,7 +49,7 @@ Example: The user pays 10.00 USDC.
### Onramp Fee
When onramping to crypto using the Universal Bridge, a fee is charged by the third-party provider. This fee is determined by the provider and is typically a percentage
When onramping to crypto using the Payments, a fee is charged by the third-party provider. This fee is determined by the provider and is typically a percentage
of the amount being onramped. This fee may vary based on the provider and payment method used.
There are no protocol fees on onramp transactions.
+7 -7
View File
@@ -13,17 +13,17 @@ import {
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Get Started",
title: "thirdweb Payments - Get Started",
icon: "thirdweb",
},
title: "Universal Bridge Implementation Guide — thirdweb docs",
title: "Payments Implementation Guide — thirdweb docs",
description:
"Learn how to implement cross-chain payments, bridging, and onramps with the Universal Bridge SDK",
"Learn how to implement cross-chain payments, bridging, and onramps with the Payments SDK",
});
# Get Started
Learn how to integrate Universal Bridge into your application for cross-chain payments, bridging, swapping, and fiat onramps. This guide covers the core SDK modules and practical implementation examples.
Learn how to integrate Payments into your application for cross-chain payments, bridging, swapping, and fiat onramps. This guide covers the core SDK modules and practical implementation examples.
---
@@ -39,7 +39,7 @@ Learn how to integrate Universal Bridge into your application for cross-chain pa
</Step>
<Step title="Get Your Client ID">
Log in to [the thirdweb dashboard](https://thirdweb.com/team). Navigate to the **Settings** page and create an API key to get your **Client ID**. You'll need your Client ID to interact with the Universal Bridge.
Log in to [the thirdweb dashboard](https://thirdweb.com/team). Navigate to the **Settings** page and create an API key to get your **Client ID**. You'll need your Client ID to interact with the Payments.
</Step>
<Step title="Initialize the Client">
@@ -231,7 +231,7 @@ console.log(`Found ${filteredRoutes.length} routes`);
## Transaction Status Tracking
Monitor the progress of your Universal Bridge transactions:
Monitor the progress of your Payments transactions:
```typescript
import { Bridge } from "thirdweb";
@@ -264,7 +264,7 @@ switch (status.status) {
## Error Handling
Universal Bridge functions throw `ApiError` for failed requests:
Payments functions throw `ApiError` for failed requests:
```typescript
import { Bridge, ApiError } from "thirdweb";
@@ -10,17 +10,17 @@ import OnrampStepOne from "../../assets/avax-to-usd.png";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Cross-Chain Swapping",
title: "thirdweb Payments - Cross-Chain Swapping",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Cross-Chain Swapping | thirdweb",
title: "thirdweb Payments - Cross-Chain Swapping | thirdweb",
description:
"Learn how to build a custom cross-chain swapping experience with thirdweb Universal Bridge.",
"Learn how to build a custom cross-chain swapping experience with thirdweb Payments.",
});
# Leverage Cross-Chain Swaps with Universal Bridge
# Leverage Cross-Chain Swaps with Payments
Learn how to enable your users to swap from any asset to any other with thirdweb's Universal Bridge.
Learn how to enable your users to swap from any asset to any other with thirdweb's Payments.
In this guide, we'll show you how to purchase 10 USDC on Optimism in Typescript.
@@ -13,15 +13,15 @@ import {
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - NFT Checkout",
title: "thirdweb Payments - NFT Checkout",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - NFT Checkout | thirdweb",
title: "thirdweb Payments - NFT Checkout | thirdweb",
description:
"Learn how to build a complete NFT checkout flow with in-app wallet login, onramp, and minting using Universal Bridge.",
"Learn how to build a complete NFT checkout flow with in-app wallet login, onramp, and minting using Payments.",
});
# NFT Checkout with Universal Bridge
# NFT Checkout with Payments
Learn how to build a seamless NFT checkout experience that combines in-app wallet authentication, fiat-to-crypto onramps, and NFT minting. This tutorial demonstrates a complete user flow where users can purchase NFTs directly with fiat currency.
@@ -692,7 +692,7 @@ function App() {
<NFTCheckout />
<footer className="text-center mt-8 text-sm text-gray-500">
<p>Powered by thirdweb Universal Bridge</p>
<p>Powered by thirdweb Payments</p>
</footer>
</div>
</div>
@@ -13,19 +13,19 @@ import {
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Onramp Integration",
title: "thirdweb Payments - Onramp Integration",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Onramp Integration | thirdweb",
title: "thirdweb Payments - Onramp Integration | thirdweb",
description:
"Learn how to integrate fiat-to-crypto onramps using Stripe, Coinbase, and Transak with Universal Bridge.",
"Learn how to integrate fiat-to-crypto onramps using Stripe, Coinbase, and Transak with Payments.",
});
# Fiat-to-Crypto Onramp Integration
Learn how to integrate seamless fiat-to-crypto onramps using Universal Bridge. This guide covers integration with Stripe, Coinbase, and Transak providers, enabling your users to purchase crypto directly with fiat currency.
Learn how to integrate seamless fiat-to-crypto onramps using Payments. This guide covers integration with Stripe, Coinbase, and Transak providers, enabling your users to purchase crypto directly with fiat currency.
Universal Bridge's onramp functionality provides a unified interface across multiple providers, automatic routing to the best rates, and comprehensive status tracking.
Payments's onramp functionality provides a unified interface across multiple providers, automatic routing to the best rates, and comprehensive status tracking.
---
@@ -196,4 +196,4 @@ import { Bridge } from "thirdweb";
- **[Onramp Providers](/pay/onramp-providers)** - Detailed provider comparison and features
- **[Webhooks](/pay/webhooks)** - Set up real-time onramp status notifications
- **[Testing](/pay/testing-pay)** - Test onramp flows in development mode
- **[Universal Bridge API](https://bridge.thirdweb.com/reference)** - Complete API reference
- **[Payments API](https://bridge.thirdweb.com/reference)** - Complete API reference
@@ -9,17 +9,17 @@ import {
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Smart Accounts",
title: "thirdweb Payments - Smart Accounts",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Smart Accounts | thirdweb",
title: "thirdweb Payments - Smart Accounts | thirdweb",
description:
"Learn how to use EIP7702 smart accounts with batched transactions for seamless Universal Bridge experiences.",
"Learn how to use EIP7702 smart accounts with batched transactions for seamless Payments experiences.",
});
# Smart Accounts with Universal Bridge
# Smart Accounts with Payments
Learn how to leverage EIP7702 smart accounts to batch Universal Bridge transactions for optimal user experience. This approach enables gasless transactions, batched execution, and seamless cross-chain operations.
Learn how to leverage EIP7702 smart accounts to batch Payments transactions for optimal user experience. This approach enables gasless transactions, batched execution, and seamless cross-chain operations.
Smart accounts with EIP7702 allow you to upgrade any EOA (Externally Owned Account) into a smart account without code changes, enabling advanced features like transaction batching and gas sponsorship.
@@ -67,7 +67,7 @@ const account = await wallet.connect({
</Step>
<Step title='Prepare Batched Bridge Transactions'>
Use Universal Bridge to prepare transactions, then batch them per step (since each step may be on a different chain):
Use Payments to prepare transactions, then batch them per step (since each step may be on a different chain):
```typescript
import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb";
@@ -86,7 +86,7 @@ const bridgePreparation = await Bridge.Buy.prepare({
```
<Callout variant="important">
**Key Rule**: You can only batch transactions that are on the same chain. Since Universal Bridge steps may span multiple chains, you must batch transactions within each step separately, not across steps.
**Key Rule**: You can only batch transactions that are on the same chain. Since Payments steps may span multiple chains, you must batch transactions within each step separately, not across steps.
</Callout>
</Step>
@@ -151,4 +151,4 @@ for (let i = 0; i < bridgePreparation.steps.length; i++) {
- **[EIP7702 Documentation](/connect/account-abstraction/overview)** - Learn more about smart account features
- **[Batching Transactions](/typescript/v5/account-abstraction/batching-transactions)** - Explore advanced batching patterns
- **[Gas Sponsorship](/connect/account-abstraction/sponsoring-gas)** - Set up custom gas sponsorship rules
- **[Universal Bridge API](/pay/webhooks)** - Monitor cross-chain operations with webhooks
- **[Payments API](/pay/webhooks)** - Monitor cross-chain operations with webhooks
+3 -3
View File
@@ -12,10 +12,10 @@ export default async function Layout(props: { children: React.ReactNode }) {
export const metadata = createMetadata({
description:
"thirdweb Universal Bridge documentation. Learn how to use the thirdweb Universal Bridge to send and receive tokens across different blockchains.",
"thirdweb Payments documentation. Learn how to use the thirdweb Payments to send and receive tokens across different blockchains.",
image: {
icon: "nebula",
title: "thirdweb Universal Bridge Docs",
title: "thirdweb Payments Docs",
},
title: "thirdweb Universal Bridge Documentation",
title: "thirdweb Payments Documentation",
});
@@ -2,10 +2,10 @@ import { createMetadata } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Onramp Providers",
title: "thirdweb Payments - Onramp Providers",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Onramp Providers | thirdweb",
title: "thirdweb Payments - Onramp Providers | thirdweb",
description:
"Integrate onramp providers for any onchain transaction and set preferred providers",
});
@@ -13,7 +13,7 @@ export const metadata = createMetadata({
# Onramp Providers
**Onramp Providers** are services that allow users to buy crypto with fiat. You can set preferred providers for your application by using the [`preferredProvider`](/connect/pay/customization/payembed#preferred-provider) parameter in the `PayEmbed` component.
Universal Bridge supports the following onramp providers:
Payments supports the following onramp providers:
- **Coinbase**
- **Transak**
+9 -7
View File
@@ -5,17 +5,19 @@ import {RocketIcon, ArrowLeftRightIcon, WalletIcon, PencilIcon, ShieldCheckIcon,
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge",
title: "thirdweb Payments",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge Docs: Web3 Payments, On-ramping, bridging & swapping",
title: "thirdweb Payments Docs: Web3 Payments, On-ramping, bridging & swapping",
description:
"Learn everything about thirdweb's web3 payments solution, Universal Bridge. Technical docs on onramping, bridging + swapping.",
"Learn everything about thirdweb's web3 payments solution, Payments. Technical docs on onramping, bridging + swapping.",
});
# Universal Bridge
# Payments
Universal Bridge allows you to create both simple and advanced payment flows for bridging, swapping, onramping, and peer-to-peer purchases. It's been used to drive millions in NFT sales, bridge native tokens to brand new chains, send stablecoins between users, and more. To get started check out the [SDK functions](https://portal.thirdweb.com/typescript/v5/buy/quote), [API reference](https://bridge.thirdweb.com/reference), or [playground](https://playground.thirdweb.com/connect/pay).
Payments allows you to create both simple and advanced payment flows for bridging, swapping, onramping, and peer-to-peer purchases. It's been used to drive millions in NFT sales, bridge native tokens to brand new chains, send stablecoins between users, and more.
To get started check out the [SDK functions](https://portal.thirdweb.com/typescript/v5/buy/quote), [API reference](https://bridge.thirdweb.com/reference), or [playground](https://playground.thirdweb.com/connect/pay).
<DocImage src={PayOverviewImage} />
@@ -64,7 +66,7 @@ Universal Bridge allows you to create both simple and advanced payment flows for
## SDK Modules
The Universal Bridge SDK is organized into several modules, each handling specific functionality:
The Payments SDK is organized into several modules, each handling specific functionality:
| Module | Purpose | Key Functions |
|--------|---------|---------------|
@@ -77,7 +79,7 @@ The Universal Bridge SDK is organized into several modules, each handling specif
## Supported Chains
Universal Bridge is supported on select EVM compatible chains. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist?service=pay).
Payments is supported on select EVM compatible chains. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist?service=pay).
<DocImage src={SupportedChains} />

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