Compare commits

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

- NeoX Mainnet 47763
- NeoX Testnt 12227332

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

- Onyx Mainnet
- zkCandy Mainnet

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

<!-- end pr-codex -->
2025-05-09 11:18:35 +00:00
Joaquim VergesandGitHub 49479d40b0 [Dashboard] Replace serverThirdwebClient with getUserThirdwebClient (#6990) 2025-05-09 00:23:29 -07:00
Joaquim VergesGitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3d7c2d4f67 [Auth] Replace mainnet with ANVIL_CHAIN in verifyTypedData tests (#6985)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-08 22:01:46 -07:00
Manan TankandGitHub 5b40f76b4d Dashboard: thirdweb client refactor (#6987) 2025-05-09 03:15:41 +02:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ef1550cff5 Version Packages (#6986)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-09 02:34:56 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b1719fd4ff chore(deps): bump github/codeql-action from 3.28.16 to 3.28.17 (#6940)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 02:27:42 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
598d4006d9 chore(deps): bump changesets/action from 1.4.10 to 1.5.0 (#6939)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 02:27:23 +02:00
Jonas DanielsandGitHub bc75ae6826 Handle JWT auth method explicitly in service-utils (#6984) 2025-05-09 02:25:42 +02:00
Joe-ThirdwebGitHubsaminagraphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
8fb8a7530c [Portal] Block native to thridweb guide (#6977)
Signed-off-by: samina <[email protected]>
Co-authored-by: samina <[email protected]>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-08 14:37:28 -07:00
Joaquim VergesandGitHub 106ca3cd01 [Dashboard] more robust engine cloud FTUX (#6980) 2025-05-08 14:29:14 -07:00
Jonas DanielsandGitHub 76a718c8f5 Force auth token usage for storage uploads (#6973) 2025-05-08 22:44:04 +02:00
GiselleNessi 1e924e037f updated alert description in team overview dashboard page (#6979)
<!--

## 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 content in the `page.tsx` file related to the `TeamProjectsPage` component and modifying the alert message to provide clearer information about managing engines within projects.

### Detailed summary
- Reordered the placement of the `<TeamProjectsPage>` component.
- Updated the alert message to clarify that engines, contracts, project settings, and more are managed within projects.

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

<!-- end pr-codex -->
2025-05-08 16:06:27 +00:00
Joaquim VergesandGitHub 99d2497b51 [Dashboard] Add Indonesia to SMS country select options (#6978) 2025-05-08 01:43:23 -07:00
Joaquim VergesandGitHub a5a1d17f3b [Dashboard] Add info alert about Engines moving to projects (#6976) 2025-05-07 20:47:18 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
62c179d775 Version Packages (#6975)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-07 18:44:36 -07:00
Joaquim VergesandGitHub e96ebba2cb [SDK] Add 7702 support + minimal account implementation (#6874) 2025-05-07 18:09:14 -07:00
879 changed files with 12187 additions and 5056 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ We use [Turborepo](https://turbo.build/repo/docs) to manage the repository, and
We use [pnpm](https://pnpm.io) for package management across the repo. `pnpm` is similar to `npm` or `yarn` but with more efficient disk space usage.
**With the v5 SDK, we've consolidated everything into a single project at [/packages/thirdweb](./packages/thirdweb). You can still find the legacy packages at [/legacy_packages](./legacy_packages).**
**With the v5 SDK, we've consolidated everything into a single project at [/packages/thirdweb](../packages/thirdweb). You can still find the legacy packages at [/legacy_packages](../legacy_packages).**
This single package provides a performant & lightweight SDK to interact with any EVM chain across Node, React, and React Native. Learn more about how to use the thirdweb SDK in our [documentation](https://portal.thirdweb.com/typescript/v5).
+3 -3
View File
@@ -46,7 +46,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -59,7 +59,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -72,4 +72,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
- name: Create release Pull Request or publish to NPM
id: changesets
uses: changesets/action@06245a4e0a36c064a573d4150030f5ec548e4fcc #v1.4.0
uses: changesets/action@746c25e23caa47dceb6a48ee85b4cbc5a9f5f293 #v1.4.0
with:
publish: pnpm release
version: pnpm version-packages
+1
View File
@@ -123,6 +123,7 @@ const baseNextConfig: NextConfig = {
webpackBuildWorker: true,
webpackMemoryOptimizations: true,
serverSourceMaps: false,
taint: true,
},
serverExternalPackages: ["pino-pretty"],
async headers() {
+2 -2
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function acceptInvite(options: {
teamId: string;
@@ -17,7 +17,7 @@ export async function acceptInvite(options: {
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${options.teamId}/invites/${options.inviteId}/accept`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/invites/${options.inviteId}/accept`,
{
method: "POST",
headers: {
+2 -2
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function reSubscribePlan(options: {
teamId: string;
@@ -14,7 +14,7 @@ export async function reSubscribePlan(options: {
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${options.teamId}/checkout/resubscribe-plan`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/checkout/resubscribe-plan`,
{
method: "PUT",
headers: {
+13 -10
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function confirmEmailWithOTP(otp: string) {
const token = await getAuthToken();
@@ -12,16 +12,19 @@ export async function confirmEmailWithOTP(otp: string) {
};
}
const res = await fetch(`${API_SERVER_URL}/v1/account/confirmEmail`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/account/confirmEmail`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
confirmationToken: otp,
}),
},
body: JSON.stringify({
confirmationToken: otp,
}),
});
);
if (!res.ok) {
const json = await res.json();
@@ -2,7 +2,8 @@
import { defineDashboardChain } from "lib/defineDashboardChain";
import { ZERO_ADDRESS, isAddress, toTokens } from "thirdweb";
import { getWalletBalance } from "thirdweb/wallets";
import { getUserThirdwebClient } from "../../app/(app)/api/lib/getAuthToken";
import { MORALIS_API_KEY } from "../constants/server-envs";
import { serverThirdwebClient } from "../constants/thirdweb-client.server";
type BalanceQueryResponse = Array<{
balance: string;
@@ -23,7 +24,6 @@ export async function getTokenBalancesFromMoralis(params: {
error: string;
}
> {
const client = await getUserThirdwebClient();
const { contractAddress, chainId } = params;
if (!isAddress(contractAddress)) {
@@ -39,7 +39,7 @@ export async function getTokenBalancesFromMoralis(params: {
const balance = await getWalletBalance({
address: contractAddress,
chain,
client,
client: serverThirdwebClient,
});
return [
{
@@ -61,7 +61,7 @@ export async function getTokenBalancesFromMoralis(params: {
const resp = await fetch(tokenBalanceEndpoint, {
method: "GET",
headers: {
"x-api-key": process.env.MORALIS_API_KEY || "",
"x-api-key": MORALIS_API_KEY,
},
});
@@ -12,7 +12,8 @@ import type { WalletNFT } from "lib/wallet/nfts/types";
import { getVercelEnv } from "../../lib/vercel-utils";
import { isAlchemySupported } from "../../lib/wallet/nfts/isAlchemySupported";
import { isMoralisSupported } from "../../lib/wallet/nfts/isMoralisSupported";
import { DASHBOARD_THIRDWEB_CLIENT_ID } from "../constants/env";
import { NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID } from "../constants/public-envs";
import { MORALIS_API_KEY } from "../constants/server-envs";
type WalletNFTApiReturn =
| { result: WalletNFT[]; error?: undefined }
@@ -59,13 +60,13 @@ export async function getWalletNFTs(params: {
}
}
if (isMoralisSupported(chainId) && process.env.MORALIS_API_KEY) {
if (isMoralisSupported(chainId) && MORALIS_API_KEY) {
const url = generateMoralisUrl({ chainId, owner });
const response = await fetch(url, {
method: "GET",
headers: {
"X-API-Key": process.env.MORALIS_API_KEY,
"X-API-Key": MORALIS_API_KEY,
},
next: {
revalidate: 10, // cache for 10 seconds
@@ -148,7 +149,7 @@ async function getWalletNFTsFromInsight(params: {
const response = await fetch(url, {
headers: {
"x-client-id": DASHBOARD_THIRDWEB_CLIENT_ID,
"x-client-id": NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
},
});
+8 -13
View File
@@ -1,7 +1,11 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL, THIRDWEB_ENGINE_CLOUD_URL } from "../constants/env";
import {
NEXT_PUBLIC_ENGINE_CLOUD_URL,
NEXT_PUBLIC_THIRDWEB_API_HOST,
} from "../constants/public-envs";
import { ANALYTICS_SERVICE_URL } from "../constants/server-envs";
type ProxyActionParams = {
pathname: string;
@@ -76,22 +80,13 @@ async function proxy<T>(
}
export async function apiServerProxy<T>(params: ProxyActionParams) {
return proxy<T>(API_SERVER_URL, params);
return proxy<T>(NEXT_PUBLIC_THIRDWEB_API_HOST, params);
}
export async function engineCloudProxy<T>(params: ProxyActionParams) {
return proxy<T>(THIRDWEB_ENGINE_CLOUD_URL, params);
}
export async function payServerProxy<T>(params: ProxyActionParams) {
return proxy<T>(
process.env.NEXT_PUBLIC_PAY_URL
? `https://${process.env.NEXT_PUBLIC_PAY_URL}`
: "https://pay.thirdweb-dev.com",
params,
);
return proxy<T>(NEXT_PUBLIC_ENGINE_CLOUD_URL, params);
}
export async function analyticsServerProxy<T>(params: ProxyActionParams) {
return proxy<T>(process.env.ANALYTICS_SERVICE_URL || "", params);
return proxy<T>(ANALYTICS_SERVICE_URL, params);
}
+14 -11
View File
@@ -1,7 +1,7 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function sendTeamInvites(options: {
teamId: string;
@@ -40,17 +40,20 @@ async function sendInvite(
invite: { email: string; role: "OWNER" | "MEMBER" },
token: string,
) {
const res = await fetch(`${API_SERVER_URL}/v1/teams/${teamId}/invites`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamId}/invites`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
inviteEmail: invite.email,
inviteRole: invite.role,
}),
},
body: JSON.stringify({
inviteEmail: invite.email,
inviteRole: invite.role,
}),
});
);
if (!res.ok) {
const errorMessage = await res.text();
@@ -2,13 +2,12 @@ import "server-only";
import Stripe from "stripe";
import type { Team } from "../api/team";
import { STRIPE_SECRET_KEY } from "../constants/server-envs";
let existingStripe: Stripe | undefined;
function getStripe() {
if (!existingStripe) {
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
if (!STRIPE_SECRET_KEY) {
throw new Error("STRIPE_SECRET_KEY is not set");
}
@@ -1,6 +1,6 @@
"use server";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export async function updateAccount(values: {
name?: string;
@@ -13,7 +13,7 @@ export async function updateAccount(values: {
throw new Error("No Auth token");
}
const res = await fetch(`${API_SERVER_URL}/v1/account`, {
const res = await fetch(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/account`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
+75 -11
View File
@@ -6,11 +6,14 @@ import type {
InAppWalletStats,
RpcMethodStats,
TransactionStats,
UniversalBridgeStats,
UniversalBridgeWalletStats,
UserOpStats,
WalletStats,
WalletUserStats,
} from "types/analytics";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { ANALYTICS_SERVICE_URL } from "../constants/server-envs";
import { getChains } from "./chain";
async function fetchAnalytics(
@@ -29,34 +32,38 @@ async function fetchAnalytics(
}
// create a new URL object for the analytics server
const ANALYTICS_SERVICE_URL = new URL(
process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
const analyticsServiceUrl = new URL(
ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
);
ANALYTICS_SERVICE_URL.pathname = pathname;
analyticsServiceUrl.pathname = pathname;
for (const param of searchParams?.split("&") || []) {
const [key, value] = param.split("=");
if (!key || !value) {
throw new Error("Invalid input, no key or value provided");
}
ANALYTICS_SERVICE_URL.searchParams.append(
analyticsServiceUrl.searchParams.append(
decodeURIComponent(key),
decodeURIComponent(value),
);
}
// client id DEBUG OVERRIDE
// ANALYTICS_SERVICE_URL.searchParams.delete("clientId");
// ANALYTICS_SERVICE_URL.searchParams.delete("accountId");
// ANALYTICS_SERVICE_URL.searchParams.append(
// "clientId",
// "...",
// analyticsServiceUrl.searchParams.delete("projectId");
// analyticsServiceUrl.searchParams.delete("teamId");
// analyticsServiceUrl.searchParams.append(
// "teamId",
// "team_cm0lde33r02344w129k5hm2xz",
// );
// analyticsServiceUrl.searchParams.append(
// "projectId",
// "prj_cm4rqwx9b002qrnsnr37wqpo6",
// );
return fetch(ANALYTICS_SERVICE_URL, {
return fetch(analyticsServiceUrl, {
...init,
headers: {
"content-type": "application/json",
...init?.headers,
authorization: `Bearer ${token}`,
},
});
}
@@ -367,3 +374,60 @@ export async function getEcosystemWalletUsage(args: {
return json.data as EcosystemWalletStats[];
}
export async function getUniversalBridgeUsage(args: {
teamId: string;
projectId?: string;
from?: Date;
to?: Date;
period?: "day" | "week" | "month" | "year" | "all";
}) {
const searchParams = buildSearchParams(args);
const res = await fetchAnalytics(`v2/universal?${searchParams.toString()}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (res?.status !== 200) {
const reason = await res?.text();
console.error(
`Failed to fetch universal bridge stats: ${res?.status} - ${res.statusText} - ${reason}`,
);
return [];
}
const json = await res.json();
return json.data as UniversalBridgeStats[];
}
export async function getUniversalBridgeWalletUsage(args: {
teamId: string;
projectId: string;
from?: Date;
to?: Date;
period?: "day" | "week" | "month" | "year" | "all";
}) {
const searchParams = buildSearchParams(args);
const res = await fetchAnalytics(
`v2/universal/wallets?${searchParams.toString()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
if (res?.status !== 200) {
const reason = await res?.text();
console.error(
`Failed to fetch universal bridge wallet stats: ${res?.status} - ${res.statusText} - ${reason}`,
);
return null;
}
const json = await res.json();
return json.data as UniversalBridgeWalletStats[];
}
+16 -12
View File
@@ -1,21 +1,25 @@
import "server-only";
import type { ChainMetadata } from "thirdweb/chains";
import type { ChainService } from "../../app/(app)/(dashboard)/(chain)/types/chain";
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { API_SERVER_SECRET } from "../constants/server-envs";
export async function getGasSponsoredChains() {
if (!THIRDWEB_API_SECRET) {
if (!API_SERVER_SECRET) {
throw new Error("API_SERVER_SECRET is not set");
}
const res = await fetch(`${API_SERVER_URL}/v1/chains/gas-sponsored`, {
headers: {
"Content-Type": "application/json",
"x-service-api-key": THIRDWEB_API_SECRET,
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/chains/gas-sponsored`,
{
headers: {
"Content-Type": "application/json",
"x-service-api-key": API_SERVER_SECRET,
},
next: {
revalidate: 15 * 60, //15 minutes
},
},
next: {
revalidate: 15 * 60, //15 minutes
},
});
);
if (!res.ok) {
console.error(
@@ -37,7 +41,7 @@ export async function getGasSponsoredChains() {
export function getChains() {
return fetch(
`${API_SERVER_URL}/v1/chains`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/chains`,
// revalidate every 60 minutes
{ next: { revalidate: 60 * 60 } },
).then((res) => res.json()) as Promise<{ data: ChainMetadata[] }>;
@@ -45,7 +49,7 @@ export function getChains() {
export function getChainServices() {
return fetch(
`${API_SERVER_URL}/v1/chains/services`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/chains/services`,
// revalidate every 60 minutes
{ next: { revalidate: 60 * 60 } },
).then((res) => res.json()) as Promise<{
+8 -5
View File
@@ -1,5 +1,5 @@
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export type LinkedWallet = {
createdAt: string;
@@ -14,11 +14,14 @@ export async function getLinkedWallets() {
return null;
}
const res = await fetch(`${API_SERVER_URL}/v1/account/wallets`, {
headers: {
Authorization: `Bearer ${token}`,
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/account/wallets`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
});
);
if (res.ok) {
const json = (await res.json()) as {
+3 -3
View File
@@ -1,5 +1,5 @@
import "server-only";
import { API_SERVER_URL } from "@/constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import type { ProjectResponse } from "@thirdweb-dev/service-utils";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
@@ -13,7 +13,7 @@ export async function getProjects(teamSlug: string) {
}
const teamsRes = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/projects`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/projects`,
{
headers: {
Authorization: `Bearer ${token}`,
@@ -34,7 +34,7 @@ export async function getProject(teamSlug: string, projectSlug: string) {
}
const teamsRes = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/projects/${projectSlug}`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/projects/${projectSlug}`,
{
headers: {
Authorization: `Bearer ${token}`,
+15 -10
View File
@@ -1,4 +1,6 @@
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "../constants/env";
import "server-only";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import { API_SERVER_SECRET } from "../constants/server-envs";
export type SMSCountryTiers = {
tier1: string[];
@@ -9,18 +11,21 @@ export type SMSCountryTiers = {
};
export async function getSMSCountryTiers() {
if (!THIRDWEB_API_SECRET) {
if (!API_SERVER_SECRET) {
throw new Error("API_SERVER_SECRET is not set");
}
const res = await fetch(`${API_SERVER_URL}/v1/sms/list-country-tiers`, {
headers: {
"Content-Type": "application/json",
"x-service-api-key": THIRDWEB_API_SECRET,
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/sms/list-country-tiers`,
{
headers: {
"Content-Type": "application/json",
"x-service-api-key": API_SERVER_SECRET,
},
next: {
revalidate: 15 * 60, //15 minutes
},
},
next: {
revalidate: 15 * 60, //15 minutes
},
});
);
if (!res.ok) {
console.error(
+2 -2
View File
@@ -1,5 +1,5 @@
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export type TeamInvite = {
id: string;
@@ -25,7 +25,7 @@ export async function getTeamInvites(
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamId}/invites?skip=${options.start}&take=${options.count}`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamId}/invites?skip=${options.start}&take=${options.count}`,
{
headers: {
Authorization: `Bearer ${authToken}`,
+3 -3
View File
@@ -1,5 +1,5 @@
import "server-only";
import { API_SERVER_URL } from "@/constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
const TeamAccountRole = {
@@ -33,7 +33,7 @@ export async function getMembers(teamSlug: string) {
}
const teamsRes = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/members`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/members`,
{
headers: {
Authorization: `Bearer ${token}`,
@@ -56,7 +56,7 @@ export async function getMemberById(teamSlug: string, memberId: string) {
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/members/${memberId}`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/members/${memberId}`,
{
headers: {
Authorization: `Bearer ${token}`,
@@ -1,5 +1,5 @@
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
import type { ProductSKU } from "../lib/billing";
type InvoiceLine = {
@@ -47,7 +47,7 @@ export async function getTeamSubscriptions(slug: string) {
}
const teamRes = await fetch(
`${API_SERVER_URL}/v1/teams/${slug}/subscriptions`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${slug}/subscriptions`,
{
headers: {
Authorization: `Bearer ${token}`,
+18 -11
View File
@@ -1,5 +1,6 @@
import "server-only";
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "@/constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { API_SERVER_SECRET } from "@/constants/server-envs";
import type { TeamResponse } from "@thirdweb-dev/service-utils";
import { cookies } from "next/headers";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
@@ -14,11 +15,14 @@ export async function getTeamBySlug(slug: string) {
return null;
}
const teamRes = await fetch(`${API_SERVER_URL}/v1/teams/${slug}`, {
headers: {
Authorization: `Bearer ${token}`,
const teamRes = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${slug}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
});
);
if (teamRes.ok) {
return (await teamRes.json())?.result as Team;
}
@@ -26,11 +30,14 @@ export async function getTeamBySlug(slug: string) {
}
export async function service_getTeamBySlug(slug: string) {
const teamRes = await fetch(`${API_SERVER_URL}/v1/teams/${slug}`, {
headers: {
"x-service-api-key": THIRDWEB_API_SECRET,
const teamRes = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${slug}`,
{
headers: {
"x-service-api-key": API_SERVER_SECRET,
},
},
});
);
if (teamRes.ok) {
return (await teamRes.json())?.result as Team;
@@ -49,7 +56,7 @@ export async function getTeams() {
return null;
}
const teamsRes = await fetch(`${API_SERVER_URL}/v1/teams`, {
const teamsRes = await fetch(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams`, {
headers: {
Authorization: `Bearer ${token}`,
},
@@ -66,7 +73,7 @@ export async function getDefaultTeam() {
return null;
}
const res = await fetch(`${API_SERVER_URL}/v1/teams/~`, {
const res = await fetch(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/~`, {
headers: {
Authorization: `Bearer ${token}`,
},
@@ -0,0 +1 @@
export const UB_BASE_URL = process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST;
@@ -1,7 +1,8 @@
"use server";
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
import { NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST } from "../../constants/public-envs";
const UB_BASE_URL = process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST;
const UB_BASE_URL = NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST;
type Webhook = {
url: string;
@@ -15,13 +16,15 @@ type Webhook = {
export async function getWebhooks(props: {
clientId: string;
teamId: string;
}) {
const authToken = await getAuthToken();
const res = await fetch(`${UB_BASE_URL}/v1/developer/webhooks`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-client-id-override": props.clientId,
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
});
@@ -37,12 +40,20 @@ export async function getWebhooks(props: {
export async function createWebhook(props: {
clientId: string;
teamId: string;
version?: number;
url: string;
label: string;
secret?: string;
}) {
const authToken = await getAuthToken();
console.log(
"UB_BASE_URL",
UB_BASE_URL,
props.clientId,
props.teamId,
authToken,
);
const res = await fetch(`${UB_BASE_URL}/v1/developer/webhooks`, {
method: "POST",
body: JSON.stringify({
@@ -53,7 +64,8 @@ export async function createWebhook(props: {
}),
headers: {
"Content-Type": "application/json",
"x-client-id-override": props.clientId,
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
});
@@ -68,6 +80,7 @@ export async function createWebhook(props: {
export async function deleteWebhook(props: {
clientId: string;
teamId: string;
webhookId: string;
}) {
const authToken = await getAuthToken();
@@ -77,7 +90,8 @@ export async function deleteWebhook(props: {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"x-client-id-override": props.clientId,
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
},
@@ -108,7 +122,7 @@ export async function getFees(props: {
headers: {
"Content-Type": "application/json",
"x-team-id": props.teamId,
"x-client-id-override": props.clientId,
"x-client-id": props.clientId,
Authorization: `Bearer ${authToken}`,
},
});
@@ -133,8 +147,8 @@ export async function updateFee(props: {
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-client-id-override": props.clientId,
"x-team-id": props.teamId,
"x-client-id": props.clientId,
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify({
@@ -150,3 +164,87 @@ export async function updateFee(props: {
return;
}
export type PaymentsResponse = {
data: Payment[];
meta: {
totalCount: number;
};
};
export type Payment = {
id: string;
blockNumber?: bigint;
transactionId: string;
clientId: string;
sender: string;
receiver: string;
developerFeeRecipient: string;
developerFeeBps: number;
transactions: Array<{
chainId: number;
transactionHash: string;
}>;
status: "PENDING" | "COMPLETED" | "FAILED" | "NOT_FOUND";
type: "buy" | "sell" | "transfer";
originAmount: bigint;
destinationAmount: bigint;
purchaseData: unknown;
originToken: {
address: string;
symbol: string;
decimals: number;
chainId: number;
};
destinationToken: {
address: string;
symbol: string;
decimals: number;
chainId: number;
};
createdAt: string;
};
export async function getPayments(props: {
clientId: string;
teamId: string;
limit?: number;
offset?: number;
}) {
const authToken = await getAuthToken();
// Build URL with query parameters if provided
let url = `${UB_BASE_URL}/v1/developer/payments`;
const queryParams = new URLSearchParams();
if (props.limit) {
queryParams.append("limit", props.limit.toString());
}
if (props.offset) {
queryParams.append("offset", props.offset.toString());
}
// Append query params to URL if any exist
const queryString = queryParams.toString();
if (queryString) {
url = `${url}?${queryString}`;
}
const res = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
Authorization: `Bearer ${authToken}`,
},
});
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
const json = await res.json();
return json as PaymentsResponse;
}
@@ -0,0 +1,39 @@
"use server";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { UB_BASE_URL } from "./constants";
export type TokenMetadata = {
name: string;
symbol: string;
address: string;
decimals: number;
chainId: number;
iconUri?: string;
};
export async function getUniversalBridgeTokens(props: {
chainId?: number;
}) {
const url = new URL(`${UB_BASE_URL}/v1/tokens`);
if (props.chainId) {
url.searchParams.append("chainId", String(props.chainId));
}
url.searchParams.append("limit", "1000");
const res = await fetch(url.toString(), {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
} as Record<string, string>,
});
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
const json = await res.json();
return json.data as Array<TokenMetadata>;
}
+3 -2
View File
@@ -1,5 +1,6 @@
import "server-only";
import { unstable_cache } from "next/cache";
import { ANALYTICS_SERVICE_URL } from "../../constants/server-envs";
export type RPCUsageDataItem = {
date: string;
@@ -16,7 +17,7 @@ export const fetchRPCUsage = unstable_cache(
to: string;
period: "day" | "week" | "month" | "year" | "all";
}) => {
const analyticsEndpoint = process.env.ANALYTICS_SERVICE_URL as string;
const analyticsEndpoint = ANALYTICS_SERVICE_URL;
const url = new URL(`${analyticsEndpoint}/v2/rpc/usage-types`);
url.searchParams.set("teamId", params.teamId);
if (params.projectId) {
@@ -79,7 +80,7 @@ export const getLast24HoursRPCUsage = unstable_cache(
projectId?: string;
authToken: string;
}) => {
const analyticsEndpoint = process.env.ANALYTICS_SERVICE_URL as string;
const analyticsEndpoint = ANALYTICS_SERVICE_URL;
const url = new URL(`${analyticsEndpoint}/v2/rpc/24-hours`);
url.searchParams.set("teamId", params.teamId);
if (params.projectId) {
+3 -3
View File
@@ -2,7 +2,7 @@
import "server-only";
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
export type VerifiedDomainResponse =
| {
@@ -27,7 +27,7 @@ export async function checkDomainVerification(
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamIdOrSlug}/verified-domain`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamIdOrSlug}/verified-domain`,
{
headers: {
Authorization: `Bearer ${token}`,
@@ -54,7 +54,7 @@ export async function createDomainVerification(
}
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamIdOrSlug}/verified-domain`,
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamIdOrSlug}/verified-domain`,
{
method: "POST",
body: JSON.stringify({ domain }),
@@ -71,7 +71,7 @@ export function MultiNetworkSelector(props: {
return false;
}
if (Number.isInteger(Number.parseInt(searchValue))) {
if (Number.isInteger(Number(searchValue))) {
return String(chain.chainId).startsWith(searchValue);
}
return chain.name.toLowerCase().includes(searchValue.toLowerCase());
@@ -144,18 +144,26 @@ export function SingleNetworkSelector(props: {
side?: "left" | "right" | "top" | "bottom";
disableChainId?: boolean;
align?: "center" | "start" | "end";
disableTestnets?: boolean;
placeholder?: string;
client: ThirdwebClient;
}) {
const { allChains, idToChain } = useAllChainsData();
const chainsToShow = useMemo(() => {
if (!props.chainIds) {
return allChains;
let chains = allChains;
if (props.disableTestnets) {
chains = chains.filter((chain) => !chain.testnet);
}
const chainIdSet = new Set(props.chainIds);
return allChains.filter((chain) => chainIdSet.has(chain.chainId));
}, [allChains, props.chainIds]);
if (props.chainIds) {
const chainIdSet = new Set(props.chainIds);
chains = chains.filter((chain) => chainIdSet.has(chain.chainId));
}
return chains;
}, [allChains, props.chainIds, props.disableTestnets]);
const options = useMemo(() => {
return chainsToShow.map((chain) => {
@@ -173,7 +181,7 @@ export function SingleNetworkSelector(props: {
return false;
}
if (Number.isInteger(Number.parseInt(searchValue))) {
if (Number.isInteger(Number(searchValue))) {
return String(chain.chainId).startsWith(searchValue);
}
return chain.name.toLowerCase().includes(searchValue.toLowerCase());
@@ -218,6 +226,7 @@ export function SingleNetworkSelector(props: {
<SelectWithSearch
searchPlaceholder="Search by Name or Chain ID"
value={String(props.chainId)}
showCheck={false}
options={options}
onValueChange={(chainId) => {
props.onChange(Number(chainId));
@@ -137,7 +137,7 @@ function RenderSidebarGroup(props: {
}
if ("separator" in link) {
return <SidebarSeparator className="my-1" />;
return <SidebarSeparator key={Math.random()} className="my-1" />;
}
return (
@@ -0,0 +1,49 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import {
BadgeContainer,
storybookThirdwebClient,
} from "../../../stories/utils";
import { TokenSelector } from "./TokenSelector";
const meta = {
title: "blocks/Cards/TokenSelector",
component: Story,
parameters: {
nextjs: {
appDirectory: true,
},
},
} satisfies Meta<typeof Story>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Variants: Story = {
args: {},
};
function Story() {
return (
<div className="container flex max-w-6xl flex-col gap-8 py-10">
<Variant label="No Chains selected by default" />
</div>
);
}
function Variant(props: {
label: string;
selectedChainId?: number;
}) {
const [tokenAddress, setTokenAddress] = useState<string>("");
return (
<BadgeContainer label={props.label}>
<TokenSelector
tokenAddress={tokenAddress}
chainId={props.selectedChainId}
client={storybookThirdwebClient}
onChange={setTokenAddress}
/>
</BadgeContainer>
);
}
@@ -0,0 +1,124 @@
import { useCallback, useMemo } from "react";
import type { ThirdwebClient } from "thirdweb";
import { shortenAddress } from "thirdweb/utils";
import { useTokensData } from "../../../hooks/tokens/tokens";
import { replaceIpfsUrl } from "../../../lib/sdk";
import { fallbackChainIcon } from "../../../utils/chain-icons";
import { cn } from "../../lib/utils";
import { Badge } from "../ui/badge";
import { Img } from "./Img";
import { SelectWithSearch } from "./select-with-search";
type Option = { label: string; value: string };
export function TokenSelector(props: {
tokenAddress: string | undefined;
onChange: (tokenAddress: string) => void;
className?: string;
popoverContentClassName?: string;
chainId?: number;
side?: "left" | "right" | "top" | "bottom";
disableChainId?: boolean;
align?: "center" | "start" | "end";
placeholder?: string;
client: ThirdwebClient;
disabled?: boolean;
enabled?: boolean;
}) {
const { tokens, isFetching } = useTokensData({
chainId: props.chainId,
enabled: props.enabled,
});
const options = useMemo(() => {
return tokens.allTokens.map((token) => {
return {
label: token.symbol,
value: `${token.chainId}:${token.address}`,
};
});
}, [tokens.allTokens]);
const searchFn = useCallback(
(option: Option, searchValue: string) => {
const token = tokens.addressChainToToken.get(option.value);
if (!token) {
return false;
}
if (Number.isInteger(Number(searchValue))) {
return String(token.chainId).startsWith(searchValue);
}
return (
token.name.toLowerCase().includes(searchValue.toLowerCase()) ||
token.symbol.toLowerCase().includes(searchValue.toLowerCase()) ||
token.address.toLowerCase().includes(searchValue.toLowerCase())
);
},
[tokens],
);
const renderOption = useCallback(
(option: Option) => {
const token = tokens.addressChainToToken.get(option.value);
if (!token) {
return option.label;
}
const resolvedSrc = token.iconUri
? replaceIpfsUrl(token.iconUri, props.client)
: fallbackChainIcon;
return (
<div className="flex items-center justify-between gap-4">
<span className="flex grow gap-2 truncate text-left">
<Img
// render different image element if src changes to avoid showing old image while loading new one
key={resolvedSrc}
className={cn("size-5 rounded-full object-contain")}
src={resolvedSrc}
loading={"lazy"}
alt=""
// eslint-disable-next-line @next/next/no-img-element
fallback={<img src={fallbackChainIcon} alt="" />}
skeleton={
<div className="animate-pulse rounded-full bg-border" />
}
/>
{token.symbol}
</span>
{!props.disableChainId && (
<Badge variant="outline" className="gap-2 max-sm:hidden">
<span className="text-muted-foreground">Address</span>
{shortenAddress(token.address, 4)}
</Badge>
)}
</div>
);
},
[tokens, props.disableChainId, props.client],
);
return (
<SelectWithSearch
searchPlaceholder="Search by name or symbol"
value={props.tokenAddress}
options={options}
onValueChange={(tokenAddress) => {
props.onChange(tokenAddress);
}}
closeOnSelect={true}
showCheck={false}
placeholder={
isFetching ? "Loading Tokens..." : props.placeholder || "Select Token"
}
overrideSearchFn={searchFn}
renderOption={renderOption}
className={props.className}
popoverContentClassName={props.popoverContentClassName}
disabled={isFetching || props.disabled}
side={props.side}
align={props.align}
/>
);
}
@@ -0,0 +1,36 @@
"use client";
import { XIcon } from "lucide-react";
import { useLocalStorage } from "../../../hooks/useLocalStorage";
import { Button } from "../ui/button";
export function DismissibleAlert(props: {
title: React.ReactNode;
description: React.ReactNode;
localStorageId: string;
}) {
const [isVisible, setIsVisible] = useLocalStorage(
props.localStorageId,
true,
false,
);
if (!isVisible) return null;
return (
<div className="relative rounded-lg border border-border bg-card p-4">
<Button
onClick={() => setIsVisible(false)}
className="absolute top-4 right-4 h-auto w-auto p-1 text-muted-foreground"
aria-label="Close alert"
variant="ghost"
>
<XIcon className="size-5" />
</Button>
<div>
<h2 className="mb-0.5 font-semibold">{props.title} </h2>
<div className="text-muted-foreground text-sm">{props.description}</div>
</div>
</div>
);
}
@@ -34,6 +34,7 @@ interface SelectWithSearchProps
side?: "left" | "right" | "top" | "bottom";
align?: "center" | "start" | "end";
closeOnSelect?: boolean;
showCheck?: boolean;
}
export const SelectWithSearch = React.forwardRef<
@@ -52,6 +53,7 @@ export const SelectWithSearch = React.forwardRef<
popoverContentClassName,
searchPlaceholder,
closeOnSelect,
showCheck = true,
...props
},
ref,
@@ -126,7 +128,7 @@ export const SelectWithSearch = React.forwardRef<
<span
className={cn(
"truncate text-muted-foreground text-sm",
selectedOption && "text-foreground",
selectedOption && "w-full text-foreground",
)}
>
{renderOption && selectedOption
@@ -193,9 +195,11 @@ export const SelectWithSearch = React.forwardRef<
i === optionsToShow.length - 1 ? lastItemRef : undefined
}
>
<div className="flex size-4 items-center justify-center">
{isSelected && <CheckIcon className="size-4" />}
</div>
{showCheck && (
<div className="flex size-4 items-center justify-center">
{isSelected && <CheckIcon className="size-4" />}
</div>
)}
<div className="min-w-0 grow">
{renderOption ? renderOption(option) : option.label}
@@ -12,6 +12,7 @@ export type ChartConfig = {
[k in string]: {
label?: React.ReactNode;
icon?: React.ComponentType;
isCurrency?: boolean;
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
@@ -0,0 +1,45 @@
"use client";
import { Button } from "@/components/ui/button";
import type React from "react";
import { useRef } from "react";
interface ImageUploadProps {
value: File | undefined;
onChange?: (files: File[]) => void;
children?: React.ReactNode;
variant?: React.ComponentProps<typeof Button>["variant"];
className?: string;
multiple?: boolean;
accept: string;
}
export function ImageUploadButton(props: ImageUploadProps) {
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
props.onChange?.(files);
};
return (
<div>
<Button
variant={props.variant}
onClick={() => fileInputRef.current?.click()}
className={props.className}
>
{props.children}
</Button>
<input
ref={fileInputRef}
type="file"
multiple={props.multiple}
accept={props.accept}
onChange={handleFileChange}
className="hidden"
aria-label="Upload image"
/>
</div>
);
}
@@ -54,11 +54,11 @@ export function ToolTipLabel(props: {
align={props.align}
sideOffset={10}
className={cn(
"max-w-[400px] whitespace-normal leading-relaxed",
"max-w-[400px] whitespace-normal p-0 leading-relaxed",
props.contentClassName,
)}
>
<div className="flex items-center gap-1.5 p-2 text-sm">
<div className="flex items-center gap-1.5 p-4 text-sm">
{props.leftIcon}
{props.label}
{props.rightIcon}
@@ -0,0 +1,11 @@
export const isProd =
(process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_VERCEL_ENV) ===
"production";
export const PROD_OR_DEV_URL = isProd ? "thirdweb.com" : "thirdweb-dev.com";
export const BASE_URL = isProd
? "https://thirdweb.com"
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
: "http://localhost:3000") || "https://thirdweb-dev.com";
-62
View File
@@ -1,62 +0,0 @@
export const DASHBOARD_THIRDWEB_CLIENT_ID =
process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID || "";
export const DASHBOARD_THIRDWEB_SECRET_KEY =
process.env.DASHBOARD_SECRET_KEY || "";
export const NEXT_PUBLIC_NEBULA_APP_CLIENT_ID =
process.env.NEXT_PUBLIC_NEBULA_APP_CLIENT_ID || "";
export const NEBULA_APP_SECRET_KEY = process.env.NEBULA_APP_SECRET_KEY || "";
export const THIRDWEB_VAULT_URL =
process.env.NEXT_PUBLIC_THIRDWEB_VAULT_URL || "";
export const THIRDWEB_ENGINE_CLOUD_URL =
process.env.NEXT_PUBLIC_ENGINE_CLOUD_URL || "";
export const THIRDWEB_API_SECRET = process.env.API_SERVER_SECRET || "";
export const IPFS_GATEWAY_URL =
(process.env.NEXT_PUBLIC_IPFS_GATEWAY_URL as string) ||
"https://{clientId}.ipfscdn.io/ipfs/{cid}/{path}";
export const isProd =
(process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_VERCEL_ENV) ===
"production";
export const PROD_OR_DEV_URL = isProd ? "thirdweb.com" : "thirdweb-dev.com";
export const DASHBOARD_STORAGE_URL =
process.env.NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER ||
"https://storage.thirdweb.com";
export const API_SERVER_URL =
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com";
export const BRIDGE_URL =
process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST || "https://bridge.thirdweb.com";
/**
* Faucet stuff
*/
// Cloudflare Turnstile Site key
export const TURNSTILE_SITE_KEY =
process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "";
export const THIRDWEB_ENGINE_FAUCET_WALLET =
process.env.NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET || "";
export const THIRDWEB_ENGINE_URL = process.env.THIRDWEB_ENGINE_URL;
export const THIRDWEB_ACCESS_TOKEN = process.env.THIRDWEB_ACCESS_TOKEN;
// Comma-separated list of chain IDs to disable faucet for.
export const DISABLE_FAUCET_CHAIN_IDS = process.env.DISABLE_FAUCET_CHAIN_IDS;
export const BASE_URL = isProd
? "https://thirdweb.com"
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
: "http://localhost:3000") || "https://thirdweb-dev.com";
export const NEXT_PUBLIC_NEBULA_URL = process.env.NEXT_PUBLIC_NEBULA_URL;
export const INSIGHT_SERVICE_API_KEY =
process.env.INSIGHT_SERVICE_API_KEY || "";
@@ -0,0 +1,36 @@
export const NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID =
process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID || "";
export const NEXT_PUBLIC_NEBULA_APP_CLIENT_ID =
process.env.NEXT_PUBLIC_NEBULA_APP_CLIENT_ID || "";
export const NEXT_PUBLIC_THIRDWEB_VAULT_URL =
process.env.NEXT_PUBLIC_THIRDWEB_VAULT_URL || "";
export const NEXT_PUBLIC_ENGINE_CLOUD_URL =
process.env.NEXT_PUBLIC_ENGINE_CLOUD_URL || "";
export const NEXT_PUBLIC_IPFS_GATEWAY_URL =
(process.env.NEXT_PUBLIC_IPFS_GATEWAY_URL as string) ||
"https://{clientId}.ipfscdn.io/ipfs/{cid}/{path}";
export const NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER =
process.env.NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER ||
"https://storage.thirdweb.com";
export const NEXT_PUBLIC_THIRDWEB_API_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com";
export const NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST || "https://bridge.thirdweb.com";
export const NEXT_PUBLIC_TURNSTILE_SITE_KEY =
process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "";
export const NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET =
process.env.NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET || "";
export const NEXT_PUBLIC_NEBULA_URL = process.env.NEXT_PUBLIC_NEBULA_URL || "";
export const NEXT_PUBLIC_DEMO_ENGINE_URL =
process.env.NEXT_PUBLIC_DEMO_ENGINE_URL || "";
@@ -0,0 +1,117 @@
import "server-only";
import { experimental_taintUniqueValue } from "react";
import { isProd } from "./env-utils";
// Make sure to taint the server only envs here with experimental_taintUniqueValue ONLY if they contain a UNIQUE sensitive value
// if an env has a generic value that may appear naturally in client components - do not taint it
export const DASHBOARD_THIRDWEB_SECRET_KEY =
process.env.DASHBOARD_SECRET_KEY || "";
experimental_taintUniqueValue(
"Do not pass DASHBOARD_THIRDWEB_SECRET_KEY to the client",
process,
DASHBOARD_THIRDWEB_SECRET_KEY,
);
export const NEBULA_APP_SECRET_KEY = process.env.NEBULA_APP_SECRET_KEY || "";
if (NEBULA_APP_SECRET_KEY) {
experimental_taintUniqueValue(
"Do not pass NEBULA_APP_SECRET_KEY to the client",
process,
NEBULA_APP_SECRET_KEY,
);
}
export const API_SERVER_SECRET = process.env.API_SERVER_SECRET || "";
if (API_SERVER_SECRET) {
experimental_taintUniqueValue(
"Do not pass API_SERVER_SECRET to the client",
process,
API_SERVER_SECRET,
);
}
export const THIRDWEB_ENGINE_URL = process.env.THIRDWEB_ENGINE_URL || "";
if (THIRDWEB_ENGINE_URL) {
experimental_taintUniqueValue(
"Do not pass THIRDWEB_ENGINE_URL to the client",
process,
THIRDWEB_ENGINE_URL,
);
}
export const THIRDWEB_ACCESS_TOKEN = process.env.THIRDWEB_ACCESS_TOKEN || "";
if (THIRDWEB_ACCESS_TOKEN) {
experimental_taintUniqueValue(
"Do not pass THIRDWEB_ACCESS_TOKEN to the client",
process,
THIRDWEB_ACCESS_TOKEN,
);
}
// Comma-separated list of chain IDs to disable faucet for.
// Note: Can not taint this - because the value is too generic
export const DISABLE_FAUCET_CHAIN_IDS =
process.env.DISABLE_FAUCET_CHAIN_IDS || "";
export const INSIGHT_SERVICE_API_KEY =
process.env.INSIGHT_SERVICE_API_KEY || "";
// The value of INSIGHT_SERVICE_API_KEY on dev is generic - so we don't taint it
// TODO - have better INSIGHT_SERVICE_API_KEY on dev
if (isProd && INSIGHT_SERVICE_API_KEY) {
experimental_taintUniqueValue(
"Do not pass INSIGHT_SERVICE_API_KEY to the client",
process,
INSIGHT_SERVICE_API_KEY,
);
}
export const MORALIS_API_KEY = process.env.MORALIS_API_KEY || "";
if (MORALIS_API_KEY) {
experimental_taintUniqueValue(
"Do not pass MORALIS_API_KEY to the client",
process,
MORALIS_API_KEY,
);
}
export const ANALYTICS_SERVICE_URL = process.env.ANALYTICS_SERVICE_URL || "";
export const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY || "";
if (STRIPE_SECRET_KEY) {
experimental_taintUniqueValue(
"Do not pass STRIPE_SECRET_KEY to the client",
process,
STRIPE_SECRET_KEY,
);
}
export const API_ROUTES_CLIENT_ID = process.env.API_ROUTES_CLIENT_ID || "";
export const TURNSTILE_SECRET_KEY = process.env.TURNSTILE_SECRET_KEY || "";
if (TURNSTILE_SECRET_KEY) {
experimental_taintUniqueValue(
"Do not pass TURNSTILE_SECRET_KEY to the client",
process,
TURNSTILE_SECRET_KEY,
);
}
export const REDIS_URL = process.env.REDIS_URL || "";
if (REDIS_URL) {
experimental_taintUniqueValue(
"Do not pass REDIS_URL to the client",
process,
REDIS_URL,
);
}
@@ -0,0 +1,11 @@
import { getConfiguredThirdwebClient } from "./thirdweb.server";
export function getClientThirdwebClient(params?: {
jwt: string | undefined | null;
teamId: string | undefined | null;
}) {
return getConfiguredThirdwebClient({
secretKey: params?.jwt ?? undefined,
teamId: params?.teamId ?? undefined,
});
}
@@ -0,0 +1,9 @@
import "server-only";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "./server-envs";
import { getConfiguredThirdwebClient } from "./thirdweb.server";
export const serverThirdwebClient = getConfiguredThirdwebClient({
teamId: undefined,
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
});
@@ -4,7 +4,7 @@ import { useActiveAccount } from "thirdweb/react";
import type { GetAuthTokenResponse } from "../../app/(app)/api/auth/get-auth-token/route";
import { LAST_USED_TEAM_ID } from "../../constants/cookies";
import { getCookie } from "../../lib/cookie";
import { getThirdwebClient } from "./thirdweb.server";
import { getClientThirdwebClient } from "./thirdweb-client.client";
// returns a thirdweb client with optional JWT passed i
@@ -38,7 +38,7 @@ export function useThirdwebClient(jwt?: string) {
return useMemo(
// prefer jwt from props over the one from the token query if it exists
() =>
getThirdwebClient({
getClientThirdwebClient({
jwt: jwt || query.data,
teamId: lastUsedTeamId,
}),
@@ -1,8 +1,7 @@
import {
DASHBOARD_THIRDWEB_CLIENT_ID,
DASHBOARD_THIRDWEB_SECRET_KEY,
IPFS_GATEWAY_URL,
} from "@/constants/env";
NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
NEXT_PUBLIC_IPFS_GATEWAY_URL,
} from "@/constants/public-envs";
import {
THIRDWEB_BUNDLER_DOMAIN,
THIRDWEB_INAPP_WALLET_DOMAIN,
@@ -12,7 +11,7 @@ import {
THIRDWEB_SOCIAL_API_DOMAIN,
THIRDWEB_STORAGE_DOMAIN,
} from "constants/urls";
import { createThirdwebClient } from "thirdweb";
import { type ThirdwebClient, createThirdwebClient } from "thirdweb";
import { populateEip712Transaction } from "thirdweb/transaction";
import {
getTransactionDecorator,
@@ -22,15 +21,10 @@ import {
import { getZkPaymasterData } from "thirdweb/wallets/smart";
import { getVercelEnv } from "../../lib/vercel-utils";
// returns a thirdweb client with optional JWT passed in
export function getThirdwebClient(
options:
| {
jwt: string | null | undefined;
teamId: string | undefined;
}
| undefined,
) {
export function getConfiguredThirdwebClient(options: {
secretKey: string | undefined;
teamId: string | undefined;
}): ThirdwebClient {
if (getVercelEnv() !== "production") {
// if not on production: run this when creating a client to set the domains
setThirdwebDomains({
@@ -78,12 +72,12 @@ export function getThirdwebClient(
}
return createThirdwebClient({
teamId: options?.teamId,
secretKey: options?.jwt ? options.jwt : DASHBOARD_THIRDWEB_SECRET_KEY,
clientId: DASHBOARD_THIRDWEB_CLIENT_ID,
teamId: options.teamId,
secretKey: options.secretKey,
clientId: NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
config: {
storage: {
gatewayUrl: IPFS_GATEWAY_URL,
gatewayUrl: NEXT_PUBLIC_IPFS_GATEWAY_URL,
},
},
});
+5 -4
View File
@@ -39,9 +39,10 @@ export function getFiltersFromSearchParams(params: {
};
const defaultInterval =
differenceInCalendarDays(range.to, range.from) > 30
? "week"
: ("day" as const);
params.interval ??
(differenceInCalendarDays(range.to, range.from) > 30
? ("week" as const)
: ("day" as const));
return {
range,
@@ -50,6 +51,6 @@ export function getFiltersFromSearchParams(params: {
? ("day" as const)
: params.interval === "week"
? ("week" as const)
: defaultInterval,
: (defaultInterval as "day" | "week"),
};
}
@@ -1,5 +1,5 @@
import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Metadata } from "next";
import { getClientThirdwebClient } from "../../../../../@/constants/thirdweb-client.client";
import { UniversalBridgeEmbed } from "./components/client/UniversalBridgeEmbed";
const title = "Universal Bridge: Swap, Bridge, and On-Ramp";
@@ -19,7 +19,7 @@ export default async function RoutesPage({
searchParams,
}: { searchParams: Record<string, string | string[]> }) {
const { chainId } = searchParams;
const client = getThirdwebClient(undefined);
const client = getClientThirdwebClient(undefined);
return (
<div className="relative mx-auto flex h-screen w-full flex-col items-center justify-center overflow-hidden border py-10">
<main className="container z-10 flex justify-center">
@@ -1,8 +1,8 @@
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import type { ThirdwebClient } from "thirdweb";
import { defineChain } from "thirdweb";
import { getChainMetadata } from "thirdweb/chains";
import { serverThirdwebClient } from "../../../../../../../@/constants/thirdweb-client.server";
type RouteListCardProps = {
originChainId: number;
@@ -15,7 +15,6 @@ type RouteListCardProps = {
destinationTokenIconUri?: string | null;
destinationTokenSymbol: string;
destinationTokenName: string;
client: ThirdwebClient;
};
export async function RouteListCard({
@@ -27,7 +26,6 @@ export async function RouteListCard({
destinationTokenAddress,
destinationTokenIconUri,
destinationTokenName,
client,
}: RouteListCardProps) {
const [
originChain,
@@ -42,13 +40,13 @@ export async function RouteListCard({
originTokenIconUri
? resolveSchemeWithErrorHandler({
uri: originTokenIconUri,
client,
client: serverThirdwebClient,
})
: undefined,
destinationTokenIconUri
? resolveSchemeWithErrorHandler({
uri: destinationTokenIconUri,
client,
client: serverThirdwebClient,
})
: undefined,
]);
@@ -1,7 +1,7 @@
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { TableCell, TableRow } from "@/components/ui/table";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import type { ThirdwebClient } from "thirdweb";
import { defineChain, getChainMetadata } from "thirdweb/chains";
type RouteListRowProps = {
@@ -15,7 +15,6 @@ type RouteListRowProps = {
destinationTokenIconUri?: string | null;
destinationTokenSymbol?: string;
destinationTokenName?: string;
client: ThirdwebClient;
};
export async function RouteListRow({
@@ -27,7 +26,6 @@ export async function RouteListRow({
destinationTokenAddress,
destinationTokenIconUri,
destinationTokenSymbol,
client,
}: RouteListRowProps) {
const [
originChain,
@@ -42,13 +40,13 @@ export async function RouteListRow({
originTokenIconUri
? resolveSchemeWithErrorHandler({
uri: originTokenIconUri,
client,
client: serverThirdwebClient,
})
: undefined,
destinationTokenIconUri
? resolveSchemeWithErrorHandler({
uri: destinationTokenIconUri,
client,
client: serverThirdwebClient,
})
: undefined,
]);
@@ -6,7 +6,6 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Address } from "thirdweb";
import { checksumAddress } from "thirdweb/utils";
import { getRoutes } from "../../../utils";
@@ -137,7 +136,6 @@ export async function RoutesData(props: {
const startIndex = (activePage - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedRoutes = routesToRender.slice(startIndex, endIndex);
const client = getThirdwebClient(undefined);
return (
<>
@@ -172,7 +170,6 @@ export async function RoutesData(props: {
destinationTokenIconUri={route.destinationToken.iconUri}
destinationTokenSymbol={route.destinationToken.symbol}
destinationTokenName={route.destinationToken.name}
client={client}
/>
))}
</TableBody>
@@ -196,7 +193,6 @@ export async function RoutesData(props: {
destinationTokenIconUri={route.destinationToken.iconUri}
destinationTokenSymbol={route.destinationToken.symbol}
destinationTokenName={route.destinationToken.name}
client={client}
/>
</li>
))}
@@ -1,6 +1,7 @@
import "server-only";
import { BRIDGE_URL, DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
import { NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST } from "@/constants/public-envs";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
import type { Route } from "./types/route";
@@ -9,7 +10,7 @@ export async function getRoutes({
}: {
limit?: number;
} = {}) {
const url = new URL(`${BRIDGE_URL}/v1/routes`);
const url = new URL(`${NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST}/v1/routes`);
if (limit) {
url.searchParams.set("limit", limit.toString());
}
@@ -20,9 +20,9 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
THIRDWEB_ENGINE_FAUCET_WALLET,
TURNSTILE_SITE_KEY,
} from "@/constants/env";
NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET,
NEXT_PUBLIC_TURNSTILE_SITE_KEY,
} from "@/constants/public-envs";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -90,7 +90,7 @@ export function FaucetButton({
// eslint-disable-next-line no-restricted-syntax
const definedChain = mapV4ChainToV5Chain(chain);
const faucetWalletBalanceQuery = useWalletBalance({
address: THIRDWEB_ENGINE_FAUCET_WALLET,
address: NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET,
chain: definedChain,
client,
});
@@ -260,7 +260,7 @@ export function FaucetButton({
<FormControl>
<Turnstile
className="mt-4"
siteKey={TURNSTILE_SITE_KEY}
siteKey={NEXT_PUBLIC_TURNSTILE_SITE_KEY}
onSuccess={(token) => field.onChange(token)}
/>
</FormControl>
@@ -342,7 +342,7 @@ function SendFundsToFaucetModalContent(props: {
const sendNativeTokenTx = prepareTransaction({
chain: props.chain,
client: props.client,
to: THIRDWEB_ENGINE_FAUCET_WALLET,
to: NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET,
value: toWei(values.amount.toString()),
});
@@ -370,8 +370,8 @@ function SendFundsToFaucetModalContent(props: {
copyIconPosition="right"
variant="outline"
className="w-full justify-between bg-card py-2 font-mono"
textToCopy={THIRDWEB_ENGINE_FAUCET_WALLET}
textToShow={THIRDWEB_ENGINE_FAUCET_WALLET}
textToCopy={NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET}
textToShow={NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET}
tooltip={undefined}
/>
</div>
@@ -3,7 +3,8 @@
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Skeleton } from "@/components/ui/skeleton";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { DASHBOARD_THIRDWEB_CLIENT_ID, isProd } from "@/constants/env";
import { isProd } from "@/constants/env-utils";
import { NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID } from "@/constants/public-envs";
import { useQuery } from "@tanstack/react-query";
import { CircleCheckIcon, XIcon } from "lucide-react";
import { hostnameEndsWith } from "utils/url";
@@ -13,7 +14,7 @@ function useChainStatswithRPC(_rpcUrl: string) {
let rpcUrl = _rpcUrl.replace(
// eslint-disable-next-line no-template-curly-in-string
"${THIRDWEB_API_KEY}",
DASHBOARD_THIRDWEB_CLIENT_ID,
NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
);
// based on the environment hit dev or production
@@ -1,6 +1,5 @@
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import type { ThirdwebClient } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { ChainIcon } from "../../../../components/server/chain-icon";
import { PayModalButton } from "../client/PayModal";
@@ -9,7 +8,6 @@ import { SectionTitle } from "./SectionTitle";
export function BuyFundsSection(props: {
chain: ChainMetadata;
client: ThirdwebClient;
}) {
const sanitizedChainName = props.chain.name.replace("Mainnet", "").trim();
@@ -22,7 +20,6 @@ export function BuyFundsSection(props: {
<ChainIcon
className="-mr-2 size-12 rounded-full border p-1"
iconUrl={props.chain.icon?.url}
client={props.client}
/>
<CreditCardIcon
bg="hsl(var(--background))"
@@ -1,4 +1,3 @@
import type { ThirdwebClient } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { getFaucetClaimAmount } from "../../../../../../api/testnet-faucet/claim/claim-amount";
import { ChainIcon } from "../../../../components/server/chain-icon";
@@ -9,9 +8,8 @@ import { SectionTitle } from "./SectionTitle";
export async function FaucetSection(props: {
chain: ChainMetadata;
isLoggedIn: boolean;
client: ThirdwebClient;
}) {
const { chain, isLoggedIn, client } = props;
const { chain, isLoggedIn } = props;
// Check eligibility.
const sanitizedChainName = chain.name.replace("Mainnet", "").trim();
@@ -26,7 +24,6 @@ export async function FaucetSection(props: {
<ChainIcon
className="-mr-2 size-12 rounded-full border p-1"
iconUrl={props.chain.icon?.url}
client={client}
/>
<GiftIcon bg="hsl(var(--background))" className="-ml-2 size-12" />
</div>
@@ -1,7 +1,6 @@
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import Link from "next/link";
import type { ThirdwebClient } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { mapV4ChainToV5Chain } from "../../../../../../../../contexts/map-chains";
import { ChainIcon } from "../../../../components/server/chain-icon";
@@ -11,7 +10,6 @@ type ChainHeaderProps = {
headerImageUrl?: string;
logoUrl?: string;
chain: ChainMetadata;
client: ThirdwebClient;
};
// TODO: improve the behavior when clicking "Get started with thirdweb", currently just redirects to the dashboard
@@ -41,7 +39,6 @@ export function ChainHeader(props: ChainHeaderProps) {
{/* chain logo */}
<ChainIcon
client={props.client}
iconUrl={props.logoUrl}
className={cn(
"-translate-y-[50%] absolute top-0 left-0 size-20 overflow-hidden rounded-full border border-border bg-muted p-2 lg:size-36 lg:p-4",
@@ -13,7 +13,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { ChevronDownIcon, TicketCheckIcon } from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";
@@ -71,10 +71,7 @@ export default async function ChainPageLayout(props: {
}
const chainMetadata = await getChainMetadata(chain.chainId);
const client = getThirdwebClient({
jwt: authToken,
teamId: undefined,
});
const client = getClientThirdwebClient(undefined);
const chainPromptPrefix = `\
You are assisting users exploring the chain ${chain.name} (Chain ID: ${chain.chainId}). Provide concise insights into the types of applications and activities prevalent on this chain, such as DeFi protocols, NFT marketplaces, or gaming platforms. Highlight notable projects or trends without delving into technical details like consensus mechanisms or gas fees.
@@ -163,7 +160,6 @@ The following is the user's message:
headerImageUrl={chainMetadata?.headerImgUrl}
logoUrl={chain.icon?.url}
chain={chain}
client={client}
/>
<div className="h-4 md:h-8" />
@@ -1,8 +1,7 @@
/* eslint-disable @next/next/no-img-element */
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { ImageResponse } from "next/og";
import { download } from "thirdweb/storage";
import { fetchChain } from "utils/fetchChain";
import { serverThirdwebClient } from "../../../../../../@/constants/thirdweb-client.server";
// Route segment config
export const runtime = "edge";
@@ -79,7 +78,7 @@ export default async function Image({
chain.icon?.url && hasWorkingChainIcon
? download({
uri: chain.icon.url,
client: getThirdwebClient(undefined),
client: serverThirdwebClient,
}).then((res) => res.arrayBuffer())
: undefined,
// download the background image (based on chain)
@@ -1,6 +1,5 @@
import { CircleAlertIcon } from "lucide-react";
import { getRawAccount } from "../../../../account/settings/getAccount";
import { getUserThirdwebClient } from "../../../../api/lib/getAuthToken";
import { getChain, getChainMetadata } from "../../utils";
import NextSteps from "./components/client/NextSteps";
import { BuyFundsSection } from "./components/server/BuyFundsSection";
@@ -19,7 +18,6 @@ export default async function Page(props: {
const chainMetadata = await getChainMetadata(chain.chainId);
const isDeprecated = chain.status === "deprecated";
const client = await getUserThirdwebClient();
const account = await getRawAccount();
return (
@@ -44,9 +42,9 @@ export default async function Page(props: {
{/* Faucet / Buy Funds */}
{chain.testnet ? (
<FaucetSection chain={chain} isLoggedIn={!!account} client={client} />
<FaucetSection chain={chain} isLoggedIn={!!account} />
) : chain.services.find((c) => c.service === "pay" && c.enabled) ? (
<BuyFundsSection chain={chain} client={client} />
<BuyFundsSection chain={chain} />
) : null}
{/* Chain Overview */}
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../../_utils/getContractFromParams";
import { getContractPageMetadata } from "../../_utils/getContractPageMetadata";
@@ -20,17 +19,17 @@ export default async function Page(props: {
notFound();
}
if (info.chainMetadata.chainId === localhost.id) {
if (info.isLocalhostChain) {
return (
<ContractDirectListingsPageClient
contract={info.contract}
contract={info.clientContract}
isLoggedIn={!!account}
/>
);
}
const { isDirectListingSupported, isInsightSupported } =
await getContractPageMetadata(info.contract);
await getContractPageMetadata(info.serverContract);
if (!isDirectListingSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
@@ -38,7 +37,7 @@ export default async function Page(props: {
return (
<ContractDirectListingsPage
contract={info.contract}
contract={info.clientContract}
isLoggedIn={!!account}
isInsightSupported={isInsightSupported}
/>
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../../_utils/getContractFromParams";
import { getContractPageMetadata } from "../../_utils/getContractPageMetadata";
@@ -21,17 +20,17 @@ export default async function Page(props: {
const twAccount = await getRawAccount();
if (info.chainMetadata.chainId === localhost.id) {
if (info.isLocalhostChain) {
return (
<ContractEnglishAuctionsPageClient
contract={info.contract}
contract={info.clientContract}
isLoggedIn={!!twAccount}
/>
);
}
const { isEnglishAuctionSupported, isInsightSupported } =
await getContractPageMetadata(info.contract);
await getContractPageMetadata(info.serverContract);
if (!isEnglishAuctionSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
@@ -39,7 +38,7 @@ export default async function Page(props: {
return (
<ContractEnglishAuctionsPage
contract={info.contract}
contract={info.clientContract}
isLoggedIn={!!twAccount}
isInsightSupported={isInsightSupported}
/>
@@ -2,7 +2,7 @@
import { useQuery } from "@tanstack/react-query";
import type { ThirdwebContract } from "thirdweb";
import { getContractPageMetadataSetup } from "../_utils/getContractPageMetadata";
import { getContractPageMetadataSetup } from "../_utils/getContractPageMetadataSetup";
export function useContractPageMetadata(contract: ThirdwebContract) {
return useQuery({
@@ -11,7 +11,7 @@ export function ConfigureCustomChain(props: {
chainSlug: string;
}) {
const { chainSlug } = props;
const isSlugNumber = Number.isInteger(Number.parseInt(chainSlug));
const isSlugNumber = Number.isInteger(Number(chainSlug));
const [isConfigured, setIsConfigured] = useState(false);
if (isConfigured) {
@@ -1,10 +1,9 @@
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { mapV4ChainToV5Chain } from "contexts/map-chains";
import { cookies } from "next/headers";
import { getAddress, getContract, isAddress } from "thirdweb";
import { LAST_USED_TEAM_ID } from "../../../../../../../constants/cookies";
import { localhost } from "thirdweb/chains";
import { fetchChainWithLocalOverrides } from "../../../../../../../utils/fetchChainWithLocalOverrides";
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
import { getUserThirdwebClient } from "../../../../../api/lib/getAuthToken";
export async function getContractPageParamsInfo(params: {
contractAddress: string;
@@ -19,24 +18,27 @@ export async function getContractPageParamsInfo(params: {
}
// attempt to get the auth token
const token = await getAuthToken();
const cookiesObj = await cookies();
const teamId = cookiesObj.get(LAST_USED_TEAM_ID)?.value;
const contract = getContract({
const serverContract = getContract({
address: contractAddress,
// eslint-disable-next-line no-restricted-syntax
chain: mapV4ChainToV5Chain(chainMetadata),
client: serverThirdwebClient,
});
const clientContract = getContract({
address: contractAddress,
// eslint-disable-next-line no-restricted-syntax
chain: mapV4ChainToV5Chain(chainMetadata),
// if we have the auth token pass it into the client
client: getThirdwebClient({
jwt: token || undefined,
teamId,
}),
client: await getUserThirdwebClient(),
});
return {
chainMetadata,
contract,
serverContract,
clientContract,
isLocalhostChain: chainMetadata.chainId === localhost.id,
};
}
@@ -1,26 +1,7 @@
import { resolveFunctionSelectors } from "lib/selectors";
import "server-only";
import type { ThirdwebContract } from "thirdweb";
import { contractType as getContractType } from "thirdweb/extensions/thirdweb";
import {
isERC20ClaimConditionsSupported,
isERC721ClaimConditionsSupported,
} from "./detectedFeatures/claimConditions";
import {
isAccountContract,
isAccountFactoryContract,
isAccountPermissionsSupported,
} from "./detectedFeatures/erc4337";
import {
isDirectListingSupported,
isEnglishAuctionSupported,
} from "./detectedFeatures/marketplace";
import { isModularCoreContract } from "./detectedFeatures/modular";
import {
isPermissionsEnumerableSupported,
isPermissionsSupported,
} from "./detectedFeatures/permissions";
import { supportedERCs } from "./detectedFeatures/supportedERCs";
import { type EmbedTypeToShow, getEmbedTypeToShow } from "./getEmbedTypeToShow";
import { getContractPageMetadataSetup } from "./getContractPageMetadataSetup";
import type { EmbedTypeToShow } from "./getEmbedTypeToShow";
import { isInsightSupportedForChain } from "./isAnalyticsSupportedForChain";
export type ContractPageMetadata = {
@@ -49,54 +30,3 @@ export type ContractPageMetadata = {
export async function getContractPageMetadata(contract: ThirdwebContract) {
return getContractPageMetadataSetup(contract, isInsightSupportedForChain);
}
export async function getContractPageMetadataSetup(
contract: ThirdwebContract,
isAnalyticsSupportedFn: (chainId: number) => Promise<boolean>,
): Promise<ContractPageMetadata> {
const [
functionSelectorsResult,
isInsightSupportedResult,
contractTypeResult,
] = await Promise.allSettled([
resolveFunctionSelectors(contract),
isAnalyticsSupportedFn(contract.chain.id),
getContractType({ contract }),
]);
const functionSelectors =
functionSelectorsResult.status === "fulfilled"
? functionSelectorsResult.value
: [];
const isInsightSupported =
isInsightSupportedResult.status === "fulfilled"
? isInsightSupportedResult.value
: true;
const contractType =
contractTypeResult.status === "fulfilled" ? contractTypeResult.value : null;
return {
supportedERCs: supportedERCs(functionSelectors),
isDirectListingSupported: isDirectListingSupported(functionSelectors),
isEnglishAuctionSupported: isEnglishAuctionSupported(functionSelectors),
isPermissionsSupported: isPermissionsSupported(functionSelectors),
isPermissionsEnumerableSupported:
isPermissionsEnumerableSupported(functionSelectors),
isModularCore: isModularCoreContract(functionSelectors),
embedType: getEmbedTypeToShow(functionSelectors),
isInsightSupported: isInsightSupported,
isSplitSupported: contractType === "Split",
isVoteContract: contractType === "VoteERC20",
isERC721ClaimConditionsSupported:
isERC721ClaimConditionsSupported(functionSelectors),
isERC20ClaimConditionsSupported:
isERC20ClaimConditionsSupported(functionSelectors),
isAccountFactory: isAccountFactoryContract(functionSelectors),
isAccount: isAccountContract(functionSelectors),
isAccountPermissionsSupported:
isAccountPermissionsSupported(functionSelectors),
functionSelectors,
};
}
@@ -0,0 +1,97 @@
import { resolveFunctionSelectors } from "lib/selectors";
import type { ThirdwebContract } from "thirdweb";
import { contractType as getContractType } from "thirdweb/extensions/thirdweb";
import {
isERC20ClaimConditionsSupported,
isERC721ClaimConditionsSupported,
} from "./detectedFeatures/claimConditions";
import {
isAccountContract,
isAccountFactoryContract,
isAccountPermissionsSupported,
} from "./detectedFeatures/erc4337";
import {
isDirectListingSupported,
isEnglishAuctionSupported,
} from "./detectedFeatures/marketplace";
import { isModularCoreContract } from "./detectedFeatures/modular";
import {
isPermissionsEnumerableSupported,
isPermissionsSupported,
} from "./detectedFeatures/permissions";
import { supportedERCs } from "./detectedFeatures/supportedERCs";
import { type EmbedTypeToShow, getEmbedTypeToShow } from "./getEmbedTypeToShow";
type ContractPageMetadata = {
supportedERCs: {
isERC721: boolean;
isERC1155: boolean;
isERC20: boolean;
};
isDirectListingSupported: boolean;
isEnglishAuctionSupported: boolean;
isPermissionsSupported: boolean;
isVoteContract: boolean;
isPermissionsEnumerableSupported: boolean;
isModularCore: boolean;
embedType: EmbedTypeToShow;
isInsightSupported: boolean;
isSplitSupported: boolean;
isERC721ClaimConditionsSupported: boolean;
isERC20ClaimConditionsSupported: boolean;
isAccountFactory: boolean;
isAccount: boolean;
isAccountPermissionsSupported: boolean;
functionSelectors: string[];
};
export async function getContractPageMetadataSetup(
contract: ThirdwebContract,
isAnalyticsSupportedFn: (chainId: number) => Promise<boolean>,
): Promise<ContractPageMetadata> {
const [
functionSelectorsResult,
isInsightSupportedResult,
contractTypeResult,
] = await Promise.allSettled([
resolveFunctionSelectors(contract),
isAnalyticsSupportedFn(contract.chain.id),
getContractType({ contract }),
]);
const functionSelectors =
functionSelectorsResult.status === "fulfilled"
? functionSelectorsResult.value
: [];
const isInsightSupported =
isInsightSupportedResult.status === "fulfilled"
? isInsightSupportedResult.value
: true;
const contractType =
contractTypeResult.status === "fulfilled" ? contractTypeResult.value : null;
return {
supportedERCs: supportedERCs(functionSelectors),
isDirectListingSupported: isDirectListingSupported(functionSelectors),
isEnglishAuctionSupported: isEnglishAuctionSupported(functionSelectors),
isPermissionsSupported: isPermissionsSupported(functionSelectors),
isPermissionsEnumerableSupported:
isPermissionsEnumerableSupported(functionSelectors),
isModularCore: isModularCoreContract(functionSelectors),
embedType: getEmbedTypeToShow(functionSelectors),
isInsightSupported: isInsightSupported,
isSplitSupported: contractType === "Split",
isVoteContract: contractType === "VoteERC20",
isERC721ClaimConditionsSupported:
isERC721ClaimConditionsSupported(functionSelectors),
isERC20ClaimConditionsSupported:
isERC20ClaimConditionsSupported(functionSelectors),
isAccountFactory: isAccountFactoryContract(functionSelectors),
isAccount: isAccountContract(functionSelectors),
isAccountPermissionsSupported:
isAccountPermissionsSupported(functionSelectors),
functionSelectors,
};
}
@@ -1,4 +1,4 @@
import { INSIGHT_SERVICE_API_KEY } from "@/constants/env";
import { INSIGHT_SERVICE_API_KEY } from "@/constants/server-envs";
import { getVercelEnv } from "lib/vercel-utils";
const thirdwebDomain =
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
import { AccountSignersClient } from "./AccountSigners.client";
@@ -18,18 +17,18 @@ export default async function Page(props: {
notFound();
}
const { contract, chainMetadata } = info;
const { clientContract, serverContract, isLocalhostChain } = info;
if (chainMetadata.chainId === localhost.id) {
return <AccountSignersClient contract={contract} />;
if (isLocalhostChain) {
return <AccountSignersClient contract={clientContract} />;
}
const { isAccountPermissionsSupported } =
await getContractPageMetadata(contract);
await getContractPageMetadata(serverContract);
if (!isAccountPermissionsSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
return <AccountSigners contract={contract} />;
return <AccountSigners contract={clientContract} />;
}
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
@@ -19,14 +18,15 @@ export default async function Page(props: {
notFound();
}
const { contract, chainMetadata } = info;
const { clientContract, serverContract, chainMetadata, isLocalhostChain } =
info;
const account = await getRawAccount();
if (contract.chain.id === localhost.id) {
if (isLocalhostChain) {
return (
<AccountPageClient
contract={contract}
contract={clientContract}
chainMetadata={chainMetadata}
isLoggedIn={!!account}
/>
@@ -34,7 +34,7 @@ export default async function Page(props: {
}
const { isAccount, isInsightSupported } =
await getContractPageMetadata(contract);
await getContractPageMetadata(serverContract);
if (!isAccount) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
@@ -42,7 +42,7 @@ export default async function Page(props: {
return (
<AccountPage
contract={contract}
contract={clientContract}
chainMetadata={chainMetadata}
isLoggedIn={!!account}
isInsightSupported={isInsightSupported}
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
@@ -19,19 +18,24 @@ export default async function Page(props: {
notFound();
}
const { contract, chainMetadata } = info;
const { clientContract, serverContract, isLocalhostChain } = info;
const account = await getRawAccount();
const [account, { isAccountFactory }] = await Promise.all([
getRawAccount(),
isLocalhostChain
? { isAccountFactory: undefined }
: getContractPageMetadata(serverContract),
]);
if (chainMetadata.chainId === localhost.id) {
return <AccountsPageClient contract={contract} isLoggedIn={!!account} />;
if (isLocalhostChain) {
return (
<AccountsPageClient contract={clientContract} isLoggedIn={!!account} />
);
}
const { isAccountFactory } = await getContractPageMetadata(contract);
if (!isAccountFactory) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
return <AccountsPage contract={contract} isLoggedIn={!!account} />;
return <AccountsPage contract={clientContract} isLoggedIn={!!account} />;
}
@@ -1,35 +0,0 @@
"use client";
import type { ThirdwebContract } from "thirdweb";
import { ErrorPage, LoadingPage } from "../_components/page-skeletons";
import { RedirectToContractOverview } from "../_components/redirect-contract-overview.client";
import { useContractPageMetadata } from "../_hooks/useContractPageMetadata";
import { ContractAnalyticsPage } from "./ContractAnalyticsPage";
export function ContractAnalyticsPageClient(props: {
contract: ThirdwebContract;
writeFnSelectorToNameRecord: Record<string, string>;
eventSelectorToNameRecord: Record<string, string>;
}) {
const metadataQuery = useContractPageMetadata(props.contract);
if (metadataQuery.isPending) {
return <LoadingPage />;
}
if (metadataQuery.isError) {
return <ErrorPage />;
}
if (!metadataQuery.data.isInsightSupported) {
return <RedirectToContractOverview contract={props.contract} />;
}
return (
<ContractAnalyticsPage
contract={props.contract}
writeFnSelectorToNameRecord={props.writeFnSelectorToNameRecord}
eventSelectorToNameRecord={props.eventSelectorToNameRecord}
/>
);
}
@@ -1,11 +1,9 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { type ThirdwebContract, resolveContractAbi } from "thirdweb/contract";
import { type Abi, toEventSelector, toFunctionSelector } from "thirdweb/utils";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
import { ContractAnalyticsPage } from "./ContractAnalyticsPage";
import { ContractAnalyticsPageClient } from "./ContractAnalyticsPage.client";
export default async function Page(props: {
params: Promise<{
@@ -20,21 +18,13 @@ export default async function Page(props: {
notFound();
}
const { eventSelectorToName, writeFnSelectorToName } = await getSelectors(
info.contract,
);
if (info.chainMetadata.chainId === localhost.id) {
return (
<ContractAnalyticsPageClient
contract={info.contract}
writeFnSelectorToNameRecord={writeFnSelectorToName}
eventSelectorToNameRecord={eventSelectorToName}
/>
);
}
const { isInsightSupported } = await getContractPageMetadata(info.contract);
const [
{ eventSelectorToName, writeFnSelectorToName },
{ isInsightSupported },
] = await Promise.all([
getSelectors(info.serverContract),
getContractPageMetadata(info.serverContract),
]);
if (!isInsightSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
@@ -42,7 +32,7 @@ export default async function Page(props: {
return (
<ContractAnalyticsPage
contract={info.contract}
contract={info.clientContract}
writeFnSelectorToNameRecord={writeFnSelectorToName}
eventSelectorToNameRecord={eventSelectorToName}
/>
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { ClaimConditions } from "../_components/claim-conditions/claim-conditions";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
@@ -18,27 +17,39 @@ export default async function Page(props: {
if (!info) {
notFound();
}
const { contract, chainMetadata } = info;
const { clientContract, serverContract, isLocalhostChain } = info;
const account = await getRawAccount();
const [
account,
{
isERC20ClaimConditionsSupported,
isERC721ClaimConditionsSupported,
supportedERCs,
},
] = await Promise.all([
getRawAccount(),
isLocalhostChain
? {
isERC20ClaimConditionsSupported: undefined,
isERC721ClaimConditionsSupported: undefined,
supportedERCs: undefined,
}
: getContractPageMetadata(serverContract),
]);
if (chainMetadata.chainId === localhost.id) {
return <ClaimConditionsClient contract={contract} isLoggedIn={!!account} />;
if (isLocalhostChain) {
return (
<ClaimConditionsClient contract={clientContract} isLoggedIn={!!account} />
);
}
const {
isERC20ClaimConditionsSupported,
isERC721ClaimConditionsSupported,
supportedERCs,
} = await getContractPageMetadata(contract);
if (!isERC20ClaimConditionsSupported && !isERC721ClaimConditionsSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
return (
<ClaimConditions
contract={contract}
contract={clientContract}
isERC20={supportedERCs.isERC20}
isLoggedIn={!!account}
isMultiphase={true}
@@ -1,5 +1,4 @@
import { notFound } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { resolveContractAbi } from "thirdweb/contract";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { ContractCodePage } from "./contract-code-page";
@@ -18,23 +17,24 @@ export default async function Page(props: {
notFound();
}
const { contract, chainMetadata } = info;
const { clientContract, serverContract, chainMetadata, isLocalhostChain } =
info;
if (contract.chain.id === localhost.id) {
if (isLocalhostChain) {
return (
<ContractCodePageClient
contract={contract}
contract={clientContract}
chainMetadata={chainMetadata}
/>
);
}
const abi = await resolveContractAbi(contract).catch(() => undefined);
const abi = await resolveContractAbi(serverContract).catch(() => undefined);
return (
<ContractCodePage
abi={abi}
contract={contract}
contract={clientContract}
chainMetadata={chainMetadata}
/>
);
@@ -53,27 +53,28 @@ export default async function Page(props: {
notFound();
}
const { contract } = info;
const { clientContract, serverContract } = info;
const isModularCore = (await getContractPageMetadata(contract)).isModularCore;
const isModularCore = (await getContractPageMetadata(serverContract))
.isModularCore;
let twCloneFactoryContract: Readonly<
ContractOptions<[], `0x${string}`>
> | null = null;
try {
twCloneFactoryContract = await getDeployedCloneFactoryContract({
chain: contract.chain,
client: contract.client,
chain: serverContract.chain,
client: serverContract.client,
});
} catch {}
const originalCode = await eth_getCode(
getRpcClient({
client: contract.client,
chain: contract.chain,
client: serverContract.client,
chain: serverContract.chain,
}),
{
address: contract.address,
address: serverContract.address,
},
);
@@ -82,14 +83,14 @@ export default async function Page(props: {
let isDirectDeploy = false;
try {
const res = await fetch(
`https://contract.thirdweb-dev.com/creation/${contract.chain.id}/${contract.address}`,
`https://contract.thirdweb-dev.com/creation/${serverContract.chain.id}/${serverContract.address}`,
);
const creationData = await res.json();
if (creationData.status === "1" && creationData.result[0]?.txHash) {
const rpcClient = getRpcClient({
client: contract.client,
chain: contract.chain,
client: serverContract.client,
chain: serverContract.chain,
});
creationTxReceipt = await eth_getTransactionReceipt(rpcClient, {
hash: creationData.result[0]?.txHash,
@@ -141,7 +142,7 @@ export default async function Page(props: {
const chainMetadata = await getChainMetadata(chain);
const rpcRequest = getRpcClient({
client: contract.client,
client: serverContract.client,
chain,
});
const code = await eth_getCode(rpcRequest, {
@@ -167,14 +168,14 @@ export default async function Page(props: {
}
}),
)
).filter((c) => c.chainId !== contract.chain.id);
).filter((c) => c.chainId !== serverContract.chain.id);
let coreMetadata: FetchDeployMetadataResult | undefined;
try {
coreMetadata = (
await fetchPublishedContractsFromDeploy({
contract,
client: contract.client,
contract: serverContract,
client: serverContract.client,
})
).at(-1) as FetchDeployMetadataResult;
} catch {}
@@ -201,7 +202,7 @@ export default async function Page(props: {
// fetch events from contract
if (!modules && creationBlockNumber) {
const events = await getContractEvents({
contract: contract,
contract: serverContract,
events: [moduleEvent],
});
@@ -216,11 +217,13 @@ export default async function Page(props: {
if (!modules && initializeData) {
// biome-ignore lint/suspicious/noExplicitAny: FIXME
const decodedData: any = await decodeFunctionData({
contract,
contract: serverContract,
data: initializeData,
});
const abi = await resolveContractAbi(contract).catch(() => undefined);
const abi = await resolveContractAbi(serverContract).catch(
() => undefined,
);
if (abi) {
const initializeFunction = abi.find(
@@ -241,11 +244,11 @@ export default async function Page(props: {
(
await fetchPublishedContractsFromDeploy({
contract: getContract({
chain: contract.chain,
client: contract.client,
chain: serverContract.chain,
client: serverContract.client,
address: m,
}),
client: contract.client,
client: serverContract.client,
})
).at(-1),
),
@@ -275,7 +278,7 @@ export default async function Page(props: {
initializeData={initializeData}
inputSalt={inputSalt}
data={chainsDeployedOn}
coreContract={contract}
coreContract={clientContract}
initCode={initCode}
isDirectDeploy={isDirectDeploy}
/>
@@ -29,7 +29,7 @@ export function SingleNetworkSelector(props: {
return false;
}
if (Number.isInteger(Number.parseInt(searchValue))) {
if (Number.isInteger(Number(searchValue))) {
return String(chain.chainId).startsWith(searchValue);
}
return chain.name.toLowerCase().includes(searchValue.toLowerCase());
@@ -15,5 +15,5 @@ export default async function Page(props: {
notFound();
}
return <EventsFeed contract={info.contract} />;
return <EventsFeed contract={info.clientContract} />;
}
@@ -1,5 +1,4 @@
import { notFound } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { resolveContractAbi } from "thirdweb/contract";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
@@ -19,25 +18,26 @@ export default async function Page(props: {
notFound();
}
const { contract, chainMetadata } = info;
const { clientContract, serverContract, chainMetadata, isLocalhostChain } =
info;
const account = await getRawAccount();
if (contract.chain.id === localhost.id) {
if (isLocalhostChain) {
return (
<ContractExplorerPageClient
contract={contract}
contract={clientContract}
chainMetadata={chainMetadata}
isLoggedIn={!!account}
/>
);
}
const abi = await resolveContractAbi(contract).catch(() => undefined);
const abi = await resolveContractAbi(serverContract).catch(() => undefined);
return (
<ContractExplorerPage
contract={contract}
contract={clientContract}
abi={abi}
chainMetadata={chainMetadata}
isLoggedIn={!!account}
@@ -4,7 +4,6 @@ import type { MinimalTeamsAndProjects } from "components/contract-components/con
import { resolveFunctionSelectors } from "lib/selectors";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getContractMetadata } from "thirdweb/extensions/common";
import { isAddress, isContractDeployed } from "thirdweb/utils";
import { shortenIfAddress } from "utils/usedapp-external";
@@ -40,7 +39,8 @@ export default async function Layout(props: {
return <ConfigureCustomChain chainSlug={params.chain_id} />;
}
const { contract, chainMetadata } = info;
const { clientContract, serverContract, chainMetadata, isLocalhostChain } =
info;
if (chainMetadata.status === "deprecated") {
notFound();
@@ -51,11 +51,11 @@ export default async function Layout(props: {
const client = await getUserThirdwebClient();
const teamsAndProjects = await getTeamsAndProjectsIfLoggedIn();
if (contract.chain.id === localhost.id) {
if (isLocalhostChain) {
return (
<ContractPageLayoutClient
chainMetadata={chainMetadata}
contract={contract}
contract={clientContract}
teamsAndProjects={teamsAndProjects}
client={client}
>
@@ -65,25 +65,27 @@ export default async function Layout(props: {
}
// check if the contract exists
const isValidContract = await isContractDeployed(contract).catch(() => false);
const isValidContract = await isContractDeployed(serverContract).catch(
() => false,
);
if (!isValidContract) {
// TODO - replace 404 with a better page to upsell deploy or other thirdweb products
notFound();
}
const contractPageMetadata = await getContractPageMetadata(contract);
const contractPageMetadata = await getContractPageMetadata(serverContract);
const sidebarLinks = getContractPageSidebarLinks({
chainSlug: chainMetadata.slug,
contractAddress: contract.address,
contractAddress: serverContract.address,
metadata: contractPageMetadata,
});
const { contractMetadata, externalLinks } =
await getContractMetadataHeaderData(contract);
await getContractMetadataHeaderData(serverContract);
const contractAddress = info.contract.address;
const chainName = info.chainMetadata.name;
const chainId = info.contract.chain.id;
const contractAddress = serverContract.address;
const chainName = chainMetadata.name;
const chainId = chainMetadata.chainId;
const contractPromptPrefix = `A user is viewing the contract address ${contractAddress} on ${chainName} (Chain ID: ${chainId}). Provide a concise summary of this contract's functionalities, such as token minting, staking, or governance mechanisms. Focus on what the contract enables users to do, avoiding transaction execution details unless requested.
Users may be interested in how to interact with the contract. Outline common interaction patterns, such as claiming rewards, participating in governance, or transferring assets. Emphasize the contract's capabilities without guiding through transaction processes unless asked.
@@ -103,7 +105,7 @@ The following is the user's message:`;
return (
<ContractPageLayout
chainMetadata={chainMetadata}
contract={contract}
contract={clientContract}
sidebarLinks={sidebarLinks}
dashboardContractMetadata={contractMetadata}
externalLinks={externalLinks}
@@ -119,7 +121,7 @@ The following is the user's message:`;
client={client}
nebulaParams={{
messagePrefix: contractPromptPrefix,
chainIds: [info.contract.chain.id],
chainIds: [chainId],
wallet: accountAddress ?? undefined,
}}
examplePrompts={examplePrompts.map((prompt) => ({
@@ -177,9 +179,9 @@ export async function generateMetadata(props: {
}
const [functionSelectors, contractMetadata] = await Promise.all([
resolveFunctionSelectors(info.contract),
resolveFunctionSelectors(info.serverContract),
getContractMetadata({
contract: info.contract,
contract: info.serverContract,
}),
]);
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
@@ -19,24 +18,26 @@ export default async function Page(props: {
notFound();
}
const { contract } = info;
const { clientContract, serverContract, isLocalhostChain } = info;
const account = await getRawAccount();
if (contract.chain.id === localhost.id) {
if (isLocalhostChain) {
return (
<ContractEditModulesPageClient
contract={contract}
contract={clientContract}
isLoggedIn={!!account}
/>
);
}
const { isModularCore } = await getContractPageMetadata(contract);
const { isModularCore } = await getContractPageMetadata(serverContract);
if (!isModularCore) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
return <ContractEditModulesPage contract={contract} isLoggedIn={!!account} />;
return (
<ContractEditModulesPage contract={clientContract} isLoggedIn={!!account} />
);
}
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../../account/settings/getAccount";
import { getAuthTokenWalletAddress } from "../../../../../../api/lib/getAuthToken";
import { getContractPageParamsInfo } from "../../_utils/getContractFromParams";
@@ -30,11 +29,11 @@ export default async function Page(props: {
getRawAccount(),
]);
const { contract } = info;
if (contract.chain.id === localhost.id) {
const { clientContract, serverContract, isLocalhostChain } = info;
if (isLocalhostChain) {
return (
<TokenIdPageClient
contract={contract}
contract={clientContract}
tokenId={params.tokenId}
isLoggedIn={!!account}
accountAddress={accountAddress || undefined}
@@ -42,7 +41,7 @@ export default async function Page(props: {
);
}
const { supportedERCs } = await getContractPageMetadata(contract);
const { supportedERCs } = await getContractPageMetadata(serverContract);
if (!supportedERCs.isERC721 && !supportedERCs.isERC1155) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
@@ -50,7 +49,7 @@ export default async function Page(props: {
return (
<TokenIdPage
contract={contract}
contract={clientContract}
isErc721={supportedERCs.isERC721}
tokenId={params.tokenId}
isLoggedIn={!!account}
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
@@ -21,13 +20,15 @@ export default async function Page(props: {
const account = await getRawAccount();
const { contract } = info;
if (contract.chain.id === localhost.id) {
return <ContractNFTPageClient contract={contract} isLoggedIn={!!account} />;
const { clientContract, serverContract, isLocalhostChain } = info;
if (isLocalhostChain) {
return (
<ContractNFTPageClient contract={clientContract} isLoggedIn={!!account} />
);
}
const { supportedERCs, functionSelectors } =
await getContractPageMetadata(contract);
await getContractPageMetadata(serverContract);
if (!supportedERCs.isERC721 && !supportedERCs.isERC1155) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
@@ -35,7 +36,7 @@ export default async function Page(props: {
return (
<ContractNFTPage
contract={contract}
contract={clientContract}
isErc721={supportedERCs.isERC721}
functionSelectors={functionSelectors}
isLoggedIn={!!account}
@@ -1,5 +1,6 @@
import { isProd } from "@/constants/env-utils";
import { API_ROUTES_CLIENT_ID } from "@/constants/server-envs";
/* eslint-disable @next/next/no-img-element */
import { isProd } from "@/constants/env";
import { ImageResponse } from "@vercel/og";
import { getContractMetadata } from "thirdweb/extensions/common";
import { getContractPageParamsInfo } from "./_utils/getContractFromParams";
@@ -27,7 +28,7 @@ export default async function Image({
}
const contractMetadata = await getContractMetadata({
contract: info.contract,
contract: info.serverContract,
});
if (contractMetadata.name === null) {
@@ -40,7 +41,7 @@ export default async function Image({
return contractOGImageTemplate({
displayName: contractDisplayName,
contractAddress: info.contract.address,
contractAddress: info.serverContract.address,
chainName: info.chainMetadata.name,
logo: contractMetadata.image,
});
@@ -125,8 +126,8 @@ function textShortener(text: string) {
return `${words.join(" ")} ...`;
}
const IPFS_GATEWAY = process.env.API_ROUTES_CLIENT_ID
? `https://${process.env.API_ROUTES_CLIENT_ID}.${isProd ? "ipfscdn.io/ipfs/" : "thirdwebstorage-dev.com/ipfs/"}`
const IPFS_GATEWAY = API_ROUTES_CLIENT_ID
? `https://${API_ROUTES_CLIENT_ID}.${isProd ? "ipfscdn.io/ipfs/" : "thirdwebstorage-dev.com/ipfs/"}`
: "https://ipfs.io/ipfs/";
function replaceAnyIpfsUrlWithGateway(url: string) {
@@ -147,7 +147,6 @@ export function PublishedByUI(props: {
publisher: string;
version: string | undefined;
isBeta: boolean;
client: ThirdwebClient;
}) {
return (
<ContractCard
@@ -160,7 +159,6 @@ export function PublishedByUI(props: {
moduleId: m.name,
version: m.version,
}))}
client={props.client}
/>
);
}
@@ -25,7 +25,6 @@ export const PublishedBy: React.FC<PublishedByProps> = async ({ contract }) => {
name={props.name}
publisher={props.publisher}
version={props.version}
client={contract.client}
/>
);
};
@@ -1,6 +1,5 @@
import { notFound } from "next/navigation";
import { ErrorBoundary } from "react-error-boundary";
import { localhost } from "thirdweb/chains";
import { getContractPageParamsInfo } from "./_utils/getContractFromParams";
import { getContractPageMetadata } from "./_utils/getContractPageMetadata";
import { ContractOverviewPage } from "./overview/ContractOverviewPage";
@@ -20,21 +19,22 @@ export default async function Page(props: {
notFound();
}
const { contract, chainMetadata } = info;
if (contract.chain.id === localhost.id) {
const { clientContract, serverContract, chainMetadata, isLocalhostChain } =
info;
if (isLocalhostChain) {
return (
<ContractOverviewPageClient
chainMetadata={chainMetadata}
contract={contract}
contract={clientContract}
/>
);
}
const contractPageMetadata = await getContractPageMetadata(contract);
const contractPageMetadata = await getContractPageMetadata(serverContract);
return (
<ContractOverviewPage
contract={contract}
contract={clientContract}
hasDirectListings={contractPageMetadata.isDirectListingSupported}
hasEnglishAuctions={contractPageMetadata.isEnglishAuctionSupported}
isErc1155={contractPageMetadata.supportedERCs.isERC1155}
@@ -48,7 +48,7 @@ export default async function Page(props: {
functionSelectors={contractPageMetadata.functionSelectors}
publishedBy={
<ErrorBoundary fallback={null}>
<PublishedBy contract={contract} />
<PublishedBy contract={serverContract} />
</ErrorBoundary>
}
/>
@@ -1,5 +1,4 @@
import { notFound } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
@@ -21,11 +20,11 @@ export default async function Page(props: {
const account = await getRawAccount();
const { contract } = info;
if (contract.chain.id === localhost.id) {
const { clientContract, serverContract, isLocalhostChain } = info;
if (isLocalhostChain) {
return (
<ContractPermissionsPageClient
contract={contract}
contract={clientContract}
chainMetadata={info.chainMetadata}
isLoggedIn={!!account}
/>
@@ -33,11 +32,11 @@ export default async function Page(props: {
}
const { isPermissionsEnumerableSupported } =
await getContractPageMetadata(contract);
await getContractPageMetadata(serverContract);
return (
<ContractPermissionsPage
contract={contract}
contract={clientContract}
chainSlug={info.chainMetadata.slug}
detectedPermissionEnumerable={isPermissionsEnumerableSupported}
isLoggedIn={!!account}
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
@@ -20,18 +19,23 @@ export default async function Page(props: {
}
const account = await getRawAccount();
const { contract } = info;
if (contract.chain.id === localhost.id) {
const { clientContract, serverContract, isLocalhostChain } = info;
if (isLocalhostChain) {
return (
<ContractProposalsPageClient contract={contract} isLoggedIn={!!account} />
<ContractProposalsPageClient
contract={clientContract}
isLoggedIn={!!account}
/>
);
}
const { isVoteContract } = await getContractPageMetadata(contract);
const { isVoteContract } = await getContractPageMetadata(serverContract);
if (!isVoteContract) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
return <ContractProposalsPage contract={contract} isLoggedIn={!!account} />;
return (
<ContractProposalsPage contract={clientContract} isLoggedIn={!!account} />
);
}
@@ -1,6 +1,5 @@
import { DEFAULT_FEE_RECIPIENT } from "constants/addresses";
import { notFound } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getPlatformFeeInfo } from "thirdweb/extensions/common";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
@@ -21,30 +20,33 @@ export default async function Page(props: {
notFound();
}
const { contract } = info;
const { clientContract, serverContract, isLocalhostChain } = info;
if (contract.chain.id === localhost.id) {
if (isLocalhostChain) {
const account = await getRawAccount();
return (
<ContractSettingsPageClient contract={contract} isLoggedIn={!!account} />
<ContractSettingsPageClient
contract={clientContract}
isLoggedIn={!!account}
/>
);
}
const [account, metadata] = await Promise.all([
getRawAccount(),
getContractPageMetadata(info.contract),
getContractPageMetadata(serverContract),
]);
let hasDefaultFeeConfig = true;
try {
const feeInfo = await getPlatformFeeInfo({ contract });
const feeInfo = await getPlatformFeeInfo({ contract: serverContract });
hasDefaultFeeConfig =
feeInfo[0].toLowerCase() === DEFAULT_FEE_RECIPIENT.toLowerCase();
} catch {}
return (
<ContractSettingsPage
contract={info.contract}
contract={clientContract}
functionSelectors={metadata.functionSelectors}
isLoggedIn={!!account}
hasDefaultFeeConfig={hasDefaultFeeConfig}
@@ -15,5 +15,5 @@ export default async function Page(props: {
notFound();
}
return <ContractSourcesPage contract={info.contract} />;
return <ContractSourcesPage contract={info.clientContract} />;
}
@@ -1,5 +1,4 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
@@ -18,21 +17,26 @@ export default async function Page(props: {
if (!info) {
notFound();
}
const { contract } = info;
const { clientContract, serverContract, isLocalhostChain } = info;
const twAccount = await getRawAccount();
if (contract.chain.id === localhost.id) {
if (isLocalhostChain) {
return (
<ContractSplitPageClient contract={contract} isLoggedIn={!!twAccount} />
<ContractSplitPageClient
contract={clientContract}
isLoggedIn={!!twAccount}
/>
);
}
const { isSplitSupported } = await getContractPageMetadata(contract);
const { isSplitSupported } = await getContractPageMetadata(serverContract);
if (!isSplitSupported) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
return <ContractSplitPage contract={contract} isLoggedIn={!!twAccount} />;
return (
<ContractSplitPage contract={clientContract} isLoggedIn={!!twAccount} />
);
}
@@ -1,5 +1,4 @@
import { notFound } from "next/navigation";
import { localhost } from "thirdweb/chains";
import {
isClaimToSupported,
isMintToSupported,
@@ -25,22 +24,22 @@ export default async function Page(props: {
const account = await getRawAccount();
if (info.contract.chain.id === localhost.id) {
if (info.isLocalhostChain) {
return (
<ContractTokensPageClient
contract={info.contract}
contract={info.clientContract}
isLoggedIn={!!account}
/>
);
}
const { supportedERCs, functionSelectors } = await getContractPageMetadata(
info.contract,
info.serverContract,
);
return (
<ContractTokensPage
contract={info.contract}
contract={info.clientContract}
isERC20={supportedERCs.isERC20}
isMintToSupported={isMintToSupported(functionSelectors)}
isClaimToSupported={isClaimToSupported(functionSelectors)}
@@ -1,5 +1,6 @@
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { mapV4ChainToV5Chain } from "contexts/map-chains";
import { ZERO_ADDRESS, toTokens } from "thirdweb";
import {
@@ -9,18 +10,16 @@ import {
getRpcClient,
} from "thirdweb/rpc";
import { hexToNumber, shortenAddress, toEther } from "thirdweb/utils";
import { getUserThirdwebClient } from "../../../../../api/lib/getAuthToken";
import { getChain } from "../../../utils";
export default async function Page(props: {
params: Promise<{ chain_id: string; txHash: `0x${string}` }>;
}) {
const params = await props.params;
const client = await getUserThirdwebClient();
const chain = await getChain(params.chain_id);
const rpcRequest = getRpcClient({
client,
client: serverThirdwebClient,
// Do not include chain overrides for chain pages
// eslint-disable-next-line no-restricted-syntax
chain: mapV4ChainToV5Chain(chain),
@@ -14,10 +14,7 @@ import {
import { ChevronDownIcon } from "lucide-react";
import { headers } from "next/headers";
import Link from "next/link";
import {
getAuthToken,
getUserThirdwebClient,
} from "../../../../api/lib/getAuthToken";
import { getAuthToken } from "../../../../api/lib/getAuthToken";
import {
AllFilters,
ChainOptionsFilter,
@@ -83,8 +80,6 @@ export default async function ChainListLayout(props: {
? "table"
: "grid";
const client = await getUserThirdwebClient();
return (
<>
<div className="flex h-14 border-border border-b pl-7">
@@ -155,7 +150,6 @@ export default async function ChainListLayout(props: {
searchParams={searchParams}
activeView={activeView}
isLoggedIn={!!authToken}
client={client}
/>
</section>
</>
@@ -7,7 +7,6 @@ import {
TableRow,
} from "@/components/ui/table";
import Fuse from "fuse.js";
import type { ThirdwebClient } from "thirdweb";
import { StarButton } from "../../../components/client/star-button";
import type {
ChainMetadataWithServices,
@@ -142,7 +141,6 @@ export async function ChainsData(props: {
searchParams: SearchParams;
activeView: "table" | "grid";
isLoggedIn: boolean;
client: ThirdwebClient;
}) {
const { chainsToRender, totalCount, filteredCount } = await getChainsToRender(
props.searchParams,
@@ -179,7 +177,6 @@ export async function ChainsData(props: {
<TableBody>
{paginatedChains.map((chain) => (
<ChainListRow
client={props.client}
key={chain.chainId}
chainId={chain.chainId}
chainName={chain.name}
@@ -210,7 +207,6 @@ export async function ChainsData(props: {
{paginatedChains.map((chain) => (
<li key={chain.chainId} className="h-full">
<ChainListCard
client={props.client}
key={chain.chainId}
chainId={chain.chainId}
chainName={chain.name}
@@ -3,7 +3,6 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { CircleAlertIcon, TicketCheckIcon } from "lucide-react";
import Link from "next/link";
import type { JSX } from "react";
import type { ThirdwebClient } from "thirdweb";
import { ChainIcon } from "../../../components/server/chain-icon";
import type { ChainSupportedService } from "../../../types/chain";
import { getChainMetadata } from "../../../utils";
@@ -17,7 +16,6 @@ type ChainListCardProps = {
currencySymbol: string;
isDeprecated: boolean;
iconUrl?: string;
client: ThirdwebClient;
};
export async function ChainListCard({
@@ -29,7 +27,6 @@ export async function ChainListCard({
enabledServices,
favoriteButton,
iconUrl,
client,
}: ChainListCardProps) {
const chainMetadata = await getChainMetadata(chainId);
@@ -38,7 +35,7 @@ export async function ChainListCard({
<Card className="h-full w-full transition-colors hover:border-active-border">
<CardHeader className="flex flex-row items-center justify-between p-4">
<div className="flex flex-row items-center gap-2">
<ChainIcon iconUrl={iconUrl} className="size-6" client={client} />
<ChainIcon iconUrl={iconUrl} className="size-6" />
<Link
className="group static before:absolute before:top-0 before:right-0 before:bottom-0 before:left-0 before:z-0 before:content-['']"
href={`/${chainSlug}`}
@@ -10,7 +10,6 @@ import {
} from "lucide-react";
import Link from "next/link";
import type { JSX } from "react";
import type { ThirdwebClient } from "thirdweb";
import { ChainIcon } from "../../../components/server/chain-icon";
import { products } from "../../../components/server/products";
import type { ChainSupportedService } from "../../../types/chain";
@@ -25,7 +24,6 @@ type ChainListRowProps = {
currencySymbol: string;
isDeprecated: boolean;
iconUrl?: string;
client: ThirdwebClient;
};
export async function ChainListRow({
@@ -37,7 +35,6 @@ export async function ChainListRow({
enabledServices,
favoriteButton,
iconUrl,
client,
}: ChainListRowProps) {
const chainMetadata = await getChainMetadata(chainId);
return (
@@ -47,7 +44,7 @@ export async function ChainListRow({
<div className="flex w-[370px] flex-row items-center gap-4">
<div className="flex items-center gap-2">
{favoriteButton && <div className="mr-6"> {favoriteButton} </div>}
<ChainIcon iconUrl={iconUrl} className="size-6" client={client} />
<ChainIcon iconUrl={iconUrl} className="size-6" />
<Link
href={`/${chainSlug}`}
className="group static before:absolute before:top-0 before:right-0 before:bottom-0 before:left-0 before:z-0 before:content-['']"
@@ -1,9 +1,6 @@
import type { Metadata } from "next";
import { headers } from "next/headers";
import {
getAuthToken,
getUserThirdwebClient,
} from "../../../api/lib/getAuthToken";
import { getAuthToken } from "../../../api/lib/getAuthToken";
import {
AllFilters,
ChainOptionsFilter,
@@ -45,8 +42,6 @@ export default async function ChainListPage(props: {
? "table"
: "grid";
const client = await getUserThirdwebClient();
return (
<section className="container mx-auto flex h-full flex-col px-4 py-10">
<header className="flex flex-col gap-4">
@@ -81,7 +76,6 @@ export default async function ChainListPage(props: {
searchParams={searchParams}
activeView={activeView}
isLoggedIn={!!authToken}
client={client}
/>
</section>
);
@@ -1,21 +1,20 @@
/* eslint-disable @next/next/no-img-element */
import "server-only";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import { cn } from "@/lib/utils";
import type { ThirdwebClient } from "thirdweb";
import { fallbackChainIcon } from "../../../../../../utils/chain-icons";
export async function ChainIcon(props: {
iconUrl?: string;
className?: string;
client: ThirdwebClient;
}) {
if (props.iconUrl) {
let imageLink = fallbackChainIcon;
const resolved = resolveSchemeWithErrorHandler({
client: props.client,
client: serverThirdwebClient,
uri: props.iconUrl,
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

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