Compare commits

...
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
c645b42684 Version Packages (#6208)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-12 01:36:43 -08:00
Joaquim VergesandGitHub 7583074c06 Update page.mdx (#6235)
Signed-off-by: Joaquim Verges <[email protected]>
2025-02-12 01:30:39 -08:00
Joaquim VergesandGitHub fbafb65f5c [SDK] fix: Polish PayEmbed UI and balance display (#6237) 2025-02-12 01:30:20 -08:00
Joaquim VergesandGitHub 6b286c7e83 [SDK] fix: Disable amount editing for non-wallet funding modes (#6236) 2025-02-12 01:23:28 -08:00
joaquim-verges 159ffbfaee [SDK] feat: Add verifyClaim export and canClaim function (#6234)
Fixes TOOL-3381

<!--

## 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 exposing the `canClaim` extension function for ERC20, ERC721, and ERC1155 drops, allowing for better claim verification and improving the overall functionality of the drops system.

### Detailed summary
- Added `canClaim` function to `erc20`, `erc721`, and `erc1155` extensions.
- Updated tests to include `canClaim` functionality.
- Modified JSON ABI files to include `verifyClaim` function.
- Adjusted gas values in tests.
- Enhanced error handling in `extract-error.ts`.

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

<!-- end pr-codex -->
2025-02-12 07:56:01 +00:00
kumaryash90 0c1bb6ccae [Dashboard] Deploy config updates (#6159)
TOOL-3389

<!--

## 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 default fee configuration for contract deployments, updates the handling of platform fees, and improves the UI components related to fee management, ensuring that default values are used consistently throughout the application.

### Detailed summary
- Added `DEFAULT_FEE_RECIPIENT` and `DEFAULT_FEE_BPS` to `addresses.ts`.
- Introduced `hasDefaultFeeConfig` in `ContractSettingsPage`.
- Filtered out specific functions related to platform fees in `contract-function.tsx`.
- Integrated platform fee logic in `CustomContractForm`.
- Updated `PlatformFeeFieldset` to reflect marketplace conditions.

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

<!-- end pr-codex -->
2025-02-11 22:00:08 +00:00
Shubham ParkhiandGitHub dceb47de8b [SDK/Dashboard/Portal] Feature/Fix: fix typo in docs (#6229)
Signed-off-by: Shubham Parkhi <[email protected]>
2025-02-10 21:19:02 -08:00
Jonas DanielsandGitHub dfbdbd06e5 use supportPlan instead of billingPlan for support tickets (#6228) 2025-02-10 21:17:24 -08:00
66bbedbe3a [SDK] Feature: add treasure and treasure topaz (#6191)
Co-authored-by: Joaquim Verges <[email protected]>
2025-02-10 19:42:15 -08:00
Joaquim VergesandGitHub 2fbf105d15 [SDK] fix: Disable quote retries and show fiat values (#6210) 2025-02-10 19:41:42 -08:00
FirekeeperandGitHub afcaa91873 Docs remove legacy enc key arg (#6220) 2025-02-10 19:41:08 -08:00
Manan TankandGitHub 51f955352d [TOOL-3377] Portal: Remove v4 SDK links, setup missing redirects (#6222) 2025-02-10 19:40:48 -08:00
Joaquim VergesandGitHub 6b2a90ac89 [SDK] feat: Add session key update handling and expose utility (#6217) 2025-02-10 19:40:06 -08:00
MananTank cf53184bb1 [TOOL-3379] Dashboard: Add team selector in support form (#6227) 2025-02-10 22:21:18 +00:00
MananTank 815b9b1ce1 [NEB-92] Dashboard: Add Nebula Analytics dashboard (#6198)
<!-- start pr-codex -->

## PR-Codex overview
This PR primarily focuses on enhancing the `Nebula` analytics functionality by adding new components and improving data handling for analytics. It introduces new utilities for managing date ranges and search parameters, along with updates to the UI for better user interaction.

### Detailed summary
- Added `getNebulaAnalyticsRangeFromSearchParams` function in `utils.ts`.
- Updated `ProjectTabs` to remove `isOnNebulaWaitList` prop.
- Introduced `normalizeTime` function in `time.ts` for date normalization.
- Created `NebulaAnalyticsFilter` component for filtering analytics data.
- Implemented `NebulaAnalyticsPage` and `NebulaAnalyticDashboard` for displaying analytics.
- Enhanced `DateRangeSelector` with popover alignment options.
- Added `NebulaAnalyticsDashboardUI` for rendering analytics data and charts.
- Integrated `responsive-rsc` for responsive search parameters.
- Updated `package.json` to include `responsive-rsc` dependency.
- Removed calls to `getTeamNebulaWaitList` in `layout.tsx` and associated logic.

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

<!-- end pr-codex -->
2025-02-10 20:05:31 +00:00
Maxim EvtushandGitHub ae1baa5105 fix: typos in documentation files (#6215)
Signed-off-by: Maxim Evtush <[email protected]>
2025-02-10 10:40:32 -08:00
saminacodes 92266b7459 [Portal] Cosmetic Changes (#6218)
CORE-0000

---

## PR-Codex overview
This PR focuses on updating content and structure across various pages in the `apps/portal` application, including the addition of new features, improved descriptions, and adjustments to the sidebar navigation.

### Detailed summary
- Deleted `supported-chains.png` and `context-filters.png`.
- Added new content for `faqs`, `troubleshoot`, and `Nebula` guides.
- Updated the sidebar with new links and icons.
- Changed import paths for images.
- Enhanced descriptions in `Deploy`, `Contracts`, and `Insight` pages.
- Added a new section for "Supported Chains" in `Contracts` and `Insight` pages.

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

<!-- end pr-codex -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the documentation and content for the `thirdweb` platform, particularly for the `Insight` and `Nebula` sections, while also restructuring some sidebar links and enhancing the user interface with new images.

### Detailed summary
- Deleted `supported-chains` image and `page.mdx` files.
- Added new content for `Insight FAQs` and `Troubleshoot Guide`.
- Updated `Nebula` description and added a support link.
- Changed sidebar items for `Insight` and `Contracts`.
- Introduced new features and layout in `Insight` and `Contracts` pages.
- Added support for displaying `Supported Chains` images in relevant sections.

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

<!-- end pr-codex -->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updates to the `insight` and `nebula` sections of the application, including the addition of new content, restructuring of sidebar navigation, and changes to image imports. It also enhances the user interface with improved documentation and guidance.

### Detailed summary
- Deleted `apps/portal/src/app/insight/supported-chains/page.mdx`.
- Added new content for `Insight FAQs`, `Insight Troubleshoot Guide`, and `Nebula Troubleshoot Guide`.
- Updated sidebar navigation in `apps/portal/src/app/insight/sidebar.tsx`.
- Changed the name and href for several sidebar items.
- Reorganized `Supported Chains` references and added a new image import in multiple files.
- Enhanced content descriptions in `apps/portal/src/app/contracts/deploy/overview/page.mdx`.
- Added new features and video tutorials in `apps/portal/src/app/insight/page.mdx`.
- Updated layout and structure of `Contracts` section in `apps/portal/src/app/contracts/page.mdx`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
2025-02-10 17:19:27 +00:00
MananTank 80ad84b803 [TOOL-3376] Dashboard: Fix 404 on publish contract page (#6221) 2025-02-10 17:01:59 +00:00
saminacodes 633f0cadf1 [Portal] Nebula documentation update (#6201)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the documentation for `thirdweb Nebula`, adding detailed metadata, explanations of new features, and clarifying existing concepts. It includes updates to various pages, improving user understanding of plugins, sessions, context filters, and response handling.

### Detailed summary
- Added `metadata` exports to multiple `.mdx` files.
- Enhanced FAQ entries with links and additional context.
- Updated `sidebar` to include new key concepts.
- Added sections on `Chat & Execute endpoints`, `Execute Configuration`, and `Context Filters`.
- Introduced new content on `Response Handling` and streaming vs non-streaming responses.
- Clarified `Sessions` management and benefits.
- Replaced `Stack` with `Grid` in the `page.mdx` and updated links.
- Improved descriptions and examples for API usage and configurations.

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

<!-- end pr-codex -->
2025-02-10 16:00:45 +00:00
Phillip HoandGitHub 91b0367b23 [service-utils] fix auth cache for cf workers (#6219) 2025-02-10 23:51:58 +08:00
MananTank c86e13b522 [TOOL-3366] Dashboard: Contract Deployment UI improvements, Reduce Chakra Usage (#6211)
<!--

## 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 refining the UI components and improving the overall structure of the contract deployment forms. It introduces new styles, modifies existing components, and enhances accessibility.

### Detailed summary
- Updated `ThemeProvider` to enable color scheme.
- Changed class names for various components to improve styling.
- Simplified props for `SolidityInput` components.
- Replaced `FormControl` with `FormFieldSetup` for better structure.
- Enhanced accessibility by modifying error messages and labels.
- Streamlined imports and removed unnecessary components.
- Improved the layout and styling of various fieldsets and buttons.
- Added new utility functions for better handling of form states.

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

<!-- end pr-codex -->
2025-02-09 21:35:45 +05:30
MananTank bc42c2fdfb [TOOL-3368] Dashboard: Improve page load perf of explore page and other few others (#6214)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on updating the contract fetching logic in the `dashboard` application to improve the retrieval of the latest published contracts. It replaces older functions with new ones that specifically fetch the latest contract versions.

### Detailed summary
- Added `export const dynamic = "force-static";` to `page.tsx` files.
- Replaced `getPublishedContractsWithPublisherMapping` with `getLatestPublishedContractsWithPublisherMapping` in multiple files.
- Introduced `getLatestPublishedContractsWithPublisherMapping` function to fetch the latest contract version.
- Updated `fetchPublishedContractVersions` to use `getSortedPublishedContractVersions`.
- Created `fetchLatestPublishedContractVersion` for fetching the latest version details.
- Modified `ContractCard` to use `ClientOnly` for rendering the `ContractPublisher`.

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

<!-- end pr-codex -->
2025-02-09 15:33:14 +00:00
Phillip HoandGitHub cad9c74bef [service-utils] Omit team_id for client usageV2 events (#6213) 2025-02-09 17:59:56 +08:00
Phillip HoandGitHub 6f2f92ddea [service-utils]H elper to call client usageV2 reporting endpoint (#6212) 2025-02-09 17:26:08 +08:00
Joaquim VergesandGitHub 97c9ab7e1c [SDK] feat: Batch approvals and swaps for smart wallets (#6209) 2025-02-08 03:04:11 -08:00
d4mr b73fa4b16f [dashboard-engine] updates for circle and wallet credentials (#6202)
closes TOOL-3351

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces support for managing Circle wallet credentials in the thirdweb Engine, enhancing the wallet management features by allowing users to create, update, and configure Circle wallet credentials.

### Detailed summary
- Added `Wallet Credentials` section in the sidebar.
- Introduced `Circle` wallet option in backend wallet configurations.
- Created new components for managing Circle wallet credentials.
- Implemented credential creation and update functionalities.
- Updated UI components to support Circle wallet management.
- Enhanced documentation for Circle wallet setup and usage.

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

<!-- end pr-codex -->
2025-02-08 09:45:46 +00:00
Phillip HoandGitHub cbfe9ec459 [service-utils] Allow sending client-side usageV2 events (#6207) 2025-02-08 13:07:57 +08:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
fecd3a6c16 Version Packages (#6205)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-07 20:07:21 -08:00
Phillip HoandGitHub 8aeb4d6308 chore: Export sources array (#6206) 2025-02-08 12:00:43 +08:00
Joaquim VergesandGitHub f1cd2539d1 [SDK] chore: Update coinbase/wallet-sdk (#6204) 2025-02-07 19:31:39 -08:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
a322e16853 Version Packages (#6184)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-07 16:10:52 -08:00
0xFirekeeper e50d77d28f Docs up to .NET 2.17.1 / Unity 5.17.1 (#6200)
Closes TOOL-2991
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces enhancements to the `sidebar` structure and API documentation, adds new wallet authentication options, and refines existing content for clarity. It also includes new RPC overrides and updates to the `InAppWallet` and `EcosystemWallet` functionalities.

### Detailed summary
- Added `rpcOverrides` example to `page.mdx`.
- Updated `sidebar.tsx` to include "API Reference" and "Blockchain API" sections.
- Replaced "Blockchain API" with "Nebula AI" in `sidebar.tsx`.
- Enhanced `EcosystemWallet` and `InAppWallet` functionalities with new login options.
- Clarified documentation on wallet connections and authentication methods.
- Added examples for `SiweExternal` login process.
- Updated various sections in `page.mdx` for better clarity and detail.

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

<!-- end pr-codex -->
2025-02-07 21:06:12 +00:00
MananTank 7f3b91da37 [TOOL-3346] Portal: Add Nebula icon support in dynamic OG image generation (#6199) 2025-02-07 21:02:35 +00:00
MananTank 41d50443d9 [TOOL-3328] Dashboard: Show error message in toast on Email submission fail in onboaridng flow (#6186)
<!-- start pr-codex -->

## PR-Codex overview
This PR enhances error handling in the `AccountForm` component by improving the way errors are processed and displayed to the user.

### Detailed summary
- Replaced the `onError` parameter's type from `Error` to a more generic `error`.
- Added console logging for errors.
- Implemented specific error handling for duplicate email and invalid email address cases.
- Integrated `toast` notifications for user-friendly error messages.

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

<!-- end pr-codex -->
2025-02-07 20:40:27 +00:00
catalyst17andcatalyst17 a8ac98bd49 feat: allowedToCreateChains in TeamResponse (#6172)
<!--

## 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 the `canCreatePublicChains` property to the `mocks.ts` and modifying its type in `api.ts` to enhance the API's capabilities regarding public chain creation.

### Detailed summary
- Added `canCreatePublicChains: false` to the object in `mocks.ts`.
- Updated the type of `canCreatePublicChains` in `api.ts` to `boolean | null`.

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

<!-- end pr-codex -->
2025-02-07 11:52:05 -08:00
YashandGitHub 0781da9dca [SDK] Check bytecode prefix (#6196) 2025-02-07 09:52:00 -08:00
MananTank c5dfad8dbb [TOOL-3298] Playground: Add Insight playground (#6185)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces several enhancements and new features to the `playground-web` application, including a new `Spinner` component, improved layout for the `APIHeader`, and various updates to forms, UI components, and hooks for better functionality and usability.

### Detailed summary
- Added `isProd` export in `env.ts`.
- Introduced `Spinner` component in `Spinner.tsx` with CSS animations.
- Updated `Layout` to include `APIHeader` with title and links.
- Enhanced `RenderCode` to support new scrollable container class.
- Added `Anchor` component with improved link handling.
- Modified `CodeClient` to accept new props for scrollable container.
- Implemented `useShowMore` hook for dynamic item display.
- Updated `tailwind.config.ts` for new color schemes.
- Refactored `AppSidebar` to accept dynamic links.
- Introduced `fetchAllBlueprints` function for sidebar link generation.
- Added `Alert`, `Tooltip`, and `Table` components with improved styling.
- Enhanced `SelectWithSearch` for better option handling and search functionality.
- Updated various components to use client-side rendering (`"use client"`).
- Improved error handling and loading states in image components.
- Added new utility functions for better link and chain handling.

> The following files were skipped due to too many changes: `apps/playground-web/src/components/ui/select.tsx`, `apps/playground-web/src/app/insight/[blueprint_slug]/blueprint-playground.client.tsx`, `pnpm-lock.yaml`

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

<!-- end pr-codex -->
2025-02-07 15:29:21 +00:00
Joaquim VergesandGitHub 9663079062 [SDK] feat: Add session keys to smart wallet options (#6194) 2025-02-07 04:37:28 -08:00
Joaquim VergesandGitHub 1f6bb7c329 [SDK] feat: Add fiat value display to buy token input (#6193) 2025-02-07 04:19:24 -08:00
Joaquim VergesandGitHub 30e13e6b91 [SDK] feat: Add editable token amount input to buy screen (#6192) 2025-02-07 03:54:22 -08:00
Joaquim VergesandGitHub 2dfc245d44 [SDK] feat: Add provider selection to fiat onramp (#6190) 2025-02-07 01:41:47 -08:00
jnsdls 64d7bf358f prefer explicit clientId over secretKey computation (#6188)
TOOL-0000

### TL;DR

Prioritize explicitly passed `clientId` over computed values when creating a Thirdweb client.

### What changed?

Modified the client initialization logic to prefer explicitly passed `clientId` parameters over computing them from `secretKey`. Only falls back to computing the `clientId` from `secretKey` when no explicit `clientId` is provided.

### How to test?

1. Create a Thirdweb client with both `clientId` and `secretKey` parameters
2. Verify that the provided `clientId` is used instead of computing one from the `secretKey`
3. Create a client with only `secretKey` and verify the `clientId` is computed correctly

### Why make this change?

To give developers more control over client configuration by respecting explicitly provided parameters rather than always computing values from the secret key. This allows for more flexible client setup scenarios where the computed `clientId` might not be desired.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the behavior of the `createThirdwebClient()` function to prioritize the explicitly provided `clientId` over deriving it from the `secretKey`. It also modifies the corresponding test to reflect this new behavior.

### Detailed summary
- Updated `createThirdwebClient()` to prefer `clientId` over computed value from `secretKey`.
- Changed test description to indicate `clientId` is not ignored when `secretKey` is provided.
- Updated test expectation to check that `clientId` is equal to the explicitly provided value instead of the computed one.

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

<!-- end pr-codex -->
2025-02-07 08:28:29 +00:00
jnsdls a6e9c97c6f add redirect from /connect/pay to /connect/universal-bridge (#6189)
fixes: TOOL-0000
2025-02-07 08:07:35 +00:00
Joaquim VergesandGitHub ee57ded902 [SDK] fix: Requery allowances on quote screen return (#6187) 2025-02-06 21:00:01 -08:00
MananTank 6d3947ab0c [TOOL-3314] Playground: Fix Next.js errors in Headless UI example pages (#6175)
Fixed "Can not import server (async) components in client components" error

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces new preview components for wallet, NFT, token, and account functionalities in the `playground-web` application. It enhances code modularity by separating previews into distinct files, improving readability and maintainability.

### Detailed summary
- Added `wallet-previews.tsx` with previews for `WalletIcon`, `WalletName`, and formatting options.
- Created `nft-previews.tsx` with previews for `NFTMedia`, `NFTName`, and `NFTDescription`.
- Introduced `token-previews.tsx` with previews for `TokenIcon`, `TokenName`, and `TokenSymbol`.
- Developed `account-previews.tsx` for `AccountAddress`, `AccountName`, and `AccountBalance` with various configurations.
- Updated example files (`wallet-examples.tsx`, `nft-examples.tsx`, `token-examples.tsx`, `account-examples.tsx`) to utilize new preview components, enhancing code clarity.

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

<!-- end pr-codex -->
2025-02-06 20:10:27 +00:00
MananTank 632f1d138f [TOOL-3299] Playground: Update Sidebar, Minor improvements and fixes (#6174) 2025-02-06 20:10:26 +00:00
Phillip HoandGitHub 18f0e575c4 [service-utils] Update list of usageV2 sources (#6183) 2025-02-06 17:39:33 +08:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
e732b11e45 Version Packages (#6166)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-06 00:46:47 -08:00
Joaquim VergesandGitHub f77165e2d1 [SDK] fix: Skip swap approvals and use local gas prices (#6182) 2025-02-06 00:36:31 -08:00
Phillip HoandGitHub dfd64d2896 [service-utils] Expose more kafka configs, set max in-flight requests (#6181) 2025-02-06 11:55:23 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4e703fdb5d build(deps-dev): bump vitest from 3.0.4 to 3.0.5 in the npm_and_yarn group (#6167)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-05 14:07:21 -08:00
Aaron RamirezandGitHub af88935680 [Portal Documentation] Fix: Typo Fix (#6156)
Signed-off-by: Aaron Ramirez <[email protected]>
2025-02-05 14:06:09 -08:00
gregandGitHub c50c82aacd fix: remove pay framer page (#6178) 2025-02-05 14:02:23 -08:00
MananTank 0fa8354dec [TOOL-3318] Dashboard: Add Upload Account Avatar feature in account settings page (#6179) 2025-02-05 20:44:06 +00:00
gregfromstl 769a927627 [Dashboard] Feature: add universal-bridge framer page (#6176)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on modifying the routes in the `framer-rewrites.js` file for the dashboard application, specifically adding and updating certain paths.

### Detailed summary
- Added the route `"/connect/universal-bridge"`.
- Updated the existing route list by retaining `"/connect/pay"`, `"/connect/account-abstraction"`, and `"/nebula"`.
- Removed the comment indicating `// --insight`.

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

<!-- end pr-codex -->
2025-02-05 18:53:54 +00:00
saminacodes ceff873cba [Portal] Add video and update prompt guide (#6163)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `Nebula` documentation by adding new video tutorials and reorganizing content related to supported chains. It also expands the action descriptions in the `prompt-guide` to include bridging and swapping currencies.

### Detailed summary
- Added `Grid` and `ArticleCard` components to introduce video tutorials on using `Nebula`.
- Moved the section on supported chains to follow the new video tutorials.
- Expanded action descriptions in the `prompt-guide` to include "Bridge & Swap" functionality.

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

<!-- end pr-codex -->
2025-02-05 18:44:49 +00:00
arcoraven 9a432af1cf fix: Tweak 404 page (#6173)
<!-- start pr-codex -->

## PR-Codex overview
This PR updates the content of the `NotFoundPage` component to enhance the user experience by changing the messaging displayed when a page is not found.

### Detailed summary
- Changed the message from "Looks like web3 can't be found here." to "Looks like you're lost!".
- Updated the text from "Try our" to "Find live content on our".
- Removed "instead" and added a period after the `TrackedLinkTW` component.

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

<!-- end pr-codex -->
2025-02-05 14:35:21 +00:00
Phillip HoandGitHub ee6bd4f898 [service-utils] fix: export kafka producer (#6171) 2025-02-05 14:03:50 +08:00
Phillip HoandGitHub ccf6329810 [service-utils] Add kafka producer to emit raw messages (#6170) 2025-02-05 12:36:46 +08:00
Phillip HoandGitHub 821caa63d7 fix: Fix usageV2 imports, allow sdk source (#6164) 2025-02-05 11:31:22 +08:00
kumaryash90 09e396d856 [SDK] Update implementations (#6162)
TOOL-3283

<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the `MarketplaceV3` address in the `zksync` implementation file, likely reflecting a new deployment or contract version.

### Detailed summary
- Added a new `MarketplaceV3` address: `0x4027561E163a420c4e5Db46E07EBd581CAa8Bb62` at line 2741.
- Removed the previous `MarketplaceV3` address: `0x9EB0830B0b10010F2a53383517A7D0B75531Bb1b`.

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

<!-- end pr-codex -->
2025-02-05 01:26:13 +00:00
jnsdls 9573b2f9d2 move account abstraction page to connect directory (#6161)
closes: TOOL-3311
2025-02-04 22:39:11 +00:00
MananTank b004a07e5d [NEB-76] Dashboard: Add Nebula service in chainlist, Update all product icons, links (#6158)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new `Nebula` product to the application, updates several links to point directly to the `thirdweb` site, and refines various UI components for better styling and functionality.

### Detailed summary
- Added `nebula` to the `products` array in `chain.ts`.
- Updated links in `products` to `thirdweb.com` for several items.
- Added `className` to `<TableHeader>` for styling.
- Refined styling and classes in several components, including `<Star>` and `<ProductIcon>`.
- Removed unused functions related to link generation.
- Updated SVG icons to improve accessibility and presentation.

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

<!-- end pr-codex -->
2025-02-04 22:08:01 +00:00
MananTank 2396b4e461 [NEB-87] Dashboard, Docs - Use updated Nebula API (#6157)
<!--

## 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 API's context handling by replacing the `context_filter` with a simplified `context` structure, changing types, and cleaning up related components to improve clarity and functionality in the codebase.

### Detailed summary
- Changed `description` type from `string` to `React.ReactNode` in `APIParameter`.
- Replaced `nebulaContextFilterPathParameter` and `nebulaExecuteConfigPathParameter` with `nebulaContextParameter`.
- Updated JSON structures to use `context` instead of `context_filter`.
- Modified related API response types and request parameters to align with the new context structure.
- Refactored components to use `NebulaContext` instead of `ContextFilters`.
- Adjusted context handling in various components and functions for consistency.

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

<!-- end pr-codex -->
2025-02-04 21:59:22 +00:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
ca6c913361 Version Packages (#6132)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-03 22:41:38 -08:00
MananTank cf166e4c04 [TOOL-3216] Dashboard: Add Faucet Refill, Fix toast colors on light mode (#6147)
<!--

## 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 UI and functionality of the dashboard by updating theme management, enhancing the `FaucetButton` component, and integrating a new modal for refilling the faucet. It also modifies color schemes for buttons and introduces a loading state.

### Detailed summary
- Removed `<Toaster richColors />` from `RootLayout`.
- Added `<ToasterSetup />` component in `AppRouterProviders` for dynamic theming.
- Updated button color schemes in `sdk-component-theme.ts`.
- Enhanced `FaucetButton` with loading states and a new modal for refilling.
- Introduced `SendFundsToFaucetModalButton` and `SendFundsToFaucetModalContent` components.
- Added form validation for the amount in the faucet refill modal.

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

<!-- end pr-codex -->
2025-02-04 01:03:52 +00:00
MananTank a51192ffbd [TOOL-3055] Dashboard: Add Refresh Balances button for engine backend wallets (#6153)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the `BackendWalletsTable` component by improving the balance display functionality, adding a refresh feature, and optimizing some data fetching mechanisms.

### Detailed summary
- Updated `backendWalletBalance` keys in `cache-keys.ts`.
- Introduced `backendWalletBalanceAll` function.
- Added `isFetching` state to balance fetching logic.
- Changed balance skeleton size.
- Replaced `LinkButton` with `Link` for external navigation.
- Added a refresh button with tooltip for balance updates.
- Included `queryClient` in dependencies for memoized columns.

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

<!-- end pr-codex -->
2025-02-04 01:00:27 +00:00
MananTank 6fd36aabc7 [TOOL-3286] Dashboard: Add Feedback button on header, Add more links in mobile burger menu (#6145) 2025-02-03 19:43:05 +00:00
kumaryash90 08cc48910d [SDK] Update implementation addr extraction logic (#6093)
TOOL-3258

<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces support for EIP-7702 by adding functionality to extract the implementation address from a specific bytecode pattern. It includes a new utility function and a corresponding test case.

### Detailed summary
- Added EIP-7702 support in `extractMinimalProxyImplementationAddress.ts` to extract the implementation address from bytecode.
- Implemented a check for bytecode length and prefix.
- Added a test case in `extractMinimalProxyImplementationAddress.test.ts` to validate the new functionality.

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

<!-- end pr-codex -->
2025-02-03 17:56:14 +00:00
MananTank f03ded300f [TOOL-3276] SDK: Fix JSDoc comments of download function (#6133)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on updating the import path for the `createThirdwebClient` function from `@thirdweb-dev/sdk` to `thirdweb` in the `download.ts` file.

### Detailed summary
- Changed import of `createThirdwebClient` from `@thirdweb-dev/sdk` to `thirdweb` in multiple instances within the `download.ts` file.

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

<!-- end pr-codex -->
2025-02-02 03:27:49 +00:00
kumaryash90 b651bae4ba Update addresses (#6141)
TOOL-3279

<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refining type imports in `usageV2.ts`, adding a new address in `implementations.ts`, and simplifying the `package.json` formatting for better readability.

### Detailed summary
- Changed import style in `usageV2.ts` to use `import type`.
- Added a new address entry in `implementations.ts`.
- Reformatted `typesVersions` and `files` sections in `package.json` for improved clarity.

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

<!-- end pr-codex -->
2025-02-02 01:19:51 +00:00
Phillip HoandGitHub 66833a832c fix: usageV2 API endpoint (#6140) 2025-01-31 20:20:58 -08:00
Alec AnanianandGitHub 9d5828eeab [SDK] Fix: skip factory entrypoint lookup for ZKsync chains (#6116) 2025-01-31 17:10:33 -08:00
Jonas DanielsandGitHub b191658fcd update login script URL to HTTPS (#6138) 2025-01-31 16:26:11 -08:00
Jonas DanielsandGitHub a6b7e8d818 update dependencies (#6137) 2025-01-31 16:23:07 -08:00
Phillip HoandGitHub f4f93e6353 [service-utils] ESM imports for compression (#6139) 2025-01-31 16:17:41 -08:00
gregfromstl b693b78645 [SDK] Fix: Switch underlying admin wallet with ecosystem smart account (#6119)
<!--

## 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 functionality of smart wallets in the `thirdweb` ecosystem, ensuring they correctly trigger chain switches and improving related wallet checks and configurations.

### Detailed summary
- Fixed smart wallets to trigger chain switch on admin wallets.
- Updated `smartWalletGetCapabilities` to accept a generic `Wallet` type.
- Implemented `isSmartWallet` function to check for smart wallet status.
- Modified `getSmartWallet` to throw an error for non-smart wallets.
- Refactored wallet checks in `createConnectionManager`.
- Added tests for `isSmartWallet` and `getSmartWallet`.

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

<!-- end pr-codex -->
2025-01-31 22:21:58 +00:00
jnsdls daceeed2ec remove deprecated NFT embed functionality (#6136)
fixes: CORE-720
2025-01-31 22:18:32 +00:00
Jonas DanielsandGitHub 5f7c726917 add JWT signing and redirect for login flow (#6129) 2025-01-31 13:44:37 -08:00
Jonas DanielsandGitHub 2405456e5f add knip dependency to login app (#6135) 2025-01-31 13:03:26 -08:00
Phillip HoandGitHub a76e6534c4 [service-utils] switch to lz4 for compression lib (#6131) 2025-01-31 10:41:53 -08:00
Phillip HoandGitHub 0fb586db52 [service-utils] fix: use lz4js for usageV2 (#6130) 2025-01-31 10:22:48 -08:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
f8ebbe54d9 Version Packages (#6118)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-31 09:17:07 -08:00
Phillip HoandGitHub ca756819db [service-utils] CJS import for kafkajs-lz4 (#6127) 2025-01-30 17:29:44 -08:00
Jonas DanielsandGitHub 7217e774b2 update login permissions UI and styling (#6128) 2025-01-30 17:26:07 -08:00
Jonas DanielsandGitHub 5de72c5ecd fix arrow function in login script (#6126) 2025-01-30 17:05:46 -08:00
Jonas DanielsandGitHub 60c5c26583 add thirdweb login client script (#6125) 2025-01-30 16:57:22 -08:00
Jonas DanielsandGitHub dc74c3b4b9 add Suspense wrapper to root layout (#6124) 2025-01-30 16:15:42 -08:00
Jonas DanielsandGitHub 69a921d01f tw:login (#6122) 2025-01-30 16:09:20 -08:00
Phillip HoandGitHub 9abdb92028 [service-utils] import LZ4 compression for usageV2 (#6123) 2025-01-30 15:59:08 -08:00
Phillip HoandGitHub 6ed2fd13de [service-utils] fix: Add LZ4 package for usageV2 compression (#6121) 2025-01-30 14:55:37 -08:00
Phillip HoandGitHub a22457a69c [service-utils] Expose broker settings for usageV2 (#6120) 2025-01-30 12:38:52 -08:00
gregfromstl ae675db8c8 [Wagmi Adapter] Fix: use smart account chain in getProvider (#6117)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on fixing the `getProvider` function in the `wagmi-adapter` package to improve the autoconnection behavior with the account factory by enhancing the chain selection logic.

### Detailed summary
- Updated `getProvider` function to include `args.smartAccount?.chain?.id` in chain selection.
- Changed the logic for defining `chain` to prioritize `params?.chainId`, followed by the smart account's chain ID, the last stored chain ID, and defaulting to `1`.

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

<!-- end pr-codex -->
2025-01-30 19:17:54 +00:00
MananTank 54c9333fc9 [TOOL-3268] Dashboard: Fix in-app wallet settings page form submit not working (#6115) 2025-01-30 17:12:59 +00:00
MananTank 541cf0b3e9 [TOOL-3259] Dashboard: Fix Claimable Module UI crashing on for ERC20 token currency set in claim condition (#6114)
<!--

## 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 `ClaimableModule` component by integrating the `useQuery` hook for fetching currency decimals and refining the logic for determining when to fetch this data.

### Detailed summary
- Added `useQuery` to fetch currency decimals based on the `claimConditionCurrency`.
- Refactored the logic to check if currency decimals should be fetched.
- Updated the conditional rendering of fetched data in the `ClaimableModule`.
- Made the "Add Claim Condition" button disabled based on `props.isOwnerAccount`.

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

<!-- end pr-codex -->
2025-01-30 15:25:38 +00:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
c65022174d Version Packages (#6111)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-29 22:45:20 -08:00
Phillip HoandGitHub 6818cbabf2 chore: change to per-product usageV2 topic (#6113) 2025-01-29 22:32:42 -08:00
gregfromstl a7fb844d87 [Dashboard] Fix: Remove learn more button (#6109)
<!--

## 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 simplifying the `WalletAddress` component by removing the "Learn more" button and its associated link to the blog, while also cleaning up the imports by removing the unused `ExternalLinkIcon`.

### Detailed summary
- Removed the unused import of `ExternalLinkIcon` from `lucide-react`.
- Eliminated the "Learn more" button, which included:
  - The button element with `asChild`, `variant`, `className`, and `size` props.
  - The inner `<a>` element linking to the blog post.

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

<!-- end pr-codex -->
2025-01-30 06:05:07 +00:00
Manan TankandGitHub bf431961ec [TOOL-3260] SDK: Fix Pay Modal logging react-query error when opened (#6104) 2025-01-29 16:59:25 -08:00
gregandGitHub c5d4cab8f8 [Portal] Fix: deployContract doc imports (#6108) 2025-01-29 16:55:21 -08:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
1ee0dffec4 Version Packages (#6107)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-29 14:52:28 -08:00
Jonas DanielsandGitHub 33611409b0 update dependencies (#6106) 2025-01-29 14:34:20 -08:00
Joaquim VergesandGitHub ab55aec10c [SDK] feat: Optional wallet activation during connection (#6105) 2025-01-29 14:21:08 -08:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
ca22e4659e Version Packages (#6097)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-29 12:18:06 -08:00
gregandGitHub 411895c002 [Wagmi Adapter] Fix: Fallback to smart account chain in autoconnect (#6100) 2025-01-29 11:35:51 -08:00
Jonas DanielsGitHubsaminacodesJoaquim Vergesgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>samina
cfa1c20a91 Saminacodes patch 1 (#6103)
Co-authored-by: saminacodes <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: samina <[email protected]>
2025-01-29 11:34:04 -08:00
Jonas DanielsGitHubsaminaJoaquim Vergesgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>samina
2b16f90715 Open ai docs (#6102)
Co-authored-by: samina <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: samina <[email protected]>
2025-01-29 11:33:18 -08:00
Jonas DanielsGitHubJoaquim Vergesgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
e8892b152f Pass team instead of project to rate limit (#6101)
Co-authored-by: Joaquim Verges <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-29 11:32:13 -08:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
073b25afd7 Version Packages (#6083)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-28 17:40:13 -08:00
Joaquim VergesandGitHub 4550bb2663 [SDK] feat: Expose Account and Wallet components in React Native (#6082) 2025-01-28 16:29:50 -08:00
Manan TankandGitHub f5f8a40320 [TOOL-3251] Dashboard: Add Rotate Secret Key feature in Project Settings page (#6087) 2025-01-28 14:21:25 -08:00
Joaquim VergesandGitHub 3569763501 [Dashboard] fix: Use API key for in-app wallet analytics (#6086) 2025-01-29 09:44:02 +13:00
Joaquim VergesandGitHub 1401f8d039 [SDK] fix: Auto-connection of in-app wallets in React Native (#6076) 2025-01-29 07:13:09 +13:00
Jonas DanielsandGitHub 1616b7f619 update dependencies (#6079) 2025-01-27 23:08:39 -08:00
saminaandGitHub aa548a359f Nebula docs (#6080) 2025-01-27 19:22:38 -08:00
Jonas DanielsandGitHub eef52dfa7e update plan selection copy and tracking (#6078) 2025-01-27 16:56:30 -08:00
Joaquim VergesandGitHub 19ec571500 chore: Update PR template format (#6077) 2025-01-28 13:13:07 +13:00
Jonas DanielsandGitHub 47dce2e05c remove growth trial eligibility (#6070) 2025-01-27 15:13:51 -08:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
8b4695224c Version Packages (#6075)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-28 12:00:53 +13:00
Joaquim VergesandGitHub 94e2d5b8dd [SDK] fix: Account linking for ecosystem smart wallets (#6074) 2025-01-28 11:44:05 +13:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
40fa323294 Version Packages (#6073)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-28 11:02:56 +13:00
Joaquim VergesandGitHub 2cebb420e6 [SDK] fix: Show link profiles for ecosystem smart wallets (#6072) 2025-01-28 10:59:05 +13:00
Joaquim VergesandGitHub e89ddb24f2 [SDK] fix: Update ecosystem name and admin wallet handling (#6071) 2025-01-28 10:54:27 +13:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
86f205e2aa Version Packages (#6069)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-28 10:14:31 +13:00
Joaquim VergesandGitHub 6c277ae676 [SDK] fix: Respect chain param in in-app wallet connection (#6068) 2025-01-28 09:57:07 +13:00
MananTank 153651df35 [TOOL-3241] Fix previous account shown after logging out in onboarding flow (#6061) 2025-01-27 19:49:51 +00:00
MananTank 8ba460b6fc [NEB-77] Nebula: Various UI updates (#6060) 2025-01-27 18:18:13 +00:00
MananTank 1e61a9edfc [TOOL-3238] Fix Engine backend wallet balances not updating on chain change (#6059) 2025-01-27 17:00:48 +00:00
MananTank d03e687aa9 [TOOL-3231] Show View Project Link after creating a project in Modal (#6055)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces the `teamSlug` prop across multiple components to enhance API key management by associating it with specific teams. This allows for better project handling and user navigation within the dashboard.

### Detailed summary
- Added `teamSlug` prop to `CreateAPIKeyModal` and related components.
- Updated `embed-setup.tsx` to handle `teamSlug` as `undefined`.
- Integrated `teamSlug` in `TeamProjectsPage` and `AccountHeader`.
- Modified `CreateAPIKeyDialog` to include `teamSlug` in props.
- Enhanced `APIKeyDetails` to fetch projects based on `teamSlug`.
- Implemented conditional rendering for "View Project" button based on `teamSlug`.

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

<!-- end pr-codex -->
2025-01-27 09:52:19 +00:00
Joaquim VergesandGitHub e1c0dbe19e [SDK] chore: Increase CJS bundle size limit to 130kb (#6058) 2025-01-27 20:42:56 +13:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
3e7df081d7 Version Packages (#6048)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-27 20:41:53 +13:00
Joaquim VergesandGitHub b6f189c220 [SDK] update dependencies (#6057) 2025-01-27 16:48:24 +13:00
Phillip HoandGitHub fb4d0a26f1 [service-utils] Replace client_id with project_id in usage_v2 (#6050) 2025-01-24 17:02:15 +08:00
Phillip HoandGitHub bc345371ba [service-utils] usage_v2 accepts a project_id field (#6049) 2025-01-24 16:44:26 +08:00
Phillip HoandGitHub 7a8e4e5f86 [service-utils] Export UsageV2 helpers (#6047) 2025-01-24 15:55:41 +08:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
6ba3b30b60 Version Packages (#6030)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-24 15:08:14 +13:00
Joaquim VergesandGitHub 5ad442cf0c [SDK] fix: Simplify in-app wallet logout flow (#6046) 2025-01-24 14:49:15 +13:00
Phillip HoandGitHub 5756c74a71 [service-utils] feat: Add usageV2 support (#6021) 2025-01-24 08:52:35 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a1cff7a579 build(deps-dev): bump checkly from 4.15.0 to 4.18.0 (#6045)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 13:46:30 +13:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
eafaefec20 build(deps-dev): bump vite from 6.0.10 to 6.0.11 in the npm_and_yarn group (#6022)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 13:37:31 +13:00
Manan TankandGitHub b81d7a1854 [TOOL-3207] Portal: Delete V4 SDK pages, Setup knip lint, Fix lint config (#6039) 2025-01-24 13:37:03 +13:00
Winston YeoandGitHub 21cc45adc2 Winston/tool 3161 cannot create backend wallets using latest version (#6025) 2025-01-24 13:36:33 +13:00
Joaquim VergesandGitHub a67d342779 [SDK] fix: Improve smart wallet signature verification (#6040) 2025-01-24 13:35:58 +13:00
Joaquim VergesandGitHub 60edce49c0 [SDK] fix: Improve WalletConnect mobile connection handling (#6042) 2025-01-24 13:35:31 +13:00
Joaquim VergesandGitHub 20c0dca958 [Portal] fix: Update useAutoConnect hook reference link (#6043) 2025-01-24 13:35:11 +13:00
Joaquim VergesandGitHub 4b30b847e2 [Dashboard] fix: Use formatted ABI for contract function calls (#6044) 2025-01-24 13:34:47 +13:00
MananTank b2cc17fd1e [TOOL-3220] Dashboard: Reset Project pagination on filter change, SDK: Fix WalletProvider JSDoc (#6041) 2025-01-23 23:58:47 +00:00
MananTank ace1dc5b09 [TOOL-3124] Dashboard: paginate projects, sort by Montly active users (#6027)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the project analytics in the dashboard by changing how project data is retrieved and displayed. It updates the calculation of user connections to reflect monthly active users and introduces pagination for project listings.

### Detailed summary
- Replaced `getProjectsWithTotalWallets` with `getProjectsWithAnalytics`.
- Updated project data structure to include `monthlyActiveUsers` instead of `totalConnections`.
- Introduced pagination in the `TeamProjectsPage` component.
- Changed sorting criteria from `totalConnections` to `monthlyActiveUsers`.
- Updated UI elements to reflect the new data fields.

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

<!-- end pr-codex -->
2025-01-23 12:09:59 +00:00
Joaquim VergesandGitHub 9f79277344 [Dashboard] perf: Optimize chain and contract data processing (#6035) 2025-01-23 13:47:14 +13:00
MananTank 6e0fd205ff [TOOL-2964] Dashboard: Fix crash on unix time converter page for invalid unix time, UI improvements (#6036)
=
2025-01-22 23:58:04 +00:00
MananTank 69b1ee9176 [TOOL-3082] Dashboard: Replace NetworkSelectorButton with SingleNetworkSelector where switching chain in wallet is not required (#6031)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces enhancements to the `SingleNetworkSelector` component and its usage across various files. It adds new props for alignment and positioning, replaces existing network selector buttons, and integrates chain ID handling in several components.

### Detailed summary
- Added `side`, `disableChainId`, and `align` props to `SingleNetworkSelector`.
- Updated `SingleNetworkSelector` to conditionally render the chain ID badge.
- Replaced `NetworkSelectorButton` with `SingleNetworkSelector` in multiple components.
- Integrated `chainId` state management using `useState` in `BackendWalletsSection` and `ImportForm`.
- Updated `BackendWalletsTable` and `BackendWalletBalanceCell` to accept `chainId` as a prop.
- Changed the way active chains are retrieved with `useActiveChainAsDashboardChain` to `useV5DashboardChain`.

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

<!-- end pr-codex -->
2025-01-22 23:48:14 +00:00
MananTank 0a19142a24 [TOOL-3120] Redirect to last visited team page if authenticated (#6028) 2025-01-22 23:39:05 +00:00
MananTank feb4fd0908 [TOOL-3123] Dashboard: Order projects by total wallets (#6026)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the `CopyTextButton` component, optimizing the project fetching logic, and improving project display features by integrating total wallet connections in the dashboard.

### Detailed summary
- Changed `tooltip` type in `CopyTextButton` to `string | undefined`.
- Updated the API call in `getChangelog` to limit results to 7.
- Replaced cookie-based token retrieval with `getAuthToken` in `getProjects`.
- Added `getProjectsWithTotalWallets` to calculate total wallet connections.
- Updated `TeamProjectsPage` to display projects with total connections.
- Modified sorting logic in `TeamProjectsPage` to include total connections.
- Replaced `CopyButton` with `CopyTextButton` in `ProjectCard`.
- Enhanced project display to show total users.

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

<!-- end pr-codex -->
2025-01-22 23:30:11 +00:00
gregfromstl 1d8695eb42 [SDK] Fix: Opt out of 1559 on lumia mainnet (#6032)
<!-- start pr-codex -->

## PR-Codex overview
This PR adds support for the `Lumia Mainnet` in the fee data configuration.

### Detailed summary
- Added a new entry for `Lumia Mainnet` in the fee data array.

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

<!-- end pr-codex -->
2025-01-22 22:34:34 +00:00
Joaquim VergesandGitHub 858dc53a25 [Dashboard] fix: Add error reason to analytics API error logs (#6034) 2025-01-23 11:34:16 +13:00
Joaquim VergesandGitHub 8997fac1b0 [Dashboard] fix: Improve error logging for analytics API failures (#6033) 2025-01-23 11:25:35 +13:00
gregfromstl 403e4224e4 Chore: Remove Jakub from CODEOWNERS (#6029)
<!-- start pr-codex -->

## PR-Codex overview
This PR modifies the `.github/CODEOWNERS` file to remove a specific code owner from the `apps/` directory.

### Detailed summary
- Removed `@jakubkrehel` as a code owner for the `apps/` directory.
- Retained `@thirdweb-dev/developer-tools` and `@thirdweb-dev/core-platform` as code owners for the `apps/` directory.
- `scripts/` code ownership remains unchanged with `@jnsdls` and `@joaquim-verges`.

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

<!-- end pr-codex -->
2025-01-22 20:17:33 +00:00
Phillip HoandGitHub de039ef462 [service-utils] fix: Don't hash cache key (#6023) 2025-01-23 04:16:16 +08:00
Joaquim VergesandGitHub 35fd3bcafa [Dashboard] feat: Add transaction analytics and tracking (#6019) 2025-01-23 08:36:04 +13:00
MananTank 80a22ff45e [TOOL-3173] Portal: Update hero iamge, OG image on landing page (#6017) 2025-01-22 21:46:05 +05:30
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
eefa2ab424 Version Packages (#6018)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-22 17:40:37 +13:00
Joaquim VergesandGitHub ab9a148b75 [SDK] feat: Add transaction tracking for smart wallets (#6020) 2025-01-22 17:12:38 +13:00
gregandGitHub 8bbee03c77 [SDK] Fix: Allow smart wallets for site linking (#6015) 2025-01-21 17:02:11 -08:00
gregandGitHub b38604cce8 [SDK] Fix: Do not remove chain listener on disconnect (#6011) 2025-01-21 15:45:55 -08:00
MananTank 59eda22043 [TOOL-3066] Dashboard: Fix 404 on example asset files, robots.txt, sitemap.xml (#6014) 2025-01-21 21:17:22 +00:00
MananTank 577636c037 [TOOL-3156] Dashboard: Fix localhost engine not loading, fetch engine permission on client side (#6013)
<!-- start pr-codex -->

## PR-Codex overview
This PR enhances the engine access permission handling in the dashboard. It introduces a new component, `EnsureEnginePermission`, which checks permissions before rendering engine details and handles various error states more effectively.

### Detailed summary
- Added `account` to the return object in `getEngineInstancePageMeta.ts`.
- Improved URL handling in `getEngineAccessPermission.ts`.
- Introduced `EnsureEnginePermission` component.
- Replaced direct permission checks in `Layout` with `EnsureEnginePermission`.
- Removed `EngineInstanceLayoutContent` component.

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

<!-- end pr-codex -->
2025-01-21 20:50:21 +00:00
jnsdls 1d844ac5d6 add connect/pay to framer rewrites (#6012)
closes: CORE-708

### TL;DR
Added `/connect/pay` route to Framer rewrites

### What changed?
Added a new route `/connect/pay` to the list of Framer rewrites in the dashboard application

### How to test?
1. Navigate to `/connect/pay` in the dashboard
2. Verify the route resolves correctly
3. Ensure the page loads without any routing errors

### Why make this change?
To enable proper routing and rendering of the new payment page in the Connect section of the dashboard
2025-01-21 19:21:28 +00:00
MananTank c7be92f75b [TOOL-3166] Update portal theming to match dashboard (#6010)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on updating the styling and structure of various components in the `apps/portal` project, enhancing UI consistency and accessibility. It introduces color changes, layout adjustments, and modifies several components' props for better integration.

### Detailed summary
- Deleted `docs-hero.png` and `docs-hero.svg`.
- Updated `fill` attribute in `UnrealEngineIcon.tsx` and `UnityIcon.tsx` to `currentColor`.
- Changed color definitions in `layout.tsx` and several components to use `text-foreground` and `text-muted-foreground`.
- Modified class names in `Paragraph.tsx`, `InlineCode.tsx`, and `Document` components for improved styling.
- Adjusted button classes in `Button.tsx` and `Feedback.tsx` for consistency.
- Refactored links to use `DocLink` for better routing.
- Updated `Callout` component styling to use `bg-card`.
- Enhanced accessibility features across various components by modifying text colors and ensuring proper contrast.
- Modified `tailwind.config.ts` to introduce new color variables for better theme management.
- Improved layout consistency in multiple `page.mdx` files by adjusting grid gaps and spacing.

> The following files were skipped due to too many changes: `apps/portal/src/app/contracts/deploy/overview/page.mdx`, `apps/portal/src/components/others/ScrollShadow/ScrollShadow.tsx`, `apps/portal/src/components/ui/select.tsx`, `apps/portal/src/components/others/Sidebar.tsx`, `apps/portal/src/app/contracts/explore/pre-built-modular/token-drop/page.mdx`, `apps/portal/src/components/others/DocSearch.tsx`, `apps/portal/src/icons/products/nebula/NebulaIcon.tsx`, `apps/portal/src/app/contracts/explore/pre-built-modular/nft-drop/page.mdx`, `apps/portal/src/app/connect/wallet/overview/page.mdx`, `apps/portal/src/app/globals.css`, `apps/portal/src/app/page.tsx`, `apps/portal/src/app/contracts/explore/pre-built-modular/edition-drop/page.mdx`, `apps/portal/src/icons/sdks/ReactIcon.tsx`, `apps/portal/src/app/contracts/explore/pre-built-modular/edition/page.mdx`, `apps/portal/src/app/contracts/explore/pre-built-modular/nft-collection/page.mdx`, `apps/portal/src/app/Header.tsx`

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

<!-- end pr-codex -->
2025-01-21 19:11:53 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>greg
79ed399e56 build(deps-dev): bump @types/react-dom from 19.0.2 to 19.0.3 (#6000)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: greg <[email protected]>
2025-01-21 21:05:25 +13:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ded5e22bb7 build(deps-dev): bump tailwindcss from 3.4.16 to 3.4.17 (#6001)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-21 21:05:10 +13:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5029345461 build(deps-dev): bump vite from 6.0.3 to 6.0.10 (#6002)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-21 21:04:57 +13:00
Joaquim VergesGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
c9ccc59464 Version Packages (#6003)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-20 17:47:15 -08:00
gregfromstl dc05f2ea3c [SDK]Fix: use base autoconnect in wagmi adapter (#6005)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces minor updates to the `@thirdweb-dev/wagmi-adapter` package, enhancing the autoconnection feature to accept URL token parameters and making adjustments to the package's metadata.

### Detailed summary
- Added support for URL token parameters in the autoconnection feature within `packages/wagmi-adapter/src/connector.ts`.
- Updated the `autoConnect` function call to include `wallets: [wallet]`.
- Minor updates to the `package.json` file's metadata and dependencies.

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

<!-- end pr-codex -->
2025-01-21 01:13:26 +00:00
Joaquim VergesandGitHub 7d9986f066 [Dashboard] fix: handle unreachable engine instances (#6006) 2025-01-21 14:00:59 +13:00
Joaquim VergesandGitHub bb6c71e968 fix: fallback to eip1193 provider chain when switching chains is not supported (#6004) 2025-01-21 13:59:32 +13:00
Giselle Chacon NessiandGitHub 89beb3c5d3 enhance manage billing visibility in dashboard (#5990) 2025-01-21 11:44:54 +13:00
Giselle Chacon NessiandGitHub 3b98fbf8bb fix: default option tab in docs (#5991) 2025-01-21 11:42:21 +13:00
gregandGitHub 8b5cb47339 [SDK] Feature: Adds deployMarketplaceContract (#5989) 2025-01-20 14:17:21 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0760706dbb build(deps-dev): bump @storybook/react from 8.4.7 to 8.5.0 (#5995)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 13:21:00 -08:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
eded4ff1be Version Packages (#5955)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-18 22:37:50 +13:00
Joaquim VergesandGitHub b705406c5d [Dashboard] fix: Update dashboard redirect paths to /team (#5986) 2025-01-18 22:05:04 +13:00
gregfromstl 17374d15f2 [Portal] Fix: Supported Chains Page (#5975)
TOOL-3130
2025-01-18 06:59:19 +00:00
MananTank 79769d6859 [TOOL-3054] Dashboard: Fix Engine backend wallet list not updating on create/delete (#5983) 2025-01-17 22:35:10 +00:00
gregfromstl b6d65cf1c4 [SDK] Feature: Add Mode chain definition (#5982)
---
title: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"
---

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

## Notes for the reviewer
Anything important to call out? Be sure to also clarify these in your comments.

## How to test
Unit tests, playground, etc.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new blockchain chain called `mode` to the `thirdweb` package, enhancing the available options for developers.

### Detailed summary
- Added `mode` as a predefined chain in `packages/thirdweb/src/chains/chain-definitions/mode.ts`.
- Exported `mode` from `packages/thirdweb/src/exports/chains.ts`.
- Defined `mode` with properties such as `id`, `name`, `nativeCurrency`, and `blockExplorers`.

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

<!-- end pr-codex -->
2025-01-17 22:25:40 +00:00
saminaandGitHub a92617f0b0 docs cleanup (#5981) 2025-01-18 11:24:45 +13:00
MananTank 8e82ec860c [TOOL-3119] Dashboard: Move Connect settings from Project settings to Connect pages (#5980)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on restructuring and enhancing the routing and layout of the `settings` pages within the dashboard, as well as integrating new components for better user experience in the `account-abstraction` section.

### Detailed summary
- Added a new `Settings` route in multiple layout files.
- Refactored paths in `ProjectGeneralSettingsPageForTeams` to point to the new settings structure.
- Updated imports to reflect the new file structure for components.
- Introduced `AccountAbstractionAnalytics` and `AccountAbstractionSummary` components.
- Modified layout components to improve styling and organization.
- Replaced `AccountAbstractionPage` with `AccountAbstractionLayout` for better structure.
- Implemented loading states for API calls in `AccountAbstractionSettingsPage`.

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

<!-- end pr-codex -->
2025-01-17 22:00:23 +00:00
Joaquim VergesandGitHub 0b6239735e [SDK] feat: Support multiple messages in Nebula API and update input props (#5972) 2025-01-18 10:37:17 +13:00
MananTank df4da68d68 [TOOL-3128] SDK: Fix typo in deployPublishedContract JSDoc example (#5977) 2025-01-17 21:10:03 +00:00
MananTank 94ba9cf7aa [TOOL-3091] Dashboard: Remove trending contracts, Hide Contract Search (#5978) 2025-01-17 21:01:03 +00:00
gregfromstl 1b64d6eb2d [Playground] Feature: Adds smart account auth (#5979)
---
title: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"
---

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

## Notes for the reviewer
Anything important to call out? Be sure to also clarify these in your comments.

## How to test
Unit tests, playground, etc.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new smart account authentication feature in the `playground-web` application, allowing users to sign in using Ethereum. It adds a new component for the authentication button and integrates it into the main application page.

### Detailed summary
- Added `SmartAccountAuthButton` component for Ethereum authentication.
- Implemented `SmartAccountAuthPreview` to display JWT cookies.
- Updated `page.tsx` to include `SmartAccountAuth` section.
- Added a code example demonstrating the authentication button usage.

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

<!-- end pr-codex -->
2025-01-17 20:35:18 +00:00
MananTank f1afa3b52d [TOOL-3112]: Various Engine Overview page improvements, Chart style adjustments, Improved Pagination component (#5976)
<!-- start pr-codex -->

## PR-Codex overview
This PR primarily focuses on improving the styling and functionality of various chart components, enhancing pagination controls, and refining transaction details presentation in the dashboard application.

### Detailed summary
- Added `strokeWidth` and `className` properties to several chart components for consistent styling.
- Updated pagination components to include input for direct page navigation.
- Enhanced transaction details with improved layout and added tooltips.
- Refined UI elements for better visibility and usability across various components.

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

<!-- end pr-codex -->
2025-01-17 19:09:58 +00:00
kumaryash90 dbb64ea190 [SDK] Update implementations (#5973)
TOOL-3135

## 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 implementations for `MarketplaceV3` and `ZKSYNC_WETH` in the `thirdweb` package, specifically for the `zksync` deployment.

### Detailed summary
- Added a new entry for `MarketplaceV3` with the address `0x9EB0830B0b10010F2a53383517A7D0B75531Bb1b` for the network ID `[555271]`.
- Added a new entry for `ZKSYNC_WETH` with the address `0xb0b8b267d44c64BA6dD1Daf442949887c85199f6` for the network ID `[555271]`.

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

<!-- end pr-codex -->
2025-01-17 16:09:44 +00:00
Joaquim VergesandGitHub eed734c766 [SDK] feat: Add pay event tracking for transaction modal states (#5971) 2025-01-17 13:11:41 +13:00
MananTank a3a3062ae1 [TOOL-3111] Nebula: Add events on transaction button (#5970) 2025-01-16 21:55:22 +00:00
MananTank 4ffcf305ab [TOOL-3096] React SDK: Fix NFT components not including contract address in queryKey (#5966)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on improving the handling of NFT metadata in the `thirdweb` library by adding `contractAddress` to various query keys, which resolves issues with metadata caching when multiple contracts share the same token ID.

### Detailed summary
- Added `contractAddress` to the query keys in `getQueryKey` functions for `NFTName`, `NFTMedia`, and `NFTDescription`.
- Updated related queries to include `contractAddress`.
- Fixed metadata display issues when rendering multiple contracts with the same token ID.
- Enhanced tests in `name.test.tsx` and `media.test.tsx` to validate the new `contractAddress` parameter.

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

<!-- end pr-codex -->
2025-01-16 20:48:24 +00:00
MananTank fe3f24e309 [TOOL-3110] Dashboard: Engine pages cleanup: Fetch engine info on server + Use layout (#5968)
Engine pages were in a weird "pages router" type of state - Its now properly migrated to app router

* Common skeleton is moved to layout
* engine instances are fetched on server

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring and enhancing the engine instance management in the application. It removes obsolete files, improves error handling, and introduces new utility functions for fetching engine instances and permissions.

### Detailed summary
- Deleted multiple obsolete `.tsx` files related to engine instance management.
- Improved error handling in engine import functionality.
- Added `getEngineAccessPermission` and `getEngineInstances` utility functions.
- Refactored components to utilize new utility functions for fetching engine data.
- Enhanced the layout and error handling in the engine instance pages.
- Updated components to use consistent instance data and permissions checks.

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

<!-- end pr-codex -->
2025-01-16 20:34:33 +00:00
Joaquim VergesandGitHub 9f5510018e docs: Reorder sidebar groups and update claim params tags (#5969) 2025-01-17 09:33:39 +13:00
73894d23b9 [Dashboard] - Update copyright year to 2025 (#5936)
Co-authored-by: Joaquim Verges <[email protected]>
Co-authored-by: greg <[email protected]>
2025-01-16 12:14:34 -08:00
saminacodes 4538441334 nebula doc restructure (#5964)
---
title: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"
---

NEB-73

## Notes for the reviewer
Mostly documentation changes to add Nebula restructure and also add tutorial for the Eliza Plugin

## How to test
View preview.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `Nebula` documentation and user experience by adding new content, updating existing descriptions, and introducing a new `RocketIcon`. It also restructures the sidebar and adds new pages for getting started and Eliza plugin integration.

### Detailed summary
- Added new `RocketIcon` to the `sidebar`.
- Updated `Nebula` description in `page.mdx` for improved clarity.
- Introduced `get-started` page with setup instructions for the `Nebula API`.
- Improved `Eliza` plugin documentation with new prerequisites and setup steps.
- Updated sidebar links and removed the old `Use Cases` section.
- Added images for better visual guidance in documentation.

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

<!-- end pr-codex -->
2025-01-16 19:32:28 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Joaquim Verges
913f2e4366 Bump shiki from 1.24.2 to 1.27.0 (#5953)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joaquim Verges <[email protected]>
2025-01-17 08:24:49 +13:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
da4ba21a52 build(deps-dev): bump react-native from 0.76.5 to 0.76.6 (#5943)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-17 08:23:49 +13:00
MananTank 8d9854daf1 [TOOL-3095] Portal: Enable Table of contents in Nebula pages, Adjust TOC component (#5965)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on refining the `Layout` component's structure and updating the styling for the `TOCLink` component to enhance the user interface.

### Detailed summary
- In `Layout`, the `DocLayout` component now wraps `props.children` directly without unnecessary line breaks.
- In `TOCLink`, the `data-active` class styling was changed from `text-accent-500` to `text-f-100` for active links.

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

<!-- end pr-codex -->
2025-01-16 18:51:34 +00:00
gregandGitHub 9cbcbe7760 [SDK] Fix: Lumia Testnet is pre-1559 (#5967) 2025-01-16 10:51:16 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Joaquim Verges
00f1d29654 build(deps-dev): bump eslint-config-next from 15.1.2 to 15.1.4 (#5942)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joaquim Verges <[email protected]>
2025-01-16 20:28:18 +13:00
MananTank b511a695f9 [TOOL-3056]: Add Engine transactions bar chart, Make shadcn table header sticky (#5963)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the user interface and functionality of various components in the dashboard application, particularly around data visualization and table displays.

### Detailed summary
- Updated `chartClassName` for better aspect ratio in multiple chart components.
- Added `tableScrollableClassName` and `tableContainerClassName` to improve table layouts.
- Modified text in `engine-overview.tsx` for clarity.
- Introduced new props in `ThirdwebBarChart` for customizable tooltips and titles.
- Implemented data processing logic in `TransactionsTable` for analytics display.

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

<!-- end pr-codex -->
2025-01-16 01:52:15 +00:00
MananTank ea34a975ca [TOOL-3090] Dashboard: Add Deprecated alert in Embeds page, UI adjustments (#5960)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on updating the `EmbedSetup` component to enhance user experience by introducing a deprecation alert for NFT embeds, reorganizing the UI layout, and adding new configuration options.

### Detailed summary
- Added an `Alert` component indicating the deprecation of thirdweb NFT Embeds.
- Replaced `Flex` with `div` for layout simplification.
- Consolidated form controls for configuration and added new fields for `Client ID`, `RPC Url`, and `Gasless` options.
- Introduced a theme selection dropdown.
- Updated embed code section and preview area for clarity.

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

<!-- end pr-codex -->
2025-01-16 01:52:13 +00:00
Joaquim VergesandGitHub a834199dac [SDK] test: Refactor smart wallet signature verification tests (#5962) 2025-01-16 14:46:18 +13:00
MananTank be7759d2fe [TOOL-3075] Dashboard: Adjust theming, Improved light mode (#5956)
Fix "inverted look" of light mode - light mode now correctly has a slightly dark background and lighter cards on top

* Instead of `bg-muted/50`, use `bg-card` instead. `--card` color is updated
* For cards that are links, don't change the background color on hover - apply a subtle border change instead - added a `--active-border` for this - this looks great in both light and dark mode
* Prefer using `bg-accent` for hover effects instead of `bg-muted` (even though they are currently both same color) for adding hover accent. Use `bg-accent/50` for applying a very subtle accent - use cases: clickable table row

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the styles of various components in the application to replace the `bg-muted` class with `bg-card`, enhancing the visual consistency across the UI.

### Detailed summary
- Changed `className` from `bg-muted/50` to `bg-card` in multiple components.
- Updated hover effects from `hover:bg-muted` to `hover:bg-card`.
- Modified background colors in various layouts to use `bg-card` instead of `bg-muted`.
- Adjusted button and input styles for consistent theming.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/table.tsx`, `apps/dashboard/src/components/configure-networks/ConfigureNetworkForm.tsx`, `apps/dashboard/src/app/team/[team_slug]/[project_slug]/insight/[blueprint_slug]/blueprint-playground.client.tsx`, `apps/dashboard/src/@/styles/globals.css`, `apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/embed/embed-setup.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/PayAnalytics.tsx`, `apps/dashboard/src/app/(dashboard)/(chain)/chainlist/components/client/filters.tsx`, `apps/dashboard/src/app/components/sdk-component-theme.ts`, `apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx`, `apps/dashboard/src/app/team/components/Analytics/EmptyState.tsx`, `apps/dashboard/src/components/settings/Account/Billing/CreditsItem.tsx`

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

<!-- end pr-codex -->
2025-01-16 01:08:04 +00:00
MananTank 15178b2f96 [NEB-71] Nebula: Update example prompts (#5957)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces a new type `ExamplePrompt` and a list of example prompts in `examplePrompts.ts`. It replaces hardcoded example prompts in `EmptyStateChatPageContent.tsx` with a dynamic mapping from the newly created `examplePrompts`, enhancing maintainability and readability.

### Detailed summary
- Added type `ExamplePrompt` with `title` and `message` properties.
- Created `examplePrompts` array containing several example prompts.
- Refactored `EmptyStateChatPageContent.tsx` to dynamically render `ExamplePrompt` components using `examplePrompts`.
- Removed hardcoded example prompts from `EmptyStateChatPageContent.tsx`.

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

<!-- end pr-codex -->
2025-01-16 00:38:38 +00:00
Joe-Thirdweb afd0ccfdfd [Dashboard] Feature: Chainlist page updates (#5959)
---
title: "[Dashboard] Feature: Chainlist page updates"
---

Issue Tags:
INFRA-610
INFRA-698
XSOL-1806
XSOL-1824
INFA-676
XSOL-1046
XSOL-1086

## Notes for the reviewer

Updated Chain Page Banners, About, and CTA:
- EVM on Flow Mainnet/Testnet-> All updated
- Lisk Mainnet/Testnet -> Banner update
- Plume Mainnet/Testnet -> Banner update
- Rivalz Mainnet/Testnet -> All updated
- Saakuru Mainnet/Testnet -> All updated
- Viction Mainnet/Testnet -> All updated
- Worldchain Mainnet/Testnet -> All updated
- Soneium Mainnet -> All updated

Adding OP Superchain banner:
- Celo
- Metal L2
- Ozean
- Unichain
- Worldchain
- Sonium

## How to test
Visit the chain pages for each chain.

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the asset imports and metadata for various blockchain networks, including adding new assets for `Flow`, `Rivalz`, `Saakuru`, `Viction`, and `World Chain`, while removing outdated assets and enhancing descriptions for some blockchain networks.

### Detailed summary
- Added imports for new assets: `flowBanner`, `flowCTA`, `rivalzBanner`, `rivalzCTA`, `saakuruBanner`, `saakuruCTA`, `victionBanner`, `victionCTA`, `worldChainBanner`, `worldChainCTA`.
- Removed imports for deleted assets: `plumev2Banner`, `plumev2CTA`.
- Updated `chainMetaRecord` to include new entries for `Flow`, `Rivalz`, `Saakuru`, `Viction`, and `World Chain`.
- Enhanced descriptions for `Flow`, `Rivalz`, `Saakuru`, and `Viction`.
- Deprecated the `Plume` entries and updated their descriptions.
- Removed commented-out code related to `fhenix` and `metal` CTAs.
- Added gas sponsorship for several blockchain entries.

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

<!-- end pr-codex -->
2025-01-15 23:57:35 +00:00
gregandGitHub 07a5c98b5b [Dashboard] Fix: AA Testnet Transaction Count (#5958) 2025-01-15 15:56:58 -08:00
Joaquim VergesandGitHub 51a28c4ed6 [SDK] test: Skip Nebula AI tests by default (#5961) 2025-01-16 12:11:12 +13:00
Nischit Prasad Nhuchhe PradhanandGitHub c290997b11 track ratelimit only on creation (#5950) 2025-01-15 15:17:12 +08:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
81b146a87a Version Packages (#5921)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-15 15:03:18 +13:00
Joaquim VergesandGitHub b10f306fba feat: Add Nebula AI chat and transaction execution API (#5948) 2025-01-15 14:50:13 +13:00
Joaquim VergesandGitHub d1c03b0cbc [SDK] Feat: Add engineAccount() for backend transaction handling (#5947) 2025-01-15 14:36:24 +13:00
saminaandGitHub 9ae56bc765 Updated FAQs for Nebula (#5951)
Signed-off-by: samina <[email protected]>
2025-01-15 14:21:16 +13:00
MananTank 0fdfb8aa77 [TOOL-2473] Dashboard: Update RPC method analytics chart to show % values (#5954)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the `RpcMethodBarChartCardUI` component by improving data handling, updating the `valueFormatter` function, and refining the chart display logic. It also introduces new RPC methods and adjusts the layout of the chart.

### Detailed summary
- Updated `valueFormatter` in `chart.tsx` to accept a second parameter.
- Changed the gap between elements in the chart tooltip from `gap-2` to `gap-5`.
- Adjusted condition to check for `item.value` to `item.value !== undefined`.
- Added randomness to the data generation logic to simulate empty data.
- Introduced new RPC methods in the `commonMethods` array.
- Updated the `BadgeContainer` labels for clarity on RPC methods displayed.
- Enhanced data processing in `RpcMethodBarChartCardUI` for better method display.
- Implemented a total count for methods in the chart data.
- Updated the tooltip label formatter to use `formatDate`.
- Modified the Y-axis configuration to remove unused properties.

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

<!-- end pr-codex -->
2025-01-14 21:51:00 +00:00
ianthirdweb 46bf17c393 Add onramp providers page to Pay overview (#5836)
Added onramp page and preferredPartner information to customization pages

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on adding support for specifying preferred onramp providers in various components and updating documentation accordingly.

### Detailed summary
- Added `Onramp Providers` option in the sidebar.
- Introduced `Preferred Provider` section in `payembed` and `connectbutton` documentation.
- Created a new `onramp-providers` page detailing supported providers and how to set a preferred provider.
- Updated `send-transaction` documentation to include `preferredProvider` parameter.

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

<!-- end pr-codex -->
2025-01-14 19:42:24 +00:00
MananTank a6fe226239 [TOOL-3051] Dashboard: Move everything to App router, Delete Pages router (#5952)
<!-- start pr-codex -->

## PR-Codex overview
This PR primarily focuses on the removal of unused files and the refactoring of several components to improve code quality and maintainability, including updates to API calls and component properties.

### Detailed summary
- Deleted multiple unused files from various directories.
- Refactored API calls to use `apiServerProxy` for better error handling.
- Updated component props to be more flexible (e.g., `favoriteButton` can now be `undefined`).
- Improved UI components by removing unnecessary class names and simplifying their structure.
- Enhanced type definitions for better TypeScript support.
- Added new metadata and structured content for landing pages.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(landing)/account-abstraction/page.tsx`, `apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/pay/webhooks/components/webhooks.client.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts`, `apps/dashboard/src/app/(landing)/in-app-wallets/page.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts`

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

<!-- end pr-codex -->
2025-01-14 18:49:01 +00:00
ElasticBottle 7a3dff01cd [SDK]: move autoConnect function into it's own standalone function (#5889)
---
title: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"
---

https://linear.app/thirdweb/issue/TOOL-2697/add-js-autoconnect

## 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 auto-connect functionality for wallets in the `thirdweb` library. It enhances the modularity and usability of the `autoConnect` feature, allowing it to be used outside of React components and streamlining wallet connection processes.

### Detailed summary
- Modified `if` condition in `timeoutPromise.ts` to check for `typeof window`.
- Updated import paths for `AutoConnectProps` in `AutoConnect.tsx` files.
- Exposed `autoConnect` function in `wallets.ts` for external use.
- Adjusted exports in `react.ts` and `react.native.ts` for `AutoConnectProps`.
- Refactored `useAutoConnectCore` to use `autoConnectCore`.
- Consolidated wallet connection logic in `autoConnect` and `autoConnectCore`.
- Updated tests to reflect changes in wallet connection handling and added new test cases for `autoConnect`.
- Enhanced error handling and timeout logic in wallet connection processes.

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

<!-- end pr-codex -->
2025-01-14 17:43:36 +00:00
gregandGitHub e331e433ac [SDK] Refactor: More Viem to Ox migration (#5790) 2025-01-14 09:43:18 -08:00
Nischit Prasad Nhuchhe PradhanandGitHub 23d80011de Auth cache key fix (#5949) 2025-01-14 19:56:47 +08:00
MananTank 815f47cd12 [TOOL-3039] Fix page zoom on iOS safari for shadcn inputs and textarea (#5933) 2025-01-14 10:18:28 +00:00
MananTank 22dc493e62 [TOOL-3046]: Do not show invalid string error if NFT description is undefined (#5939) 2025-01-14 10:08:30 +00:00
MananTank fbf26d82d5 [TOOL-2972] Portal: Add missing redirects for contract references (#5934) 2025-01-14 09:58:27 +00:00
MananTank d7f2249fb1 [TOOL-3032] Next.js build memory optimizations (#5938)
* Disable eslint as part of next.js build - We are already running eslint in separate pipeline
* Enable Next.js memory optimization flags

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on enhancing the configuration of multiple `Next.js` applications by adding ESLint settings, production source maps, and experimental features. It also modifies security headers and refines the handling of IPFS gateways.

### Detailed summary
- Added `eslint` configuration with `ignoreDuringBuilds: true` to multiple `next.config` files.
- Set `productionBrowserSourceMaps` to `false`.
- Enabled experimental features: `webpackBuildWorker`, `webpackMemoryOptimizations`, and `serverSourceMaps`.
- Updated security headers in `apps/dashboard/next.config.ts` for consistent formatting.
- Refined `determineIpfsGateways` function for IPFS gateway handling.
- Maintained existing configurations for redirects, rewrites, images, and compiler settings.

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

<!-- end pr-codex -->
2025-01-14 09:46:30 +00:00
MananTank 0682f26cb0 [TOOL-3047] Dashboard: Fix TransactionButton opening "No funds" Modal when quickly clicking it after switching chain (#5941) 2025-01-13 23:28:37 +00:00
MananTank 0e2b3df42a [TOOL-3009] Dashboard: Support all valid domains as ENS name and not just .eth (#5937)
<!-- start pr-codex -->

## PR-Codex overview
This PR introduces the `isValidENSName` utility function to validate ENS names, replacing the previous `isEnsName` checks across multiple files. It refines address validation and enhances the overall handling of ENS names within the codebase.

### Detailed summary
- Added `isValidENSName` utility function for ENS name validation.
- Replaced occurrences of `isEnsName` with `isValidENSName` in various files.
- Updated validation logic in components and utilities to improve address and ENS name handling.
- Enhanced error messages and conditions related to ENS name checks.

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

<!-- end pr-codex -->
2025-01-13 18:18:06 +00:00
ElasticBottle c33980352a [Portal] update docs on pre-generation and account abstraction (#5912)
https://linear.app/thirdweb/issue/TOOL-2988/update-docs-to-call-out-pregeneration-caveats

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing documentation and functionality related to wallet connections and smart accounts in the `thirdweb` library, particularly emphasizing account abstraction and the use of smart wallets.

### Detailed summary
- Added `backend` to the `AuthList` component in `AuthList.tsx`.
- Updated documentation in `ConnectEmbed.tsx` and `ConnectButton.tsx` regarding ecosystem wallets and account abstraction.
- Introduced a warning callout in `page.mdx` for generating smart wallet addresses using `predictSmartAccountAddress`.

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

<!-- end pr-codex -->
2025-01-13 18:01:22 +00:00
saminacodes a693958bf4 added web3 onboard page (#5940)
---
title: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"
---

CORE-694

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new section for "Web3 Onboard" in the sidebar and adds a corresponding documentation page in `page.mdx`, providing information about the acquisition of Web3Onboard by Blocknative and guidance for migration to thirdweb's SDK.

### Detailed summary
- Added a new entry for `Web3 Onboard` in the sidebar of `sidebar.tsx`.
- Created a new documentation page in `page.mdx` detailing the acquisition of Web3Onboard and migration instructions.
- Included links to Web3 Onboard's documentation and thirdweb's support site.

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

<!-- end pr-codex -->
2025-01-13 17:11:12 +00:00
332536bdee Update page.mdx (#5930)
Signed-off-by: samina <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
2025-01-13 12:41:17 +13:00
MananTank 4745786ba1 [NEB-54] Nebula: Improved Transaction action UI (#5931) 2025-01-12 19:22:04 +00:00
MananTank 3cb1ab9f98 [TOOL-3038] Replace h-screen with h-dvh - fix iOS safari height issues (#5932) 2025-01-12 19:18:01 +00:00
MananTank 5c554730d0 [TOOL-3036] Nebula: Various UI improvements (#5929) 2025-01-11 19:10:16 +00:00
MananTank 6953d06246 [TOOL-3021] Insight Playground: Enforce chain query param to be required + support anyOf schema (#5925) 2025-01-11 02:19:08 +00:00
MananTank 4b5661b981 [TOOL-3007] Dashboard: Replace chainsaw with insight (#5926)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing the analytics functionality within the `dashboard` application by introducing new utility functions, updating existing API calls, and improving the data handling for contract analytics.

### Detailed summary
- Added export of `toEventSelector` from `thirdweb/utils`.
- Introduced `INSIGHT_SERVICE_API_KEY` in environment constants.
- Modified `ContractAnalyticsPageClient` to accept new props for function and event selectors.
- Created new analytics functions for total contract events, unique wallets, and transactions.
- Updated `isAnalyticsSupportedForChain` to use `INSIGHT_SERVICE_API_KEY`.
- Refactored analytics components to use new hooks for contract events and functions.
- Improved data fetching and error handling in analytics hooks.
- Enhanced UI components for displaying analytics data with loading states.

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

<!-- end pr-codex -->
2025-01-11 00:22:11 +00:00
kumaryash90 42a313f3b2 [SDK/Dashboard] Fix: Deploy specified version of published contracts (#5923)
TOOL-3013
TOOL-3014

## 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 deployment process for contracts in the `thirdweb` package, including changes to version handling and contract IDs.

### Detailed summary
- Updated `version` handling in the `custom-contract.tsx` file.
- Changed `contractId` from `"Forwarder"` to `"ForwarderEOAOnly"` in `deploy-pack.ts`.
- Added `version` to the options in `deployMarketplaceContract`.
- Modified `getAllDefaultConstructorParamsForImplementation` to use a dynamic `contractId`.

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

<!-- end pr-codex -->
2025-01-10 18:57:04 +00:00
Joaquim VergesandGitHub 7fb5ce1cc3 [SDK] fix: Reset deploy flag on bundler errors (#5924) 2025-01-10 21:40:43 +13:00
ba86d51847 [TOOL-2896] Portal: Add Nebula API docs (#5916)
Co-authored-by: samina <[email protected]>
2025-01-10 12:49:23 +13:00
MananTank 5866088894 [TOOL-2822]: Insight Playground: Move chain from subdomain to query param (#5922)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on modifying the handling of a parameter in the `BlueprintPlayground` component, changing its name from `chainId` to `chain`, and updating its usage throughout the codebase.

### Detailed summary
- Changed the `domain` URL format to remove `chainId`.
- Updated the parameter name from `chainId` to `chain` in `modifyParametersForPlayground`.
- Adjusted the parameter handling in `ParameterSection` to reflect the new name.
- Updated the schema creation to default to `z.string()` for parameters without a schema.

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

<!-- end pr-codex -->
2025-01-09 21:59:16 +00:00
MananTank ed0d215f4a [TOOL-2995]: Fix Engine Contract Subscription UI issue with checkboxes (#5920)
---
title: "[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 user interface for checkbox components in the `batch-lazy-mint.tsx` and `add-contract-subscription-button.tsx` files. It improves the handling of checkbox states and integrates labels more effectively.

### Detailed summary
- In `batch-lazy-mint.tsx`, replaced `Checkbox` with a more integrated `CheckboxWithLabel`.
- Updated checkbox handling to use `form.watch` and `form.setValue` for state management.
- In `add-contract-subscription-button.tsx`, converted checkboxes to use `CheckboxWithLabel` for better labeling.
- Simplified checkbox state handling by removing `form.register`.
- Adjusted label elements from `<Text>` to `<span>` for consistency.

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

<!-- end pr-codex -->
2025-01-09 21:19:46 +00:00
MananTank c5c6f9d741 [TOOL-2999] SDK: automatically sign message for SIWE in connect UI for Ecosystem wallets (#5914)
---
title: "[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 `SignatureScreen` component by updating the signing flow for different wallet types and enhancing the user experience by removing unnecessary prompts for signing messages.

### Detailed summary
- Added `isHeadlessSignSupported` function to determine wallet support.
- Updated `SignatureScreen` logic to handle headless signing for supported wallets.
- Refactored tests to improve clarity and organization.
- Enhanced error handling and loading states in the UI.
- Introduced mocks for `useActiveWallet` and `useAdminWallet` in tests.

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

<!-- end pr-codex -->
2025-01-09 16:22:25 +00:00
1403 changed files with 34558 additions and 755387 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ packages/eslint-config-thirdweb/ @thirdweb-dev/core-platform @edwardysun
packages/tw-tsconfig/ @thirdweb-dev/core-platform @edwardysun
# apps
apps/ @thirdweb-dev/developer-tools @thirdweb-dev/core-platform @jakubkrehel
apps/ @thirdweb-dev/developer-tools @thirdweb-dev/core-platform
# .github folder + .changeset + turbo config is owned by jonas for now
scripts/ @jnsdls @joaquim-verges
+6 -3
View File
@@ -1,12 +1,15 @@
---
title: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"
---
<!--
## 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.
-->
+1 -1
View File
@@ -2,7 +2,7 @@ name: Auto Author Assign
on:
pull_request:
types: [opened, reopened]
types: [opened, reopened, ready_for_review, draft]
permissions:
pull-requests: write
+5 -1
View File
@@ -27,5 +27,9 @@
},
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
}
},
"eslint.workingDirectories": [
{ "pattern": "./packages/*/" },
{ "pattern": "./apps/*/" }
]
}
+2
View File
@@ -9,6 +9,8 @@ module.exports = [
// -- connect
"/connect",
"/connect/sign-in",
"/connect/account-abstraction",
"/connect/universal-bridge",
// -- nebula
"/nebula",
// --insight
+3 -1
View File
@@ -3,7 +3,9 @@
"next": true,
"ignore": [
"src/@/components/ui/**",
"src/components/notices/AnnouncementBanner.tsx"
"src/components/notices/AnnouncementBanner.tsx",
"src/components/cmd-k-search/index.tsx",
"src/lib/search.ts"
],
"project": ["src/**"],
"ignoreBinaries": ["only-allow", "biome"],
-1
View File
@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+185 -180
View File
@@ -19,204 +19,209 @@ const ContentSecurityPolicy = `
`;
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "X-XSS-Protection",
value: "1; mode=block",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "Referrer-Policy",
value: "origin-when-cross-origin",
},
{
key: "Content-Security-Policy",
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "X-XSS-Protection",
value: "1; mode=block",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "Referrer-Policy",
value: "origin-when-cross-origin",
},
{
key: "Content-Security-Policy",
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
];
function determineIpfsGateways() {
// add the clientId ipfs gateways
const remotePatterns: RemotePattern[] = [];
if (process.env.API_ROUTES_CLIENT_ID) {
remotePatterns.push({
protocol: "https",
hostname: `${process.env.API_ROUTES_CLIENT_ID}.ipfscdn.io`,
});
remotePatterns.push({
protocol: "https",
hostname: `${process.env.API_ROUTES_CLIENT_ID}.thirdwebstorage-staging.com`,
});
remotePatterns.push({
protocol: "https",
hostname: `${process.env.API_ROUTES_CLIENT_ID}.thirdwebstorage-dev.com`,
});
} else {
// this should only happen in development
remotePatterns.push({
protocol: "https",
hostname: "ipfs.io",
});
}
// also add the dashboard clientId ipfs gateway if it is set
if (process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID) {
remotePatterns.push({
protocol: "https",
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.ipfscdn.io`,
});
remotePatterns.push({
protocol: "https",
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.thirdwebstorage-staging.com`,
});
remotePatterns.push({
protocol: "https",
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.thirdwebstorage-dev.com`,
});
}
return remotePatterns;
// add the clientId ipfs gateways
const remotePatterns: RemotePattern[] = [];
if (process.env.API_ROUTES_CLIENT_ID) {
remotePatterns.push({
protocol: "https",
hostname: `${process.env.API_ROUTES_CLIENT_ID}.ipfscdn.io`,
});
remotePatterns.push({
protocol: "https",
hostname: `${process.env.API_ROUTES_CLIENT_ID}.thirdwebstorage-staging.com`,
});
remotePatterns.push({
protocol: "https",
hostname: `${process.env.API_ROUTES_CLIENT_ID}.thirdwebstorage-dev.com`,
});
} else {
// this should only happen in development
remotePatterns.push({
protocol: "https",
hostname: "ipfs.io",
});
}
// also add the dashboard clientId ipfs gateway if it is set
if (process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID) {
remotePatterns.push({
protocol: "https",
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.ipfscdn.io`,
});
remotePatterns.push({
protocol: "https",
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.thirdwebstorage-staging.com`,
});
remotePatterns.push({
protocol: "https",
hostname: `${process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID}.thirdwebstorage-dev.com`,
});
}
return remotePatterns;
}
const SENTRY_OPTIONS: SentryBuildOptions = {
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
org: "thirdweb-dev",
project: "dashboard",
// An auth token is required for uploading source maps.
authToken: process.env.SENTRY_AUTH_TOKEN,
// Suppresses source map uploading logs during build
silent: true,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
org: "thirdweb-dev",
project: "dashboard",
// An auth token is required for uploading source maps.
authToken: process.env.SENTRY_AUTH_TOKEN,
// Suppresses source map uploading logs during build
silent: true,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
tunnelRoute: "/err",
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
tunnelRoute: "/err",
// Hides source maps from generated client bundles
hideSourceMaps: true,
// Hides source maps from generated client bundles
hideSourceMaps: true,
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Enables automatic instrumentation of Vercel Cron Monitors.
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: false,
// Enables automatic instrumentation of Vercel Cron Monitors.
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: false,
};
const baseNextConfig: NextConfig = {
serverExternalPackages: ["pino-pretty"],
async headers() {
return [
{
// Apply these headers to all routes in your application.
source: "/(.*)",
headers: [
...securityHeaders,
{
key: "accept-ch",
value: "sec-ch-viewport-width",
},
],
},
];
},
async redirects() {
return getRedirects();
},
async rewrites() {
return [
{
source: "/thirdweb.eth",
destination: "/deployer.thirdweb.eth",
},
{
source: "/thirdweb.eth/:path*",
destination: "/deployer.thirdweb.eth/:path*",
},
// re-write /home to / (this is so that logged in users will be able to go to /home and NOT be redirected to the logged in app)
{
source: "/home",
destination: "/",
},
...FRAMER_PATHS.map((path) => ({
source: path,
destination: `https://landing.thirdweb.com${path}`,
})),
];
},
images: {
dangerouslyAllowSVG: true,
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
remotePatterns: [
{
protocol: "https",
hostname: "**.thirdweb.com",
},
...determineIpfsGateways(),
],
},
compiler: {
emotion: true,
},
reactStrictMode: true,
eslint: {
ignoreDuringBuilds: true,
},
productionBrowserSourceMaps: false,
experimental: {
webpackBuildWorker: true,
webpackMemoryOptimizations: true,
serverSourceMaps: false,
},
serverExternalPackages: ["pino-pretty"],
async headers() {
return [
{
// Apply these headers to all routes in your application.
source: "/(.*)",
headers: [
...securityHeaders,
{
key: "accept-ch",
value: "sec-ch-viewport-width",
},
],
},
];
},
async redirects() {
return getRedirects();
},
async rewrites() {
return [
{
source: "/thirdweb.eth",
destination: "/deployer.thirdweb.eth",
},
{
source: "/thirdweb.eth/:path*",
destination: "/deployer.thirdweb.eth/:path*",
},
// re-write /home to / (this is so that logged in users will be able to go to /home and NOT be redirected to the logged in app)
{
source: "/home",
destination: "/",
},
...FRAMER_PATHS.map((path) => ({
source: path,
destination: `https://landing.thirdweb.com${path}`,
})),
];
},
images: {
dangerouslyAllowSVG: true,
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
remotePatterns: [
{
protocol: "https",
hostname: "**.thirdweb.com",
},
...determineIpfsGateways(),
],
},
compiler: {
emotion: true,
},
reactStrictMode: true,
};
function getConfig(): NextConfig {
if (process.env.NODE_ENV === "production") {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { withPlausibleProxy } = require("next-plausible");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { withSentryConfig } = require("@sentry/nextjs");
return withBundleAnalyzer(
withPlausibleProxy({
customDomain: "https://pl.thirdweb.com",
scriptName: "pl",
})(
withSentryConfig(
{
...baseNextConfig,
experimental: {
webpackBuildWorker: true,
webpackMemoryOptimizations: true,
},
// @ts-expect-error - this is a valid option
webpack: (config) => {
if (config.cache) {
config.cache = Object.freeze({
type: "memory",
});
}
config.externals.push("pino-pretty");
config.module = {
...config.module,
exprContextCritical: false,
};
// Important: return the modified config
return config;
},
},
SENTRY_OPTIONS,
),
),
);
}
// otherwise return the base
return baseNextConfig;
if (process.env.NODE_ENV === "production") {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { withPlausibleProxy } = require("next-plausible");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { withSentryConfig } = require("@sentry/nextjs");
return withBundleAnalyzer(
withPlausibleProxy({
customDomain: "https://pl.thirdweb.com",
scriptName: "pl",
})(
withSentryConfig(
{
...baseNextConfig,
// @ts-expect-error - this is a valid option
webpack: (config) => {
if (config.cache) {
config.cache = Object.freeze({
type: "memory",
});
}
config.externals.push("pino-pretty");
config.module = {
...config.module,
exprContextCritical: false,
};
// Important: return the modified config
return config;
},
},
SENTRY_OPTIONS,
),
),
);
}
// otherwise return the base
return baseNextConfig;
}
export default getConfig();
+46 -46
View File
@@ -31,29 +31,29 @@
"@marsidev/react-turnstile": "^1.1.0",
"@n8tb1t/use-scroll-position": "^2.0.3",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-alert-dialog": "^1.1.5",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-dialog": "1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.3",
"@radix-ui/react-hover-card": "^1.1.3",
"@radix-ui/react-dialog": "1.1.5",
"@radix-ui/react-dropdown-menu": "^2.1.5",
"@radix-ui/react-hover-card": "^1.1.5",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.3",
"@radix-ui/react-popover": "^1.1.5",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.2",
"@radix-ui/react-select": "^2.1.3",
"@radix-ui/react-select": "^2.1.5",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tooltip": "1.1.5",
"@sentry/nextjs": "8.45.1",
"@shazow/whatsabi": "^0.18.0",
"@tanstack/react-query": "5.62.16",
"@radix-ui/react-tooltip": "1.1.7",
"@sentry/nextjs": "8.53.0",
"@shazow/whatsabi": "^0.19.0",
"@tanstack/react-query": "5.66.0",
"@tanstack/react-table": "^8.20.6",
"@thirdweb-dev/service-utils": "workspace:*",
"@vercel/functions": "^1.5.2",
"@vercel/og": "^0.6.4",
"abitype": "1.0.7",
"@vercel/functions": "^1.6.0",
"@vercel/og": "^0.6.5",
"abitype": "1.0.8",
"chakra-react-select": "^4.7.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -62,20 +62,19 @@
"date-fns": "4.1.0",
"fetch-event-stream": "0.1.5",
"flat": "^6.0.1",
"framer-motion": "11.15.0",
"framer-motion": "12.0.6",
"fuse.js": "7.0.0",
"input-otp": "^1.4.1",
"ioredis": "^5.4.1",
"ipaddr.js": "^2.2.0",
"lucide-react": "0.468.0",
"next": "15.1.3",
"lucide-react": "0.474.0",
"next": "15.1.6",
"next-plausible": "^3.12.4",
"next-seo": "^6.5.0",
"next-themes": "^0.4.4",
"nextjs-toploader": "^1.6.12",
"openapi-types": "^12.1.3",
"p-limit": "^6.2.0",
"papaparse": "^5.4.1",
"papaparse": "^5.5.2",
"pluralize": "^8.0.0",
"posthog-js": "1.67.1",
"qrcode": "^1.5.3",
@@ -84,17 +83,18 @@
"react-day-picker": "^8.10.1",
"react-dom": "19.0.0",
"react-dropzone": "^14.3.5",
"react-error-boundary": "^4.1.2",
"react-error-boundary": "^5.0.0",
"react-hook-form": "7.54.2",
"react-markdown": "^9.0.1",
"react-table": "^7.8.0",
"recharts": "2.14.1",
"recharts": "2.15.1",
"remark-gfm": "^4.0.0",
"responsive-rsc": "0.0.7",
"server-only": "^0.0.1",
"shiki": "1.24.2",
"sonner": "^1.7.1",
"shiki": "1.27.0",
"sonner": "^1.7.4",
"spdx-correct": "^3.2.0",
"swagger-ui-react": "^5.18.2",
"swagger-ui-react": "^5.18.3",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"thirdweb": "workspace:*",
@@ -104,42 +104,42 @@
},
"devDependencies": {
"@chakra-ui/cli": "^2.4.1",
"@chromatic-com/storybook": "3.2.2",
"@next/bundle-analyzer": "15.1.0",
"@next/eslint-plugin-next": "15.1.3",
"@playwright/test": "1.49.1",
"@storybook/addon-essentials": "8.4.7",
"@storybook/addon-interactions": "8.4.7",
"@storybook/addon-links": "8.4.7",
"@storybook/addon-onboarding": "8.4.7",
"@storybook/addon-viewport": "8.4.7",
"@storybook/blocks": "8.4.7",
"@storybook/nextjs": "8.4.7",
"@storybook/react": "8.4.7",
"@storybook/test": "8.4.7",
"@chromatic-com/storybook": "3.2.4",
"@next/bundle-analyzer": "15.1.6",
"@next/eslint-plugin-next": "15.1.6",
"@playwright/test": "1.50.1",
"@storybook/addon-essentials": "8.5.2",
"@storybook/addon-interactions": "8.5.2",
"@storybook/addon-links": "8.5.2",
"@storybook/addon-onboarding": "8.5.2",
"@storybook/addon-viewport": "8.5.2",
"@storybook/blocks": "8.5.2",
"@storybook/nextjs": "8.5.2",
"@storybook/react": "8.5.2",
"@storybook/test": "8.5.2",
"@types/color": "4.2.0",
"@types/node": "22.10.2",
"@types/node": "22.13.0",
"@types/papaparse": "^5.3.15",
"@types/pluralize": "^0.0.33",
"@types/qrcode": "^1.5.5",
"@types/react": "19.0.2",
"@types/react-dom": "19.0.2",
"@types/react": "19.0.8",
"@types/react-dom": "19.0.3",
"@types/react-table": "^7.7.20",
"@types/spdx-correct": "^3.1.3",
"@types/swagger-ui-react": "^4.18.3",
"@types/swagger-ui-react": "^4.19.0",
"@typescript-eslint/eslint-plugin": "7.14.1",
"@typescript-eslint/parser": "7.14.1",
"autoprefixer": "^10.4.19",
"checkly": "^4.15.0",
"checkly": "^4.19.1",
"eslint": "8.57.0",
"eslint-config-biome": "1.9.4",
"eslint-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
"eslint-plugin-storybook": "^0.11.1",
"knip": "5.41.0",
"knip": "5.43.6",
"next-sitemap": "^4.2.3",
"postcss": "8.4.49",
"storybook": "8.4.7",
"tailwindcss": "3.4.16",
"typescript": "5.7.2"
"postcss": "8.5.1",
"storybook": "8.5.2",
"tailwindcss": "3.4.17",
"typescript": "5.7.3"
}
}
@@ -1,2 +1,2 @@
name,description,external_url,background_color,youtube_url,additional,properties,can be,added
name,description,external_url,background_color,youtube_url,additional,properties,can be,added
Token 0 Name,Token 0 Description,https://thirdweb.com,#0098EE,,every,row,is a ,property
1 name description external_url background_color youtube_url additional properties can be added
2 Token 0 Name Token 0 Description https://thirdweb.com #0098EE every row is a property

Before

Width:  |  Height:  |  Size: 881 KiB

After

Width:  |  Height:  |  Size: 881 KiB

+15 -3
View File
@@ -21,7 +21,7 @@ const legacyDashboardToTeamRedirects = [
},
{
source: "/dashboard/settings/api-keys",
destination: "/team/~/~/projects",
destination: "/team",
permanent: false,
},
{
@@ -160,12 +160,12 @@ async function redirects() {
},
{
source: "/create-api-key",
destination: "/team/~/~/projects",
destination: "/team",
permanent: false,
},
{
source: "/dashboard/settings",
destination: "/team/~/~/projects",
destination: "/team",
permanent: false,
},
{
@@ -331,6 +331,18 @@ async function redirects() {
destination: "/templates/:slug",
permanent: false,
},
// redirect /account-abstraction to /connect/account-abstraction
{
source: "/account-abstraction",
destination: "/connect/account-abstraction",
permanent: false,
},
// redirect /connect/pay to /connect/universal-bridge
{
source: "/connect/pay",
destination: "/connect/universal-bridge",
permanent: false,
},
// PREVIOUS CAMPAIGNS
{
source: "/unlimited-wallets",
@@ -0,0 +1,28 @@
"use server";
type EmailSignupParams = {
email: string;
send_welcome_email?: boolean;
};
export async function emailSignup(payLoad: EmailSignupParams) {
const response = await fetch(
"https://api.beehiiv.com/v2/publications/pub_9f54090a-6d14-406b-adfd-dbb30574f664/subscriptions",
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.BEEHIIV_API_KEY}`,
},
method: "POST",
body: JSON.stringify({
email: payLoad.email,
send_welcome_email: payLoad.send_welcome_email || false,
utm_source: "thirdweb.com",
}),
},
);
return {
status: response.status,
};
}
@@ -1,15 +1,11 @@
"use server";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { defineDashboardChain } from "lib/defineDashboardChain";
import type { NextApiRequest, NextApiResponse } from "next";
import { ZERO_ADDRESS, isAddress, toTokens } from "thirdweb";
import { getWalletBalance } from "thirdweb/wallets";
export type BalanceQueryRequest = {
chainId: number;
address: string;
};
export type BalanceQueryResponse = Array<{
type BalanceQueryResponse = Array<{
balance: string;
decimals: number;
name?: string;
@@ -18,21 +14,30 @@ export type BalanceQueryResponse = Array<{
display_balance: string;
}>;
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== "POST") {
return res.status(400).json({ error: "invalid method" });
}
export async function getTokenBalancesFromMoralis(params: {
contractAddress: string;
chainId: number;
}): Promise<
| { data: BalanceQueryResponse; error: undefined }
| {
data: undefined;
error: string;
}
> {
const { contractAddress, chainId } = params;
const { chainId, address } = req.body;
if (!isAddress(address)) {
return res.status(400).json({ error: "invalid address" });
if (!isAddress(contractAddress)) {
return {
data: undefined,
error: "invalid address",
};
}
const getNativeBalance = async (): Promise<BalanceQueryResponse> => {
// eslint-disable-next-line no-restricted-syntax
const chain = defineDashboardChain(chainId, undefined);
const balance = await getWalletBalance({
address,
address: contractAddress,
chain,
client: getThirdwebClient(),
});
@@ -50,7 +55,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const getTokenBalances = async (): Promise<BalanceQueryResponse> => {
const _chain = encodeURIComponent(`0x${chainId?.toString(16)}`);
const _address = encodeURIComponent(address);
const _address = encodeURIComponent(contractAddress);
const tokenBalanceEndpoint = `https://deep-index.moralis.io/api/v2/${_address}/erc20?chain=${_chain}`;
const resp = await fetch(tokenBalanceEndpoint, {
@@ -59,6 +64,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
"x-api-key": process.env.MORALIS_API_KEY || "",
},
});
if (!resp.ok) {
resp.body?.cancel();
return [];
@@ -76,7 +82,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
getTokenBalances(),
]);
return res.status(200).json([...nativeBalance, ...tokenBalances]);
};
export default handler;
return {
error: undefined,
data: [...nativeBalance, ...tokenBalances],
};
}
@@ -0,0 +1,117 @@
"use server";
import {
generateAlchemyUrl,
isAlchemySupported,
transformAlchemyResponseToNFT,
} from "lib/wallet/nfts/alchemy";
import {
generateMoralisUrl,
isMoralisSupported,
transformMoralisResponseToNFT,
} from "lib/wallet/nfts/moralis";
import {
generateSimpleHashUrl,
isSimpleHashSupported,
transformSimpleHashResponseToNFT,
} from "lib/wallet/nfts/simpleHash";
import type { WalletNFT } from "lib/wallet/nfts/types";
type WalletNFTApiReturn =
| { result: WalletNFT[]; error?: undefined }
| { result?: undefined; error: string };
export async function getWalletNFTs(params: {
chainId: number;
owner: string;
}): Promise<WalletNFTApiReturn> {
const { chainId, owner } = params;
const supportedChainSlug = await isSimpleHashSupported(chainId);
if (supportedChainSlug && process.env.SIMPLEHASH_API_KEY) {
const url = generateSimpleHashUrl({ chainSlug: supportedChainSlug, owner });
const response = await fetch(url, {
method: "GET",
headers: {
"X-API-KEY": process.env.SIMPLEHASH_API_KEY,
},
next: {
revalidate: 10, // cache for 10 seconds
},
});
if (response.status >= 400) {
return {
error: response.statusText,
};
}
try {
const parsedResponse = await response.json();
const result = await transformSimpleHashResponseToNFT(
parsedResponse,
owner,
);
return { result };
} catch {
return { error: "error parsing response" };
}
}
if (isAlchemySupported(chainId)) {
const url = generateAlchemyUrl({ chainId, owner });
const response = await fetch(url, {
next: {
revalidate: 10, // cache for 10 seconds
},
});
if (response.status >= 400) {
return { error: response.statusText };
}
try {
const parsedResponse = await response.json();
const result = await transformAlchemyResponseToNFT(parsedResponse, owner);
return { result, error: undefined };
} catch (err) {
console.error("Error fetching NFTs", err);
return { error: "error parsing response" };
}
}
if (isMoralisSupported(chainId) && process.env.MORALIS_API_KEY) {
const url = generateMoralisUrl({ chainId, owner });
const response = await fetch(url, {
method: "GET",
headers: {
"X-API-Key": process.env.MORALIS_API_KEY,
},
next: {
revalidate: 10, // cache for 10 seconds
},
});
if (response.status >= 400) {
return { error: response.statusText };
}
try {
const parsedResponse = await response.json();
const result = await transformMoralisResponseToNFT(
await parsedResponse,
owner,
);
return { result };
} catch (err) {
console.error("Error fetching NFTs", err);
return { error: "error parsing response" };
}
}
return { error: "unsupported chain" };
}
+98
View File
@@ -0,0 +1,98 @@
"use server";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";
type ProxyActionParams = {
pathname: string;
searchParams?: Record<string, string>;
method: "GET" | "POST" | "PUT" | "DELETE";
body?: string;
headers?: Record<string, string>;
};
type ProxyActionResult<T extends object> =
| {
status: number;
ok: true;
data: T;
}
| {
status: number;
ok: false;
error: string;
};
async function proxy<T extends object>(
baseUrl: string,
params: ProxyActionParams,
): Promise<ProxyActionResult<T>> {
const authToken = await getAuthToken();
// build URL
const url = new URL(baseUrl);
url.pathname = params.pathname;
if (params.searchParams) {
for (const key in params.searchParams) {
url.searchParams.append(key, params.searchParams[key] as string);
}
}
const res = await fetch(url, {
method: params.method,
headers: {
...params.headers,
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
},
body: params.body,
});
if (!res.ok) {
try {
const errorMessage = await res.text();
return {
status: res.status,
ok: false,
error: errorMessage || res.statusText,
};
} catch {
return {
status: res.status,
ok: false,
error: res.statusText,
};
}
}
return {
status: res.status,
ok: true,
data: await res.json(),
};
}
export async function analyticsServerProxy<T extends object = object>(
params: ProxyActionParams,
) {
return proxy<T>(
process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
params,
);
}
export async function apiServerProxy<T extends object = object>(
params: ProxyActionParams,
) {
return proxy<T>(API_SERVER_URL, params);
}
export async function payServerProxy<T extends object = object>(
params: ProxyActionParams,
) {
return proxy<T>(
process.env.NEXT_PUBLIC_PAY_URL
? `https://${process.env.NEXT_PUBLIC_PAY_URL}`
: "https://pay.thirdweb-dev.com",
params,
);
}
@@ -5,6 +5,7 @@ import { API_SERVER_URL } from "../constants/env";
export async function updateAccount(values: {
name?: string;
email?: string;
image?: string | null;
}) {
const token = await getAuthToken();
+29 -1
View File
@@ -3,6 +3,7 @@ import type {
AnalyticsQueryParams,
InAppWalletStats,
RpcMethodStats,
TransactionStats,
UserOpStats,
WalletStats,
WalletUserStats,
@@ -77,7 +78,10 @@ export async function getUserOpUsage(
});
if (res?.status !== 200) {
console.error("Failed to fetch user ops usage");
const reason = await res?.text();
console.error(
`Failed to fetch user ops usage: ${res?.status} - ${res.statusText} - ${reason}`,
);
return [];
}
@@ -85,6 +89,30 @@ export async function getUserOpUsage(
return json.data as UserOpStats[];
}
export async function getClientTransactions(
params: AnalyticsQueryParams,
): Promise<TransactionStats[]> {
const searchParams = buildSearchParams(params);
const res = await fetchAnalytics(
`v1/transactions/client?${searchParams.toString()}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
if (res?.status !== 200) {
const reason = await res?.text();
console.error(
`Failed to fetch client transactions stats: ${res?.status} - ${res.statusText} - ${reason}`,
);
return [];
}
const json = await res.json();
return json.data as TransactionStats[];
}
export async function getRpcMethodUsage(
params: AnalyticsQueryParams,
): Promise<RpcMethodStats[]> {
+3 -12
View File
@@ -1,7 +1,6 @@
import "server-only";
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
import { API_SERVER_URL } from "@/constants/env";
import { cookies } from "next/headers";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
export type Project = {
id: string;
@@ -21,11 +20,7 @@ export type Project = {
};
export async function getProjects(teamSlug: string) {
const cookiesManager = await cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;
const token = await getAuthToken();
if (!token) {
return [];
@@ -46,11 +41,7 @@ export async function getProjects(teamSlug: string) {
}
export async function getProject(teamSlug: string, projectSlug: string) {
const cookiesManager = await cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;
const token = await getAuthToken();
if (!token) {
return null;
+6 -1
View File
@@ -24,8 +24,9 @@ export type Team = {
image?: string;
billingPlan: "pro" | "growth" | "free" | "starter";
billingStatus: "validPayment" | (string & {}) | null;
supportPlan: "pro" | "growth" | "free" | "starter";
billingEmail: string | null;
growthTrialEligible: boolean | null;
growthTrialEligible: false;
enabledScopes: EnabledTeamScope[];
};
@@ -47,6 +48,10 @@ export async function getTeamBySlug(slug: string) {
return null;
}
export function getTeamById(id: string) {
return getTeamBySlug(id);
}
export async function getTeams() {
const token = await getAuthToken();
if (!token) {
@@ -12,7 +12,7 @@ export function ProjectAvatar(props: {
className={cn("rounded-lg border border-border", props.className)}
alt={""}
fallback={
<div className="flex items-center justify-center bg-muted/50">
<div className="flex items-center justify-center bg-card">
<BoxIcon className="size-[50%] text-muted-foreground" />
</div>
}
@@ -30,7 +30,7 @@ export function DangerSettingCard(props: {
return (
<div
className={cn(
"overflow-hidden rounded-lg border border-red-500/70",
"overflow-hidden rounded-lg border border-red-500/70 bg-card",
props.className,
)}
>
@@ -1,10 +1,11 @@
import { Label } from "@/components/ui/label";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { AsteriskIcon, InfoIcon } from "lucide-react";
import type React from "react";
export function FormFieldSetup(props: {
htmlFor?: string;
label: string;
label: React.ReactNode;
errorMessage: React.ReactNode | undefined;
children: React.ReactNode;
tooltip?: React.ReactNode;
@@ -25,7 +25,7 @@ export function Img(props: imgElementProps) {
: _status;
const { className, fallback, skeleton, ...restProps } = props;
const defaultSkeleton = <div className="animate-pulse bg-accent" />;
const defaultFallback = <div className="bg-muted" />;
const defaultFallback = <div className="bg-accent" />;
const imgRef = useRef<HTMLImageElement>(null);
useIsomorphicLayoutEffect(() => {
@@ -47,7 +47,7 @@ export function MobileSidebar(props: {
const defaultTrigger = (
<Button
className={cn(
"w-full justify-between gap-2 bg-muted/50 text-left lg:hidden",
"w-full justify-between gap-2 bg-card text-left lg:hidden",
props.triggerClassName,
)}
variant="outline"
@@ -16,6 +16,8 @@ type Option = { label: string; value: string };
export function MultiNetworkSelector(props: {
selectedChainIds: number[];
onChange: (chainIds: number[]) => void;
disableChainId?: boolean;
className?: string;
}) {
const { allChains, idToChain } = useAllChainsData();
@@ -53,16 +55,24 @@ export function MultiNetworkSelector(props: {
return (
<div className="flex justify-between gap-4">
<span className="flex grow gap-2 truncate text-left">
<ChainIcon
className="size-5"
ipfsSrc={chain.icon?.url}
loading="lazy"
/>
{cleanChainName(chain.name)}
</span>
<Badge variant="outline" className="gap-2">
<span className="text-muted-foreground">Chain ID</span>
{chain.chainId}
</Badge>
{!props.disableChainId && (
<Badge variant="outline" className="gap-2">
<span className="text-muted-foreground">Chain ID</span>
{chain.chainId}
</Badge>
)}
</div>
);
},
[idToChain],
[idToChain, props.disableChainId],
);
return (
@@ -79,6 +89,7 @@ export function MultiNetworkSelector(props: {
disabled={allChains.length === 0}
overrideSearchFn={searchFn}
renderOption={renderOption}
className={props.className}
/>
);
}
@@ -90,6 +101,9 @@ export function SingleNetworkSelector(props: {
popoverContentClassName?: string;
// if specified - only these chains will be shown
chainIds?: number[];
side?: "left" | "right" | "top" | "bottom";
disableChainId?: boolean;
align?: "center" | "start" | "end";
}) {
const { allChains, idToChain } = useAllChainsData();
@@ -140,16 +154,19 @@ export function SingleNetworkSelector(props: {
ipfsSrc={chain.icon?.url}
loading="lazy"
/>
{chain.name}
{cleanChainName(chain.name)}
</span>
<Badge variant="outline" className="gap-2 max-sm:hidden">
<span className="text-muted-foreground">Chain ID</span>
{chain.chainId}
</Badge>
{!props.disableChainId && (
<Badge variant="outline" className="gap-2 max-sm:hidden">
<span className="text-muted-foreground">Chain ID</span>
{chain.chainId}
</Badge>
)}
</div>
);
},
[idToChain],
[idToChain, props.disableChainId],
);
const isLoadingChains = allChains.length === 0;
@@ -162,12 +179,15 @@ export function SingleNetworkSelector(props: {
onValueChange={(chainId) => {
props.onChange(Number(chainId));
}}
closeOnSelect={true}
placeholder={isLoadingChains ? "Loading Chains..." : "Select Chain"}
overrideSearchFn={searchFn}
renderOption={renderOption}
className={props.className}
popoverContentClassName={props.popoverContentClassName}
disabled={isLoadingChains}
side={props.side}
align={props.align}
/>
);
}
@@ -28,7 +28,7 @@ export const Mobile: Story = {
function Story() {
return (
<div className="min-h-screen bg-background py-6 text-foreground">
<div className="min-h-dvh bg-background py-6 text-foreground">
<div className="container flex max-w-[1100px] flex-col gap-10 lg:p-10">
<BadgeContainer label="No Header">
<SettingsCard
@@ -30,7 +30,7 @@ export function SettingsCard(
}>,
) {
return (
<div className="relative rounded-lg border border-border bg-muted/50">
<div className="relative rounded-lg border border-border bg-card">
<div
className={cn(
"relative border-border border-b px-4 py-6 lg:px-6",
@@ -67,7 +67,7 @@ export function RenderSidebarLinks(props: { links: SidebarLink[] }) {
// biome-ignore lint/suspicious/noArrayIndexKey: items won't be reordered
key={i}
href={link.href}
className="flex items-center gap-2 rounded-md px-3 py-2 text-muted-foreground text-sm hover:bg-muted"
className="flex items-center gap-2 rounded-md px-3 py-2 text-muted-foreground text-sm hover:bg-accent"
activeClassName="text-foreground"
exactMatch={link.exactMatch}
>
@@ -19,7 +19,7 @@ export function AppFooter(props: AppFooterProps) {
return (
<footer
className={cn(
"w-full border-border border-t bg-muted/50 py-6 md:py-8",
"w-full border-border border-t bg-card py-6 md:py-8",
props.className,
)}
>
@@ -28,7 +28,9 @@ export function AppFooter(props: AppFooterProps) {
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="flex flex-row items-center gap-3">
<ThirdwebMiniLogo className="h-7" />
<p className="text-muted-foreground text-sm">© 2024 thirdweb</p>
<p className="text-muted-foreground text-sm">
© {new Date().getFullYear()} thirdweb
</p>
</div>
<div className="flex flex-row gap-3">
<Button size="icon" variant="ghost" asChild className="size-9">
@@ -8,8 +8,13 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { formatDate } from "date-fns";
import { useMemo } from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import {
EmptyChartState,
LoadingChartState,
} from "../../../../components/analytics/empty-chart-state";
type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
// chart config
@@ -19,6 +24,7 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
// chart className
chartClassName?: string;
isPending: boolean;
};
export function ThirdwebAreaChart<TConfig extends ChartConfig>(
@@ -26,64 +32,70 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
) {
const configKeys = useMemo(() => Object.keys(props.config), [props.config]);
return (
<div className="rounded-lg border border-border bg-muted/50 px-4 pt-10 pb-4">
<div className="rounded-lg border border-border px-4 pt-10 pb-4">
<ChartContainer config={props.config} className={props.chartClassName}>
<AreaChart
accessibilityLayer
data={props.data}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={20}
tickFormatter={(value) => new Date(value).toLocaleDateString()}
/>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<defs>
{configKeys.map((key) => (
<linearGradient
key={key}
id={`fill_${key}`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={`var(--color-${key})`}
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor={`var(--color-${key})`}
stopOpacity={0.1}
/>
</linearGradient>
))}
</defs>
{configKeys.map((key) => (
<Area
key={key}
dataKey={key}
type="natural"
fill={`url(#fill_${key})`}
fillOpacity={0.4}
stroke={`var(--color-${key})`}
stackId="a"
{props.isPending ? (
<LoadingChartState />
) : props.data.length === 0 ? (
<EmptyChartState />
) : (
<AreaChart
accessibilityLayer
data={props.data}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={20}
tickFormatter={(value) => formatDate(new Date(value), "MMM dd")}
/>
))}
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<defs>
{configKeys.map((key) => (
<linearGradient
key={key}
id={`fill_${key}`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={`var(--color-${key})`}
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor={`var(--color-${key})`}
stopOpacity={0.1}
/>
</linearGradient>
))}
</defs>
{configKeys.map((key) => (
<Area
key={key}
dataKey={key}
type="natural"
fill={`url(#fill_${key})`}
fillOpacity={0.4}
stroke={`var(--color-${key})`}
stackId="a"
/>
))}
{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} className="pt-8" />
)}
</AreaChart>
{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} className="pt-8" />
)}
</AreaChart>
)}
</ChartContainer>
</div>
);
@@ -17,7 +17,13 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { formatDate } from "date-fns";
import { useMemo } from "react";
import {
EmptyChartState,
LoadingChartState,
} from "../../../../components/analytics/empty-chart-state";
import { cn } from "../../../lib/utils";
type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
// metadata
@@ -30,6 +36,10 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
variant?: "stacked" | "grouped";
// chart className
chartClassName?: string;
isPending: boolean;
toolTipLabelFormatter?: (label: string, payload: unknown) => React.ReactNode;
hideLabel?: boolean;
titleClassName?: string;
};
export function ThirdwebBarChart<TConfig extends ChartConfig>(
@@ -39,51 +49,71 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
// if there are more than 4 keys then we should stack them by default
const variant =
props.variant || configKeys.length > 4 ? "stacked" : "grouped";
return (
<Card>
<CardHeader>
<CardTitle>{props.title}</CardTitle>
<CardTitle className={cn("mb-2", props.titleClassName)}>
{props.title}
</CardTitle>
{props.description && (
<CardDescription>{props.description}</CardDescription>
)}
</CardHeader>
<CardContent>
<ChartContainer config={props.config} className={props.chartClassName}>
<BarChart accessibilityLayer data={props.data}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={10}
tickFormatter={(value) => new Date(value).toLocaleDateString()}
/>
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} />
)}
{configKeys.map((key, idx) => (
<Bar
key={key}
dataKey={key}
// if stacked then they should all be the same stackId
// if grouped then they should all be unique stackId (so the key works great)
stackId={variant === "stacked" ? "a" : key}
fill={`var(--color-${key})`}
// if stacked then we need to figure out the radius based on the index in the array
// if grouped then we can just use the same radius for all
radius={
variant === "stacked"
? idx === 0
? [0, 0, 4, 4]
: idx === configKeys.length - 1
? [4, 4, 0, 0]
: [0, 0, 0, 0]
: [4, 4, 4, 4]
{props.isPending ? (
<LoadingChartState />
) : props.data.length === 0 ? (
<EmptyChartState />
) : (
<BarChart accessibilityLayer data={props.data}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={10}
tickFormatter={(value) => formatDate(new Date(value), "MMM d")}
/>
<ChartTooltip
content={
<ChartTooltipContent
hideLabel={
props.hideLabel !== undefined ? props.hideLabel : true
}
labelFormatter={props.toolTipLabelFormatter}
/>
}
/>
))}
</BarChart>
{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} />
)}
{configKeys.map((key, idx) => (
<Bar
key={key}
dataKey={key}
// if stacked then they should all be the same stackId
// if grouped then they should all be unique stackId (so the key works great)
stackId={variant === "stacked" ? "a" : key}
fill={`var(--color-${key})`}
// if stacked then we need to figure out the radius based on the index in the array
// if grouped then we can just use the same radius for all
radius={
variant === "stacked"
? idx === 0
? [0, 0, 4, 4]
: idx === configKeys.length - 1
? [4, 4, 0, 0]
: [0, 0, 0, 0]
: [4, 4, 4, 4]
}
strokeWidth={1}
className="stroke-background"
/>
))}
</BarChart>
)}
</ChartContainer>
</CardContent>
</Card>
@@ -23,7 +23,7 @@ export function UnexpectedValueErrorMessage(props: {
{stringifiedValue && (
<div className="mt-3">
<p className="mb-0.5 text-muted-foreground text-sm">Value Received</p>
<ScrollShadow className="rounded-lg bg-muted/50">
<ScrollShadow className="rounded-lg bg-card">
<code className="block whitespace-pre p-4 font-mono text-xs">
{stringifiedValue}
</code>
@@ -202,7 +202,7 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
event.stopPropagation();
handleClear();
}}
className="rounded p-1 hover:bg-muted"
className="rounded p-1 hover:bg-accent"
>
<XIcon className="h-4 cursor-pointer text-muted-foreground" />
</div>
@@ -57,8 +57,9 @@ export const PricingCard: React.FC<PricingCardProps> = ({
return (
<div
className={cn(
"z-[999] flex w-full flex-col gap-6 rounded-xl border border-border bg-muted/50 p-4 md:p-6",
"z-10 flex w-full flex-col gap-6 rounded-xl border border-border bg-card p-4 md:p-6",
current && "border-blue-500",
highlighted && "border-active-border",
)}
style={
highlighted
@@ -31,6 +31,9 @@ interface SelectWithSearchProps
) => boolean;
renderOption?: (option: { value: string; label: string }) => React.ReactNode;
popoverContentClassName?: string;
side?: "left" | "right" | "top" | "bottom";
align?: "center" | "start" | "end";
closeOnSelect?: boolean;
}
export const SelectWithSearch = React.forwardRef<
@@ -48,6 +51,7 @@ export const SelectWithSearch = React.forwardRef<
overrideSearchFn,
popoverContentClassName,
searchPlaceholder,
closeOnSelect,
...props
},
ref,
@@ -107,7 +111,7 @@ export const SelectWithSearch = React.forwardRef<
}, [searchValue]);
return (
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen} modal>
<PopoverTrigger asChild>
<Button
ref={ref}
@@ -118,23 +122,24 @@ export const SelectWithSearch = React.forwardRef<
className,
)}
>
<div className="flex w-full items-center justify-between">
<div className="flex w-full items-center justify-between gap-2">
<span
className={cn(
"text-muted-foreground text-sm",
"truncate text-muted-foreground text-sm",
selectedOption && "text-foreground",
)}
>
{selectedOption?.label || placeholder}
</span>
<ChevronDown className="h-4 cursor-pointer text-muted-foreground" />
<ChevronDown className="size-4 cursor-pointer text-muted-foreground" />
</div>
</Button>
</PopoverTrigger>
<PopoverContent
className={cn("z-[10001] p-0", popoverContentClassName)}
align="center"
align={props.align || "center"}
side={props.side}
sideOffset={10}
onEscapeKeyDown={() => setIsPopoverOpen(false)}
style={{
@@ -174,7 +179,12 @@ export const SelectWithSearch = React.forwardRef<
key={option.value}
role="option"
aria-selected={isSelected}
onClick={() => onValueChange(option.value)}
onClick={() => {
onValueChange(option.value);
if (closeOnSelect) {
setIsPopoverOpen(false);
}
}}
variant="ghost"
className="flex w-full cursor-pointer justify-start gap-3 rounded-sm px-3 py-2 text-left"
ref={
@@ -8,7 +8,7 @@ import {
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import { useClipboard } from "hooks/useClipboard";
import { Check, Copy, ExternalLinkIcon } from "lucide-react";
import { Check, Copy } from "lucide-react";
import { useMemo } from "react";
import { type ThirdwebClient, isAddress } from "thirdweb";
import { ZERO_ADDRESS } from "thirdweb";
@@ -121,7 +121,11 @@ export function WalletAddress(props: {
>
{walletAvatarLink && (
<Avatar>
<AvatarImage src={walletAvatarLink} alt={profile.name} />
<AvatarImage
src={walletAvatarLink}
alt={profile.name}
className="object-cover"
/>
{profile.name && (
<AvatarFallback>
{profile.name.slice(0, 2)}
@@ -144,21 +148,6 @@ export function WalletAddress(props: {
);
})
)}
<Button
asChild
variant="upsell"
className="flex flex-row items-center gap-2 text-sm"
size="sm"
>
<a
target="_blank"
href="https://blog.thirdweb.com/changelog/introducing-the-social-sdk?ref=dashboard-social-wallet"
rel="noreferrer"
>
Learn more
<ExternalLinkIcon className="size-4" />
</a>
</Button>
</div>
</HoverCardContent>
</HoverCard>
@@ -0,0 +1,58 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer, mobileViewport } from "../../stories/utils";
import { PaginationButtons } from "./pagination-buttons";
const meta = {
title: "blocks/PaginationButtons",
component: Story,
parameters: {
nextjs: {
appDirectory: true,
},
},
} satisfies Meta<typeof Story>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Desktop: Story = {
args: {},
};
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};
function Story() {
return (
<div className="container flex max-w-[1000px] flex-col gap-8 py-10">
<Variant label="10 Pages" totalPages={10} />
<Variant label="100 Pages" totalPages={100} />
<Variant label="3 Pages" totalPages={2} />
<Variant label="6 Pages" totalPages={6} />
<Variant label="1 Page - nothing rendered" totalPages={1} />
</div>
);
}
function Variant(props: {
label: string;
totalPages: number;
}) {
const [activePage, setActivePage] = useState(1);
return (
<BadgeContainer label={props.label}>
<div className="border bg-card py-5">
<PaginationButtons
activePage={activePage}
onPageClick={setActivePage}
totalPages={props.totalPages}
/>
</div>
</BadgeContainer>
);
}
@@ -9,6 +9,11 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { ArrowUpRightIcon } from "lucide-react";
import { useState } from "react";
import { cn } from "../lib/utils";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
export const PaginationButtons = (props: {
activePage: number;
@@ -16,6 +21,47 @@ export const PaginationButtons = (props: {
onPageClick: (page: number) => void;
}) => {
const { activePage, totalPages, onPageClick: setPage } = props;
const [inputHasError, setInputHasError] = useState(false);
const [pageNumberInput, setPageNumberInput] = useState("");
if (totalPages === 1) {
return null;
}
function handlePageInputSubmit() {
const page = Number(pageNumberInput);
setInputHasError(false);
if (Number.isInteger(page) && page > 0 && page <= totalPages) {
setPage(page);
setPageNumberInput("");
} else {
setInputHasError(true);
}
}
// just render all the page buttons directly
if (totalPages <= 6) {
const pages = [...Array(totalPages)].map((_, i) => i + 1);
return (
<Pagination>
<PaginationContent>
{pages.map((page) => (
<PaginationItem key={page}>
<PaginationLink
isActive={activePage === page}
onClick={() => {
setPage(page);
}}
>
{page}
</PaginationLink>
</PaginationItem>
))}
</PaginationContent>
</Pagination>
);
}
return (
<Pagination>
@@ -28,24 +74,28 @@ export const PaginationButtons = (props: {
}}
/>
</PaginationItem>
{/* First page + ... */}
{activePage - 3 > 0 && (
<PaginationItem className="max-sm:hidden">
<PaginationEllipsis />
</PaginationItem>
)}
{activePage - 2 > 0 && (
<PaginationItem className="max-sm:hidden">
<PaginationLink
onClick={() => {
setPage(activePage - 2);
}}
>
{activePage - 2}
</PaginationLink>
</PaginationItem>
<>
<PaginationItem>
<PaginationLink
onClick={() => {
setPage(1);
}}
>
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis className="max-sm:w-3" />
</PaginationItem>
</>
)}
{activePage - 1 > 0 && (
<PaginationItem>
<PaginationItem className="max-sm:hidden">
<PaginationLink
onClick={() => {
setPage(activePage - 1);
@@ -55,11 +105,13 @@ export const PaginationButtons = (props: {
</PaginationLink>
</PaginationItem>
)}
<PaginationItem>
<PaginationLink isActive>{activePage}</PaginationLink>
</PaginationItem>
{activePage + 1 <= totalPages && (
<PaginationItem>
<PaginationItem className="max-sm:hidden">
<PaginationLink
onClick={() => {
setPage(activePage + 1);
@@ -69,22 +121,26 @@ export const PaginationButtons = (props: {
</PaginationLink>
</PaginationItem>
)}
{activePage + 2 <= totalPages && (
<PaginationItem className="max-sm:hidden">
<PaginationLink
onClick={() => {
setPage(activePage + 2);
}}
>
{activePage + 2}
</PaginationLink>
</PaginationItem>
)}
{/* ... + Last page */}
{activePage + 3 <= totalPages && (
<PaginationItem className="max-sm:hidden">
<PaginationEllipsis />
</PaginationItem>
<>
<PaginationItem>
<PaginationEllipsis className="max-sm:w-3" />
</PaginationItem>
<PaginationItem>
<PaginationLink
onClick={() => {
setPage(totalPages);
}}
>
{totalPages}
</PaginationLink>
</PaginationItem>
</>
)}
<PaginationItem>
<PaginationNext
disabled={activePage === totalPages}
@@ -93,6 +149,34 @@ export const PaginationButtons = (props: {
}}
/>
</PaginationItem>
<div className="relative flex items-center">
<Input
value={pageNumberInput}
onChange={(e) => {
setInputHasError(false);
setPageNumberInput(e.target.value);
}}
type="number"
placeholder="Page"
className={cn(
"w-[60px] bg-transparent [appearance:textfield] max-sm:placeholder:text-sm lg:w-[100px] lg:pr-8 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
inputHasError && "text-red-500",
)}
onKeyDown={(e) => {
if (e.key === "Enter") {
handlePageInputSubmit();
}
}}
/>
<Button
variant="ghost"
className="absolute right-1 h-auto w-auto p-2 max-sm:hidden"
onClick={handlePageInputSubmit}
>
<ArrowUpRightIcon className="size-4" />
</Button>
</div>
</PaginationContent>
</Pagination>
);
@@ -9,7 +9,7 @@ import { ToolTipLabel } from "./tooltip";
export function CopyTextButton(props: {
textToShow: string;
textToCopy: string;
tooltip: string;
tooltip: string | undefined;
className?: string;
iconClassName?: string;
variant?:
@@ -27,6 +27,7 @@ export function DatePickerWithRange(props: {
header?: React.ReactNode;
footer?: React.ReactNode;
labelOverride?: string;
popoverAlign?: "start" | "end" | "center";
}) {
const [screen, setScreen] = React.useState<"from" | "to">("from");
const { from, to, setFrom, setTo } = props;
@@ -65,7 +66,11 @@ export function DatePickerWithRange(props: {
</PopoverTrigger>
{/* Popover */}
<PopoverContent className="w-auto p-0" align="start">
<PopoverContent
className="w-auto p-0"
align={props.popoverAlign || "start"}
sideOffset={10}
>
<DynamicHeight>
<div>
{!isValid && (
+1 -1
View File
@@ -4,7 +4,7 @@ import * as React from "react";
import { cn } from "@/lib/utils";
const alertVariants = cva(
"dark:bg-muted/50 relative w-full rounded-lg border border-border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
"bg-card relative w-full rounded-lg border border-border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
@@ -20,7 +20,7 @@ export const AllVariants: Story = {
function Component() {
return (
<div className="flex min-h-screen flex-col gap-6 bg-background p-6 text-foreground">
<div className="flex min-h-dvh flex-col gap-6 bg-background p-6 text-foreground">
<Variants size="default" />
<Variants size="sm" />
<Variants size="icon" />
+8 -5
View File
@@ -113,7 +113,10 @@ const ChartTooltipContent = React.forwardRef<
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
valueFormatter?: (v: unknown) => string | undefined;
valueFormatter?: (
v: unknown,
payLoad: unknown,
) => React.ReactNode | undefined;
}
>(
(
@@ -183,7 +186,7 @@ const ChartTooltipContent = React.forwardRef<
<div
ref={ref}
className={cn(
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-popover px-2.5 py-1.5 text-xs shadow-xl",
className,
)}
>
@@ -233,7 +236,7 @@ const ChartTooltipContent = React.forwardRef<
)}
<div
className={cn(
"flex flex-1 justify-between gap-2 leading-none",
"flex flex-1 justify-between gap-5 leading-none",
nestLabel ? "items-end" : "items-center",
)}
>
@@ -243,10 +246,10 @@ const ChartTooltipContent = React.forwardRef<
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
{item.value !== undefined && (
<span className="font-medium font-mono text-foreground tabular-nums">
{valueFormatter
? valueFormatter(item.value) || item.value
? valueFormatter(item.value, item) || item.value
: item.value}
</span>
)}
@@ -21,7 +21,7 @@ export function CodeBlockContainer(props: {
return (
<div
className={cn(
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-background",
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-card",
props.className,
)}
>
@@ -33,7 +33,7 @@ const ImageUpload = React.forwardRef<HTMLInputElement, ImageUploadProps>(
<div
{...getRootProps()}
className={cn(
"group relative flex cursor-pointer flex-col items-center justify-center gap-1.5 overflow-hidden rounded-md border bg-card p-4 py-6 text-muted-foreground transition-all hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"group relative flex cursor-pointer flex-col items-center justify-center gap-1.5 overflow-hidden rounded-md border bg-background p-4 py-6 text-muted-foreground transition-all hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
isDragActive ? "border-primary" : "border-input",
!activeFile ? "w-full" : "min-h-32 min-w-32",
className,
+1 -1
View File
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-card px-3 py-2 text-sm ring-offset-background selection:bg-foreground selection:text-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background selection:bg-foreground selection:text-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
)}
ref={ref}
@@ -63,11 +63,11 @@ const PaginationPrevious = ({
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
className={cn("h-10 gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
<span className="max-sm:hidden">Previous</span>
</PaginationLink>
);
PaginationPrevious.displayName = "PaginationPrevious";
@@ -79,10 +79,10 @@ const PaginationNext = ({
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
className={cn("h-10 gap-1 pr-2.5", className)}
{...props}
>
<span>Next</span>
<span className="max-sm:hidden">Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
@@ -51,7 +51,7 @@ const RadioGroupItemButton = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"group flex min-w-32 cursor-pointer items-center space-x-3 space-y-0 rounded-lg border px-4 py-3 font-medium transition-all hover:border-foreground/25 hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 peer-hover:border-nonce data-[state=checked]:border-muted-foreground",
"group flex min-w-32 cursor-pointer items-center space-x-3 space-y-0 rounded-lg border px-4 py-3 font-medium transition-all hover:border-foreground/25 hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 peer-hover:border-nonce data-[state=checked]:border-muted-foreground",
className,
)}
{...props}
@@ -31,7 +31,7 @@ function randomName() {
function Component() {
return (
<div className="flex min-h-screen flex-col gap-10 bg-background p-6 text-foreground">
<div className="flex min-h-dvh flex-col gap-10 bg-background p-6 text-foreground">
<BadgeContainer label="5 items, no placeholder, no label, value filled">
<SelectDemo listItems={5} selectFirst />
</BadgeContainer>
@@ -21,7 +21,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-card px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
"flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
)}
{...props}
@@ -114,7 +114,9 @@ function TableDemo(props: {
<TableRow
key={invoice.invoice}
linkBox={props.linkBox}
className={cn(props.linkBox && "cursor-pointer hover:bg-muted")}
className={cn(
props.linkBox && "cursor-pointer hover:bg-accent/50",
)}
>
<TableCell className="font-medium">
<Link
+15 -4
View File
@@ -25,7 +25,8 @@ const TableHeader = React.forwardRef<
<thead
ref={ref}
className={cn(
"border-border border-b bg-muted/50 [&_tr]:border-b",
"border-border border-b bg-background",
"sticky top-0 z-20 border-none before:absolute before:inset-0 before:border-border before:border-b",
className,
)}
{...props}
@@ -53,7 +54,7 @@ const TableFooter = React.forwardRef<
<tfoot
ref={ref}
className={cn(
"border-border border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
"border-border border-t bg-card font-medium [&>tr]:last:border-b-0",
className,
)}
{...props}
@@ -125,10 +126,20 @@ const TableCaption = React.forwardRef<
));
TableCaption.displayName = "TableCaption";
function TableContainer(props: { children: React.ReactNode }) {
function TableContainer(props: {
children: React.ReactNode;
className?: string;
scrollableContainerClassName?: string;
}) {
return (
<ScrollShadow
className="relative whitespace-nowrap rounded-lg border border-border"
shadowClassName="z-30"
disableTopShadow
className={cn(
"relative whitespace-nowrap rounded-lg border border-border bg-card",
props.className,
)}
scrollableClassName={props.scrollableContainerClassName}
shadowColor="hsl(var(--muted))"
>
{props.children}
+1 -1
View File
@@ -51,7 +51,7 @@ export function TabLinks(props: {
href={tab.href}
aria-disabled={tab.isDisabled}
className={cn(
"relative h-auto rounded-lg px-3 font-normal text-muted-foreground text-sm hover:bg-muted lg:text-sm",
"relative h-auto rounded-lg px-3 font-normal text-muted-foreground text-sm hover:bg-accent lg:text-sm",
!tab.isActive && !tab.isDisabled && "hover:text-foreground",
tab.isDisabled && "pointer-events-none",
tab.isActive && "!text-foreground",
@@ -10,7 +10,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-card px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
)}
ref={ref}
@@ -36,6 +36,8 @@ export function ToolTipLabel(props: {
leftIcon?: React.ReactNode;
hoverable?: boolean;
contentClassName?: string;
side?: "top" | "right" | "bottom" | "left";
align?: "center" | "start" | "end";
}) {
if (!props.label) {
return props.children;
@@ -48,6 +50,8 @@ export function ToolTipLabel(props: {
{props.children}
</TooltipTrigger>
<TooltipContent
side={props.side}
align={props.align}
sideOffset={10}
className={cn(
"max-w-[400px] whitespace-normal leading-relaxed",
+3
View File
@@ -41,3 +41,6 @@ export const BASE_URL = isProd
: "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 || "";
+19 -13
View File
@@ -6,12 +6,12 @@
:root,
[data-theme="light"] {
/* bg - neutral */
--background: 0 0% 100%;
--background: 0 0% 98%;
--popover: 0 0% 100%;
--card: 0 0% 100%;
--secondary: 0 0% 92%;
--muted: 0 0% 95%;
--accent: 0 0% 95%;
--secondary: 0 0% 90%;
--muted: 0 0% 93%;
--accent: 0 0% 93%;
--inverted: 0 0% 4%;
/* bg - colorful */
@@ -34,9 +34,10 @@
--destructive-text: 357.15deg 100% 68.72%;
/* Borders */
--border: 0 0% 80%;
--input: 0 0% 80%;
--ring: 0 0% 85%;
--border: 0 0% 85%;
--active-border: 0 0% 70%;
--input: 0 0% 85%;
--ring: 0 0% 80%;
/* Others */
--radius: 0.5rem;
@@ -46,12 +47,12 @@
[data-theme="dark"] {
/* bg - neutral */
--background: 0 0% 0%;
--card: 0 0% 0%;
--card: 0 0% 3.92%;
--popover: 0 0% 0%;
--secondary: 0 0% 10%;
--muted: 0 0% 9%;
--accent: 0 0% 9%;
--inverted: 0 0% 98%;
--secondary: 0 0% 11%;
--muted: 0 0% 11%;
--accent: 0 0% 11%;
--inverted: 0 0% 100%;
/* bg - colorful */
--primary: 221 83% 54%;
@@ -74,6 +75,7 @@
/* Borders */
--border: 0 0% 15%;
--active-border: 0 0% 22%;
--ring: 0 0% 30%;
--input: 0 0% 15%;
}
@@ -157,10 +159,14 @@ input:-webkit-autofill:active {
transition: background-color 5000s ease-in-out 0s;
}
.shiki,
.shiki span {
background-color: transparent !important;
}
.dark .shiki,
.dark .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--background) !important;
/* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
@@ -76,8 +76,8 @@ export const engineKeys = {
[...engineKeys.all, address, "instances"] as const,
backendWallets: (instance: string) =>
[...engineKeys.all, instance, "backendWallets"] as const,
transactions: (instance: string) =>
[...engineKeys.all, instance, "transactions"] as const,
transactions: (instance: string, params: object) =>
[...engineKeys.all, instance, "transactions", params] as const,
permissions: (instance: string) =>
[...engineKeys.all, instance, "permissions"] as const,
accessTokens: (instance: string) =>
@@ -95,7 +95,8 @@ export const engineKeys = {
backendWallet: (address: string, chainId: number) =>
["backendWallet", address, chainId] as const,
backendWalletBalance: (address: string, chainId: number) =>
[...engineKeys.backendWallet(address, chainId), "balance"] as const,
["backendWallet", "balance", address, chainId] as const,
backendWalletBalanceAll: () => ["backendWallet", "balance"] as const,
corsUrls: (instance: string) =>
[...engineKeys.all, instance, "corsUrls"] as const,
ipAllowlist: (instance: string) =>
@@ -124,4 +125,6 @@ export const engineKeys = {
[...engineKeys.all, engineId, "alerts"] as const,
notificationChannels: (engineId: string) =>
[...engineKeys.all, engineId, "notificationChannels"] as const,
walletCredentials: (instance: string) =>
[...engineKeys.all, instance, "walletCredentials"] as const,
};
@@ -1,6 +1,6 @@
import { analyticsServerProxy, apiServerProxy } from "@/actions/proxies";
import type { Team } from "@/api/team";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { THIRDWEB_ANALYTICS_API_HOST, THIRDWEB_API_HOST } from "constants/urls";
import { useAllChainsData } from "hooks/chains/allChains";
import { useActiveAccount } from "thirdweb/react";
import type { UserOpStats } from "types/analytics";
@@ -39,6 +39,7 @@ export type Account = {
id: string;
isStaff: boolean;
creatorWalletAddress: string;
image?: string | null;
name?: string;
email?: string;
advancedEnabled: boolean;
@@ -216,19 +217,30 @@ export function useAccountCredits() {
return useQuery({
queryKey: accountKeys.credits(address || ""),
queryFn: async () => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/account/credits`, {
type Result = {
data: BillingCredit[];
error?: { message: string };
};
const res = await apiServerProxy<Result>({
pathname: "/v1/account/credits",
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const json = await res.json();
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
}
const credits = (json.data as BillingCredit[]).filter(
const credits = json.data.filter(
(credit) =>
credit.remainingValueUsdCents > 0 &&
(!credit.expiresAt || credit.expiresAt > new Date().toISOString()) &&
@@ -241,6 +253,8 @@ export function useAccountCredits() {
});
}
type UserOpUsageQueryResult = (UserOpStats & { chainId?: string })[];
async function getUserOpUsage(args: {
clientId: string;
from?: Date;
@@ -249,32 +263,35 @@ async function getUserOpUsage(args: {
}) {
const { clientId, from, to, period } = args;
const searchParams = new URLSearchParams();
searchParams.append("clientId", clientId);
const searchParams: Record<string, string> = {
clientId,
};
if (from) {
searchParams.append("from", from.toISOString());
searchParams.from = from.toISOString();
}
if (to) {
searchParams.append("to", to.toISOString());
searchParams.to = to.toISOString();
}
if (period) {
searchParams.append("period", period);
searchParams.period = period;
}
const res = await fetch(
`${THIRDWEB_ANALYTICS_API_HOST}/v1/user-ops?${searchParams.toString()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
const json = await res.json();
if (res.status !== 200) {
throw new Error(json.message);
const res = await analyticsServerProxy<{ data: UserOpUsageQueryResult }>({
pathname: "/v1/user-ops",
method: "GET",
searchParams: searchParams,
headers: {
"Content-Type": "application/json",
},
});
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
return json.data;
}
@@ -297,13 +314,12 @@ export function useUserOpUsageAggregate(args: {
"all",
),
queryFn: async () => {
const userOpStats: (UserOpStats & { chainId?: string })[] =
await getUserOpUsage({
clientId,
from,
to,
period: "all",
});
const userOpStats = await getUserOpUsage({
clientId,
from,
to,
period: "all",
});
// Aggregate stats across wallet types
return userOpStats.reduce(
@@ -369,7 +385,13 @@ export function useUpdateAccount() {
return useMutation({
mutationFn: async (input: UpdateAccountInput) => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/account`, {
type Result = {
data: object;
error?: { message: string };
};
const res = await apiServerProxy<Result>({
pathname: "/v1/account",
method: "PUT",
headers: {
"Content-Type": "application/json",
@@ -377,7 +399,11 @@ export function useUpdateAccount() {
body: JSON.stringify(input),
});
const json = await res.json();
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
@@ -400,15 +426,25 @@ export function useUpdateNotifications() {
return useMutation({
mutationFn: async (input: UpdateAccountNotificationsInput) => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/account/notifications`, {
method: "PUT",
type Result = {
data: object;
error?: { message: string };
};
const res = await apiServerProxy<Result>({
pathname: "/v1/account/notifications",
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ preferences: input }),
});
const json = await res.json();
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
@@ -430,21 +466,31 @@ export function useConfirmEmail() {
return useMutation({
mutationFn: async (input: ConfirmEmailInput) => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/account/confirmEmail`, {
method: "PUT",
type Result = {
error?: { message: string };
data: { team: Team; account: Account };
};
const res = await apiServerProxy<Result>({
pathname: "/v1/account/confirmEmail",
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
const json = await res.json();
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
}
return json.data as { team: Team; account: Account };
return json.data;
},
onSuccess: async () => {
// invalidate related cache, since could be relinking account
@@ -469,18 +515,25 @@ export function useResendEmailConfirmation() {
return useMutation({
mutationFn: async () => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/account/resendEmailConfirmation`,
{
method: "POST",
type Result = {
error?: { message: string };
data: object;
};
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({}),
const res = await apiServerProxy<Result>({
pathname: "/v1/account/resendEmailConfirmation",
method: "POST",
headers: {
"Content-Type": "application/json",
},
);
const json = await res.json();
body: JSON.stringify({}),
});
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
@@ -496,52 +549,38 @@ export function useResendEmailConfirmation() {
});
}
export function useApiKeys(props: {
isLoggedIn: boolean;
}) {
const address = useActiveAccount()?.address;
return useQuery({
queryKey: apiKeys.keys(address || ""),
queryFn: async () => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/keys`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
return json.data as ApiKey[];
},
enabled: !!address && props.isLoggedIn,
});
}
export function useCreateApiKey() {
const address = useActiveAccount()?.address;
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: CreateKeyInput) => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/keys`, {
type Result = {
data: ApiKey;
error?: { message: string };
};
const res = await apiServerProxy<Result>({
pathname: "/v1/keys",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
const json = await res.json();
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
}
return json.data as ApiKey;
return json.data;
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: apiKeys.keys(address || ""),
@@ -556,16 +595,25 @@ export function useUpdateApiKey() {
return useMutation({
mutationFn: async (input: UpdateKeyInput) => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/keys/${input.id}`, {
method: "PUT",
type Result = {
data: ApiKey;
error?: { message: string };
};
const res = await apiServerProxy<Result>({
pathname: `/v1/keys/${input.id}`,
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
const json = await res.json();
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
@@ -587,14 +635,25 @@ export function useRevokeApiKey() {
return useMutation({
mutationFn: async (id: string) => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/keys/${id}/revoke`, {
type Result = {
data: ApiKey;
error?: { message: string };
};
const res = await apiServerProxy<Result>({
pathname: `/v1/keys/${id}/revoke`,
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
const json = await res.json();
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
@@ -614,21 +673,35 @@ export const usePolicies = (serviceId?: string) => {
return useQuery({
queryKey: ["policies", serviceId],
queryFn: async () => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/policies?serviceId=${serviceId}`,
{
method: "GET",
if (!serviceId) {
throw new Error();
}
headers: {
"Content-Type": "application/json",
},
type Result = {
data: ApiKeyServicePolicy;
error?: { message: string };
};
const res = await apiServerProxy<Result>({
pathname: "/v1/policies",
method: "GET",
headers: {
"Content-Type": "application/json",
},
);
const json = await res.json();
searchParams: {
serviceId,
},
});
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
}
return json.data as ApiKeyServicePolicy;
return json.data;
},
enabled: !!serviceId,
});
@@ -641,9 +714,14 @@ export const useUpdatePolicies = () => {
serviceId: string;
data: ApiKeyServicePolicy;
}) => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/policies`, {
method: "POST",
type Result = {
data: ApiKeyServicePolicy;
error?: { message: string };
};
const res = await apiServerProxy<Result>({
pathname: "/v1/policies",
method: "POST",
headers: {
"Content-Type": "application/json",
},
@@ -652,11 +730,16 @@ export const useUpdatePolicies = () => {
data: input.data,
}),
});
const json = await res.json();
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
}
return json.data as ApiKeyServicePolicy;
return json.data;
},
onSuccess: (_, variables) => {
return queryClient.invalidateQueries({
@@ -673,19 +756,25 @@ export function useRevokeAuthorizedWallet() {
return useMutation({
mutationFn: async (variables: { authorizedWalletId: string }) => {
const { authorizedWalletId } = variables;
type Result = {
data: object;
error?: { message: string };
};
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/authorized-wallets/${authorizedWalletId}/revoke`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({}),
const res = await apiServerProxy<Result>({
pathname: `/v1/authorized-wallets/${authorizedWalletId}/revoke`,
method: "POST",
headers: {
"Content-Type": "application/json",
},
);
const json = await res.json();
body: JSON.stringify({}),
});
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
@@ -706,20 +795,30 @@ export function useAuthorizedWallets() {
return useQuery({
queryKey: authorizedWallets.authorizedWallets(address || ""),
queryFn: async () => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/authorized-wallets`, {
method: "GET",
type Result = {
data: AuthorizedWallet[];
error?: { message: string };
};
const res = await apiServerProxy<Result>({
pathname: "/v1/authorized-wallets",
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const json = await res.json();
if (!res.ok) {
throw new Error(res.error);
}
const json = res.data;
if (json.error) {
throw new Error(json.error.message);
}
return json.data as AuthorizedWallet[];
return json.data;
},
enabled: !!address,
gcTime: 0,
@@ -1,7 +1,6 @@
import { type ThirdwebContract, ZERO_ADDRESS } from "thirdweb";
import { hasRole } from "thirdweb/extensions/permissions";
import { useActiveAccount, useReadContract } from "thirdweb/react";
import type { RequiredParam } from "utils/types";
export function useIsAdmin(contract: ThirdwebContract, failOpen = true) {
const address = useActiveAccount()?.address;
@@ -19,7 +18,7 @@ export function useIsAdmin(contract: ThirdwebContract, failOpen = true) {
export function useIsAdminOrSelf(
contract: ThirdwebContract,
self: RequiredParam<string>,
self: string | null | undefined,
) {
const address = useActiveAccount()?.address;
const isAdmin = useIsAdmin(contract);
@@ -1,12 +1,18 @@
"use client";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiServerProxy } from "@/actions/proxies";
import {
keepPreviousData,
useMutation,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import type { ResultItem } from "app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/metrics/components/StatusCodes";
import { THIRDWEB_API_HOST } from "constants/urls";
import type { EngineBackendWalletType } from "lib/engine";
import { useState } from "react";
import { useActiveAccount, useActiveWalletChain } from "thirdweb/react";
import { useActiveAccount } from "thirdweb/react";
import invariant from "tiny-invariant";
import type { EngineStatus } from "../../../app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table";
import { engineKeys } from "../cache-keys";
export type EngineTier = "STARTER" | "PREMIUM" | "ENTERPRISE";
@@ -47,37 +53,6 @@ const getEngineRequestHeaders = (token: string | null): HeadersInit => {
};
};
// TODO - add authToken in queryKey instead
export function useEngineInstances() {
const address = useActiveAccount()?.address;
return useQuery({
queryKey: engineKeys.instances(address || ""),
queryFn: async (): Promise<EngineInstance[]> => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine`, {
method: "GET",
});
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
}
const json = await res.json();
const instances = (json.data?.instances as EngineInstance[]) || [];
return instances.map((instance) => {
// Sanitize: Add trailing slash if not present.
const url = instance.url.endsWith("/")
? instance.url
: `${instance.url}/`;
return {
...instance,
url,
};
});
},
enabled: !!address,
});
}
// GET Requests
export type BackendWallet = {
address: string;
@@ -98,7 +73,7 @@ export function useEngineBackendWallets(params: {
}) {
const { instanceUrl, authToken } = params;
return useQuery({
queryKey: [engineKeys.backendWallets(instanceUrl), authToken],
queryKey: [...engineKeys.backendWallets(instanceUrl), authToken],
queryFn: async () => {
const res = await fetch(`${instanceUrl}backend-wallet/get-all?limit=50`, {
method: "GET",
@@ -118,7 +93,8 @@ type EngineFeature =
| "CONTRACT_SUBSCRIPTIONS"
| "IP_ALLOWLIST"
| "HETEROGENEOUS_WALLET_TYPES"
| "SMART_BACKEND_WALLETS";
| "SMART_BACKEND_WALLETS"
| "WALLET_CREDENTIALS";
interface EngineSystemHealth {
status: string;
@@ -210,16 +186,19 @@ export function useEngineGetDeploymentPublicConfiguration(
return useQuery<DeploymentPublicConfigurationResponse>({
queryKey: engineKeys.deploymentPublicConfiguration(),
queryFn: async () => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/teams/${input.teamSlug}/engine/deployments/public-configuration`,
{ method: "GET" },
);
const res = await apiServerProxy<{
data: DeploymentPublicConfigurationResponse;
}>({
pathname: `/v1/teams/${input.teamSlug}/engine/deployments/public-configuration`,
method: "GET",
});
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
const json = await res.json();
return json.data as DeploymentPublicConfigurationResponse;
const json = res.data;
return json.data;
},
});
}
@@ -233,22 +212,19 @@ interface UpdateDeploymentInput {
export function useEngineUpdateDeployment() {
return useMutation({
mutationFn: async (input: UpdateDeploymentInput) => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/teams/${input.teamSlug}/engine/deployments/${input.deploymentId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
serverVersion: input.serverVersion,
}),
const res = await apiServerProxy({
pathname: `/v1/teams/${input.teamSlug}/engine/deployments/${input.deploymentId}`,
method: "PUT",
headers: {
"Content-Type": "application/json",
},
);
// we never use the response body
res.body?.cancel();
body: JSON.stringify({
serverVersion: input.serverVersion,
}),
});
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
},
});
@@ -262,13 +238,13 @@ export function useEngineRemoveFromDashboard() {
mutationFn: async (instanceId: string) => {
invariant(instanceId, "instance is required");
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/${instanceId}`, {
const res = await apiServerProxy({
pathname: `/v1/engine/${instanceId}`,
method: "DELETE",
});
// we never use the response body
res.body?.cancel();
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
},
@@ -296,20 +272,17 @@ export function useEngineDeleteCloudHosted() {
reason,
feedback,
}: DeleteCloudHostedInput) => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v2/engine/deployments/${deploymentId}/infrastructure/delete`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ reason, feedback }),
const res = await apiServerProxy({
pathname: `/v2/engine/deployments/${deploymentId}/infrastructure/delete`,
method: "POST",
headers: {
"Content-Type": "application/json",
},
);
// we never use the response body
res.body?.cancel();
body: JSON.stringify({ reason, feedback }),
});
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
},
@@ -333,21 +306,19 @@ export function useEngineEditInstance() {
return useMutation({
mutationFn: async ({ instanceId, name, url }: EditEngineInstanceInput) => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/${instanceId}`, {
const res = await apiServerProxy({
pathname: `/v1/engine/${instanceId}`,
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name, url }),
});
// we never use the response body
res.body?.cancel();
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.instances(address || ""),
@@ -419,13 +390,29 @@ export function useEngineTransactions(params: {
instanceUrl: string;
autoUpdate: boolean;
authToken: string;
queryParams?: {
limit?: number;
page?: number;
status?: EngineStatus;
};
}) {
const { instanceUrl, autoUpdate, authToken } = params;
return useQuery({
queryKey: engineKeys.transactions(instanceUrl),
queryKey: engineKeys.transactions(instanceUrl, params),
queryFn: async () => {
const res = await fetch(`${instanceUrl}transaction/get-all`, {
const url = new URL(`${instanceUrl}transaction/get-all`);
if (params.queryParams) {
for (const key in params.queryParams) {
const value =
params.queryParams[key as keyof typeof params.queryParams];
if (value !== undefined) {
url.searchParams.append(key, value.toString());
}
}
}
const res = await fetch(url, {
method: "GET",
headers: getEngineRequestHeaders(authToken),
});
@@ -434,8 +421,8 @@ export function useEngineTransactions(params: {
return (json.result as TransactionResponse) || {};
},
enabled: !!instanceUrl,
refetchInterval: autoUpdate ? 4_000 : false,
placeholderData: keepPreviousData,
});
}
@@ -483,12 +470,12 @@ export function useEngineBackendWalletBalance(params: {
instanceUrl: string;
address: string;
authToken: string;
chainId: number;
}) {
const { instanceUrl, address, authToken } = params;
const chainId = useActiveWalletChain()?.id;
const { instanceUrl, address, authToken, chainId } = params;
return useQuery({
queryKey: engineKeys.backendWalletBalance(address, chainId || 1),
queryKey: engineKeys.backendWalletBalance(address, chainId),
queryFn: async () => {
invariant(chainId, "chainId is required");
@@ -874,6 +861,10 @@ export type SetWalletConfigInput =
gcpKmsKeyRingId: string;
gcpApplicationCredentialEmail: string;
gcpApplicationCredentialPrivateKey: string;
}
| {
type: "circle";
circleApiKey: string;
};
export function useEngineSetWalletConfig(params: {
@@ -883,8 +874,8 @@ export function useEngineSetWalletConfig(params: {
const { instanceUrl, authToken } = params;
const queryClient = useQueryClient();
return useMutation<WalletConfigResponse, void, SetWalletConfigInput>({
mutationFn: async (input) => {
return useMutation({
mutationFn: async (input: SetWalletConfigInput) => {
invariant(instanceUrl, "instance is required");
const res = await fetch(`${instanceUrl}configuration/wallets`, {
@@ -898,7 +889,7 @@ export function useEngineSetWalletConfig(params: {
throw new Error(json.error.message);
}
return json.result;
return json.result as WalletConfigResponse;
},
onSuccess: () => {
return queryClient.invalidateQueries({
@@ -908,10 +899,17 @@ export function useEngineSetWalletConfig(params: {
});
}
export type CreateBackendWalletInput = {
type: EngineBackendWalletType;
label?: string;
};
export type CreateBackendWalletInput =
| {
type: Exclude<EngineBackendWalletType, "circle" | "smart:circle">;
label?: string;
}
| {
type: "circle" | "smart:circle";
label?: string;
credentialId: string;
isTestnet: boolean;
};
export function useEngineCreateBackendWallet(params: {
instanceUrl: string;
@@ -1675,14 +1673,16 @@ export function useEngineSystemMetrics(engineId: string) {
return useQuery({
queryKey: engineKeys.systemMetrics(engineId),
queryFn: async () => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/metrics`,
);
const res = await apiServerProxy({
method: "GET",
pathname: `/v1/engine/${engineId}/metrics`,
});
if (!res.ok) {
setEnabled(false);
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
const json = (await res.json()) as EngineResourceMetrics;
const json = res.data as EngineResourceMetrics;
return json;
},
@@ -1708,16 +1708,19 @@ export function useEngineAlertRules(engineId: string) {
return useQuery({
queryKey: engineKeys.alertRules(engineId),
queryFn: async () => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/alert-rules`,
{ method: "GET" },
);
const res = await apiServerProxy<{
data: EngineAlertRule[];
}>({
pathname: `/v1/engine/${engineId}/alert-rules`,
method: "GET",
});
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
const json = await res.json();
return json.data as EngineAlertRule[];
const json = res.data;
return json.data;
},
});
}
@@ -1734,16 +1737,23 @@ export function useEngineAlerts(engineId: string, limit: number, offset = 0) {
return useQuery({
queryKey: engineKeys.alerts(engineId),
queryFn: async () => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/alerts?limit=${limit}&offset=${offset}`,
{ method: "GET" },
);
const res = await apiServerProxy<{
data: EngineAlert[];
}>({
pathname: `/v1/engine/${engineId}/alerts`,
searchParams: {
limit: `${limit}`,
offset: `${offset}`,
},
method: "GET",
});
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
const json = await res.json();
return json.data as EngineAlert[];
const json = res.data;
return json.data;
},
});
}
@@ -1778,16 +1788,19 @@ export function useEngineNotificationChannels(engineId: string) {
return useQuery({
queryKey: engineKeys.notificationChannels(engineId),
queryFn: async () => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/notification-channels`,
{ method: "GET" },
);
const res = await apiServerProxy<{
data: EngineNotificationChannel[];
}>({
pathname: `/v1/engine/${engineId}/notification-channels`,
method: "GET",
});
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
const json = await res.json();
return json.data as EngineNotificationChannel[];
const json = res.data;
return json.data;
},
});
}
@@ -1803,22 +1816,23 @@ export function useEngineCreateNotificationChannel(engineId: string) {
return useMutation({
mutationFn: async (input: CreateNotificationChannelInput) => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/notification-channels`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
const res = await apiServerProxy<{
data: EngineNotificationChannel;
}>({
pathname: `/v1/engine/${engineId}/notification-channels`,
method: "POST",
headers: {
"Content-Type": "application/json",
},
);
body: JSON.stringify(input),
});
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
const json = await res.json();
return json.data as EngineNotificationChannel;
const json = res.data;
return json.data;
},
onSuccess: () => {
return queryClient.invalidateQueries({
@@ -1833,16 +1847,14 @@ export function useEngineDeleteNotificationChannel(engineId: string) {
return useMutation({
mutationFn: async (notificationChannelId: string) => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/notification-channels/${notificationChannelId}`,
{
method: "DELETE",
},
);
const res = await apiServerProxy({
pathname: `/v1/engine/${engineId}/notification-channels/${notificationChannelId}`,
method: "DELETE",
});
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
throw new Error(res.error);
}
res.body?.cancel();
},
onSuccess: () => {
return queryClient.invalidateQueries({
@@ -1851,3 +1863,118 @@ export function useEngineDeleteNotificationChannel(engineId: string) {
},
});
}
export interface WalletCredential {
id: string;
type: string;
label: string;
isDefault: boolean | null;
createdAt: string;
updatedAt: string;
}
interface CreateWalletCredentialInput {
type: "circle";
label: string;
entitySecret?: string;
isDefault?: boolean;
}
export function useEngineWalletCredentials(params: {
instanceUrl: string;
authToken: string;
page?: number;
limit?: number;
}) {
const { instanceUrl, authToken, page = 1, limit = 100 } = params;
return useQuery({
queryKey: [...engineKeys.walletCredentials(instanceUrl), page, limit],
queryFn: async () => {
const res = await fetch(
`${instanceUrl}wallet-credentials?page=${page}&limit=${limit}`,
{
method: "GET",
headers: getEngineRequestHeaders(authToken),
},
);
const json = await res.json();
return (json.result as WalletCredential[]) || [];
},
enabled: !!instanceUrl,
});
}
export function useEngineCreateWalletCredential(params: {
instanceUrl: string;
authToken: string;
}) {
const { instanceUrl, authToken } = params;
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: CreateWalletCredentialInput) => {
invariant(instanceUrl, "instance is required");
const res = await fetch(`${instanceUrl}wallet-credentials`, {
method: "POST",
headers: getEngineRequestHeaders(authToken),
body: JSON.stringify(input),
});
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
return json.result as WalletCredential;
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.walletCredentials(instanceUrl),
});
},
});
}
interface UpdateWalletCredentialInput {
label?: string;
isDefault?: boolean;
entitySecret?: string;
}
export function useEngineUpdateWalletCredential(params: {
instanceUrl: string;
authToken: string;
}) {
const { instanceUrl, authToken } = params;
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
id,
...input
}: UpdateWalletCredentialInput & { id: string }) => {
invariant(instanceUrl, "instance is required");
const res = await fetch(`${instanceUrl}wallet-credentials/${id}`, {
method: "PUT",
headers: getEngineRequestHeaders(authToken),
body: JSON.stringify(input),
});
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
return json.result as WalletCredential;
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.walletCredentials(instanceUrl),
});
},
});
}
@@ -1,47 +1,36 @@
import { getTokenBalancesFromMoralis } from "@/actions/getBalancesFromMoralis";
import {
queryOptions,
useMutation,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import type {
BalanceQueryRequest,
BalanceQueryResponse,
} from "pages/api/moralis/balances";
import { toast } from "sonner";
import { type ThirdwebContract, sendAndConfirmTransaction } from "thirdweb";
import { distribute, distributeByToken } from "thirdweb/extensions/split";
import { useActiveAccount } from "thirdweb/react";
import invariant from "tiny-invariant";
async function getSplitBalances(contract: ThirdwebContract) {
const query = await fetch("/api/moralis/balances", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
chainId: contract.chain.id,
address: contract.address,
} as BalanceQueryRequest),
});
if (query.status >= 400) {
throw new Error(await query.json().then((r) => r.error));
}
return query.json() as Promise<BalanceQueryResponse>;
}
function getQuery(contract: ThirdwebContract) {
function getTokenBalancesQuery(contract: ThirdwebContract) {
return queryOptions({
queryKey: ["split-balances", contract.chain.id, contract.address],
queryFn: () => getSplitBalances(contract),
queryFn: async () => {
const res = await getTokenBalancesFromMoralis({
chainId: contract.chain.id,
contractAddress: contract.address,
});
if (!res.data) {
throw new Error(res.error);
}
return res.data;
},
retry: false,
});
}
export function useSplitBalances(contract: ThirdwebContract) {
return useQuery(getQuery(contract));
return useQuery(getTokenBalancesQuery(contract));
}
export function useSplitDistributeFunds(contract: ThirdwebContract) {
@@ -53,8 +42,8 @@ export function useSplitDistributeFunds(contract: ThirdwebContract) {
invariant(account, "No active account");
const balances =
// get the cached data if it exists, otherwise fetch it
queryClient.getQueryData(getQuery(contract).queryKey) ||
(await queryClient.fetchQuery(getQuery(contract)));
queryClient.getQueryData(getTokenBalancesQuery(contract).queryKey) ||
(await queryClient.fetchQuery(getTokenBalancesQuery(contract)));
const distributions = balances
.filter((token) => token.display_balance !== "0.0")
@@ -80,7 +69,7 @@ export function useSplitDistributeFunds(contract: ThirdwebContract) {
return await Promise.all(distributions);
},
onSettled: () => {
queryClient.invalidateQueries(getQuery(contract));
queryClient.invalidateQueries(getTokenBalancesQuery(contract));
},
});
}
@@ -1,5 +1,6 @@
import { getWalletNFTs } from "@/actions/getWalletNFTs";
import { useQuery } from "@tanstack/react-query";
import type { WalletNFTApiReturn } from "pages/api/wallet/nfts/[chainId]";
import invariant from "tiny-invariant";
export function useWalletNFTs(params: {
chainId: number;
@@ -8,10 +9,11 @@ export function useWalletNFTs(params: {
return useQuery({
queryKey: ["walletNfts", params.chainId, params.walletAddress],
queryFn: async () => {
const response = await fetch(
`/api/wallet/nfts/${params.chainId}?owner=${params.walletAddress}`,
);
return (await response.json()) as WalletNFTApiReturn;
invariant(params.walletAddress, "walletAddress is required");
return getWalletNFTs({
chainId: params.chainId,
owner: params.walletAddress,
});
},
enabled: !!params.walletAddress,
});
@@ -1,13 +1,32 @@
"use client";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
THIRDWEB_ENGINE_FAUCET_WALLET,
TURNSTILE_SITE_KEY,
} from "@/constants/env";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { zodResolver } from "@hookform/resolvers/zod";
import { Turnstile } from "@marsidev/react-turnstile";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { CanClaimResponseType } from "app/api/testnet-faucet/can-claim/CanClaimResponseType";
@@ -17,9 +36,21 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { toUnits } from "thirdweb";
import {
type ThirdwebClient,
prepareTransaction,
toUnits,
toWei,
} from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { useActiveAccount, useWalletBalance } from "thirdweb/react";
import type { Chain } from "thirdweb/chains";
import {
useActiveAccount,
useActiveWalletChain,
useSendTransaction,
useSwitchActiveWalletChain,
useWalletBalance,
} from "thirdweb/react";
import { z } from "zod";
import { isOnboardingComplete } from "../../../../../../login/onboarding/isOnboardingRequired";
@@ -137,6 +168,35 @@ export function FaucetButton({
const form = useForm<z.infer<typeof claimFaucetSchema>>();
// loading state
if (faucetWalletBalanceQuery.isPending || canClaimFaucetQuery.isPending) {
return (
<Button variant="outline" className="w-full gap-2">
Checking Faucet <Spinner className="size-3" />
</Button>
);
}
// faucet is empty
if (isFaucetEmpty) {
return (
<div className="w-full">
<div className="mb-3 text-center text-muted-foreground text-sm">
Faucet is empty right now
</div>
<SendFundsToFaucetModalButton
chain={definedChain}
isLoggedIn={!!twAccount}
client={client}
chainMeta={chain}
onFaucetRefill={() => {
faucetWalletBalanceQuery.refetch();
}}
/>
</div>
);
}
// Force users to log in to claim the faucet
if (!address || !twAccount) {
return (
@@ -164,24 +224,6 @@ export function FaucetButton({
);
}
// loading state
if (faucetWalletBalanceQuery.isPending || canClaimFaucetQuery.isPending) {
return (
<Button variant="outline" className="w-full gap-2">
Checking Faucet <Spinner className="size-3" />
</Button>
);
}
// faucet is empty
if (isFaucetEmpty) {
return (
<Button variant="outline" disabled className="!opacity-100 w-full">
Faucet is empty right now
</Button>
);
}
// Can not claim
if (canClaimFaucetQuery.data && canClaimFaucetQuery.data.canClaim === false) {
return (
@@ -250,3 +292,165 @@ export function FaucetButton({
</div>
);
}
const faucetFormSchema = z.object({
amount: z.coerce.number().refine((value) => value > 0, {
message: "Amount must be greater than 0",
}),
});
function SendFundsToFaucetModalButton(props: {
chain: Chain;
isLoggedIn: boolean;
client: ThirdwebClient;
chainMeta: ChainMetadata;
onFaucetRefill: () => void;
}) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="default" className="w-full">
Refill Faucet
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader className="mb-2">
<DialogTitle>Refill Faucet</DialogTitle>
<DialogDescription>Send funds to faucet wallet</DialogDescription>
</DialogHeader>
<SendFundsToFaucetModalContent {...props} />
</DialogContent>
</Dialog>
);
}
function SendFundsToFaucetModalContent(props: {
chain: Chain;
isLoggedIn: boolean;
client: ThirdwebClient;
chainMeta: ChainMetadata;
onFaucetRefill: () => void;
}) {
const account = useActiveAccount();
const activeChain = useActiveWalletChain();
const switchActiveWalletChain = useSwitchActiveWalletChain();
const sendTxMutation = useSendTransaction({
payModal: false,
});
const switchChainMutation = useMutation({
mutationFn: async () => {
await switchActiveWalletChain(props.chain);
},
});
const form = useForm<z.infer<typeof faucetFormSchema>>({
resolver: zodResolver(faucetFormSchema),
defaultValues: {
amount: 0.1,
},
});
function onSubmit(values: z.infer<typeof faucetFormSchema>) {
const sendNativeTokenTx = prepareTransaction({
chain: props.chain,
client: props.client,
to: THIRDWEB_ENGINE_FAUCET_WALLET,
value: toWei(values.amount.toString()),
});
const promise = sendTxMutation.mutateAsync(sendNativeTokenTx);
toast.promise(promise, {
success: `Sent ${values.amount} ${props.chainMeta.nativeCurrency.symbol} to faucet`,
error: `Failed to send ${values.amount} ${props.chainMeta.nativeCurrency.symbol} to faucet`,
});
promise.then(() => {
props.onFaucetRefill();
});
}
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex min-w-0 flex-col gap-5"
>
<div className="min-w-0">
<p className="mb-2 text-foreground text-sm"> Faucet Wallet </p>
<CopyTextButton
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}
tooltip={undefined}
/>
</div>
<FormField
control={form.control}
name="amount"
render={({ field }) => (
<FormItem>
<FormLabel>Amount</FormLabel>
<FormControl>
<div className="relative">
<Input
{...field}
type="number"
className="h-auto bg-card text-2xl md:text-2xl [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
/>
<div className="-translate-y-1/2 absolute top-1/2 right-4 text-muted-foreground text-sm">
{props.chainMeta.nativeCurrency.symbol}
</div>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{!account && (
<CustomConnectWallet
chain={props.chain}
loginRequired={false}
isLoggedIn={props.isLoggedIn}
connectButtonClassName="!w-full"
detailsButtonClassName="!w-full"
/>
)}
{account && activeChain && (
<div>
{activeChain.id === props.chain.id ? (
<Button
key="submit"
type="submit"
className="mt-4 w-full gap-2"
disabled={sendTxMutation.isPending}
>
{sendTxMutation.isPending && <Spinner className="size-4" />}
Send funds to faucet
</Button>
) : (
<Button
key="switch"
className="mt-4 w-full gap-2"
disabled={switchChainMutation.isPending}
onClick={() => switchChainMutation.mutate()}
>
Switch to {props.chainMeta.name}{" "}
{switchChainMutation.isPending && (
<Spinner className="size-4" />
)}
</Button>
)}
</div>
)}
</form>
</Form>
);
}
@@ -12,7 +12,7 @@ export function BuyFundsSection(props: { chain: ChainMetadata }) {
return (
<section>
<SectionTitle title="Buy Funds" />
<div className="flex justify-center rounded-lg border border-border bg-muted/30 px-4 py-10">
<div className="flex justify-center rounded-lg border border-border bg-card px-4 py-10">
<div className="flex max-w-[520px] flex-col items-center ">
<div className="flex items-center">
<ChainIcon
@@ -11,7 +11,7 @@ export function ChainOverviewSection(props: { chain: ChainMetadata }) {
return (
<section>
<SectionTitle title="Chain Overview" />
<div className="grid grid-cols-1 gap-6 rounded-lg border bg-muted/50 p-4 md:grid-cols-2 md:p-6 lg:grid-cols-3 lg:gap-8">
<div className="grid grid-cols-1 gap-6 rounded-lg border bg-card p-4 md:grid-cols-2 md:p-6 lg:grid-cols-3 lg:gap-8">
{/* Info */}
{chain.infoURL && (
<PrimaryInfoItem title="Info">
@@ -7,7 +7,7 @@ export function ClaimChainSection() {
return (
<section>
<SectionTitle title="Claim Chain" />
<div className="rounded-lg border bg-muted/50 p-4">
<div className="rounded-lg border bg-card p-4">
<p className="mb-1"> This chain page is unclaimed </p>
<p className="mb-5 text-muted-foreground text-sm">
Are you the owner of this ecosystem? Claim this page to add additional
@@ -19,7 +19,7 @@ export async function FaucetSection(props: {
return (
<section>
<SectionTitle title="Faucet" />
<div className="flex justify-center rounded-lg border border-border bg-muted/50 px-4 py-10">
<div className="flex justify-center rounded-lg border border-border bg-card px-4 py-10">
<div className="flex w-full max-w-[520px] flex-col items-center ">
<div className="flex items-center">
<ChainIcon
@@ -25,7 +25,7 @@ export function SupportedProductsSection(props: {
return (
<div
key={product.id}
className="relative flex gap-3 rounded-lg border bg-muted/50 p-4 pr-8 transition-colors duration-300 hover:bg-muted"
className="relative flex gap-3 rounded-lg border bg-card p-4 pr-8 transition-colors hover:border-active-border"
>
<CircleCheckIcon className="absolute top-4 right-4 size-5 text-success-text" />
<product.icon className="mt-0.5 size-5 shrink-0" />
@@ -14,7 +14,7 @@ export function ExplorersSection(props: {
return (
<div
key={explorer.url}
className="relative rounded-xl border bg-muted/50 p-4 hover:bg-muted"
className="relative rounded-xl border bg-card p-4 transition-colors hover:border-active-border"
>
<ExternalLinkIcon className="absolute top-4 right-4 size-4 text-muted-foreground" />
<h3 className="mb-1 font-semibold text-base capitalize">
@@ -1,5 +1,3 @@
import { MobileSidebar } from "@/components/blocks/MobileSidebar";
import { Sidebar } from "@/components/blocks/Sidebar";
import {
Breadcrumb,
BreadcrumbItem,
@@ -15,16 +13,12 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
ArrowLeftIcon,
ChevronDownIcon,
MenuIcon,
TicketCheckIcon,
} from "lucide-react";
import { ChevronDownIcon, TicketCheckIcon } from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";
import { redirect } from "next/navigation";
import { mapV4ChainToV5Chain } from "../../../../../contexts/map-chains";
import { getAuthToken } from "../../../../api/lib/getAuthToken";
import { StarButton } from "../../components/client/star-button";
import { getChain, getChainMetadata } from "../../utils";
import { AddChainToWallet } from "./components/client/add-chain-to-wallet";
@@ -62,6 +56,7 @@ export default async function ChainPageLayout(props: {
const params = await props.params;
const { children } = props;
const chain = await getChain(params.chain_id);
const authToken = await getAuthToken();
if (params.chain_id !== chain.slug) {
redirect(chain.slug);
@@ -69,18 +64,6 @@ export default async function ChainPageLayout(props: {
const chainMetadata = await getChainMetadata(chain.chainId);
const sidebarLinks = [
{
href: `/${chain.slug}`,
label: "Overview",
exactMatch: true,
},
{
href: `/${chain.slug}/popular`,
label: "Popular Contracts",
},
];
return (
<>
<div className="flex h-14 border-border border-b pl-7">
@@ -123,9 +106,7 @@ export default async function ChainPageLayout(props: {
</BreadcrumbList>
</Breadcrumb>
</div>
<div className="container flex h-full flex-row">
<Sidebar links={sidebarLinks} />
<div className="container flex h-full max-w-[1180px] flex-row">
<div className="flex w-full flex-col pb-10">
{/* Icon + Background */}
<ChainHeader
@@ -134,28 +115,6 @@ export default async function ChainPageLayout(props: {
chain={chain}
/>
<div className="flex justify-end lg:hidden">
<MobileSidebar
links={sidebarLinks}
trigger={
<Button size="icon" variant="outline">
<MenuIcon strokeWidth={1} />
</Button>
}
footer={
<div className="mt-5 border-t pt-6 pb-2">
<Link
href="/chainlist"
className="flex items-center justify-start gap-2 px-2 text-muted-foreground text-sm hover:text-foreground"
>
<ArrowLeftIcon className="size-4" />
View all chains
</Link>
</div>
}
/>
</div>
<div className="h-4 md:h-8" />
<div className="flex flex-col gap-3 md:gap-2">
@@ -173,11 +132,13 @@ export default async function ChainPageLayout(props: {
</h1>
{/* Favorite */}
<StarButton
chainId={chain.chainId}
iconClassName="size-5"
className="p-1"
/>
{authToken && (
<StarButton
chainId={chain.chainId}
iconClassName="size-5"
className="p-1"
/>
)}
{/* Gas Sponsored badge - Desktop */}
{chainMetadata?.gasSponsored && (
@@ -1,9 +1,5 @@
import { Button } from "@/components/ui/button";
import { fetchTopContracts } from "lib/search";
import { ArrowRightIcon, CircleAlertIcon } from "lucide-react";
import Link from "next/link";
import { CircleAlertIcon } from "lucide-react";
import { getRawAccount } from "../../../../account/settings/getAccount";
import { TrendingContractSection } from "../../../trending/components/trending-table";
import { getChain, getChainMetadata } from "../../utils";
import { BuyFundsSection } from "./components/server/BuyFundsSection";
import { ChainOverviewSection } from "./components/server/ChainOverviewSection";
@@ -21,14 +17,7 @@ export default async function Page(props: {
const chainMetadata = await getChainMetadata(chain.chainId);
const isDeprecated = chain.status === "deprecated";
const [account, topContracts] = await Promise.all([
getRawAccount(),
fetchTopContracts({
chainId: chain.chainId,
perPage: 3,
timeRange: "month",
}),
]);
const account = await getRawAccount();
return (
<div className="flex flex-col gap-10">
@@ -65,31 +54,6 @@ export default async function Page(props: {
<ExplorersSection explorers={chain.explorers} />
)}
{topContracts.length > 0 && (
<section>
<div className="mb-3 flex items-center justify-between">
<h2 className="font-semibold text-xl tracking-tight">
Popular Contracts
</h2>
<Button asChild variant="outline">
<Link
href={`/${chain.slug}/popular`}
className="flex items-center gap-2"
>
See All
<ArrowRightIcon className="size-4" />
</Link>
</Button>
</div>
<TrendingContractSection
topContracts={topContracts}
chainId={chain.chainId}
showPagination={false}
searchParams={undefined}
/>
</section>
)}
{chain.services.filter((s) => s.enabled).length > 0 && (
<SupportedProductsSection services={chain.services} />
)}
@@ -1,48 +0,0 @@
import { type SortBy, fetchTopContracts } from "lib/search";
import { CircleAlertIcon } from "lucide-react";
import { TrendingContractSection } from "../../../../trending/components/trending-table";
import { getChain } from "../../../utils";
export default async function Page(props: {
params: Promise<{ chain_id: string }>;
searchParams: Promise<{ page?: number; sortBy?: SortBy }>;
}) {
const searchParams = await props.searchParams;
const params = await props.params;
const chain = await getChain(params.chain_id);
const topContracts = await fetchTopContracts({
chainId: chain.chainId,
page: searchParams.page,
sortBy: searchParams.sortBy,
perPage: 15,
timeRange: "month",
});
return (
<section className="mt-2">
<h2 className="mb-2 font-semibold text-2xl tracking-tighter">
Popular Contracts
</h2>
<p className="text-muted-foreground text-sm">
Explore contracts on Ethereum and sort them by your preferred metrics
</p>
<div className="h-8" />
{topContracts.length > 0 && (
<TrendingContractSection
topContracts={topContracts}
chainId={chain.chainId}
searchParams={searchParams}
showPagination={true}
/>
)}
{topContracts.length === 0 && (
<div className="flex h-[200px] items-center justify-center rounded-lg border border-border text-lg text-muted-foreground">
<div className="flex items-center gap-2">
<CircleAlertIcon className="size-5 text-destructive-text" />
No contracts found
</div>
</div>
)}
</section>
);
}
@@ -247,7 +247,7 @@ export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
<Tr
{...row.getRowProps()}
role="group"
className="hover:bg-muted/50"
className="hover:bg-card"
style={{ cursor: "pointer" }}
onClick={() => setTokenRow(row.original)}
borderBottomWidth={1}
@@ -4,6 +4,7 @@ import {
toTokens,
toUnits,
} from "thirdweb";
import { getContract } from "thirdweb/contract";
import type { OverrideEntry } from "thirdweb/dist/types/utils/extensions/drops/types";
import type { Prettify } from "thirdweb/dist/types/utils/type-utils";
import { getContractMetadata } from "thirdweb/extensions/common";
@@ -71,7 +72,10 @@ export async function getClaimPhasesInLegacyFormat(
conditions.map(async (condition) => {
const [currencyMetadata, metadata, contractMetadata] = await Promise.all([
await ERC20Ext.getCurrencyMetadata({
contract: { ...options.contract, address: condition.currency },
contract: getContract({
...options.contract,
address: condition.currency,
}),
}).then((m) => ({
...m,
displayValue: toTokens(condition.pricePerToken, m.decimals),
@@ -160,7 +160,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
<div
className={cn(
"flex h-full cursor-pointer rounded-md border border-border hover:border-primary",
noCsv ? "bg-red-200" : "bg-card",
noCsv ? "bg-red-200" : "bg-background",
)}
{...getRootProps()}
>
@@ -197,7 +197,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
<a
className="text-link-foreground hover:text-foreground"
download
href="/snapshot-with-maxclaimable.csv"
href="/assets/examples/snapshot-with-maxclaimable.csv"
>
<DownloadIcon className="inline size-3" /> Example
snapshot
@@ -212,7 +212,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
<a
download
className="text-link-foreground hover:text-foreground"
href="/snapshot-with-overrides.csv"
href="/assets/examples/snapshot-with-overrides.csv"
>
<DownloadIcon className="inline size-3" /> Example
snapshot
@@ -228,7 +228,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
<a
download
className="text-link-foreground hover:text-foreground"
href="/snapshot.csv"
href="/assets/examples/snapshot.csv"
>
<DownloadIcon className="inline size-3" /> Example
snapshot
@@ -243,7 +243,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
<a
download
className="text-link-foreground hover:text-foreground"
href="/snapshot-with-maxclaimable.csv"
href="/assets/examples/snapshot-with-maxclaimable.csv"
>
<DownloadIcon className="inline size-3" /> Example
snapshot
@@ -262,7 +262,7 @@ const SnapshotViewerSheetContent: React.FC<SnapshotUploadProps> = ({
<a
download
className="text-link-foreground hover:text-foreground"
href="/snapshot-with-overrides.csv"
href="/assets/examples/snapshot-with-overrides.csv"
>
<DownloadIcon className="inline size-3" /> Example
snapshot
@@ -83,7 +83,7 @@ export const MetadataHeader: React.FC<MetadataHeaderProps> = ({
{chain && (
<Link
href={`/${chain.slug}`}
className="flex w-fit shrink-0 items-center gap-2 rounded-3xl border border-border bg-muted/50 px-2.5 py-1.5 hover:bg-muted"
className="flex w-fit shrink-0 items-center gap-2 rounded-3xl border border-border bg-card px-2.5 py-1.5 hover:bg-accent"
>
<ChainIcon ipfsSrc={chain.icon?.url} className="size-4" />
{cleanedChainName && (
@@ -109,7 +109,7 @@ export const MetadataHeader: React.FC<MetadataHeaderProps> = ({
<CopyAddressButton
address={address}
copyIconPosition="left"
className="bg-muted/50 text-xs"
className="bg-card text-xs"
variant="outline"
/>
@@ -138,7 +138,7 @@ function BadgeLink(props: {
<Button
variant="outline"
asChild
className="!h-auto gap-2 rounded-lg bg-muted/50 px-3 py-1.5 text-xs capitalize"
className="!h-auto gap-2 rounded-lg bg-card px-3 py-1.5 text-xs capitalize"
>
<Link href={props.href} target="_blank">
{props.name}
@@ -39,12 +39,6 @@ export function getContractPageSidebarLinks(data: {
href: `${layoutPrefix}/events`,
exactMatch: true,
},
{
label: "Embed",
href: `${layoutPrefix}/embed`,
hide: data.metadata.embedType === null,
exactMatch: true,
},
{
label: "Analytics",
href: `${layoutPrefix}/analytics`,
@@ -1,35 +1,33 @@
import { isProd } from "@/constants/env";
import { INSIGHT_SERVICE_API_KEY } from "@/constants/env";
import { getVercelEnv } from "lib/vercel-utils";
const thirdwebDomain =
getVercelEnv() !== "production" ? "thirdweb-dev" : "thirdweb";
export async function isAnalyticsSupportedForChain(
chainId: number,
): Promise<boolean> {
try {
if (!process.env.CHAINSAW_API_KEY) {
throw new Error("Missing CHAINSAW_API_KEY env var");
}
const res = await fetch(
`https://chainsaw.${isProd ? "thirdweb" : "thirdweb-dev"}.com/service/chains/${chainId}`,
`https://insight.${thirdwebDomain}.com/service/chains/${chainId}`,
{
method: "GET",
headers: {
"content-type": "application/json",
// pass the shared secret
"x-service-api-key": process.env.CHAINSAW_API_KEY || "",
// service api key required - because this is endpoint is internal
"x-service-api-key": INSIGHT_SERVICE_API_KEY,
},
},
);
if (!res.ok) {
// assume not supported if we get a non-200 response
return false;
}
const { data } = await res.json();
return data;
} catch (e) {
console.error("Error checking if analytics is supported for chain", e);
}
const json = (await res.json()) as { data: boolean };
return json.data;
} catch (e) {
console.error(`Error checking analytics support for chain ${chainId}`);
console.error(e);
}
return false;
}
@@ -8,6 +8,8 @@ import { ContractAnalyticsPage } from "./ContractAnalyticsPage";
export function ContractAnalyticsPageClient(props: {
contract: ThirdwebContract;
writeFnSelectorToNameRecord: Record<string, string>;
eventSelectorToNameRecord: Record<string, string>;
}) {
const metadataQuery = useContractPageMetadata(props.contract);
@@ -23,5 +25,11 @@ export function ContractAnalyticsPageClient(props: {
return <RedirectToContractOverview contract={props.contract} />;
}
return <ContractAnalyticsPage contract={props.contract} />;
return (
<ContractAnalyticsPage
contract={props.contract}
writeFnSelectorToNameRecord={props.writeFnSelectorToNameRecord}
eventSelectorToNameRecord={props.eventSelectorToNameRecord}
/>
);
}
@@ -1,138 +1,117 @@
"use client";
import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart";
import { useIsomorphicLayoutEffect } from "@/lib/useIsomorphicLayoutEffect";
import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Flex,
SimpleGrid,
Skeleton,
Stat,
StatLabel,
StatNumber,
} from "@chakra-ui/react";
import { Skeleton, Stat, StatLabel, StatNumber } from "@chakra-ui/react";
import type { UseQueryResult } from "@tanstack/react-query";
import {
type AnalyticsQueryParams,
type TotalQueryResult,
useEventsAnalytics,
useFunctionsAnalytics,
useLogsAnalytics,
useTotalLogsAnalytics,
useTotalTransactionAnalytics,
useTotalWalletsAnalytics,
useTransactionAnalytics,
useUniqueWalletsAnalytics,
useContractEventAnalytics,
useContractEventBreakdown,
useContractFunctionBreakdown,
useContractTransactionAnalytics,
useContractUniqueWalletAnalytics,
useTotalContractEvents,
useTotalContractTransactionAnalytics,
useTotalContractUniqueWallets,
} from "data/analytics/hooks";
import { Suspense, useMemo, useState } from "react";
import type { ThirdwebContract } from "thirdweb";
import { Card, Heading } from "tw-components";
import { Card } from "tw-components";
import {
DateRangeSelector,
type Range,
getLastNDaysRange,
} from "../../../../../../components/analytics/date-range-selector";
interface ContractAnalyticsPageProps {
contract: ThirdwebContract;
writeFnSelectorToNameRecord: Record<string, string>;
eventSelectorToNameRecord: Record<string, string>;
}
export const ContractAnalyticsPage: React.FC<ContractAnalyticsPageProps> = ({
contract,
writeFnSelectorToNameRecord,
eventSelectorToNameRecord,
}) => {
const [startDate] = useState(
(() => {
const date = new Date();
date.setDate(date.getDate() - 30);
return date;
})(),
);
const [endDate] = useState(new Date());
useIsomorphicLayoutEffect(() => {
window?.scrollTo({ top: 0, behavior: "smooth" });
}, []);
const [range, setRange] = useState<Range>(() => getLastNDaysRange("last-30"));
return (
<Flex direction="column" gap={6}>
{contract && (
<>
<Flex gap={10} direction="column">
<Flex direction="column" gap={2}>
<Alert status="info" borderRadius="md" mb={4}>
<AlertIcon />
<AlertTitle>Analytics is in beta.</AlertTitle>
<AlertDescription>
Some data may be partially inaccurate or incomplete.
</AlertDescription>
</Alert>
<Heading as="h2" size="title.md">
Analytics
</Heading>
<Flex gap={4}>
<AnalyticsStat
chainId={contract.chain.id}
contractAddress={contract.address}
// FIXME
// eslint-disable-next-line react-compiler/react-compiler
useTotal={useTotalWalletsAnalytics}
label="Unique Wallets"
/>
<AnalyticsStat
chainId={contract.chain.id}
contractAddress={contract.address}
// FIXME
// eslint-disable-next-line react-compiler/react-compiler
useTotal={useTotalTransactionAnalytics}
label="Total Transactions"
/>
<AnalyticsStat
chainId={contract.chain.id}
contractAddress={contract.address}
// FIXME
// eslint-disable-next-line react-compiler/react-compiler
useTotal={useTotalLogsAnalytics}
label="Total Events"
/>
</Flex>
</Flex>
</Flex>
<SimpleGrid columns={{ base: 1, md: 1 }} gap={4}>
<UniqueWalletsChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
<div>
<h2 className="mb-3 font-semibold text-xl tracking-tight lg:text-2xl">
Analytics
</h2>
<TotalTransactionsChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
<AnalyticsStat
chainId={contract.chain.id}
contractAddress={contract.address}
// FIXME
// eslint-disable-next-line react-compiler/react-compiler
useTotal={useTotalContractUniqueWallets}
label="Unique Wallets"
/>
<AnalyticsStat
chainId={contract.chain.id}
contractAddress={contract.address}
// FIXME
// eslint-disable-next-line react-compiler/react-compiler
useTotal={useTotalContractTransactionAnalytics}
label="Total Transactions"
/>
<AnalyticsStat
chainId={contract.chain.id}
contractAddress={contract.address}
// FIXME
// eslint-disable-next-line react-compiler/react-compiler
useTotal={useTotalContractEvents}
label="Total Events"
/>
</div>
<TotalEventsChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
<div className="mt-8 mb-4">
<DateRangeSelector range={range} setRange={setRange} />
</div>
<FunctionBreakdownChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
<div className="flex flex-col gap-6">
<UniqueWalletsChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={range.from}
endDate={range.to}
/>
<EventBreakdownChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={startDate}
endDate={endDate}
/>
</SimpleGrid>
</>
)}
</Flex>
<TotalTransactionsChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={range.from}
endDate={range.to}
/>
<TotalEventsChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={range.from}
endDate={range.to}
/>
<FunctionBreakdownChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={range.from}
endDate={range.to}
writeFnSelectorToNameRecord={writeFnSelectorToNameRecord}
/>
<EventBreakdownChart
chainId={contract.chain.id}
contractAddress={contract.address}
startDate={range.from}
endDate={range.to}
eventSelectorToNameRecord={eventSelectorToNameRecord}
/>
</div>
</div>
);
};
@@ -143,26 +122,27 @@ type ChartProps = {
endDate: Date;
};
function UniqueWalletsChart(props: ChartProps) {
const { data } = useUniqueWalletsAnalytics(props);
const analyticsQuery = useContractUniqueWalletAnalytics(props);
return (
<ThirdwebBarChart
title="Unique Wallets"
description="The number of unique wallet addresses that have sent a transaction to this contract."
data={data || []}
data={analyticsQuery.data || []}
isPending={analyticsQuery.isPending}
config={{
wallets: {
count: {
label: "Unique Wallets",
color: "hsl(var(--chart-1))",
},
}}
chartClassName="aspect[2] lg:aspect-[4.5]"
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
/>
);
}
function TotalTransactionsChart(props: ChartProps) {
const { data } = useTransactionAnalytics({
const analyticsQuery = useContractTransactionAnalytics({
chainId: props.chainId,
contractAddress: props.contractAddress,
endDate: props.endDate,
@@ -173,46 +153,74 @@ function TotalTransactionsChart(props: ChartProps) {
<ThirdwebBarChart
title="Total Transactions"
description="The number of transactions that have been sent to this contract."
data={data || []}
data={analyticsQuery.data || []}
isPending={analyticsQuery.isPending}
config={{
count: {
label: "Transactions",
color: "hsl(var(--chart-1))",
},
}}
chartClassName="aspect[2] lg:aspect-[4.5]"
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
/>
);
}
function TotalEventsChart(props: ChartProps) {
const { data } = useLogsAnalytics(props);
const analyticsQuery = useContractEventAnalytics(props);
return (
<ThirdwebBarChart
title="Total Events"
description="The number of on-chain events that have been emitted from this contract."
data={data || []}
data={analyticsQuery.data || []}
isPending={analyticsQuery.isPending}
config={{
count: {
label: "Events",
color: "hsl(var(--chart-1))",
},
}}
chartClassName="aspect[2] lg:aspect-[4.5]"
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
/>
);
}
function FunctionBreakdownChart(props: ChartProps) {
const { data } = useFunctionsAnalytics(props);
function FunctionBreakdownChart(
props: ChartProps & {
writeFnSelectorToNameRecord: Record<string, string>;
},
) {
const analyticsQuery = useContractFunctionBreakdown(props);
// replace function selector with function name
const mappedQueryData = useMemo(() => {
return analyticsQuery.data?.map((item) => {
const modifiedItem = { time: item.time } as typeof item;
for (const key in item) {
if (key === "time") {
continue;
}
const name = props.writeFnSelectorToNameRecord[key];
const value = item[key];
if (name && value !== undefined) {
modifiedItem[name] = value;
}
}
return modifiedItem;
});
}, [analyticsQuery.data, props.writeFnSelectorToNameRecord]);
return (
<ThirdwebBarChart
title="Function Breakdown"
description="The breakdown of calls to each write function from transactions."
data={data || []}
config={Object.keys(data?.[0] || {}).reduce(
data={mappedQueryData || []}
isPending={analyticsQuery.isPending}
config={Object.keys(mappedQueryData?.[0] || {}).reduce(
(acc, key) => {
if (key === "time") {
return acc;
@@ -225,21 +233,47 @@ function FunctionBreakdownChart(props: ChartProps) {
},
{} as Record<string, { label: string; color: string }>,
)}
chartClassName="aspect[2] lg:aspect-[4.5]"
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
showLegend
/>
);
}
function EventBreakdownChart(props: ChartProps) {
const { data } = useEventsAnalytics(props);
function EventBreakdownChart(
props: ChartProps & {
eventSelectorToNameRecord: Record<string, string>;
},
) {
const analyticsQuery = useContractEventBreakdown(props);
// replace event selector with event name
const mappedQueryData = useMemo(() => {
return analyticsQuery.data?.map((item) => {
const modifiedItem = { time: item.time } as typeof item;
for (const key in item) {
if (key === "time") {
continue;
}
const name = props.eventSelectorToNameRecord[key];
const value = item[key];
if (name && value !== undefined) {
modifiedItem[name] = value;
}
}
return modifiedItem;
});
}, [analyticsQuery.data, props.eventSelectorToNameRecord]);
return (
<ThirdwebBarChart
title="Event Breakdown"
description="The breakdown of events emitted by this contract."
data={data || []}
config={Object.keys(data?.[0] || {}).reduce(
data={mappedQueryData || []}
isPending={analyticsQuery.isPending}
config={Object.keys(mappedQueryData?.[0] || {}).reduce(
(acc, key) => {
if (key === "time") {
return acc;
@@ -252,7 +286,7 @@ function EventBreakdownChart(props: ChartProps) {
},
{} as Record<string, { label: string; color: string }>,
)}
chartClassName="aspect[2] lg:aspect-[4.5]"
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
showLegend
/>
);
@@ -1,5 +1,7 @@
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";
@@ -18,8 +20,18 @@ 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} />;
return (
<ContractAnalyticsPageClient
contract={info.contract}
writeFnSelectorToNameRecord={writeFnSelectorToName}
eventSelectorToNameRecord={eventSelectorToName}
/>
);
}
const { isAnalyticsSupported } = await getContractPageMetadata(info.contract);
@@ -28,5 +40,42 @@ export default async function Page(props: {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
return <ContractAnalyticsPage contract={info.contract} />;
return (
<ContractAnalyticsPage
contract={info.contract}
writeFnSelectorToNameRecord={writeFnSelectorToName}
eventSelectorToNameRecord={eventSelectorToName}
/>
);
}
async function getSelectors(contract: ThirdwebContract) {
try {
const abi = await resolveContractAbi<Abi>(contract);
const writeFnSelectorToName: Record<string, string> = {};
const eventSelectorToName: Record<string, string> = {};
for (const item of abi) {
if (item.type === "event") {
eventSelectorToName[toEventSelector(item)] = item.name;
} else if (
// if write function
item.type === "function" &&
item.stateMutability !== "view" &&
item.stateMutability !== "pure"
) {
writeFnSelectorToName[toFunctionSelector(item)] = item.name;
}
}
return {
writeFnSelectorToName,
eventSelectorToName,
};
} catch {
return {
writeFnSelectorToName: {},
eventSelectorToName: {},
};
}
}
@@ -1,35 +0,0 @@
"use client";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
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 { EmbedSetup } from "./embed-setup";
export function EmbedSetupClient(props: {
contract: ThirdwebContract;
twAccount: Account | undefined;
}) {
const metadataQuery = useContractPageMetadata(props.contract);
if (metadataQuery.isPending) {
return <LoadingPage />;
}
if (metadataQuery.isError) {
return <ErrorPage />;
}
if (metadataQuery.data.embedType === null) {
return <RedirectToContractOverview contract={props.contract} />;
}
return (
<EmbedSetup
contract={props.contract}
ercOrMarketplace={metadataQuery.data.embedType}
twAccount={props.twAccount}
/>
);
}
@@ -1,582 +0,0 @@
"use client";
import { Button } from "@/components/ui/button";
import { CodeClient } from "@/components/ui/code/code.client";
import {
type Account,
useApiKeys,
useCreateApiKey,
} from "@3rdweb-sdk/react/hooks/useApi";
import { Flex, FormControl, Input, Select } from "@chakra-ui/react";
import { LazyCreateAPIKeyDialog } from "components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog";
import { useTrack } from "hooks/analytics/useTrack";
import { useAllChainsData } from "hooks/chains/allChains";
import { useClipboard } from "hooks/useClipboard";
import { CheckIcon, CopyIcon } from "lucide-react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import type { StoredChain } from "stores/chainStores";
import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { useActiveAccount } from "thirdweb/react";
import { Card, FormHelperText, FormLabel, Heading, Text } from "tw-components";
interface EmbedSetupProps {
contract: ThirdwebContract;
ercOrMarketplace: string;
twAccount: Account | undefined;
}
// MAKE SURE THIS IS v1 embed hashes!!
const IPFS_URI = "bafybeigdie2yyiazou7grjowoevmuip6akk33nqb55vrpezqdwfssrxyfy";
const ERC721_IPFS_URI =
"bafybeicd3qfzelz4su7ng6n523virdsgobrc5pcbarhwqv3dj3drh645pi";
interface IframeSrcOptions {
clientId: string;
chain: string;
tokenId?: string;
listingId?: string;
listingType?: string;
directListingId?: string;
englishAuctionId?: string;
relayUrl?: string;
theme?: string;
primaryColor?: string;
secondaryColor?: string;
biconomyApiKey?: string;
biconomyApiId?: string;
}
const colorOptions = [
"purple",
"blue",
"orange",
"pink",
"green",
"red",
"teal",
"cyan",
"yellow",
];
const isValidUrl = (url: string | undefined) => {
if (!url) {
return false;
}
try {
new URL(url);
return true;
} catch {
return false;
}
};
const buildIframeSrc = (
contract: ThirdwebContract,
ercOrMarketplace?: string,
options?: IframeSrcOptions,
): string => {
const contractPath =
ercOrMarketplace === "erc721" ? "" : `${ercOrMarketplace}.html`;
const contractEmbedHash =
ercOrMarketplace === "erc721" ? ERC721_IPFS_URI : IPFS_URI;
if (!contract || !options || !contractEmbedHash) {
return "";
}
const {
clientId,
chain,
tokenId,
listingId,
listingType,
directListingId,
englishAuctionId,
relayUrl,
theme,
primaryColor,
secondaryColor,
biconomyApiKey,
biconomyApiId,
} = options;
const url = new URL(
`https://embed.ipfscdn.io/ipfs/${contractEmbedHash}/${contractPath}`,
);
url.searchParams.append("contract", contract.address);
url.searchParams.append("chain", chain);
url.searchParams.append("clientId", clientId);
if (tokenId !== undefined && ercOrMarketplace === "erc1155") {
url.searchParams.append("tokenId", tokenId.toString());
}
if (listingId !== undefined && ercOrMarketplace === "marketplace") {
url.searchParams.append("listingId", listingId.toString());
}
if (
directListingId !== undefined &&
ercOrMarketplace === "marketplace-v3" &&
listingType === "direct-listing"
) {
url.searchParams.append("directListingId", directListingId.toString());
}
if (
englishAuctionId !== undefined &&
ercOrMarketplace === "marketplace-v3" &&
listingType === "english-auction"
) {
url.searchParams.append("englishAuctionId", englishAuctionId.toString());
}
if (isValidUrl(relayUrl)) {
url.searchParams.append("relayUrl", relayUrl || "");
}
if (biconomyApiKey) {
url.searchParams.append("biconomyApiKey", biconomyApiKey);
}
if (biconomyApiId) {
url.searchParams.append("biconomyApiId", biconomyApiId);
}
if (theme) {
url.searchParams.append("theme", theme);
}
if (primaryColor && primaryColor !== "default") {
url.searchParams.append("primaryColor", primaryColor);
}
if (secondaryColor && secondaryColor !== "orange") {
url.searchParams.append("secondaryColor", secondaryColor);
}
return url.toString();
};
// taken from @thirdweb-dev /chains
// @internal
function minimizeChain(
chain: ChainMetadata,
): Pick<
ChainMetadata,
| "name"
| "chain"
| "rpc"
| "nativeCurrency"
| "shortName"
| "chainId"
| "testnet"
| "slug"
| "icon"
> {
const [firstRpc] = chain.rpc;
return {
name: chain.name,
chain: chain.chain,
rpc: [firstRpc || ""],
nativeCurrency: chain.nativeCurrency,
shortName: chain.shortName,
chainId: chain.chainId,
testnet: chain.testnet,
slug: chain.slug,
icon: chain.icon,
};
}
// taken from @thirdweb-dev /chains
// @internal
type ChainConfiguration = {
rpc?: string | string[];
};
function configureChain(
chain: ChainMetadata,
chainConfig: ChainConfiguration,
): ChainMetadata {
let additionalRPCs: string[] = [];
if (chainConfig?.rpc) {
if (typeof chainConfig.rpc === "string") {
additionalRPCs = [chainConfig.rpc];
} else {
additionalRPCs = chainConfig.rpc;
}
}
// prepend additional RPCs to the chain's RPCs
return { ...chain, rpc: [...additionalRPCs, ...chain.rpc] };
}
export const EmbedSetup: React.FC<EmbedSetupProps> = ({
contract,
ercOrMarketplace,
twAccount,
}) => {
const trackEvent = useTrack();
const apiKeys = useApiKeys({
isLoggedIn: !!twAccount,
});
const createKeyMutation = useCreateApiKey();
const validApiKey = (apiKeys.data || []).find(
(apiKey) =>
(apiKey.domains.includes("*") ||
apiKey.domains.includes("embed.ipfscdn.io") ||
apiKey.domains.includes("*.ipfscdn.io")) &&
(apiKey.services || [])
.find((service) => service.name === "storage")
?.actions.includes("read") &&
!!(apiKey.services || []).find((service) => service.name === "rpc"),
);
const chainId = contract.chain.id;
const { idToChain } = useAllChainsData();
const chain: StoredChain = (chainId ? idToChain.get(chainId) : undefined) || {
name: "Unknown Chain",
chainId: chainId || -1,
rpc: [],
chain: "unknown",
nativeCurrency: {
name: "Ethereum",
symbol: "ETH",
decimals: 18,
},
shortName: "unknown",
slug: "unknown",
testnet: false,
stackType: "",
};
const { register, watch } = useForm<{
rpcUrl: string;
relayUrl: string;
tokenId: string;
listingId: string;
listingType: string;
directListingId: string;
englishAuctionId: string;
theme: string;
primaryColor: string;
secondaryColor: string;
biconomyApiKey: string;
biconomyApiId: string;
gasless: string;
}>({
defaultValues: {
rpcUrl: chain?.rpc[0],
tokenId: "0",
listingId: "0",
directListingId: "0",
englishAuctionId: "0",
theme: "dark",
primaryColor: "purple",
secondaryColor: "orange",
},
reValidateMode: "onChange",
});
const configuredChainWithNewRpc = configureChain(chain, {
rpc: watch("rpcUrl"),
});
const minimizedChain = minimizeChain(configuredChainWithNewRpc);
const iframeSrc = buildIframeSrc(contract, ercOrMarketplace, {
chain: JSON.stringify(minimizedChain),
clientId: validApiKey?.key || "",
...watch(),
});
const embedCode = useMemo(
() =>
`<iframe
src="${iframeSrc}"
width="100%"
height="750px"
style="max-width:100%;"
frameborder="0"
></iframe>`,
[iframeSrc],
);
const { hasCopied, onCopy } = useClipboard(embedCode, 3000);
const [showCreateAPIKeyModal, setShowCreateAPIKeyModal] = useState(false);
const activeAccount = useActiveAccount();
const pathname = usePathname();
return (
<Flex gap={8} direction="column">
<LazyCreateAPIKeyDialog
prefill={{
name: "Embed API Key",
domains: "embed.ipfscdn.io",
}}
open={showCreateAPIKeyModal}
onOpenChange={setShowCreateAPIKeyModal}
onCreateAndComplete={() => {
trackEvent({
category: "api-keys",
action: "create",
label: "success",
fromEmbed: true,
});
apiKeys.refetch();
}}
enableNebulaServiceByDefault={false}
/>
<Flex gap={8} direction={{ base: "column", md: "row" }}>
<Card className="flex w-full flex-col gap-5 md:w-1/2">
<Heading size="title.sm">Configuration</Heading>
{ercOrMarketplace === "marketplace" ? (
<FormControl>
<FormLabel>Listing ID</FormLabel>
<Input type="number" {...register("listingId")} />
<FormHelperText>
The listing ID the embed should display
</FormHelperText>
</FormControl>
) : null}
{ercOrMarketplace === "marketplace-v3" ? (
<FormControl>
<FormLabel>Listing type</FormLabel>
<Select {...register("listingType")}>
<option value="direct-listing">Direct Listing</option>
<option value="english-auction">English Auction</option>
</Select>
<FormHelperText>
The type of listing the embed should display
</FormHelperText>
</FormControl>
) : null}
{ercOrMarketplace === "marketplace-v3" &&
watch("listingType") === "direct-listing" ? (
<FormControl>
<FormLabel>Direct Listing ID</FormLabel>
<Input type="number" {...register("directListingId")} />
<FormHelperText>
The direct listing ID the embed should display
</FormHelperText>
</FormControl>
) : null}
{ercOrMarketplace === "marketplace-v3" &&
watch("listingType") === "english-auction" ? (
<FormControl>
<FormLabel>English Auction ID</FormLabel>
<Input type="number" {...register("englishAuctionId")} />
<FormHelperText>
The english auction ID the embed should display
</FormHelperText>
</FormControl>
) : null}
{ercOrMarketplace === "erc1155" ? (
<FormControl>
<FormLabel>Token ID</FormLabel>
<Input type="number" {...register("tokenId")} />
<FormHelperText>
The token ID the embed should display
</FormHelperText>
</FormControl>
) : null}
<FormControl>
<FormLabel>Client ID</FormLabel>
{!activeAccount ? (
<Button asChild className="w-full">
<Link
href={`/login${
pathname ? `?next=${encodeURIComponent(pathname)}` : ""
}`}
>
Sign in to create a client ID
</Link>
</Button>
) : validApiKey ? (
<Input
readOnly
disabled
value={`${validApiKey?.name} - ${validApiKey?.key}`}
/>
) : (
<Button
className="w-full"
onClick={() => {
trackEvent({
category: "api-keys",
action: "create",
label: "attempt",
fromEmbed: true,
});
setShowCreateAPIKeyModal(true);
}}
disabled={createKeyMutation.isPending}
>
Create Client ID
</Button>
)}
<FormHelperText>
You need a client ID to use embeds.{" "}
<Link
href="https://portal.thirdweb.com/account/api-keys"
className="text-link-foreground hover:text-foreground"
target="_blank"
>
Learn more
</Link>
.
</FormHelperText>
</FormControl>
<FormControl>
<FormLabel>RPC Url</FormLabel>
<Input type="url" {...register("rpcUrl")} />
<FormHelperText>
RPC the embed should use to connect to the blockchain.
</FormHelperText>
</FormControl>
{ercOrMarketplace === "marketplace" ||
ercOrMarketplace === "marketplace-v3" ? null : (
<FormControl gap={4}>
<Heading size="title.sm" my={4}>
Gasless
</Heading>
<Select {...register("gasless")} mb={4}>
<option value="false">Disabled</option>
<option value="openZeppelin">OpenZeppelin Relayer</option>
<option value="biconomy">Biconomy Relayer</option>
</Select>
{watch("gasless") === "openZeppelin" && (
<FormControl>
<FormLabel>OpenZeppelin Relayer URL</FormLabel>
<Input type="url" {...register("relayUrl")} />
</FormControl>
)}
{watch("gasless") === "biconomy" && (
<Flex gap={4} flexDir="column">
<FormControl>
<FormLabel>Biconomy API key</FormLabel>
<Input type="url" {...register("biconomyApiKey")} />
</FormControl>
<FormControl>
<FormLabel>Biconomy API ID</FormLabel>
<Input type="url" {...register("biconomyApiId")} />
</FormControl>
</Flex>
)}
<FormHelperText>
A relayer can be used to make the transaction gasless for the
end user.{" "}
<Link
target="_blank"
className="text-link-foreground hover:text-foreground"
href="https://blog.thirdweb.com/guides/setup-gasless-transactions"
>
Learn more
</Link>
</FormHelperText>
</FormControl>
)}
<FormControl>
<Heading size="title.sm" my={4}>
Customization
</Heading>
<FormLabel>Theme</FormLabel>
<Select {...register("theme")}>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">User system</option>
</Select>
<FormHelperText>
Selecting system will make it so the embed would change depending
on the user system&apos;s preferences.
</FormHelperText>
</FormControl>
<FormControl>
<FormLabel>Primary Color</FormLabel>
<Select {...register("primaryColor")}>
{ercOrMarketplace === "erc721" && (
<option value="default">Default</option>
)}
{colorOptions.map((color) => (
<option key={color} value={color}>
{color[0]?.toUpperCase() + color.substring(1)}
</option>
))}
</Select>
<FormHelperText>
Used for the main actions button backgrounds.
</FormHelperText>
</FormControl>
{ercOrMarketplace === "marketplace" ||
ercOrMarketplace === "marketplace-v3" ? (
<FormControl>
<FormLabel>Secondary Color</FormLabel>
<Select {...register("secondaryColor")}>
{colorOptions.map((color) => (
<option key={color} value={color}>
{color[0]?.toUpperCase() + color.substring(1)}
</option>
))}
</Select>
<FormHelperText>
Use for secondary actions (like when the user is connected to
the wrong network)
</FormHelperText>
</FormControl>
) : null}
</Card>
<Card className="flex w-full flex-col gap-2 md:w-1/2">
<Heading size="title.sm">Embed Code</Heading>
<CodeClient code={embedCode} lang="html" />
<Button
className="w-auto gap-2"
variant="outline"
onClick={() => {
onCopy();
trackEvent({
category: "embed",
action: "click",
label: "copy-code",
address: contract.address,
chainId,
});
}}
>
{hasCopied ? (
<CheckIcon className="size-4" />
) : (
<CopyIcon className="size-4" />
)}
{hasCopied ? "Copied!" : "Copy to clipboard"}
</Button>
</Card>
</Flex>
<div className="flex flex-col items-center gap-2">
<Heading size="title.sm">Preview</Heading>
{!validApiKey ? (
<Text>You need to create a client ID to use embeds</Text>
) : iframeSrc ? (
<iframe
title="thirdweb embed"
src={iframeSrc}
width="100%"
height="750px"
frameBorder="0"
/>
) : null}
</div>
</Flex>
);
};
@@ -1,42 +0,0 @@
import { notFound, redirect } from "next/navigation";
import { localhost } from "thirdweb/chains";
import { getRawAccount } from "../../../../../account/settings/getAccount";
import { getContractPageParamsInfo } from "../_utils/getContractFromParams";
import { getContractPageMetadata } from "../_utils/getContractPageMetadata";
import { EmbedSetupClient } from "./EmbedSetup.client";
import { EmbedSetup } from "./embed-setup";
export default async function Page(props: {
params: Promise<{
contractAddress: string;
chain_id: string;
}>;
}) {
const params = await props.params;
const info = await getContractPageParamsInfo(params);
if (!info) {
notFound();
}
const account = await getRawAccount();
const { contract } = info;
if (contract.chain.id === localhost.id) {
return <EmbedSetupClient contract={contract} twAccount={account} />;
}
const { embedType } = await getContractPageMetadata(contract);
if (embedType === null) {
redirect(`/${params.chain_id}/${params.contractAddress}`);
}
return (
<EmbedSetup
contract={contract}
ercOrMarketplace={embedType}
twAccount={account}
/>
);
}
@@ -35,7 +35,7 @@ export const ContractExplorerPage: React.FC<ContractExplorePageProps> = ({
const functions = getContractFunctionsFromAbi(abi);
return (
<div className="flex h-[70vh] flex-col">
<div className="flex h-[70vh] flex-col ">
{functions && functions.length > 0 ? (
<ContractFunctionsOverview
onlyFunctions
@@ -44,7 +44,7 @@ export const ContractExplorerPage: React.FC<ContractExplorePageProps> = ({
twAccount={twAccount}
/>
) : (
<div className="flex flex-col items-center justify-center gap-2 text-center">
<div className="flex flex-col items-center justify-center gap-2 bg-card text-center">
<h2 className="font-semibold text-2xl tracking-tight">
No callable functions discovered in ABI.
</h2>
@@ -26,6 +26,7 @@ import { ToolTipLabel } from "@/components/ui/tooltip";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
import { TransactionButton } from "components/buttons/TransactionButton";
import { addDays, fromUnixTime } from "date-fns";
import { useAllChainsData } from "hooks/chains/allChains";
@@ -119,22 +120,39 @@ function ClaimableModule(props: ModuleInstanceProps) {
claimConditionQuery.data?.startTimestamp === 0 &&
claimConditionQuery.data?.endTimestamp === 0;
const currencyContract = getContract({
address: claimConditionQuery.data?.currency || "",
chain: props.contract.chain,
client: props.contract.client,
});
const claimConditionCurrency = claimConditionQuery.data?.currency;
const shouldFetchTokenDecimals =
claimConditionQuery.data &&
claimConditionQuery.data?.currency !== ZERO_ADDRESS;
const shouldFetchCurrencyDecimals =
!!claimConditionCurrency && claimConditionCurrency !== ZERO_ADDRESS;
const currencyDecimalsQuery = useReadContract(decimals, {
contract: currencyContract,
queryOptions: {
enabled: shouldFetchTokenDecimals,
const currencyDecimalsQuery = useQuery({
queryKey: [
"currency-decimals",
{
contractAddress: props.contract.address,
chainId: props.contract.chain.id,
claimConditionCurrency,
},
],
queryFn: async () => {
if (!claimConditionCurrency) {
throw new Error();
}
const currencyContract = getContract({
address: claimConditionCurrency,
chain: props.contract.chain,
client: props.contract.client,
});
const decimalsVal = await decimals({
contract: currencyContract,
});
return decimalsVal;
},
enabled: shouldFetchCurrencyDecimals,
});
const tokenDecimalsQuery = useReadContract(decimals, {
contract: contract,
queryOptions: {
@@ -265,7 +283,7 @@ function ClaimableModule(props: ModuleInstanceProps) {
// claim conditions data is present
claimConditionQuery.data &&
// token decimals is fetched if it should be fetched
(shouldFetchTokenDecimals ? currencyDecimalsQuery.isFetched : true)
(shouldFetchCurrencyDecimals ? currencyDecimalsQuery.isFetched : true)
? {
claimCondition: claimConditionQuery.data,
currencyDecimals: currencyDecimalsQuery.data,
@@ -276,7 +294,7 @@ function ClaimableModule(props: ModuleInstanceProps) {
tokenId,
isLoading:
claimConditionQuery.isLoading ||
(!!shouldFetchTokenDecimals && currencyDecimalsQuery.isLoading),
(!!shouldFetchCurrencyDecimals && currencyDecimalsQuery.isLoading),
}}
isOwnerAccount={!!ownerAccount}
name={props.contractInfo.name}
@@ -565,6 +583,7 @@ function ClaimConditionSection(props: {
onClick={() => setAddClaimConditionButtonClicked(true)}
variant="outline"
className="w-full"
disabled={!props.isOwnerAccount}
>
Add Claim Condition
</Button>
@@ -4,13 +4,13 @@ import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { Alert, AlertTitle } from "@/components/ui/alert";
import type { Account as TWAccount } from "@3rdweb-sdk/react/hooks/useApi";
import { CircleSlash } from "lucide-react";
import type { ContractOptions } from "thirdweb";
import type { ThirdwebContract } from "thirdweb/contract";
import type { Account } from "thirdweb/wallets";
import { ModuleCard } from "./module-card";
import { useAllModuleContractInfo } from "./moduleContractInfo";
export const InstalledModulesTable = (props: {
contract: ContractOptions;
contract: ThirdwebContract;
installedModules: {
data?: string[];
isPending: boolean;
@@ -15,8 +15,9 @@ import { FormProvider, type UseFormReturn, useForm } from "react-hook-form";
import { toast } from "sonner";
import {
type Chain,
type ContractOptions,
type ThirdwebClient,
type ThirdwebContract,
getContract,
sendTransaction,
waitForReceipt,
} from "thirdweb";
@@ -47,7 +48,7 @@ type FormData = {
};
type InstallModuleFormProps = {
contract: ContractOptions;
contract: ThirdwebContract;
refetchModules: () => void;
account: Account;
installedModules: {
@@ -180,11 +181,13 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => {
return Promise.all(
moduleAddress.map(async (address) => {
const result = await resolveImplementation({
client,
address,
chain: contract.chain,
});
const result = await resolveImplementation(
getContract({
client,
address,
chain: contract.chain,
}),
);
if (!result) {
throw new Error("Failed to fetch bytecode for module");
@@ -1,6 +1,10 @@
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { fetchPublishedContractVersions } from "components/contract-components/fetch-contracts-with-versions";
import {
fetchLatestPublishedContractVersion,
fetchPublishedContractVersions,
} from "components/contract-components/fetch-contracts-with-versions";
import { isAddress } from "thirdweb";
import type { FetchDeployMetadataResult } from "thirdweb/contract";
import { resolveAddress } from "thirdweb/extensions/ens";
import invariant from "tiny-invariant";
import type { ModuleMeta } from "./install-module-params";
@@ -14,16 +18,24 @@ export async function getModuleInstalledParams(ext: ModuleMeta) {
client: getThirdwebClient(),
name: ext.publisherAddress,
});
const allPublishedModules = await fetchPublishedContractVersions(
publisherAddress,
ext.moduleName,
);
// find the version we want
const publishedModule =
ext.moduleVersion === "latest"
? allPublishedModules[0]
: allPublishedModules.find((v) => v.version === ext.moduleVersion);
let publishedModule: FetchDeployMetadataResult | undefined = undefined;
if (ext.moduleVersion === "latest") {
publishedModule = await fetchLatestPublishedContractVersion(
publisherAddress,
ext.moduleName,
);
} else {
const allPublishedModules = await fetchPublishedContractVersions(
publisherAddress,
ext.moduleName,
);
publishedModule = allPublishedModules.find(
(v) => v.version === ext.moduleVersion,
);
}
invariant(
publishedModule,

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