Compare commits

..
Author SHA1 Message Date
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
7b0040bb92 Version Packages (#4589)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-17 15:21:23 +12:00
joaquim-verges c320b189f6 feat: export Abi types from abitype (#4639)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on exporting Abi types in the `thirdweb` package.

### Detailed summary
- Exported Abi types like `Abi`, `AbiFunction`, `AbiReceive`, `AbiError`, `AbiEvent`, `AbiConstructor`, and `AbiFallback` from "abitype".
- Exported `toFunctionSelector` from "viem".

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

<!-- end pr-codex -->
2024-09-17 02:51:11 +00:00
jnsdls 6bb2904936 [Update] Refactor product links in ChainListRow component (#4638) 2024-09-17 02:40:08 +00:00
Joaquim VergesandGitHub 5bde07508a feat(pay): allow customizing PayEmbed metadata for top-up flow (#4621) 2024-09-17 14:17:14 +12:00
edwardysun ac4f68d5c8 Pay - Stop polling after status is complete (#4632)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to update the `refetchInterval` logic in `useBuyWithCryptoStatus` and `useBuyWithFiatStatus` hooks based on different status conditions.

### Detailed summary
- Updated `refetchInterval` logic based on status conditions in `useBuyWithCryptoStatus` hook.
- Updated `refetchInterval` logic based on status conditions in `useBuyWithFiatStatus` hook.

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

<!-- end pr-codex -->
2024-09-17 01:10:11 +00:00
joaquim-verges 3961ef477e feat: handle function signatures and ABIs in resolveMethod (#4628)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `resolveMethod` function in `transaction/resolve-method.ts` to handle function signatures and contract ABIs efficiently.

### Detailed summary
- Updated `resolveMethod` function to handle function signatures and contract ABIs
- Added `parseAbiItem` import and type definitions
- Modified test cases in `resolve-method.test.ts` to cover new functionality

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

<!-- end pr-codex -->
2024-09-17 00:47:38 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
085ea2bf00 Bump shiki from 1.16.2 to 1.17.7 (#4626)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 17:19:21 -07:00
joaquim-verges 2b6a2fda3d feat: invalidate wallet balance after successful purchases (#4630)
Fixes CNCT-1861

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on invalidating wallet balances on successful payments.

### Detailed summary
- Updated query key in `invalidateWalletBalance.test.ts`
- Modified query key logic in `invalidateWalletBalance.ts`
- Added `invalidateWalletBalance` function in `BuyScreen.tsx` to invalidate wallet balances on payment success

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

<!-- end pr-codex -->
2024-09-16 23:49:37 +00:00
joaquim-verges a5e605c65e test: conditionally run tests based on TW_SECRET_KEY (#4636)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds a conditional test runner based on the environment variable `TW_SECRET_KEY` to the test suite for ERC721, MintableERC20, MintableERC721, and MintableERC1155 modules.

### Detailed summary
- Modified test descriptions to include conditional test execution based on `TW_SECRET_KEY` environment variable.
- Updated test suite for ERC721, MintableERC20, MintableERC721, and MintableERC1155 modules to run tests conditionally.

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

<!-- end pr-codex -->
2024-09-16 23:30:29 +00:00
MananTank 85ddb175d7 Fix account abstraction factories table crash (#4629)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to refactor the `useFactories` function in `AccountFactories/index.tsx`.

### Detailed summary
- Refactored the `useFactories` function to improve readability and maintainability
- Updated how factories are filtered and returned
- Updated the `contracts` prop passed to `FactoryContracts` component

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

<!-- end pr-codex -->
2024-09-16 22:25:38 +00:00
jnsdls c373ea571a remove v4 (#4627) 2024-09-16 22:00:47 +00:00
MananTank fdae05fc00 Add Export to CSV button on Connect Analytics cards (#4617)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR removes the `ExportToCSVButton` component from multiple files and centralizes it in a single location. It also adds a `valueFormatter` prop to the `ChartTooltipContent` component.

### Detailed summary
- Removed `ExportToCSVButton` from multiple files and centralized it in a single location.
- Added `valueFormatter` prop to the `ChartTooltipContent` component.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/_components/WalletDistributionChartCard.tsx`

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

<!-- end pr-codex -->
2024-09-16 20:45:09 +00:00
MananTank 7c1ea4a1e2 Add Project Setting pages Layout (#4620)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to update the layout and styling of various pages in the dashboard app.

### Detailed summary
- Updated body and html styles for full height
- Added new pages for Pay Settings, In-App Wallet Settings, and Account Abstraction Settings
- Modified project selector UI link
- Updated text and layout for General Settings page
- Adjusted layout in TeamLayout and RootTeamLayout components
- Added a new Layout component for settings with sidebar links
- Added a new page for API key details with editing functionality

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

<!-- end pr-codex -->
2024-09-16 20:26:40 +00:00
jnsdls 95b3c787a3 [Update] Refactor contract publish form to use thirdweb v5 (#4605)
<!-- start pr-codex -->

## PR-Codex overview
This PR removes unnecessary files, updates type definitions, refactors contract-related components, and improves API handling.

### Detailed summary
- Removed several files related to publishing contracts and provider setup
- Updated type definitions and constants
- Refactored contract-related components for consistency
- Improved API handling and URL configurations

> The following files were skipped due to too many changes: `apps/dashboard/src/pages/explore/[category]/index.tsx`, `apps/dashboard/src/contract-ui/tabs/claim-conditions/components/claim-conditions-form/hooks.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/page.tsx`, `apps/dashboard/src/contract-ui/tabs/manage/components/moduleContractInfo.ts`, `apps/dashboard/src/constants/chains.ts`, `apps/dashboard/src/components/contract-components/contract-table/cells/description.tsx`, `apps/dashboard/src/contract-ui/tabs/claim-conditions/components/claim-conditions-form/index.tsx`, `apps/dashboard/src/components/contract-components/contract-publish-form/landing-fieldset.tsx`, `apps/dashboard/src/components/contract-components/contract-publish-form/factory-fieldset.tsx`, `apps/dashboard/src/app/(dashboard)/contracts/publish/[publish_uri]/page.tsx`, `apps/dashboard/src/components/explore/contract-card/index.tsx`, `apps/dashboard/src/components/contract-components/contract-publish-form/default-factory.tsx`, `apps/dashboard/src/components/contract-components/shared/contract-id-image.tsx`, `apps/dashboard/src/components/smart-wallets/AccountFactories/index.tsx`, `apps/dashboard/src/lib/sdk.ts`, `apps/dashboard/src/contract-ui/tabs/code/components/code-overview.tsx`, `apps/dashboard/src/components/contract-components/publisher/edit-profile.tsx`, `apps/dashboard/src/pages/profile/[profileAddress].tsx`, `apps/dashboard/src/contract-ui/tabs/claim-conditions/legacy-zod-schema.ts`, `apps/dashboard/src/contract-ui/tabs/manage/components/ModuleForm.tsx`, `apps/dashboard/src/components/contract-functions/contract-function.tsx`, `apps/dashboard/src/components/contract-components/hooks.ts`, `apps/dashboard/src/components/contract-components/contract-publish-form/index.tsx`

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

<!-- end pr-codex -->
2024-09-16 19:33:35 +00:00
jnsdls 468067e47f fix direct deploy (#4616) 2024-09-16 17:21:00 +00:00
MananTank 1d0291fb9e Sidebar and team header cleanups (#4612)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to refactor layout components and improve sidebar navigation in the dashboard app.

### Detailed summary
- Refactored layout components for better structure
- Updated sidebar navigation in various pages
- Improved handling of sidebar links and layout components

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

<!-- end pr-codex -->
2024-09-16 16:45:22 +00:00
MananTank 6e5d89bf2a Add rate limit on contact-us form using redis + move to app router (#4614)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to implement rate limiting for a HubSpot API endpoint using Redis caching.

### Detailed summary
- Added `cacheGet` and `cacheSet` functions for Redis caching
- Implemented rate limiting logic based on IP address
- Added `rateLimiter` function to handle rate limiting
- Updated handler to use `rateLimiter` before processing requests

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

<!-- end pr-codex -->
2024-09-16 16:32:48 +00:00
MananTank 2bf9fc598a Fix Empty Publish contract page (#4613)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to replace the `useSingleQueryParam` hook with `useRouter` and `useSearchParams` for better navigation handling in different components.

### Detailed summary
- Replaced `useSingleQueryParam` with `useRouter` and `useSearchParams` in multiple files for improved navigation handling.
- Updated the usage of hooks for query parameter retrieval in several components.
- Made changes to handle query parameters more efficiently in the codebase.

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

<!-- end pr-codex -->
2024-09-16 16:14:49 +00:00
Joaquim VergesandGitHub 18239dd5df chore: simplify uploaded published data (#4611) 2024-09-16 20:59:49 +12:00
jnsdls b837b690ae various dependency bumps (#4603)
<!-- start pr-codex -->

## PR-Codex overview
This PR updates various dependencies across different packages.

### Detailed summary
- Updated Vite to version 5.4.5
- Bumped `viem` to version 2.21.7
- Updated `@playwright/test` to 1.47.1
- Increased `postcss` to version 8.4.47
- Updated `tailwindcss` to 3.4.11
- Updated `@tanstack/react-query` to version 5.56.2

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

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

<!-- end pr-codex -->
2024-09-16 01:31:14 +00:00
joaquim-verges 7b94bf55fd feat: expose checkModulesCompatiblity for modular contracts (#4607)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a new function `checkModulesCompatibility` and related tests to verify module compatibility in the `thirdweb` package.

### Detailed summary
- Added `checkModulesCompatibility` function in `modules.ts`
- Created tests for `checkModulesCompatibility` in `checkModulesCompatibility.test.ts`
- Implemented `checkModulesCompatibility` logic in `checkModulesCompatibility.ts`
- Updated `arrays.ts` with a new utility function

> The following files were skipped due to too many changes: `packages/thirdweb/test/src/bytecode/mintable-erc20.ts`, `packages/thirdweb/test/src/bytecode/claimable-erc721.ts`, `packages/thirdweb/test/src/bytecode/claimable-erc20.ts`, `packages/thirdweb/test/src/bytecode/erc20core.ts`

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

<!-- end pr-codex -->
2024-09-16 01:16:42 +00:00
joaquim-verges 3009a0f6e5 feat: expose publishContract extension (#4604)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR exposes a new extension `publishContract` and refactors metadata handling in the `thirdweb` package.

### Detailed summary
- Exposed `publishContract` extension
- Refactored metadata handling in `deploy-metadata.ts`
- Updated `publisher.ts` with export changes
- Added new functions and types in `semver.ts`
- Introduced `publishContract` and related functions in `publish.ts`
- Added tests for `publishContract` in `publish.test.ts`

> The following files were skipped due to too many changes: `packages/thirdweb/src/extensions/thirdweb/write/publish.test.ts`

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

<!-- end pr-codex -->
2024-09-16 00:04:13 +00:00
joaquim-verges 07b949dd8c fix(pay): allow topping up from a different wallet with the same currency (#4602)
Fixes CNCT-1835

<!-- start pr-codex -->

---

## PR-Codex overview
This PR allows topping up from a different wallet with the same currency.

### Detailed summary
- Enable topping up from a different wallet with the same currency
- Rename "Seller" to "Receiver" in ConfirmationScreen
- Modify condition in showSwapFlow to include "fund_wallet" mode

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

<!-- end pr-codex -->
2024-09-15 22:12:10 +00:00
jnsdls 575c3e3141 react-query v4 -> v5 (#4600)
<!-- start pr-codex -->

## PR-Codex overview
This PR updates loading states in various components to use `isPending` instead of `isLoading`.

### Detailed summary
- Replaced `isLoading` with `isPending` in multiple components for loading state handling.

> The following files were skipped due to too many changes: `apps/dashboard/src/contract-ui/tabs/manage/components/InstalledModulesTable.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayCustomers.ts`, `apps/dashboard/src/components/pay/PayConfig.tsx`, `apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/server/chain-header.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/add-partner-form.client.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/components/PaymentHistory.tsx`, `apps/dashboard/src/contract-ui/tabs/split/components/distribute-button.tsx`, `apps/dashboard/src/components/onboarding/LinkWallet.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/components/TotalVolumePieChart.tsx`, `apps/dashboard/package.json`, `apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayPurchases.ts`, `apps/dashboard/src/components/engine/configuration/local-config.tsx.tsx`, `apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/server/partners-table.tsx`, `apps/dashboard/src/contract-ui/components/solidity-inputs/string-input.tsx`, `apps/dashboard/src/components/settings/Account/Billing/index.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/update-partner-form.client.tsx`, `apps/dashboard/src/contract-ui/tabs/nfts/page.tsx`, `apps/dashboard/src/components/configure-networks/Form/IconUpload.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/hooks/use-create-ecosystem.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/components/client/create-ecosystem-form.client.tsx`, `apps/dashboard/src/components/engine/engine-instances-table.tsx`, `apps/dashboard/src/components/engine/contract-subscription/contract-subscriptions-table.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/components/PayCustomersTable.tsx`, `apps/dashboard/src/pages/api/revalidate/publish.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-update-ecosystem.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-add-partner.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-delete-partner.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-update-partner.ts`, `apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/live-stats.tsx`, `apps/dashboard/src/contract-ui/tabs/shared-components/marketplace-table.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/query/useQueryWithNetwork.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/pay/components/webhooks.client.tsx`, `apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx`, `apps/dashboard/src/contract-ui/tabs/proposals/components/proposal.tsx`, `apps/dashboard/src/components/app-layouts/app.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/integration-permissions-toggle.client.tsx`, `apps/dashboard/src/app/account/settings/AccountSettingsPageUI.tsx`, `apps/dashboard/src/components/cmd-k-search/index.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useLoggedInUser.ts`, `apps/dashboard/src/components/contract-components/hooks.ts`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useRegistry.ts`, `apps/dashboard/src/contract-ui/tabs/manage/components/ModuleForm.tsx`, `apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/GeneralSettingsPage.tsx`, `apps/dashboard/src/components/storage/your-files.tsx`, `apps/dashboard/src/components/contract-functions/interactive-abi-function.tsx`, `apps/dashboard/src/components/ipfs-upload/dropzone.tsx`, `apps/dashboard/src/core-ui/nft-drawer/useNftDrawerTabs.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts`, `pnpm-lock.yaml`, `apps/dashboard/src/contract-ui/hooks/useRouteConfig.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts`

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

<!-- end pr-codex -->
2024-09-15 21:32:49 +00:00
joaquim-verges a4a41364cd feat: add optional wallet.getAdminAccount(), new useAdminWallet() hook and auto auth when using inapp+smart accounts (#4601)
Fixes CNCT-1833

## PR-Codex overview
This PR introduces a new `useAdminWallet()` hook for smart wallets and adds functionality for automatic authentication with in-app and smart accounts.

### Detailed summary
- Added `getAdminAccount` method to `Wallet` interface
- Created `useAdminWallet()` hook to get admin wallet for smart wallets
- Automatic authentication for in-app and smart accounts
- Updated various files to implement the new hook and method

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

<!-- end pr-codex -->
2024-09-15 20:34:38 +00:00
jnsdls eba1c60895 appease the knip overlords (#4594)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on refactoring and updating various components, types, and functions across the dashboard app.

### Detailed summary
- Deleted several UI components and hooks
- Updated types and functions across different files
- Refactored component props and functions

> The following files were skipped due to too many changes: `apps/dashboard/src/utils/fetchChain.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/components/module-list.client.tsx`, `apps/dashboard/src/components/analytics/area-chart.tsx`, `apps/dashboard/knip.json`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/constants.ts`, `apps/dashboard/src/components/contract-components/published-contract/index.tsx`, `apps/dashboard/src/lib/search.ts`, `apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts`, `apps/dashboard/src/components/contract-components/contract-table-v2/index.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts`, `apps/dashboard/package.json`, `apps/dashboard/src/components/contract-components/hooks.ts`, `apps/dashboard/src/constants/mappings.ts`, `pnpm-lock.yaml`

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

<!-- end pr-codex -->
2024-09-15 03:49:07 +00:00
jnsdls 1168bc702f make --turbo work (#4593)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to update file paths for image sources and add the `--turbo` flag to the `next dev` script for improved performance.

### Detailed summary
- Updated file paths for image sources in multiple components
- Added `--turbo` flag to `next dev` script in `package.json` for improved performance

> The following files were skipped due to too many changes: `apps/dashboard/src/pages/sdk.tsx`, `apps/dashboard/src/pages/learn/index.tsx`, `apps/dashboard/src/pages/solutions/gaming.tsx`, `apps/dashboard/src/pages/solutions/commerce.tsx`, `apps/dashboard/src/pages/community/ambassadors.tsx`

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

<!-- end pr-codex -->
2024-09-15 03:49:06 +00:00
jnsdls 06ef6f0a07 remove @thirdweb-dev/react (#4591)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to update dependencies and refactor provider setup in the `apps/dashboard` directory.

### Detailed summary
- Updated dependencies for `@tanstack/react-query` and `@tanstack/react-table`
- Removed unused imports and code related to `@thirdweb-dev/react`
- Refactored provider setup in `apps/dashboard/src/components/app-layouts/provider-setup.tsx` and `apps/dashboard/src/components/app-layouts/providers.tsx`

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

<!-- end pr-codex -->
2024-09-15 03:49:05 +00:00
jnsdls 390aedcc8a remove claimconditions legacy (#4590)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to refactor claim conditions components and improve error handling.

### Detailed summary
- Refactored components related to claim conditions
- Improved error handling in `errorParser.tsx`
- Updated transaction button component
- Enhanced ERC20 amount conversion logic
- Streamlined wallet confirmation logic

> The following files were skipped due to too many changes: `apps/dashboard/src/contract-ui/tabs/claim-conditions/components/snapshot-upload.tsx`, `apps/dashboard/src/contract-ui/tabs/claim-conditions/components/claim-conditions-form/hooks.ts`, `apps/dashboard/src/contract-ui/tabs/claim-conditions/components/claim-conditions-form/index.tsx`

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

<!-- end pr-codex -->
2024-09-15 03:49:04 +00:00
jnsdls ebe4975aa3 permissions in v5 (#4588)
<!-- start pr-codex -->

## PR-Codex overview
The focus of this PR is to refactor permissions handling in the dashboard.

### Detailed summary
- Refactored permissions handling to use `useReadContract`
- Removed `isPrebuilt` checks in `ContractPermission`
- Updated `getAllRoleMembers` and `createSetAllRoleMembersTx`
- Removed unnecessary imports and variables

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

<!-- end pr-codex -->
2024-09-15 00:25:26 +00:00
Jonas DanielsandGitHub 9c8beac7fe [Update] Add special case for zero address in WalletAddress component (#4599) 2024-09-14 17:22:59 -07:00
gregfromstl dc250225d2 [Playground] Feature: Track tab state in URL (#4597)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to add a `tab` parameter to the `Page` component in the sign-in button page and update the `RightSection` component to handle the tab switching functionality.

### Detailed summary
- Added `tab` parameter to `Page` component
- Updated `RightSection` to handle tab switching functionality based on the `tab` parameter
- Utilized `useRouter` and `useMemo` for tab handling
- Implemented tab switching functionality in `RightSection`

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

<!-- end pr-codex -->
2024-09-15 00:11:24 +00:00
Jonas DanielsandGitHub 11437524e4 [Update] Replace redirect with notFound for 404 handling (#4596) 2024-09-14 17:10:18 -07:00
Jonas DanielsandGitHub 1a56ed6214 [Update] Resolve avatar URI using thirdwebClient (#4598) 2024-09-14 17:10:00 -07:00
jnsdls b2a2c9784a [Update] Refactor setOverrides() call and IPFS URL resolution (#4587)
<!-- start pr-codex -->

## PR-Codex overview
The focus of this PR is to refactor the codebase to run `setOverrides()` function on app load efficiently.

### Detailed summary
- Moved `setOverrides()` function calls to run on app load in various files
- Added `useEffect` hooks to ensure `setOverrides()` runs once on app load
- Updated `replaceIpfsUrl` function to use `resolveScheme` with `thirdwebClient`
- Imported `setOverrides` function in multiple files for app initialization

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

<!-- end pr-codex -->
2024-09-14 19:52:27 +00:00
kien-ngo 5abc652871 [Dashboard] Fix missing Vote contract UI (#4592)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the condition in `useRouteConfig.tsx` to check for "VoteERC20" instead of "Vote".

### Detailed summary
- Updated condition to check for "VoteERC20" instead of "Vote" in `useRouteConfig.tsx` for enabling/disabling a component.

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

<!-- end pr-codex -->
2024-09-14 15:56:56 +00:00
kien-ngo fb0daabbf6 [Dashboard] Remove extensionDetectedState (#4557)
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on exposing Marketplace extensions for feature detection. It updates extension detection states and refactors extension method imports.

### Detailed summary
- Exposed Marketplace extensions for feature detection
- Refactored extension method imports to use `ExtensionDetectedState`
- Added functions to check support for specific methods in Marketplace extensions

> The following files were skipped due to too many changes: `packages/thirdweb/src/extensions/marketplace/direct-listings/read/getListing.ts`, `packages/thirdweb/src/extensions/marketplace/direct-listings/write/buyFromListing.ts`, `packages/thirdweb/src/exports/extensions/marketplace.ts`, `packages/thirdweb/src/extensions/marketplace/english-auctions/read/getWinningBid.ts`, `packages/thirdweb/src/extensions/marketplace/english-auctions/write/bidInAuction.ts`, `apps/dashboard/src/contract-ui/hooks/useRouteConfig.tsx`

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

<!-- end pr-codex -->
2024-09-14 01:26:53 +00:00
jnsdls b142b8a893 remove custom publisher context (#4586) 2024-09-14 00:16:24 +00:00
MananTank e80a87a0d8 Fix Engine Page contract subscriptions crash + some form validation (#4585)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to enhance contract address validation and error handling in the contract subscription feature.

### Detailed summary
- Added `isAddress` function for contract address validation
- Improved error handling for invalid contract addresses and URLs

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

<!-- end pr-codex -->
2024-09-14 00:02:10 +00:00
jnsdls ce45cb2f24 [Update] Refine billing alert message for plan-specific scenarios (#4584) 2024-09-13 23:21:26 +00:00
MananTank de362ba2f5 Refactor Create API Key Modal, Add in teams for creating projects (#4583)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds functionality to create projects in the dashboard UI.

### Detailed summary
- Added `createProject` functionality to various components
- Updated global CSS variables
- Created `LazyCreateAPIKeyDialog` for project creation dialog
- Updated UI components for project creation functionality

> The following files were skipped due to too many changes: `apps/dashboard/src/stories/stubs.ts`, `apps/dashboard/src/pages/dashboard/settings/api-keys/index.tsx`, `apps/dashboard/src/components/settings/ApiKeys/Create/CreateApiKeyModal.stories.tsx`, `apps/dashboard/src/app/team/[team_slug]/(team)/TeamOverviewPage.tsx`, `apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx`

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

<!-- end pr-codex -->
2024-09-13 23:08:24 +00:00
jnsdls ece86ccdc3 v5 deploy dashboard changes (#4498)
<!-- start pr-codex -->

## PR-Codex overview
This PR deprecates functions, updates types, refactors components, and adds modules for contract deployment.

### Detailed summary
- Deprecated functions in favor of new ones
- Updated types and added necessary type assertions
- Refactored components with new props and imports
- Added modules for contract deployment and handling metadata

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(dashboard)/published-contract/utils/module-base-64.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/components/module-list.client.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/uri-based-deploy.tsx`, `pnpm-lock.yaml`, `apps/dashboard/src/app/(dashboard)/published-contract/components/contract-actions-deploy.client.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/contract-info.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/contract-actions-published.client.tsx`, `packages/thirdweb/src/utils/any-evm/deploy-metadata.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/utils/getPublishedContractsWithPublisherMapping.ts`, `packages/thirdweb/package.json`, `apps/dashboard/src/app/(dashboard)/contracts/deploy/[compiler_uri]/page.tsx`, `apps/dashboard/src/contract-ui/tabs/code/components/code-overview.tsx`, `apps/dashboard/src/components/contract-components/contract-deploy-form/trusted-forwarders-fieldset.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/layout.tsx`, `apps/dashboard/src/components/contract-components/contract-deploy-form/contract-metadata-fieldset.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/contract-header.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/components/breadcrumbs.client.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/opengraph-image.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/page.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/[version]/opengraph-image.tsx`, `packages/thirdweb/src/utils/abi/normalizeFunctionParams.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/components/version-selector.tsx`, `packages/thirdweb/src/contract/deployment/deploy-with-abi.ts`, `apps/dashboard/src/contract-ui/tabs/manage/components/getModuleInstalledParams.ts`, `packages/thirdweb/src/extensions/prebuilts/get-required-transactions.test.ts`, `apps/dashboard/src/app/(dashboard)/published-contract/[publisher]/[contract_id]/[version]/page.tsx`, `apps/dashboard/src/app/(dashboard)/published-contract/components/publish-based-deploy.tsx`, `apps/dashboard/src/components/contract-components/fetch-contracts-with-versions.ts`, `apps/dashboard/src/components/contract-functions/contract-function.tsx`, `apps/dashboard/src/components/contract-components/contract-deploy-form/modular-contract-default-modules-fieldset.tsx`, `packages/thirdweb/src/extensions/prebuilts/get-required-transactions.ts`, `apps/dashboard/src/pages/api/og/publishedContract.tsx`, `apps/dashboard/src/components/contract-components/published-contract/index.tsx`, `apps/dashboard/src/components/contract-components/hooks.ts`, `apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx`

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

<!-- end pr-codex -->
2024-09-13 19:16:20 +00:00
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
d115307db1 Version Packages (#4570)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-13 12:01:55 -07:00
gregfromstl 5d3b39587b [React] Feature: Can hide certain wallets in the wallet switcher (#4582)
Adds the ability to hide certain wallets in the wallet switcher

```tsx
      <ConnectButton
        client={client}
        detailsModal={{
          // We hide the in-app wallet so they can't switch to it
          hiddenWallets: ["inApp"],
        }}
        accountAbstraction={{
          chain: baseSepolia,
          sponsorGas: true,
        }}
      />
```

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to add the ability to hide specific wallets in the wallet switcher UI component.

### Detailed summary
- Added `hiddenWallets` prop to `ConnectButton`, `DetailsModal`, `WalletSwitcherConnectionScreen`, `BuyScreen`, and `PayEmbed` components.
- Implemented filtering of connected wallets based on `hiddenWallets` in `WalletManagerScreen` and `WalletSwitcherDrawerContent`.
- Updated types to include `WalletId` for consistency across components.

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

<!-- end pr-codex -->
2024-09-13 18:37:29 +00:00
Jonas DanielsandGitHub 53015711fe update next version (#4576) 2024-09-13 10:42:33 -07:00
gregfromstl 5058fdb470 [SDK] Feature: X auth (#4571)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds authentication support for a new provider 'X' across various components and modules.

### Detailed summary
- Added 'X' authentication provider
- Updated authentication types and components
- Implemented 'X' authentication in multiple files

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

<!-- end pr-codex -->
2024-09-13 06:12:59 +00:00
Jonas DanielsandGitHub a47bc2233c [Update] Add address validation in WalletAddress and AddAdminButton components (#4575) 2024-09-12 21:12:47 -07:00
Edward SunandGitHub 390180592a Add preferredProvider in buyWithFiat (#4546) 2024-09-12 17:37:57 -07:00
joaquim-verges 285321b193 docs: Update in-app wallet backend API link (#4572)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates redirects and icon imports in the `portal` app and refactors the sidebar in `sidebar.tsx`.

### Detailed summary
- Added a new redirect for in-app wallet details on the server
- Reordered icon imports in `sidebar.tsx`
- Updated sidebar links for In-App Wallet and Ecosystem Wallet pages

> The following files were skipped due to too many changes: `apps/portal/src/app/connect/sidebar.tsx`

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

<!-- end pr-codex -->
2024-09-13 00:06:10 +00:00
0xFirekeeper 8035923cda .net 2.1 docs hotfix (#4569)
<!-- start pr-codex -->

## PR-Codex overview
This PR simplifies the OTP login process in the `page.mdx` file by removing the retry logic and adding a comment for handling failed login attempts.

### Detailed summary
- Removed `canRetry` variable and related logic
- Added a comment for handling failed login attempts

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

<!-- end pr-codex -->
2024-09-12 23:26:42 +00:00
gregfromstl c0778cb930 [SDK] Feature: Export uri parsing utils (#4565)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds `parseAvatarRecord` and `parseNftUri` utilities for ENS avatars and NFT URIs. Detailed summary:

### Detailed summary
- Added `parseAvatarRecord` and `parseNftUri` functions for ENS avatars and NFT URIs
- Updated `common.ts` and `ens.ts` with new exports
- Created `parseNftUri` function in `parseNft.ts`
- Added `parseAvatarRecord` function in `avatar.ts`

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

<!-- end pr-codex -->
2024-09-12 22:12:44 +00:00
arcoraven db1f79678c fix: incorrect gasLimit reference in Engine (#4566)
<!-- start pr-codex -->

## PR-Codex overview
The focus of this PR is to update the gas settings override mechanism for write transactions in the contract page.

### Detailed summary
- Updated gas settings override mechanism for transactions
- Removed individual `gasLimit` and `gasPrice` fields
- Added `gas`, `maxFeePerGas`, and `maxPriorityFeePerGas` fields
- Fields are now optional and will be estimated if omitted

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

<!-- end pr-codex -->
2024-09-12 20:54:40 +00:00
0xFirekeeper 113468e361 .NET 2.1 // Unity 5.1 Docs (#4564)
<!-- start pr-codex -->

## PR-Codex overview
The focus of this PR is to add new methods and documentation for interacting with smart wallets in the .NET SDK.

### Detailed summary
- Added methods for getting personal wallet, all admins, active signers, transaction count, and transferring in .NET SDK
- Updated documentation for In-App Wallet and Ecosystem Wallet login methods

> The following files were skipped due to too many changes: `apps/portal/src/app/dotnet/wallets/providers/ecosystem-wallet/page.mdx`, `apps/portal/src/app/dotnet/sidebar.tsx`, `apps/portal/src/app/unity/v5/wallets/ecosystem-wallet/page.mdx`

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

<!-- end pr-codex -->
2024-09-12 20:15:25 +00:00
MananTank a24de21f00 Fix no redirect to dashboard if user is already logged in and lands on login page (#4561)
Did not go the middleware route here because even if we properly check the logged in status in middleware and redirect - it's possible for user to get stuck in that case if for some reason the wallet never gets into "connecting" status and so the useLoggedInUser query never runs and never redirects the user to login page ( and even if it did - they would immediately get redirected to other page because they have a "valid" auth cookie )

so safest solution here is to let the user land on login page and once then redirect from there after wallet connection + sign in is done

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to refactor the login process in the dashboard app.

### Detailed summary
- Removed unused imports and functions
- Refactored `doLogin` function to handle redirect logic internally
- Added `useDashboardRouter` hook to handle navigation
- Improved error handling and redirect logic in the login process

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

<!-- end pr-codex -->
2024-09-12 18:37:42 +00:00
Joaquim VergesandGitHub 2d2e6cb380 test: Update ERC1967 proxy constructor data parameter (#4563) 2024-09-13 06:36:47 +12:00
MananTank 03c21903c9 Fix Popular chain missing icons on dashboard (#4562)
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the `connect-wallet` component to include support for popular chains with metadata.

### Detailed summary
- Added `useSupportedChainsRecord` import
- Added `chainsRecord` state
- Added `popularChainsWithMeta` state
- Updated `chainSections` to use `popularChainsWithMeta`
- Updated dependencies for `useMemo` in `chainSections`

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

<!-- end pr-codex -->
2024-09-12 18:13:09 +00:00
4222 changed files with 728033 additions and 421767 deletions
+1 -27
View File
@@ -10,33 +10,7 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [
"bundlers-esbuild",
"bundlers-vite",
"bundlers-webpack",
"bundlers-swc",
"runtimes-node-commonjs",
"runtimes-node-module",
"playground-web",
"thirdweb-dashboard",
"wallet-ui",
"portal",
"@thirdweb-dev/auth",
"@thirdweb-dev/chains",
"@thirdweb-dev/cli",
"@thirdweb-dev/contracts-js",
"@thirdweb-dev/crypto",
"@thirdweb-dev/generated-abis",
"@thirdweb-dev/merkletree",
"@thirdweb-dev/pay",
"@thirdweb-dev/payments",
"@thirdweb-dev/react",
"@thirdweb-dev/react-core",
"@thirdweb-dev/sdk",
"@thirdweb-dev/storage",
"@thirdweb-dev/unity-js-bridge",
"@thirdweb-dev/wallets"
],
"ignore": ["playground-web", "thirdweb-dashboard", "wallet-ui", "portal"],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"updateInternalDependents": "always",
"onlyUpdatePeerDependentsWhenOutOfRange": true
+2 -26
View File
@@ -3,42 +3,18 @@
# default ownership group
- @jnsdls @joaquim-verges @MananTank @gregfromstl
# legacy packages
legacy_packages/auth/ @jnsdls @joaquim-verges
legacy_packages/chains/ @jnsdls @joaquim-verges
legacy_packages/cli/ @jnsdls @joaquim-verges
legacy_packages/contracts-js/ @jnsdls @joaquim-verges
legacy_packages/crypto/ @jnsdls @joaquim-verges
legacy_packages/generated-abis/ @jnsdls @joaquim-verges
legacy_packages/merkletree/ @jnsdls @joaquim-verges
legacy_packages/pay/ @jnsdls @joaquim-verges
legacy_packages/payments/ @jnsdls @joaquim-verges
legacy_packages/react/ @MananTank @jnsdls @joaquim-verges
legacy_packages/react-core/ @MananTank @jnsdls @joaquim-verges
legacy_packages/react-native/ @jnsdls @joaquim-verges
legacy_packages/react-native-compat/ @jnsdls @joaquim-verges
legacy_packages/sdk/ @joaquim-verges @jnsdls
legacy_packages/storage/ @jnsdls @joaquim-verges
legacy_packages/wallets/ @MananTank @jnsdls @joaquim-verges
# legacy e2e
legacy_e2e/ @jnsdls
# packages
packages/thirdweb/ @joaquim-verges @gregfromstl @jnsdls
## specific thirdweb pieces
packages/thirdweb/src/react/ @joaquim-verges @gregfromstl @MananTank @jnsdls
packages/thirdweb/src/react/ @joaquim-verges @gregfromstl @MananTank @jnsdls @edwardysun
packages/thirdweb/src/reactive/ @joaquim-verges @gregfromstl @MananTank @jnsdls
packages/thirdweb/src/gas/ @joaquim-verges @jnsdls
packages/thirdweb/src/pay/ @joaquim-verges @gregfromstl @MananTank @jnsdls
packages/thirdweb/src/pay/ @joaquim-verges @gregfromstl @MananTank @jnsdls @edwardysun
packages/typedoc-gen/ @MananTank @jnsdls @joaquim-verges
packages/service-utils/ @arcoraven @jnsdls @joaquim-verges
packages/eslint-config-thirdweb/ @jnsdls @joaquim-verges
packages/tw-tsconfig/ @jnsdls @joaquim-verges
packages/unity-js-bridge/ @joaquim-verges @0xFirekeeper @jnsdls
# apps
apps/ @jnsdls @joaquim-verges
-3
View File
@@ -5,9 +5,6 @@ dashboard:
playground:
- changed-files:
- any-glob-to-any-file: "apps/playground-web/**/*"
legacy_packages:
- changed-files:
- any-glob-to-any-file: "legacy-packages/**/*"
packages:
- changed-files:
- any-glob-to-any-file: "packages/**/*"
-113
View File
@@ -1,113 +0,0 @@
name: CI (Legacy Packages)
on:
push:
branches: ["main"]
paths:
- "legacy_packages/**"
- "legacy_e2e/**"
- "package.json"
pull_request:
types: [opened, synchronize]
paths:
- "legacy_packages/**"
- "legacy_e2e/**"
- "package.json"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# To use Remote Caching, uncomment the next lines and follow the steps below.
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TW_SECRET_KEY: ${{ secrets.TW_SECRET_KEY }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
jobs:
optimize_ci:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check_skip.outputs.skip }}
steps:
- name: Optimize CI
id: check_skip
uses: withgraphite/graphite-ci-action@main
with:
graphite_token: ${{ secrets.GRAPHITE_OMTIMIZE_TOKEN }}
build:
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
runs-on: ubuntu-latest
name: Build Packages
steps:
- name: Check out the code
uses: actions/checkout@v4
- name: Setup & Install
uses: ./.github/composite-actions/install
- name: Build Packages
run: pnpm build:legacy_packages
lint:
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
timeout-minutes: 15
name: Lint Packages
runs-on: ubuntu-latest
steps:
- name: Check out the code
uses: actions/checkout@v4
- name: Setup & Install
uses: ./.github/composite-actions/install
- run: pnpm lint:legacy
test:
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
timeout-minutes: 15
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Check out the code
uses: actions/checkout@v4
- name: Setup & Install
uses: ./.github/composite-actions/install
- run: pnpm test:legacy
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
with:
directory: legacy_packages/
flags: legacy_packages
verbose: true
e2e:
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
timeout-minutes: 15
name: E2E Tests
runs-on: ubuntu-latest
steps:
- name: Check out the code
uses: actions/checkout@v4
- name: Setup & Install
uses: ./.github/composite-actions/install
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run e2e tests
run: pnpm e2e:legacy
env:
NODE_OPTIONS: "--max_old_space_size=4096"
CLI_E2E_API_KEY: ${{ secrets.CLI_E2E_API_KEY }}
-1
View File
@@ -8,7 +8,6 @@ on:
paths:
- ".changeset/**"
- "packages/**"
- "legacy_packages/**"
branches:
- main
-58
View File
@@ -1,58 +0,0 @@
name: Sync chains every hour
on:
# temporarily disabled chain syncing
# schedule:
# - cron: "0 * * * *" # Runs every hour
workflow_dispatch: # Allows you to manually run the workflow from the Actions tab
jobs:
sync-chains:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.0.35
- name: Run chains sync
run: bun run db:sync
working-directory: ./legacy_packages/chains
- name: Check for Changes
id: git-check
run: |
if git diff --quiet; then
echo "CHANGES_FOUND=false" >> $GITHUB_ENV
else
echo "CHANGES_FOUND=true" >> $GITHUB_ENV
fi
- name: Create or Update sync-chains.md
if: env.CHANGES_FOUND == 'true'
run: |
echo '---' > .changeset/sync-chains.md
echo '"@thirdweb-dev/chains": patch' >> .changeset/sync-chains.md
echo '---' >> .changeset/sync-chains.md
echo '' >> .changeset/sync-chains.md
echo 'Synced Chains Package' >> .changeset/sync-chains.md
- name: Commit Changes
if: env.CHANGES_FOUND == 'true'
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add -A
git commit -m "chore(chains): sync from db"
- name: Create or Update Pull Request
if: env.CHANGES_FOUND == 'true'
uses: peter-evans/create-pull-request@v6
with:
title: "chore(chains): update chains"
branch: "sync-chains"
body: "This PR was automatically created by a GitHub Action. It updates the chains package with the latest changes from the database."
commit-message: "chore(chains): sync from db"
-5
View File
@@ -10,7 +10,6 @@ dist-ssr
.cache
server/dist
public/dist
./legacy_packages/contracts-js/abis
.idea/
.yalc/
yalc.lock
@@ -18,9 +17,6 @@ yalc.lock
playwright-report/
.env/
# Artifacts
legacy_packages/cli/artifacts/
# codecov binary
codecov
@@ -28,7 +24,6 @@ codecov
sitemap*.xml
# typedoc
legacy_packages/*/typedoc/*
packages/*/typedoc/*
*storybook.log
@@ -1,6 +0,0 @@
---
"@thirdweb-dev/cli": minor
"thirdweb": minor
---
Adds --expo option to cli
@@ -1,5 +0,0 @@
---
"@thirdweb-dev/sdk": patch
---
ZkCandy addresses
@@ -1,6 +0,0 @@
---
"@thirdweb-dev/cli": patch
"@thirdweb-dev/sdk": patch
---
Override contract publish id on CLI
@@ -1,17 +0,0 @@
---
"@thirdweb-dev/merkletree": patch
"@thirdweb-dev/react-core": patch
"@thirdweb-dev/payments": patch
"@thirdweb-dev/storage": patch
"@thirdweb-dev/wallets": patch
"@thirdweb-dev/chains": patch
"@thirdweb-dev/crypto": patch
"@thirdweb-dev/service-utils": patch
"@thirdweb-dev/react": patch
"@thirdweb-dev/auth": patch
"@thirdweb-dev/cli": patch
"@thirdweb-dev/pay": patch
"@thirdweb-dev/sdk": patch
---
update dependencies
@@ -1,5 +0,0 @@
---
"@thirdweb-dev/react": patch
---
Fix error when doing buy with Crypto transaction with InApp wallet
@@ -1,5 +0,0 @@
---
"@thirdweb-dev/sdk": patch
---
Fix EIP155 check for create2 factory deployment
@@ -1,6 +0,0 @@
---
"@thirdweb-dev/wallets": patch
"@thirdweb-dev/sdk": patch
---
respect gas overrides if they are passed in
@@ -1,5 +0,0 @@
---
"@thirdweb-dev/react": patch
---
Fix wallet app not opening when using ConnectButton inside an iframe
@@ -1,5 +0,0 @@
---
"@thirdweb-dev/chains": patch
---
Synced Chains Package
-25
View File
@@ -115,31 +115,6 @@ module.exports = {
},
},
overrides: [
{
files: "src/core-ui/**/*",
rules: {
// no restricted imports
"@typescript-eslint/no-restricted-imports": [
"error",
{
paths: [
{
name: "@thirdweb-dev/sdk",
message:
"core-ui should not import from @thirdweb-dev/sdk. (except for types)",
allowTypeImports: true,
},
{
name: "@thirdweb-dev/react",
message:
"core-ui should not import from @thirdweb-dev/react. (except for types)",
allowTypeImports: true,
},
],
},
],
},
},
// disable restricted imports in tw-components
{
files: "src/tw-components/**/*",
+3 -2
View File
@@ -1,7 +1,8 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"next": true,
"ignore": ["src/graphql/**", "src/theme/*"],
"ignore": ["src/@/components/ui/**"],
"project": ["src/**"],
"ignoreBinaries": ["only-allow"]
"ignoreBinaries": ["only-allow", "knip", "biome"],
"ignoreDependencies": ["@storybook/blocks", "@thirdweb-dev/service-utils"]
}
-2
View File
@@ -1,7 +1,6 @@
// @ts-check
/**
*
* @returns {Promise<import("@thirdweb-dev/chains").Chain[]>}
*/
async function fetchChainsFromApi() {
const res = await fetch(`https://api.thirdweb.com/v1/chains`, {
@@ -21,7 +20,6 @@ async function fetchChainsFromApi() {
/**
*
* @param {number|string} chainIdOrSlug
* @returns {Promise<import("@thirdweb-dev/chains").Chain>}
*/
async function getSingleChain(chainIdOrSlug) {
const res = await fetch(
-1
View File
@@ -144,7 +144,6 @@ const moduleExports = {
reactStrictMode: true,
experimental: {
scrollRestoration: true,
esmExternals: "loose",
webpackBuildWorker: true,
serverSourceMaps: false,
},
+22 -57
View File
@@ -4,15 +4,14 @@
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "next dev",
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"format": "biome format ./src --write",
"lint": "biome check ./src && eslint ./src",
"lint": "biome check ./src && knip && eslint ./src",
"fix": "biome check ./src --fix && eslint ./src --fix",
"gen:theme-typings": "chakra-cli tokens src/theme/index.ts",
"postinstall": "pnpm run gen:theme-typings",
"find:unused": "next-unused",
"postbuild": "next-sitemap",
"build:analyze": "ANALYZE=true pnpm run build",
"knip": "knip",
@@ -45,20 +44,15 @@
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "1.1.2",
"@react-icons/all-files": "^4.1.0",
"@sentry/nextjs": "8.30.0",
"@shazow/whatsabi": "^0.14.1",
"@stripe/react-stripe-js": "^2.7.3",
"@stripe/stripe-js": "^3.5.0",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query": "5.56.2",
"@tanstack/react-table": "^8.17.3",
"@thirdweb-dev/react": "workspace:*",
"@thirdweb-dev/sdk": "workspace:*",
"@thirdweb-dev/service-utils": "workspace:*",
"@thirdweb-dev/storage": "workspace:*",
"@vercel/og": "^0.6.2",
"abitype": "1.0.5",
"chakra-react-select": "^4.7.6",
@@ -67,15 +61,14 @@
"color": "^4.2.3",
"compare-versions": "^6.1.0",
"date-fns": "^3.6.0",
"ethers": "5.7.2",
"flat": "^6.0.1",
"framer-motion": "11.5.4",
"fuse.js": "7.0.0",
"ioredis": "^5.4.1",
"ipaddr.js": "^2.2.0",
"lottie-react": "^2.4.0",
"lucide-react": "0.439.0",
"next": "14.2.9",
"lucide-react": "0.441.0",
"next": "14.2.11",
"next-plausible": "^3.12.0",
"next-seo": "^6.5.0",
"next-themes": "^0.3.0",
@@ -97,7 +90,6 @@
"react-intersection-observer": "^9.10.3",
"react-markdown": "^9.0.1",
"react-otp-input": "^3.1.1",
"react-query-v5": "npm:@tanstack/[email protected]",
"react-responsive-carousel": "^3.2.23",
"react-table": "^7.8.0",
"recharts": "^2.12.7",
@@ -111,25 +103,23 @@
"thirdweb": "workspace:*",
"tiny-invariant": "^1.3.3",
"use-debounce": "^10.0.1",
"vaul": "^0.9.1",
"zksync-ethers": "5.9.0",
"zod": "3.23.8"
},
"devDependencies": {
"@chakra-ui/cli": "^2.4.1",
"@chromatic-com/storybook": "^1.5.0",
"@next/bundle-analyzer": "14.2.9",
"@next/eslint-plugin-next": "^14.2.9",
"@playwright/test": "1.47.0",
"@storybook/addon-essentials": "^8.2.9",
"@storybook/addon-interactions": "^8.2.9",
"@storybook/addon-links": "^8.2.9",
"@storybook/addon-onboarding": "^8.2.9",
"@storybook/addon-viewport": "^8.2.9",
"@storybook/blocks": "^8.2.9",
"@storybook/nextjs": "^8.2.9",
"@storybook/react": "^8.2.9",
"@storybook/test": "^8.2.9",
"@chromatic-com/storybook": "2.0.2",
"@next/bundle-analyzer": "14.2.11",
"@next/eslint-plugin-next": "14.2.11",
"@playwright/test": "1.47.1",
"@storybook/addon-essentials": "8.3.0",
"@storybook/addon-interactions": "8.3.0",
"@storybook/addon-links": "8.3.0",
"@storybook/addon-onboarding": "8.3.0",
"@storybook/addon-viewport": "8.3.0",
"@storybook/blocks": "8.3.0",
"@storybook/nextjs": "8.3.0",
"@storybook/react": "8.3.0",
"@storybook/test": "8.3.0",
"@types/color": "^3.0.6",
"@types/node": "20.14.9",
"@types/papaparse": "^5.3.14",
@@ -151,34 +141,9 @@
"eslint-plugin-react-compiler": "0.0.0-experimental-9ed098e-20240725",
"eslint-plugin-storybook": "^0.8.0",
"next-sitemap": "^4.2.3",
"next-unused": "^0.0.6",
"storybook": "^8.2.9",
"tailwindcss": "^3.4.10",
"postcss": "8.4.47",
"storybook": "8.3.0",
"tailwindcss": "3.4.11",
"typescript": "5.6.2"
},
"next-unused": {
"alias": {},
"include": [
"components",
"hooks",
"schema",
"theme",
"utils",
"@3rdweb-sdk",
"constants",
"tw-components",
"contract-ui"
],
"exclude": [
"node_modules",
"types",
"tw-components"
],
"entrypoints": []
},
"browserslist": [
"defaults",
"unreleased versions",
"not UCAndroid > 0"
]
}
}
@@ -3,10 +3,13 @@ import { Button } from "@/components/ui/button";
import { useMutation } from "@tanstack/react-query";
import { DownloadIcon } from "lucide-react";
import { toast } from "sonner";
import { cn } from "../../lib/utils";
export function ExportToCSVButton(props: {
getData: () => Promise<{ header: string[]; rows: string[][] }>;
fileName: string;
disabled?: boolean;
className?: string;
}) {
const exportMutation = useMutation({
mutationFn: async () => {
@@ -26,12 +29,13 @@ export function ExportToCSVButton(props: {
return (
<Button
variant="outline"
className="border flex gap-2 items-center text-xs"
disabled={props.disabled}
className={cn("border flex gap-2 items-center text-xs", props.className)}
onClick={async () => {
exportMutation.mutate();
}}
>
{exportMutation.isLoading ? (
{exportMutation.isPending ? (
<>
Downloading
<Spinner className="size-3" />
@@ -14,7 +14,7 @@ export type SidebarLink = {
};
};
export type SidebarContentProps = {
type SidebarContentProps = {
header?: React.ReactNode;
links: SidebarLink[];
className?: string;
@@ -19,7 +19,7 @@ import {
} from "@/components/ui/chart";
import { useMemo } from "react";
export type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
// metadata
title: string;
description?: string;
@@ -19,7 +19,7 @@ import {
} from "@/components/ui/chart";
import { useMemo } from "react";
export type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
// metadata
title: string;
description?: string;
@@ -8,6 +8,7 @@ import {
} from "@/components/ui/hover-card";
import { Check, Copy, ExternalLinkIcon } from "lucide-react";
import { useMemo, useState } from "react";
import { isAddress } from "thirdweb";
import { ZERO_ADDRESS } from "thirdweb";
import {
Blobbie,
@@ -28,6 +29,7 @@ export function WalletAddress(props: {
}) {
// default back to zero address if no address provided
const address = useMemo(() => props.address || ZERO_ADDRESS, [props.address]);
const [shortenedAddress, lessShortenedAddress] = useMemo(() => {
return [
props.shortenAddress !== false
@@ -44,6 +46,15 @@ export function WalletAddress(props: {
const [isCopied, setIsCopied] = useState(false);
if (!isAddress(address)) {
return <span>Invalid Address ({address})</span>;
}
// special case for zero address
if (address === ZERO_ADDRESS) {
return <span className="font-mono cursor-pointer">{shortenedAddress}</span>;
}
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(address);
+5 -1
View File
@@ -113,6 +113,7 @@ const ChartTooltipContent = React.forwardRef<
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
valueFormatter?: (v: unknown) => string | undefined;
}
>(
(
@@ -130,6 +131,7 @@ const ChartTooltipContent = React.forwardRef<
color,
nameKey,
labelKey,
valueFormatter,
},
ref,
) => {
@@ -243,7 +245,9 @@ const ChartTooltipContent = React.forwardRef<
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
{item.value}
{valueFormatter
? valueFormatter(item.value) || item.value
: item.value}
</span>
)}
</div>
@@ -41,7 +41,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg duration-300 rounded-t-xl md:rounded-lg border-border",
"fixed grid w-full md:max-w-lg gap-4 border bg-background p-6 shadow-lg duration-300 rounded-t-xl md:rounded-lg border-border",
// on mobile - put the dialog at the bottom of the screen, animate - slide up and fade in
"bottom-0 left-0 right-0",
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
@@ -1,118 +0,0 @@
"use client";
import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "@/lib/utils";
const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
{...props}
/>
);
Drawer.displayName = "Drawer";
const DrawerTrigger = DrawerPrimitive.Trigger;
const DrawerPortal = DrawerPrimitive.Portal;
const DrawerClose = DrawerPrimitive.Close;
const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className,
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
));
DrawerContent.displayName = "DrawerContent";
const DrawerHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props}
/>
);
DrawerHeader.displayName = "DrawerHeader";
const DrawerFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
);
DrawerFooter.displayName = "DrawerFooter";
const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
};
@@ -1,61 +0,0 @@
"use client";
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
import type { VariantProps } from "class-variance-authority";
import * as React from "react";
import { toggleVariants } from "@/components/ui/toggle";
import { cn } from "@/lib/utils";
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
});
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, children, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn("flex items-center justify-center gap-1", className)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
));
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext);
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className,
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
);
});
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
export { ToggleGroup, ToggleGroupItem };
@@ -1,45 +0,0 @@
"use client";
import * as TogglePrimitive from "@radix-ui/react-toggle";
import { type VariantProps, cva } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";
const toggleVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-10 px-3",
sm: "h-9 px-2.5",
lg: "h-11 px-5",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
));
Toggle.displayName = TogglePrimitive.Root.displayName;
export { Toggle, toggleVariants };
+2 -2
View File
@@ -6,8 +6,8 @@ const LOGGED_IN_ONLY_PATHS = [
// anything that _starts_ with /cli is logged in only
"/cli",
"/support",
// TODO: add any other logged in only paths here
// publish page
"/contracts/publish",
];
export function isLoginRequired(pathname: string) {
+4
View File
@@ -16,3 +16,7 @@ export const isProd =
"production";
export const PROD_OR_DEV_URL = isProd ? "thirdweb.com" : "thirdweb-dev.com";
export const DASHBOARD_STORAGE_URL =
process.env.NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER ||
"https://storage.thirdweb.com";
+1 -1
View File
@@ -15,7 +15,7 @@ import { createStore } from "./reactive";
// Using useDashboardRouter instead of useRouter gives us a nice progress bar on top of the page when navigating using router.push or router.replace
// using a store instead of context to avoid triggering re-renders on root component
export const LoadingRouteHref = createStore<string | undefined>(undefined);
const LoadingRouteHref = createStore<string | undefined>(undefined);
export function useDashboardRouter() {
const router = useRouter();
+1 -1
View File
@@ -1,4 +1,4 @@
export type Store<T> = {
type Store<T> = {
getValue(): T;
setValue(newValue: T): void;
subscribe(listener: () => void): () => void;
+7 -1
View File
@@ -27,7 +27,7 @@
--muted-foreground: 0 0% 40%;
--accent-foreground: 0 0% 9%;
--destructive-foreground: 0 0% 100%;
--inverted-foreground: var(--background);
--inverted-foreground: 0 0% 100%;
--link-foreground: 221.21deg 83.19% 53.33%;
--success-text: 142.09 70.56% 35.29%;
--warning-text: 38 92% 40%;
@@ -72,6 +72,7 @@
--warning-text: 38 92% 50%;
--destructive-text: 360 72% 55%;
--success-text: 142 75% 50%;
--inverted-foreground: 0 0% 0%;
/* Borders */
--border: 0 0% 15%;
@@ -138,3 +139,8 @@
scrollbar-width: none; /* Firefox */
}
}
body,
html {
height: 100%;
}
@@ -1,4 +1,3 @@
import type { ContractType } from "constants/contracts";
import { ZERO_ADDRESS } from "thirdweb";
export const networkKeys = {
@@ -109,23 +108,6 @@ export const engineKeys = {
[...engineKeys.all, engineId, "queueMetrics"] as const,
};
export const contractKeys = {
all: ["contract"] as const,
lists: () => [...contractKeys.all, "list"] as const,
list: (address = ZERO_ADDRESS) => [...contractKeys.lists(), address] as const,
listWithFilters: (address = ZERO_ADDRESS, filters?: ContractType[]) =>
[...contractKeys.list(address), { filters }] as const,
details: () => [...contractKeys.all, "detail"] as const,
detail: (address: string = ZERO_ADDRESS) =>
[...contractKeys.details(), address] as const,
};
export const walletKeys = {
all: ["balance"] as const,
balances: (walletAddress: string) =>
[...splitsKeys.all, walletAddress] as const,
};
export const splitsKeys = {
all: ["splits"] as const,
lists: () => [...splitsKeys.all, "list"] as const,
@@ -7,7 +7,10 @@ import { getSDKTheme } from "app/components/sdk-component-theme";
import { CustomChainRenderer } from "components/selects/CustomChainRenderer";
import { mapV4ChainToV5Chain } from "contexts/map-chains";
import { useTrack } from "hooks/analytics/useTrack";
import { useSupportedChains } from "hooks/chains/configureChains";
import {
useSupportedChains,
useSupportedChainsRecord,
} from "hooks/chains/configureChains";
import {
useAddRecentlyUsedChainId,
useRecentlyUsedChains,
@@ -37,12 +40,17 @@ export const CustomConnectWallet = (props: {
// const setIsNetworkConfigModalOpen = useSetIsNetworkConfigModalOpen();
const t = theme === "light" ? "light" : "dark";
const allv4Chains = useSupportedChains();
const chainsRecord = useSupportedChainsRecord();
const favChainsQuery = useFavoriteChains();
const setIsNetworkConfigModalOpen = useSetIsNetworkConfigModalOpen();
const allChains = useMemo(() => {
return allv4Chains.map(mapV4ChainToV5Chain);
}, [allv4Chains]);
const popularChainsWithMeta = useMemo(() => {
return popularChains.map((x) => chainsRecord[x.id] || x);
}, [chainsRecord]);
const chainSections = useMemo(() => {
return [
{
@@ -51,14 +59,14 @@ export const CustomConnectWallet = (props: {
},
{
label: "Popular",
chains: popularChains,
chains: popularChainsWithMeta.map(mapV4ChainToV5Chain),
},
{
label: "Recent",
chains: recentChainsv4.map(mapV4ChainToV5Chain),
},
];
}, [recentChainsv4, favChainsQuery.data]);
}, [recentChainsv4, favChainsQuery.data, popularChainsWithMeta]);
// ensures login status on pages that need it
const { isLoading, isLoggedIn } = useLoggedInUser();
@@ -140,7 +148,7 @@ export const CustomConnectWallet = (props: {
);
};
export function ConnectWalletWelcomeScreen(props: {
function ConnectWalletWelcomeScreen(props: {
theme: "light" | "dark";
subtitle?: string;
}) {
@@ -1,3 +1,2 @@
export * from "./useActiveChainId";
export * from "./useCommon";
export * from "./useWalletNFTs";
@@ -39,7 +39,11 @@ export function useQueryWithNetwork<
networkKeys.chain(activeChainId) as readonly unknown[]
).concat(queryKey) as unknown as TQueryKey;
return useQuery(combinedQueryKey, queryFn, mergedOptions);
return useQuery({
...mergedOptions,
queryKey: combinedQueryKey,
queryFn,
});
}
export function useMutationWithInvalidate<
TData = unknown,
@@ -71,19 +75,20 @@ export function useMutationWithInvalidate<
(cacheKeysToInvalidate: TQueryKey[]) => {
return Promise.all(
cacheKeysToInvalidate.map((cacheKey) => {
return queryClient.invalidateQueries(
(networkKeys.chain(activeChainId) as readonly unknown[]).concat(
cacheKey,
) as unknown[],
);
return queryClient.invalidateQueries({
queryKey: (
networkKeys.chain(activeChainId) as readonly unknown[]
).concat(cacheKey) as unknown[],
});
}),
);
},
[queryClient, activeChainId],
);
return useMutation(mutationFn, {
return useMutation({
...options,
mutationFn,
onSuccess: (...args) => {
if (options?.onSuccess) {
options.onSuccess(...args, invalidate);
@@ -172,7 +172,7 @@ interface UpdateKeyServiceInput {
actions?: string[];
}
interface CreateKeyInput {
export interface CreateKeyInput {
name?: string;
domains?: string[];
bundleIds?: string[];
@@ -254,15 +254,16 @@ export interface BillingCredit {
interface UseAccountInput {
refetchInterval?:
| number
| false
| ((
data: Account | undefined,
query: Query<
Account,
unknown,
Error,
Account,
readonly ["account", string, "me"]
>,
) => number | false);
) => number | false | undefined)
| undefined;
}
export function useAccount({ refetchInterval }: UseAccountInput = {}) {
@@ -286,7 +287,7 @@ export function useAccount({ refetchInterval }: UseAccountInput = {}) {
return json.data as Account;
},
enabled: !!user?.address && isLoggedIn,
refetchInterval: refetchInterval ?? false,
refetchInterval,
});
}
@@ -444,8 +445,8 @@ export function useUpdateAccount() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
return useMutationWithInvalidate(
async (input: UpdateAccountInput) => {
return useMutation({
mutationFn: async (input: UpdateAccountInput) => {
invariant(user?.address, "walletAddress is required");
const res = await fetch(`${THIRDWEB_API_HOST}/v1/account`, {
@@ -465,14 +466,13 @@ export function useUpdateAccount() {
return json.data;
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
accountKeys.me(user?.address as string),
);
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: accountKeys.me(user?.address as string),
});
},
);
});
}
export function useUpdateAccountPlan(waitForWebhook?: boolean) {
@@ -507,13 +507,15 @@ export function useUpdateAccountPlan(waitForWebhook?: boolean) {
return json.data;
},
{
onSuccess: () => {
onSuccess: async () => {
// invalidate usage data as limits are different
queryClient.invalidateQueries(accountKeys.me(user?.address as string));
await queryClient.invalidateQueries({
queryKey: accountKeys.me(user?.address as string),
});
return queryClient.invalidateQueries(
accountKeys.usage(user?.address as string),
);
return queryClient.invalidateQueries({
queryKey: accountKeys.usage(user?.address as string),
});
},
},
);
@@ -545,9 +547,9 @@ export function useUpdateNotifications() {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
accountKeys.me(user?.address as string),
);
return queryClient.invalidateQueries({
queryKey: accountKeys.me(user?.address as string),
});
},
},
);
@@ -608,16 +610,19 @@ export function useConfirmEmail() {
return json.data;
},
{
onSuccess: () => {
onSuccess: async () => {
// invalidate related cache, since could be relinking account
queryClient.invalidateQueries(apiKeys.keys(user?.address as string));
queryClient.invalidateQueries(
accountKeys.usage(user?.address as string),
);
return queryClient.invalidateQueries(
accountKeys.me(user?.address as string),
);
return Promise.all([
queryClient.invalidateQueries({
queryKey: apiKeys.keys(user?.address as string),
}),
queryClient.invalidateQueries({
queryKey: accountKeys.usage(user?.address as string),
}),
queryClient.invalidateQueries({
queryKey: accountKeys.me(user?.address as string),
}),
]);
},
},
);
@@ -652,9 +657,9 @@ export function useResendEmailConfirmation() {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
accountKeys.me(user?.address as string),
);
return queryClient.invalidateQueries({
queryKey: accountKeys.me(user?.address as string),
});
},
},
);
@@ -688,9 +693,9 @@ export function useCreatePaymentMethod() {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
accountKeys.me(user?.address as string),
);
return queryClient.invalidateQueries({
queryKey: accountKeys.me(user?.address as string),
});
},
},
);
@@ -723,8 +728,8 @@ export function useCreateApiKey() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
return useMutation(
async (input: CreateKeyInput) => {
return useMutation({
mutationFn: async (input: CreateKeyInput) => {
invariant(user?.address, "walletAddress is required");
const res = await fetch(`${THIRDWEB_API_HOST}/v1/keys`, {
@@ -743,14 +748,13 @@ export function useCreateApiKey() {
return json.data as ApiKey;
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
apiKeys.keys(user?.address as string),
);
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: apiKeys.keys(user?.address as string),
});
},
);
});
}
export function useUpdateApiKey() {
@@ -780,9 +784,9 @@ export function useUpdateApiKey() {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
apiKeys.keys(user?.address as string),
);
return queryClient.invalidateQueries({
queryKey: apiKeys.keys(user?.address as string),
});
},
},
);
@@ -814,9 +818,9 @@ export function useRevokeApiKey() {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
apiKeys.keys(user?.address as string),
);
return queryClient.invalidateQueries({
queryKey: apiKeys.keys(user?.address as string),
});
},
},
);
@@ -869,41 +873,14 @@ export const useUpdatePolicies = () => {
},
{
onSuccess: (_, variables) => {
return queryClient.invalidateQueries(["policies", variables.serviceId]);
return queryClient.invalidateQueries({
queryKey: ["policies", variables.serviceId],
});
},
},
);
};
export function useAuthorizeWalletWithAccount() {
const { user } = useLoggedInUser();
return useMutationWithInvalidate(
async (variables: { token: string; deviceName?: string }) => {
invariant(user?.address, "walletAddress is required");
const res = await fetch(`${THIRDWEB_API_HOST}/v1/jwt/authorize-wallet`, {
method: "POST",
headers: {
"Content-Type": "application/json",
authorization: `Bearer ${variables.token}`,
},
body: JSON.stringify({
deviceName: variables.deviceName,
}),
});
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
return json.data;
},
);
}
export function useRevokeAuthorizedWallet() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
@@ -935,9 +912,11 @@ export function useRevokeAuthorizedWallet() {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
authorizedWallets.authorizedWallets(user?.address as string),
);
return queryClient.invalidateQueries({
queryKey: authorizedWallets.authorizedWallets(
user?.address as string,
),
});
},
},
);
@@ -965,14 +944,14 @@ export function useAuthorizedWallets() {
return json.data as AuthorizedWallet[];
},
enabled: !!user?.address && isLoggedIn,
cacheTime: 0,
gcTime: 0,
});
}
/**
*
*/
export async function fetchChainsFromApi() {
async function fetchChainsFromApi() {
// always fetch from prod for chains for now
// TODO: re-visit this
const res = await fetch("https://api.thirdweb.com/v1/chains");
@@ -1,45 +0,0 @@
export function usePascalCaseContractName(contractName: string) {
if (!contractName) {
return null;
}
if (contractName === "nft-drop") {
return "NFTDrop";
}
if (contractName === "edition-drop") {
return "EditionDrop";
}
if (contractName === "nft-collection") {
return "NFTCollection";
}
if (contractName === "edition") {
return "Edition";
}
if (contractName === "token") {
return "Token";
}
if (contractName === "marketplace") {
return "Marketplace";
}
if (contractName === "vote") {
return "Vote";
}
if (contractName === "split") {
return "Split";
}
if (contractName === "token-drop") {
return "TokenDrop";
}
if (contractName === "signature-drop") {
return "SignatureDrop";
}
if (contractName === "pack") {
return "Pack";
}
if (contractName === "multiwrap") {
return "Multiwrap";
}
if (contractName === "marketplace-v3") {
return "MarketplaceV3";
}
return contractName;
}
@@ -8,12 +8,6 @@ import { engineKeys } from "../cache-keys";
import { useMutationWithInvalidate } from "./query/useQueryWithNetwork";
import { useLoggedInUser } from "./useLoggedInUser";
export function useEngineConnectedInstance() {
const [instance, setInstance] = useState<EngineInstance | null>(null);
return { instance, setInstance };
}
export type EngineTier = "STARTER" | "PREMIUM" | "ENTERPRISE";
// Engine instances
@@ -116,7 +110,7 @@ export function useEngineBackendWallets(instance: string) {
});
}
export interface EngineSystemHealth {
interface EngineSystemHealth {
status: string;
engineVersion?: string;
features?: string[];
@@ -143,7 +137,7 @@ export function useEngineSystemHealth(
});
}
export interface EngineSystemQueueMetrics {
interface EngineSystemQueueMetrics {
result: {
queued: number;
pending: number;
@@ -197,24 +191,26 @@ interface UpdateVersionInput {
}
export function useEngineUpdateVersion() {
return useMutation(async (input: UpdateVersionInput) => {
invariant(input.engineId, "engineId is required");
return useMutation({
mutationFn: async (input: UpdateVersionInput) => {
invariant(input.engineId, "engineId is required");
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/update-version`, {
method: "POST",
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/update-version`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
engineId: input.engineId,
}),
});
// we never use the response body
res.body?.cancel();
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
}
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
engineId: input.engineId,
}),
});
// we never use the response body
res.body?.cancel();
if (!res.ok) {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
}
},
});
}
@@ -222,8 +218,8 @@ export function useEngineRemoveFromDashboard() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
return useMutation(
async (instanceId: string) => {
return useMutation({
mutationFn: async (instanceId: string) => {
invariant(instanceId, "instance is required");
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/${instanceId}`, {
@@ -235,14 +231,13 @@ export function useEngineRemoveFromDashboard() {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
}
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
engineKeys.instances(user?.address as string),
);
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.instances(user?.address as string),
});
},
);
});
}
export interface RemoveCloudHostedInput {
@@ -255,8 +250,12 @@ export function useEngineRemoveCloudHosted() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
return useMutation(
async ({ instanceId, reason, feedback }: RemoveCloudHostedInput) => {
return useMutation({
mutationFn: async ({
instanceId,
reason,
feedback,
}: RemoveCloudHostedInput) => {
const res = await fetch(
`${THIRDWEB_API_HOST}/v1/engine/${instanceId}/remove-cloud-hosted`,
{
@@ -274,14 +273,13 @@ export function useEngineRemoveCloudHosted() {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
}
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
engineKeys.instances(user?.address as string),
);
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.instances(user?.address as string),
});
},
);
});
}
export interface EditEngineInstanceInput {
@@ -294,8 +292,8 @@ export function useEngineEditInstance() {
const { user } = useLoggedInUser();
const queryClient = useQueryClient();
return useMutation(
async ({ instanceId, name, url }: EditEngineInstanceInput) => {
return useMutation({
mutationFn: async ({ instanceId, name, url }: EditEngineInstanceInput) => {
const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/${instanceId}`, {
method: "PUT",
@@ -310,14 +308,13 @@ export function useEngineEditInstance() {
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
}
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
engineKeys.instances(user?.address as string),
);
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.instances(user?.address as string),
});
},
);
});
}
export type Transaction = {
@@ -592,7 +589,9 @@ export function useEngineAddKeypair(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.keypairs(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.keypairs(instance),
});
},
},
);
@@ -625,7 +624,9 @@ export function useEngineRemoveKeypair(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.keypairs(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.keypairs(instance),
});
},
},
);
@@ -690,7 +691,9 @@ export function useEngineCreateRelayer(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.relayers(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.relayers(instance),
});
},
},
);
@@ -723,7 +726,9 @@ export function useEngineRevokeRelayer(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.relayers(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.relayers(instance),
});
},
},
);
@@ -761,7 +766,9 @@ export function useEngineUpdateRelayer(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.relayers(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.relayers(instance),
});
},
},
);
@@ -839,7 +846,9 @@ export function useEngineSetWalletConfig(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.walletConfig(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.walletConfig(instance),
});
},
},
);
@@ -872,9 +881,9 @@ export function useEngineCreateBackendWallet(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
engineKeys.backendWallets(instance),
);
return queryClient.invalidateQueries({
queryKey: engineKeys.backendWallets(instance),
});
},
},
);
@@ -908,9 +917,9 @@ export function useEngineUpdateBackendWallet(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
engineKeys.backendWallets(instance),
);
return queryClient.invalidateQueries({
queryKey: engineKeys.backendWallets(instance),
});
},
},
);
@@ -959,9 +968,9 @@ export function useEngineImportBackendWallet(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
engineKeys.backendWallets(instance),
);
return queryClient.invalidateQueries({
queryKey: engineKeys.backendWallets(instance),
});
},
},
);
@@ -990,7 +999,9 @@ export function useEngineGrantPermissions(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.permissions(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.permissions(instance),
});
},
},
);
@@ -1023,7 +1034,9 @@ export function useEngineRevokePermissions(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.permissions(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.permissions(instance),
});
},
},
);
@@ -1056,7 +1069,9 @@ export function useEngineCreateAccessToken(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.accessTokens(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.accessTokens(instance),
});
},
},
);
@@ -1089,7 +1104,9 @@ export function useEngineRevokeAccessToken(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.accessTokens(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.accessTokens(instance),
});
},
},
);
@@ -1123,7 +1140,9 @@ export function useEngineUpdateAccessToken(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.accessTokens(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.accessTokens(instance),
});
},
},
);
@@ -1158,7 +1177,9 @@ export function useEngineCreateWebhook(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.webhooks(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.webhooks(instance),
});
},
},
);
@@ -1191,7 +1212,9 @@ export function useEngineRevokeWebhook(instance: string) {
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.webhooks(instance));
return queryClient.invalidateQueries({
queryKey: engineKeys.webhooks(instance),
});
},
},
);
@@ -1208,31 +1231,33 @@ type SendTokensInput = {
export function useEngineSendTokens(instance: string) {
const token = useLoggedInUser().user?.jwt ?? null;
return useMutation(async (input: SendTokensInput) => {
invariant(instance, "instance is required");
return useMutation({
mutationFn: async (input: SendTokensInput) => {
invariant(instance, "instance is required");
const res = await fetch(
`${instance}backend-wallet/${input.chainId}/transfer`,
{
method: "POST",
headers: {
...getEngineRequestHeaders(token),
"x-backend-wallet-address": input.fromAddress,
const res = await fetch(
`${instance}backend-wallet/${input.chainId}/transfer`,
{
method: "POST",
headers: {
...getEngineRequestHeaders(token),
"x-backend-wallet-address": input.fromAddress,
},
body: JSON.stringify({
to: input.toAddress,
amount: input.amount.toString(),
currencyAddress: input.currencyAddress,
}),
},
body: JSON.stringify({
to: input.toAddress,
amount: input.amount.toString(),
currencyAddress: input.currencyAddress,
}),
},
);
const json = await res.json();
);
const json = await res.json();
if (json.error) {
throw new Error(json.error.message);
}
if (json.error) {
throw new Error(json.error.message);
}
return json.result;
return json.result;
},
});
}
@@ -1263,8 +1288,8 @@ export function useEngineSetCorsConfiguration(instance: string) {
const queryClient = useQueryClient();
const token = useLoggedInUser().user?.jwt ?? null;
return useMutation(
async (input: SetCorsUrlInput) => {
return useMutation({
mutationFn: async (input: SetCorsUrlInput) => {
invariant(instance, "instance is required");
const res = await fetch(`${instance}configuration/cors`, {
@@ -1280,12 +1305,13 @@ export function useEngineSetCorsConfiguration(instance: string) {
return json.result;
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.corsUrls(instance));
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.corsUrls(instance),
});
},
);
});
}
export function useEngineIpAllowlistConfiguration(instance: string) {
@@ -1320,8 +1346,8 @@ export function useEngineSetIpAllowlistConfiguration(instance: string) {
const queryClient = useQueryClient();
const token = useLoggedInUser().user?.jwt ?? null;
return useMutation(
async (input: SetIpAllowlistInput) => {
return useMutation({
mutationFn: async (input: SetIpAllowlistInput) => {
invariant(instance, "instance is required");
const res = await fetch(`${instance}configuration/ip-allowlist`, {
@@ -1337,12 +1363,13 @@ export function useEngineSetIpAllowlistConfiguration(instance: string) {
return json.result;
},
{
onSuccess: () => {
return queryClient.invalidateQueries(engineKeys.ipAllowlist(instance));
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.ipAllowlist(instance),
});
},
);
});
}
export interface EngineContractSubscription {
@@ -1392,8 +1419,8 @@ export function useEngineAddContractSubscription(instance: string) {
const queryClient = useQueryClient();
const token = useLoggedInUser().user?.jwt ?? null;
return useMutation(
async (input: AddContractSubscriptionInput) => {
return useMutation({
mutationFn: async (input: AddContractSubscriptionInput) => {
invariant(instance, "instance is required");
const res = await fetch(`${instance}contract-subscriptions/add`, {
@@ -1409,17 +1436,16 @@ export function useEngineAddContractSubscription(instance: string) {
return json.result;
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
engineKeys.contractSubscriptions(instance),
);
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.contractSubscriptions(instance),
});
},
);
});
}
export interface RemoveContractSubscriptionInput {
interface RemoveContractSubscriptionInput {
contractSubscriptionId: string;
}
@@ -1427,8 +1453,8 @@ export function useEngineRemoveContractSubscription(instance: string) {
const queryClient = useQueryClient();
const token = useLoggedInUser().user?.jwt ?? null;
return useMutation(
async (input: RemoveContractSubscriptionInput) => {
return useMutation({
mutationFn: async (input: RemoveContractSubscriptionInput) => {
invariant(instance, "instance is required");
const res = await fetch(`${instance}contract-subscriptions/remove`, {
@@ -1444,14 +1470,13 @@ export function useEngineRemoveContractSubscription(instance: string) {
return json.result;
},
{
onSuccess: () => {
return queryClient.invalidateQueries(
engineKeys.contractSubscriptions(instance),
);
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: engineKeys.contractSubscriptions(instance),
});
},
);
});
}
export function useEngineSubscriptionsLastBlock(
@@ -1481,7 +1506,7 @@ export function useEngineSubscriptionsLastBlock(
});
}
export interface EngineResourceMetrics {
interface EngineResourceMetrics {
error: string;
data: {
cpu: number;
@@ -3,7 +3,7 @@ import { useDashboardRouter } from "@/lib/DashboardRouter";
import { useQuery } from "@tanstack/react-query";
import type { EnsureLoginResponse } from "app/api/auth/ensure-login/route";
import { usePathname } from "next/navigation";
import { useRef } from "react";
import { useEffect, useRef } from "react";
import {
useActiveAccount,
useActiveWalletConnectionStatus,
@@ -63,20 +63,23 @@ export function useLoggedInUser(): {
return (await res.json()) as EnsureLoginResponse;
},
onSuccess: (data) => {
if (data.redirectTo) {
router.replace(data.redirectTo);
}
if (data.jwt) {
// necessary for legacy things for now (SDK picks it up from there)
// eslint-disable-next-line react-compiler/react-compiler
window.TW_AUTH_TOKEN = data.jwt;
} else {
window.TW_AUTH_TOKEN = undefined;
}
},
});
// legit use-case for now
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
if (query.data?.redirectTo) {
router.replace(query.data.redirectTo);
}
if (query.data?.jwt) {
// necessary for legacy things for now (SDK picks it up from there)
// eslint-disable-next-line react-compiler/react-compiler
window.TW_AUTH_TOKEN = query.data.jwt;
} else {
window.TW_AUTH_TOKEN = undefined;
}
}, [query.data?.redirectTo, query.data?.jwt, router]);
// if we are "disconnected" we are not logged in
if (connectionStatus === "disconnected") {
return {
@@ -15,7 +15,7 @@ import { remove } from "thirdweb/extensions/thirdweb";
import { useActiveAccount } from "thirdweb/react";
import invariant from "tiny-invariant";
function useMultiChainRegContractList(walletAddress?: string) {
export function useMultiChainRegContractList(walletAddress?: string) {
return useQuery({
queryKey: ["dashboard-registry", walletAddress, "multichain-contract-list"],
queryFn: async () => {
@@ -88,8 +88,8 @@ export function useRemoveContractMutation() {
const { chainIdToChainRecord } = useAllChainsData();
const queryClient = useQueryClient();
const account = useActiveAccount();
return useMutation(
async (data: RemoveContractParams) => {
return useMutation({
mutationFn: async (data: RemoveContractParams) => {
invariant(chainIdToChainRecord, "chains not initialzed yet");
invariant(data.chainId, "chainId not provided");
invariant(account, "No wallet connected");
@@ -111,12 +111,13 @@ export function useRemoveContractMutation() {
},
});
},
{
onSettled: () => {
return queryClient.invalidateQueries(["dashboard-registry"]);
},
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: ["dashboard-registry"],
});
},
);
});
}
type AddContractParams = {
@@ -129,8 +130,8 @@ export function useAddContractMutation() {
const queryClient = useQueryClient();
return useMutation(
async (data: AddContractParams) => {
return useMutation({
mutationFn: async (data: AddContractParams) => {
invariant(account, "cannot add a contract without an address");
return await addContractToMultiChainRegistry(
@@ -142,10 +143,11 @@ export function useAddContractMutation() {
300000n,
);
},
{
onSettled: () => {
return queryClient.invalidateQueries(["dashboard-registry"]);
},
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: ["dashboard-registry"],
});
},
);
});
}
@@ -92,10 +92,9 @@ export function FaucetButton({
});
},
onSettled: () => {
return queryClient.invalidateQueries([
"testnet-faucet-can-claim",
chainId,
]);
return queryClient.invalidateQueries({
queryKey: ["testnet-faucet-can-claim", chainId],
});
},
});
@@ -175,7 +174,7 @@ export function FaucetButton({
});
}}
>
{claimMutation.isLoading ? (
{claimMutation.isPending ? (
<>
Claiming <Spinner className="size-3" />
</>
@@ -30,7 +30,7 @@ export const AddChainToWallet: React.FC<AddChainToWalletProps> = (props) => {
},
});
const [debouncedLoading] = useDebounce(switchChainMutation.isLoading, 50);
const [debouncedLoading] = useDebounce(switchChainMutation.isPending, 50);
const disabled = debouncedLoading || activeWalletChainId === props.chain?.id;
@@ -7,13 +7,10 @@ import { thirdwebClient } from "@/constants/client";
import { isProd } from "@/constants/env";
import { useQuery } from "@tanstack/react-query";
import { CircleCheck, XIcon } from "lucide-react";
import { useState } from "react";
import { hostnameEndsWith } from "utils/url";
import { PrimaryInfoItem } from "../server/primary-info-item";
function useChainStatswithRPC(_rpcUrl: string) {
const [shouldRefetch, setShouldRefetch] = useState(true);
let rpcUrl = _rpcUrl.replace(
// eslint-disable-next-line no-template-curly-in-string
"${THIRDWEB_API_KEY}",
@@ -49,12 +46,14 @@ function useChainStatswithRPC(_rpcUrl: string) {
blockNumber: Number.parseInt(json.result, 16),
};
},
refetchInterval: shouldRefetch ? 5 * 1000 : undefined,
refetchInterval: (query) => {
if (query.state.error) {
return false;
}
return 5 * 1000;
},
enabled: !!rpcUrl,
refetchOnWindowFocus: false,
onError: () => {
setShouldRefetch(false);
},
});
}
@@ -20,7 +20,6 @@ export function ChainHeader(props: ChainHeaderProps) {
// force the banner image to be 4:1 aspect ratio and full-width on mobile devices
<div className="flex flex-col">
{!props.headerImageUrl && <div className="h-8 md:hidden" />}
<AspectRatio
ratio={props.headerImageUrl ? 4 : 8}
className="border-b border-border max-sm:-mx-4"
@@ -34,7 +33,6 @@ export function ChainHeader(props: ChainHeaderProps) {
/>
)}
</AspectRatio>
{/* below header */}
<div className="relative flex flex-row justify-end items-end">
{/* chain logo */}
@@ -88,7 +88,7 @@ export async function ChainListRow({
key={p.name}
icon={p.icon}
label={p.name}
href={`/${chainSlug}/${p.id === "contracts" ? "" : p.id}`}
href={pidToHref(p.id)}
isEnabled={enabledServices.includes(p.id)}
/>
);
@@ -100,6 +100,29 @@ export async function ChainListRow({
);
}
function pidToHref(pid: (typeof products)[number]["id"]) {
switch (pid) {
case "account-abstraction": {
return "https://portal.thirdweb.com/connect/account-abstraction/overview";
}
case "contracts": {
return "https://portal.thirdweb.com/contracts";
}
case "connect-sdk": {
return "https://portal.thirdweb.com/connect";
}
case "engine": {
return "https://portal.thirdweb.com/engine";
}
case "pay": {
return "https://portal.thirdweb.com/connect/pay/overview";
}
case "rpc-edge": {
return "https://portal.thirdweb.com/infrastructure/rpc-edge/overview";
}
}
}
function ProductIcon(props: {
icon: React.FC<{ className?: string }>;
label: string;
@@ -117,7 +140,10 @@ function ProductIcon(props: {
)
}
>
<Link href={props.href}>
<Link
href={props.href}
target={props.href.startsWith("http") ? "_blank" : undefined}
>
<props.icon className={cn("size-8", !props.isEnabled && "grayscale")} />
</Link>
</ToolTipLabel>
@@ -91,7 +91,7 @@ export function StarButton(props: {
}}
disabled={
!loggedInUser.user?.address ||
mutation.isLoading ||
mutation.isPending ||
favChainsQuery.isLoading
}
>
@@ -1,6 +1,6 @@
import "server-only";
import { redirect } from "next/navigation";
import { notFound } from "next/navigation";
import type { ChainCTAProps } from "./[chain_id]/(chainPage)/components/server/cta-card";
import zeroGCTA from "./temp-assets/0gCTA.png";
import zeroGBanner from "./temp-assets/0gLabsBanner.png";
@@ -89,7 +89,7 @@ export async function getChain(
]);
if (!chain.data) {
redirect("/404");
notFound();
}
return {
...chain.data,
@@ -0,0 +1,33 @@
import { setOverrides } from "lib/vercel-utils";
import { fetchDeployMetadata } from "thirdweb/contract";
import { thirdwebClient } from "../../../../../@/constants/client";
import { DeployContractInfo } from "../../../published-contract/components/contract-info";
import { DeployFormForUri } from "../../../published-contract/components/uri-based-deploy";
setOverrides();
type DirectDeployPageProps = {
params: {
compiler_uri: string;
};
};
export default async function DirectDeployPage(props: DirectDeployPageProps) {
const parsedUri = decodeURIComponent(props.params.compiler_uri);
const metadata = await fetchDeployMetadata({
client: thirdwebClient,
// force `ipfs://` prefix
uri: parsedUri.startsWith("ipfs://") ? parsedUri : `ipfs://${parsedUri}`,
});
return (
<div className="container py-8 flex flex-col gap-4">
<DeployContractInfo
name={metadata.name}
displayName={metadata.displayName}
description={metadata.description}
logo={metadata.logo}
/>
<DeployFormForUri contractMetadata={metadata} modules={null} />
</div>
);
}
@@ -0,0 +1,47 @@
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
import { thirdwebClient } from "@/constants/client";
import { ContractPublishForm } from "components/contract-components/contract-publish-form";
import { setOverrides } from "lib/vercel-utils";
import { revalidatePath } from "next/cache";
import { fetchDeployMetadata } from "thirdweb/contract";
setOverrides();
type DirectDeployPageProps = {
params: {
publish_uri: string;
};
};
export default async function PublishContractPage(
props: DirectDeployPageProps,
) {
const decodedPublishUri = decodeURIComponent(props.params.publish_uri);
const publishUri = decodedPublishUri.startsWith("ipfs://")
? decodedPublishUri
: `ipfs://${decodedPublishUri}`;
const publishMetadata = await fetchDeployMetadata({
uri: publishUri,
client: thirdwebClient,
});
return (
<div className="container py-8 flex flex-col gap-8">
<ChakraProviderSetup>
<ContractPublishForm
publishMetadata={publishMetadata}
onPublishSuccess={async () => {
"use server";
// we are pretty brutal here and simply invalidate ALL published contracts (for everyone!) and versions no matter what
// TODO: we can be more granular here and only invalidate the specific contract and version etc
revalidatePath(
"/(dashboard)/published-contract/[publisher]/[contract_id]",
"layout",
);
}}
/>
</ChakraProviderSetup>
</div>
);
}
@@ -31,7 +31,7 @@ export function AddPartnerForm({ ecosystem }: { ecosystem: Ecosystem }) {
resolver: zodResolver(partnerFormSchema),
});
const { addPartner, isLoading } = useAddPartner({
const { mutateAsync: addPartner, isPending } = useAddPartner({
onSuccess: () => {
form.reset();
},
@@ -182,7 +182,7 @@ export function AddPartnerForm({ ecosystem }: { ecosystem: Ecosystem }) {
/>
</div>
<Button
disabled={isLoading}
disabled={isPending}
type="submit"
variant="outline"
className="w-full lg:w-auto"
@@ -20,7 +20,11 @@ export function IntegrationPermissionsToggle({
}
| undefined
>();
const { updateEcosystem, variables, isLoading } = useUpdateEcosystem({
const {
mutateAsync: updateEcosystem,
variables,
isPending,
} = useUpdateEcosystem({
onError: (error) => {
const message =
error instanceof Error ? error.message : "Failed to create ecosystem";
@@ -31,14 +35,14 @@ export function IntegrationPermissionsToggle({
return (
<RadioGroup
defaultValue={ecosystem.permission}
value={isLoading ? variables?.permission : ecosystem.permission}
value={isPending ? variables?.permission : ecosystem.permission}
className="flex flex-col gap-2 py-2 md:flex-row md:gap-4"
>
<RadioGroupItemButton
value="PARTNER_WHITELIST"
id="PARTNER_WHITELIST"
className={cn(
isLoading &&
isPending &&
variables?.permission === "PARTNER_WHITELIST" &&
"animate-pulse",
)}
@@ -59,7 +63,7 @@ export function IntegrationPermissionsToggle({
value="ANYONE"
id="ANYONE"
className={cn(
isLoading && variables?.permission === "ANYONE" && "animate-pulse",
isPending && variables?.permission === "ANYONE" && "animate-pulse",
)}
onClick={() => {
if (ecosystem.permission === "ANYONE") return;
@@ -42,7 +42,7 @@ export function UpdatePartnerForm({
},
});
const { updatePartner, isLoading } = useUpdatePartner({
const { mutateAsync: updatePartner, isPending } = useUpdatePartner({
onSuccess: () => {
form.reset();
onSuccess();
@@ -195,12 +195,12 @@ export function UpdatePartnerForm({
/>
</div>
<Button
disabled={isLoading}
disabled={isPending}
type="submit"
variant="outline"
className="w-full"
>
{isLoading && <Loader2 className="size-4 mr-1 animate-spin" />}
{isPending && <Loader2 className="size-4 mr-1 animate-spin" />}
Update
</Button>
</form>
@@ -69,13 +69,14 @@ function EcosystemRow(props: {
partner: Partner;
ecosystem: Ecosystem;
}) {
const { deletePartner, isLoading: isDeleting } = useDeletePartner({
onError: (error) => {
const message =
error instanceof Error ? error.message : "Failed to delete partner";
toast.error(message);
},
});
const { mutateAsync: deletePartner, isPending: isDeleting } =
useDeletePartner({
onError: (error) => {
const message =
error instanceof Error ? error.message : "Failed to delete partner";
toast.error(message);
},
});
return (
<TableRow
@@ -2,7 +2,7 @@ import z from "zod";
import type { PartnerPermission } from "../../types";
import { isValidPermission } from "./utils";
export const isDomainRegex =
const isDomainRegex =
/^(?:\*|(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*(?:\*(?:\.[a-z0-9][a-z0-9-]{0,61}[a-z0-9](?:\.[a-z0-9][a-z0-9-]{0,61}[a-z0-9])*)?)|(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]|localhost(?::\d{1,5})?)$/;
export const partnerFormSchema = z.object({
@@ -6,7 +6,7 @@ import {
} from "@tanstack/react-query";
import type { Ecosystem, Partner } from "../../../types";
export type AddPartnerParams = {
type AddPartnerParams = {
ecosystem: Ecosystem;
name: string;
allowlistedDomains: string[];
@@ -24,7 +24,7 @@ export function useAddPartner(
const { isLoggedIn, user } = useLoggedInUser();
const queryClient = useQueryClient();
const { mutateAsync: addPartner, isLoading } = useMutation({
return useMutation({
// Returns the created partner object
mutationFn: async (params: AddPartnerParams): Promise<Partner> => {
if (!isLoggedIn || !user?.jwt) {
@@ -74,11 +74,9 @@ export function useAddPartner(
return data;
},
onSuccess: async (partner, variables, context) => {
await queryClient.invalidateQueries([
"ecosystem",
variables.ecosystem.id,
"partners",
]);
await queryClient.invalidateQueries({
queryKey: ["ecosystem", variables.ecosystem.id, "partners"],
});
if (onSuccess) {
return onSuccess(partner, variables, context);
}
@@ -86,6 +84,4 @@ export function useAddPartner(
},
...queryOptions,
});
return { addPartner, isLoading };
}
@@ -6,7 +6,7 @@ import {
} from "@tanstack/react-query";
import type { Ecosystem } from "../../../types";
export type DeletePartnerParams = {
type DeletePartnerParams = {
ecosystem: Ecosystem;
partnerId: string;
};
@@ -21,7 +21,7 @@ export function useDeletePartner(
const { isLoggedIn, user } = useLoggedInUser();
const queryClient = useQueryClient();
const { mutateAsync: deletePartner, isLoading } = useMutation({
return useMutation({
// Returns true on success
mutationFn: async (params: DeletePartnerParams): Promise<boolean> => {
if (!isLoggedIn || !user?.jwt) {
@@ -58,11 +58,9 @@ export function useDeletePartner(
return true;
},
onSuccess: async (partner, variables, context) => {
await queryClient.invalidateQueries([
"ecosystem",
variables.ecosystem.id,
"partners",
]);
await queryClient.invalidateQueries({
queryKey: ["ecosystem", variables.ecosystem.id, "partners"],
});
if (onSuccess) {
return onSuccess(partner, variables, context);
}
@@ -70,6 +68,4 @@ export function useDeletePartner(
},
...queryOptions,
});
return { deletePartner, isLoading };
}
@@ -6,7 +6,7 @@ import {
} from "@tanstack/react-query";
import type { Ecosystem } from "../../../types";
export type UpdateEcosystemParams = {
type UpdateEcosystemParams = {
ecosystem: Ecosystem;
permission: "PARTNER_WHITELIST" | "ANYONE";
};
@@ -21,11 +21,7 @@ export function useUpdateEcosystem(
const { isLoggedIn, user } = useLoggedInUser();
const queryClient = useQueryClient();
const {
mutateAsync: updateEcosystem,
isLoading,
variables,
} = useMutation({
return useMutation({
// Returns true if the update was successful
mutationFn: async (params: UpdateEcosystemParams): Promise<boolean> => {
if (!isLoggedIn || !user?.jwt) {
@@ -65,7 +61,9 @@ export function useUpdateEcosystem(
return true;
},
onSuccess: async (partner, variables, context) => {
await queryClient.invalidateQueries(["ecosystems"]);
await queryClient.invalidateQueries({
queryKey: ["ecosystems"],
});
if (onSuccess) {
return onSuccess(partner, variables, context);
}
@@ -73,6 +71,4 @@ export function useUpdateEcosystem(
},
...queryOptions,
});
return { updateEcosystem, isLoading, variables };
}
@@ -6,7 +6,7 @@ import {
} from "@tanstack/react-query";
import type { Ecosystem, Partner } from "../../../types";
export type UpdatePartnerParams = {
type UpdatePartnerParams = {
partnerId: string;
ecosystem: Ecosystem;
name: string;
@@ -25,7 +25,7 @@ export function useUpdatePartner(
const { isLoggedIn, user } = useLoggedInUser();
const queryClient = useQueryClient();
const { mutateAsync: updatePartner, isLoading } = useMutation({
return useMutation({
// Returns the created partner object
mutationFn: async (params: UpdatePartnerParams): Promise<Partner> => {
if (!isLoggedIn || !user?.jwt) {
@@ -73,11 +73,9 @@ export function useUpdatePartner(
return data;
},
onSuccess: async (partner, variables, context) => {
await queryClient.invalidateQueries([
"ecosystem",
variables.ecosystem.id,
"partners",
]);
await queryClient.invalidateQueries({
queryKey: ["ecosystem", variables.ecosystem.id, "partners"],
});
if (onSuccess) {
return onSuccess(partner, variables, context);
}
@@ -85,6 +83,4 @@ export function useUpdatePartner(
},
...queryOptions,
});
return { updatePartner, isLoading };
}
@@ -59,7 +59,7 @@ export function CreateEcosystemForm(props: {
},
});
const { createEcosystem, isLoading } = useCreateEcosystem({
const { mutateAsync: createEcosystem, isPending } = useCreateEcosystem({
onError: (error) => {
const message =
error instanceof Error ? error.message : "Failed to create ecosystem";
@@ -181,9 +181,9 @@ export function CreateEcosystemForm(props: {
type="submit"
variant="primary"
className="w-full"
disabled={isLoading}
disabled={isPending}
>
{isLoading && <Loader2 className="w-4 h-4 mr-1 animate-spin" />}
{isPending && <Loader2 className="w-4 h-4 mr-1 animate-spin" />}
Create
</Button>
)}
@@ -8,7 +8,7 @@ import {
import { THIRDWEB_API_HOST } from "constants/urls";
import { upload } from "thirdweb/storage";
export type CreateEcosystemParams = {
type CreateEcosystemParams = {
name: string;
logo: File;
permission: "PARTNER_WHITELIST" | "ANYONE";
@@ -24,7 +24,7 @@ export function useCreateEcosystem(
const { isLoggedIn } = useLoggedInUser();
const queryClient = useQueryClient();
const { mutateAsync: createEcosystem, isLoading } = useMutation({
return useMutation({
// Returns the created ecosystem slug
mutationFn: async (params: CreateEcosystemParams): Promise<string> => {
if (!isLoggedIn) {
@@ -75,10 +75,10 @@ export function useCreateEcosystem(
if (onSuccess) {
await onSuccess(id, variables, context);
}
return queryClient.invalidateQueries(["ecosystems"]);
return queryClient.invalidateQueries({
queryKey: ["ecosystems"],
});
},
...queryOptions,
});
return { createEcosystem, isLoading };
}
@@ -55,7 +55,7 @@ type Webhook = {
secret: string;
};
export type WebhooksPageProps = {
type WebhooksPageProps = {
clientId: string;
};
@@ -177,7 +177,9 @@ function CreateWebhookButton(props: PropsWithChildren<WebhooksPageProps>) {
return json.result as string;
},
onSuccess: () => {
return queryClient.invalidateQueries(["webhooks", props.clientId]);
return queryClient.invalidateQueries({
queryKey: ["webhooks", props.clientId],
});
},
});
return (
@@ -264,7 +266,7 @@ function CreateWebhookButton(props: PropsWithChildren<WebhooksPageProps>) {
</FormItem>
<DialogFooter>
<Button type="submit" disabled={createMutation.isLoading}>
<Button type="submit" disabled={createMutation.isPending}>
Create
</Button>
</DialogFooter>
@@ -293,7 +295,9 @@ function DeleteWebhookButton(
return json.result as string;
},
onSuccess: () => {
return queryClient.invalidateQueries(["webhooks", props.clientId]);
return queryClient.invalidateQueries({
queryKey: ["webhooks", props.clientId],
});
},
});
return (
@@ -325,7 +329,7 @@ function DeleteWebhookButton(
},
);
}}
disabled={deleteMutation.isLoading}
disabled={deleteMutation.isPending}
>
Yes, Delete Webhook
</Button>
@@ -1,14 +1,15 @@
import { AppFooter } from "@/components/blocks/app-footer";
import { DashboardHeader } from "app/components/Header/DashboardHeader";
import { ErrorProvider } from "../../contexts/error-handler";
export default function DashboardLayout(props: { children: React.ReactNode }) {
return (
<>
<ErrorProvider>
<div className="flex flex-col h-full bg-background">
<DashboardHeader />
<main className="grow">{props.children}</main>
<AppFooter />
</div>
</>
</ErrorProvider>
);
}
@@ -0,0 +1,24 @@
import { setOverrides } from "lib/vercel-utils";
import { DeployFormForPublishInfo } from "../../../../components/publish-based-deploy";
import { moduleFromBase64 } from "../../../../utils/module-base-64";
setOverrides();
export default function PublishedContractVersionDeployPage({
params,
searchParams,
}: {
params: {
publisher: string;
contract_id: string;
version: string;
};
searchParams: {
module?: string[];
};
}) {
const modules = searchParams.module
?.map((m) => moduleFromBase64(m))
.filter((m) => m !== null);
return <DeployFormForPublishInfo {...params} modules={modules} />;
}
@@ -0,0 +1,56 @@
import { fetchPublisherProfile } from "components/contract-components/fetch-contracts-with-versions";
import { format } from "date-fns/format";
import { correctAndUniqueLicenses } from "lib/licenses";
import { setOverrides } from "lib/vercel-utils";
import { getPublishedContractsWithPublisherMapping } from "../utils/getPublishedContractsWithPublisherMapping";
import { publishedContractOGImageTemplate } from "../utils/publishedContractOGImageTemplate";
export const runtime = "edge";
export const size = {
width: 1200,
height: 630,
};
export default async function Image(props: {
params: {
publisher: string;
contract_id: string;
version: string;
};
}) {
setOverrides();
const { publisher, contract_id } = props.params;
const [publishedContracts, publisherProfile] = await Promise.all([
getPublishedContractsWithPublisherMapping({
publisher: publisher,
contract_id: contract_id,
}),
fetchPublisherProfile(publisher),
]);
const publishedContract =
publishedContracts.find((p) => p.version === props.params.version) ||
publishedContracts[0];
const publishedContractName =
publishedContract?.displayName || publishedContract?.name;
return publishedContractOGImageTemplate({
title: publishedContractName,
description: publishedContract.description,
version: publishedContract.version || "latest",
publisher: publisherProfile?.name || publisher,
license: correctAndUniqueLicenses(publishedContract.licenses),
publishDate: format(
new Date(
Number.parseInt(publishedContract.publishTimestamp.toString() || "0") *
1000,
),
"MMM dd, yyyy",
),
logo: publishedContract.logo,
publisherAvatar: publisherProfile?.avatar,
});
}
@@ -0,0 +1,76 @@
import { Separator } from "@/components/ui/separator";
import { thirdwebClient } from "@/constants/client";
import { SimpleGrid } from "@chakra-ui/react";
import { isAddress } from "thirdweb";
import { resolveAddress } from "thirdweb/extensions/ens";
import { ChakraProviderSetup } from "../../../../../../@/components/ChakraProviderSetup";
import { fetchPublishedContractVersions } from "../../../../../../components/contract-components/fetch-contracts-with-versions";
import { PublishedContract } from "../../../../../../components/contract-components/published-contract";
import { setOverrides } from "../../../../../../lib/vercel-utils";
import { PublishedActions } from "../../../components/contract-actions-published.client";
import { DeployContractHeader } from "../../../components/contract-header";
setOverrides();
function mapThirdwebPublisher(publisher: string) {
if (publisher === "thirdweb.eth") {
return "deployer.thirdweb.eth";
}
return publisher;
}
type PublishedContractDeployPageProps = {
params: {
publisher: string;
contract_id: string;
version: string;
};
};
export default async function PublishedContractPage(
props: PublishedContractDeployPageProps,
) {
// resolve ENS if required
const publisherAddress = isAddress(props.params.publisher)
? props.params.publisher
: await resolveAddress({
client: thirdwebClient,
name: mapThirdwebPublisher(props.params.publisher),
});
// get all the published versions of the contract
const publishedContractVersions = await fetchPublishedContractVersions(
publisherAddress,
props.params.contract_id,
);
// determine the "active" version of the contract based on the version that is passed
const publishedContract =
publishedContractVersions.find((v) => v.version === props.params.version) ||
publishedContractVersions[0];
return (
<>
<DeployContractHeader
{...props.params}
allVersions={publishedContractVersions}
activeVersion={publishedContract}
>
<PublishedActions
{...props.params}
dispayName={publishedContract.displayName || publishedContract.name}
/>
</DeployContractHeader>
<Separator />
{/* TODO: remove the chakra things :) */}
<ChakraProviderSetup>
<SimpleGrid columns={12} gap={{ base: 6, md: 10 }} w="full">
<PublishedContract
publishedContract={publishedContract}
walletOrEns={props.params.publisher}
/>
</SimpleGrid>
</ChakraProviderSetup>
</>
);
}
@@ -0,0 +1,56 @@
"use client";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Fragment } from "react";
import { isAddress } from "thirdweb";
import { shortenAddress } from "thirdweb/utils";
export function PublishedContractBreadcrumbs() {
const pathname = usePathname() || "";
const segments = pathname.split("/").filter((segment) => segment !== "");
return (
<Breadcrumb className="py-4 px-6 border-b border-border">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink aria-label="Explore" asChild>
<Link href="/explore">Explore</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
{segments.map((segment, index) => {
const href = `/${segments.slice(0, index + 1).join("/")}`;
const isLast = index === segments.length - 1;
const renderedSegment = isAddress(segment)
? shortenAddress(segment)
: segment;
return (
<Fragment key={href}>
<BreadcrumbItem>
{isLast ? (
<BreadcrumbPage>{renderedSegment}</BreadcrumbPage>
) : (
<BreadcrumbLink asChild aria-label={segment}>
<Link href={href}>{renderedSegment}</Link>
</BreadcrumbLink>
)}
</BreadcrumbItem>
{!isLast && <BreadcrumbSeparator />}
</Fragment>
);
})}
</BreadcrumbList>
</Breadcrumb>
);
}
@@ -0,0 +1,36 @@
"use client";
import { Badge } from "@/components/ui/badge";
import { useSearchParams } from "next/navigation";
import { useMemo } from "react";
import { moduleFromBase64 } from "../../../utils/module-base-64";
function useModules() {
const modules = useSearchParams()?.getAll("module") || [];
return useMemo(
() => modules.map(moduleFromBase64).filter((m) => m !== null),
[modules],
);
}
export function ModuleList() {
const modules = useModules();
// if nothing, render nothing
if (!modules.length) {
return null;
}
// else render stuff
return (
<div className="flex flex-row flex-wrap gap-2">
{modules.map((m) => (
<Badge
key={`${m.publisher}/${m.moduleId}/${m.version}`}
variant="default"
>
{m.displayName}
</Badge>
))}
</div>
);
}
@@ -0,0 +1,25 @@
import { setOverrides } from "lib/vercel-utils";
import { DeployFormForPublishInfo } from "../../../components/publish-based-deploy";
import { moduleFromBase64 } from "../../../utils/module-base-64";
setOverrides();
type Props = {
params: {
publisher: string;
contract_id: string;
};
searchParams: {
module?: string[];
};
};
export default function PublishedContractDeployPage({
params,
searchParams,
}: Props) {
const modules = searchParams.module
?.map((m) => moduleFromBase64(m))
.filter((m) => m !== null);
return <DeployFormForPublishInfo {...params} modules={modules} />;
}
@@ -0,0 +1,35 @@
import { PublishedContractBreadcrumbs } from "./components/breadcrumbs.client";
import { getPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
type Params = { publisher: string; contract_id: string };
export default function PublishedContractLayout(props: {
children: React.ReactNode;
params: Params;
}) {
return (
<div className="flex flex-col">
<PublishedContractBreadcrumbs />
<div className="container flex flex-col gap-8 py-8">{props.children}</div>
</div>
);
}
export async function generateMetadata({ params }: { params: Params }) {
const { publisher, contract_id } = params;
const publishedContracts = await getPublishedContractsWithPublisherMapping({
publisher: publisher,
contract_id: contract_id,
});
const publishedContract = publishedContracts[0];
const publishedContractName =
publishedContract?.displayName || publishedContract?.name;
return {
title: `${publishedContractName} | Published Smart Contract`,
description: `${publishedContract.description}${publishedContract.description ? ". " : ""}Deploy ${publishedContractName} in one click with thirdweb.`,
};
}
@@ -0,0 +1,53 @@
import { fetchPublisherProfile } from "components/contract-components/fetch-contracts-with-versions";
import { format } from "date-fns/format";
import { correctAndUniqueLicenses } from "lib/licenses";
import { setOverrides } from "lib/vercel-utils";
import { getPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
import { publishedContractOGImageTemplate } from "./utils/publishedContractOGImageTemplate";
export const runtime = "edge";
export const size = {
width: 1200,
height: 630,
};
export default async function Image(props: {
params: {
publisher: string;
contract_id: string;
};
}) {
setOverrides();
const { publisher, contract_id } = props.params;
const [publishedContracts, publisherProfile] = await Promise.all([
getPublishedContractsWithPublisherMapping({
publisher: publisher,
contract_id: contract_id,
}),
fetchPublisherProfile(publisher),
]);
const publishedContract = publishedContracts[0];
const publishedContractName =
publishedContract?.displayName || publishedContract?.name;
return publishedContractOGImageTemplate({
title: publishedContractName,
description: publishedContract.description,
version: publishedContract.version || "latest",
publisher: publisherProfile?.name || publisher,
license: correctAndUniqueLicenses(publishedContract.licenses),
publishDate: format(
new Date(
Number.parseInt(publishedContract.publishTimestamp.toString() || "0") *
1000,
),
"MMM dd, yyyy",
),
logo: publishedContract.logo,
publisherAvatar: publisherProfile?.avatar,
});
}
@@ -0,0 +1,53 @@
import { Separator } from "@/components/ui/separator";
import { ChakraProviderSetup } from "../../../../../@/components/ChakraProviderSetup";
import { PublishedContract } from "../../../../../components/contract-components/published-contract";
import { setOverrides } from "../../../../../lib/vercel-utils";
import { PublishedActions } from "../../components/contract-actions-published.client";
import { DeployContractHeader } from "../../components/contract-header";
import { getPublishedContractsWithPublisherMapping } from "./utils/getPublishedContractsWithPublisherMapping";
setOverrides();
type PublishedContractDeployPageProps = {
params: {
publisher: string;
contract_id: string;
};
};
export default async function PublishedContractPage(
props: PublishedContractDeployPageProps,
) {
const publishedContractVersions =
await getPublishedContractsWithPublisherMapping({
publisher: props.params.publisher,
contract_id: props.params.contract_id,
});
const publishedContract = publishedContractVersions[0];
return (
<>
<DeployContractHeader
{...props.params}
allVersions={publishedContractVersions}
activeVersion={publishedContract}
>
<PublishedActions
{...props.params}
dispayName={publishedContract.displayName || publishedContract.name}
/>
</DeployContractHeader>
<Separator />
{/* TODO: remove the chakra things :) */}
<ChakraProviderSetup>
<div className="grid grid-cols-12 gap-6 md:gap-10 w-full">
<PublishedContract
publishedContract={publishedContract}
walletOrEns={props.params.publisher}
/>
</div>
</ChakraProviderSetup>
</>
);
}
@@ -0,0 +1,34 @@
import { thirdwebClient } from "@/constants/client";
import { isAddress } from "thirdweb";
import { resolveAddress } from "thirdweb/extensions/ens";
import { fetchPublishedContractVersions } from "../../../../../../components/contract-components/fetch-contracts-with-versions";
function mapThirdwebPublisher(publisher: string) {
if (publisher === "thirdweb.eth") {
return "deployer.thirdweb.eth";
}
return publisher;
}
export async function getPublishedContractsWithPublisherMapping(options: {
publisher: string;
contract_id: string;
}) {
const { publisher, contract_id } = options;
// resolve ENS
const publisherAddress = isAddress(publisher)
? publisher
: await resolveAddress({
client: thirdwebClient,
name: mapThirdwebPublisher(publisher),
});
// get all the published versions of the contract
const publishedContractVersions = await fetchPublishedContractVersions(
publisherAddress,
contract_id,
);
return publishedContractVersions;
}
@@ -1,37 +1,7 @@
/* eslint-disable @next/next/no-img-element */
import { isProd } from "@/constants/env";
import { ImageResponse } from "@vercel/og";
import type { NextRequest } from "next/server";
import { PublishedContractOG } from "og-lib/url-utils";
// Make sure the font exists in the specified path:
export const config = {
runtime: "edge",
};
const image = fetch(
new URL("og-lib/assets/publish/background.png", import.meta.url),
).then((res) => res.arrayBuffer());
const inter400_ = fetch(
new URL("og-lib/fonts/inter/400.ttf", import.meta.url),
).then((res) => res.arrayBuffer());
const inter500_ = fetch(
new URL("og-lib/fonts/inter/500.ttf", import.meta.url),
).then((res) => res.arrayBuffer());
const inter700_ = fetch(
new URL("og-lib/fonts/inter/700.ttf", import.meta.url),
).then((res) => res.arrayBuffer());
const ibmPlexMono400_ = fetch(
new URL("og-lib/fonts/ibm-plex-mono/400.ttf", import.meta.url),
).then((res) => res.arrayBuffer());
const ibmPlexMono500_ = fetch(
new URL("og-lib/fonts/ibm-plex-mono/500.ttf", import.meta.url),
).then((res) => res.arrayBuffer());
const ibmPlexMono700_ = fetch(
new URL("og-lib/fonts/ibm-plex-mono/700.ttf", import.meta.url),
).then((res) => res.arrayBuffer());
import { thirdwebClient } from "@/constants/client";
import { ImageResponse } from "next/og";
import { download } from "thirdweb/storage";
const OgBrandIcon: React.FC = () => (
// biome-ignore lint/a11y/noSvgWithoutTitle: not needed
@@ -160,27 +130,49 @@ function descriptionShortener(description: string) {
return `${words.join(" ")} ...`;
}
const IPFS_GATEWAY = process.env.API_ROUTES_CLIENT_ID
? `https://${process.env.API_ROUTES_CLIENT_ID}.${isProd ? "ipfscdn.io/ipfs/" : "thirdwebstorage-dev.com/ipfs/"}`
: "https://ipfs.io/ipfs/";
export async function publishedContractOGImageTemplate(params: {
title: string;
description?: string;
version: string;
publisher: string;
license: string[];
publishDate: string;
logo?: string;
publisherAvatar?: string;
extension?: string[];
}) {
const image = fetch(
new URL("og-lib/assets/publish/background.png", import.meta.url),
).then((res) => res.arrayBuffer());
function replaceAnyIpfsUrlWithGateway(url: string) {
if (url.startsWith("ipfs://")) {
return `${IPFS_GATEWAY}${url.slice(7)}`;
}
if (url.includes("/ipfs/")) {
const [, after] = url.split("/ipfs/");
return `${IPFS_GATEWAY}${after}`;
}
return url;
}
const inter400_ = fetch(
new URL("../../../../../../og-lib/fonts/inter/400.ttf", import.meta.url),
).then((res) => res.arrayBuffer());
const inter500_ = fetch(
new URL("../../../../../../og-lib/fonts/inter/500.ttf", import.meta.url),
).then((res) => res.arrayBuffer());
const inter700_ = fetch(
new URL("../../../../../../og-lib/fonts/inter/700.ttf", import.meta.url),
).then((res) => res.arrayBuffer());
export default async function handler(req: NextRequest) {
if (req.method !== "GET") {
return new Response("Method not allowed", { status: 405 });
}
const publishedContractData = PublishedContractOG.fromUrl(new URL(req.url));
const ibmPlexMono400_ = fetch(
new URL(
"../../../../../../og-lib/fonts/ibm-plex-mono/400.ttf",
import.meta.url,
),
).then((res) => res.arrayBuffer());
const ibmPlexMono500_ = fetch(
new URL(
"../../../../../../og-lib/fonts/ibm-plex-mono/500.ttf",
import.meta.url,
),
).then((res) => res.arrayBuffer());
const ibmPlexMono700_ = fetch(
new URL(
"../../../../../../og-lib/fonts/ibm-plex-mono/700.ttf",
import.meta.url,
),
).then((res) => res.arrayBuffer());
const [
inter400,
@@ -190,6 +182,8 @@ export default async function handler(req: NextRequest) {
ibmPlexMono500,
ibmPlexMono700,
imageData,
logo,
avatar,
] = await Promise.all([
inter400_,
inter500_,
@@ -198,6 +192,18 @@ export default async function handler(req: NextRequest) {
ibmPlexMono500_,
ibmPlexMono700_,
image,
params.logo
? download({
uri: params.logo,
client: thirdwebClient,
}).then((res) => res.arrayBuffer())
: undefined,
params.publisherAvatar
? download({
uri: params.publisherAvatar,
client: thirdwebClient,
}).then((res) => res.arrayBuffer())
: undefined,
]);
return new ImageResponse(
@@ -223,37 +229,30 @@ export default async function handler(req: NextRequest) {
<div tw="flex justify-between items-start w-full">
<div tw="flex flex-col flex-shrink">
<div tw="flex items-center">
{publishedContractData.logo && (
<img
src={replaceAnyIpfsUrlWithGateway(publishedContractData.logo)}
tw="w-16 h-16 rounded-xl mr-4"
alt=""
/>
)}
<h1 tw="text-6xl font-bold text-white">
{publishedContractData.name}
</h1>
{/* @ts-expect-error - this works fine */}
{logo && <img src={logo} tw="w-16 h-16 rounded-xl mr-4" alt="" />}
<h1 tw="text-6xl font-bold text-white">{params.title}</h1>
</div>
{publishedContractData?.description && (
{params?.description && (
<p tw="text-3xl font-medium text-gray-400 max-w-4xl">
{descriptionShortener(publishedContractData.description)}
{descriptionShortener(params.description)}
</p>
)}
</div>
<div tw="flex flex-col shrink-0 items-center">
<img
alt=""
tw="w-32 h-32 rounded-full"
src={
publishedContractData.publisherAvatar
? replaceAnyIpfsUrlWithGateway(
publishedContractData.publisherAvatar,
)
: `https://source.boringavatars.com/marble/120/${publishedContractData.publisher}?colors=264653,2a9d8f,e9c46a,f4a261,e76f51&square=true`
}
/>
{avatar && (
<img
alt=""
width={100}
height={100}
tw="w-32 h-32 rounded-full"
// @ts-expect-error - this works fine
src={avatar}
/>
)}
<h2 tw="text-2xl text-white font-medium max-w-full">
{publishedContractData.publisher}
{params.publisher}
</h2>
</div>
</div>
@@ -264,15 +263,15 @@ export default async function handler(req: NextRequest) {
fontFamily: "IBM Plex Mono",
}}
>
{publishedContractData.extension?.length ? (
{params.extension?.length ? (
<li
tw="flex flex-row items-center overflow-hidden w-full"
style={{ whiteSpace: "nowrap" }}
>
<PackageIcon />
<span tw="ml-2">
{Array.isArray(publishedContractData.extension)
? categorizeExtensions(publishedContractData.extension).map(
{Array.isArray(params.extension)
? categorizeExtensions(params.extension).map(
([ext, count]) => (
<span key={ext} tw="flex flex-row items-center mr-3">
{ext}
@@ -282,27 +281,27 @@ export default async function handler(req: NextRequest) {
</span>
),
)
: publishedContractData.extension}
: params.extension}
</span>
</li>
) : null}
{publishedContractData.license?.length ? (
{params.license?.length ? (
<li tw="flex flex-row items-center">
<FileTextIcon />
<span tw="ml-2">
{Array.isArray(publishedContractData.license)
? publishedContractData.license.join(", ")
: publishedContractData.license}
{Array.isArray(params.license)
? params.license.join(", ")
: params.license}
</span>
</li>
) : null}
<li tw="flex flex-row items-center">
<VersionIcon />
<span tw="ml-2">{publishedContractData.version}</span>
<span tw="ml-2">{params.version}</span>
</li>
<li tw="flex flex-row items-center">
<CalendarIcon />
<span tw="ml-2">{publishedContractData.publishDate}</span>
<span tw="ml-2">{params.publishDate}</span>
</li>
</ul>
<div tw="flex flex-shrink-0">
@@ -0,0 +1,41 @@
"use client";
import { Button } from "@/components/ui/button";
import { shareLink } from "@/lib/shareLink";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
export function DeployActions(props: {
publisher: string;
contract_id: string;
version?: string;
dispayName: string;
}) {
const searchparams = useSearchParams();
const stringifiedSearchParams = searchparams?.toString();
return (
<div className="flex gap-3">
<Button asChild variant="outline">
<Link
href={`/${props.publisher}/${props.contract_id}${props.version ? `/${props.version}` : ""}${stringifiedSearchParams ? `?${stringifiedSearchParams}` : ""}`}
target="_blank"
>
Contract
</Link>
</Button>
<Button
variant="outline"
onClick={() => {
shareLink({
title: `Deploy ${props.dispayName}`,
});
}}
>
Share
</Button>
</div>
);
}
@@ -0,0 +1,40 @@
"use client";
import { Button } from "@/components/ui/button";
import { shareLink } from "@/lib/shareLink";
import { ChevronsRightIcon } from "lucide-react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
export function PublishedActions(props: {
publisher: string;
contract_id: string;
version?: string;
dispayName: string;
}) {
const searchparams = useSearchParams();
const stringifiedSearchParams = searchparams?.toString();
return (
<div className="flex gap-3">
<Button
variant="outline"
onClick={() => {
shareLink({
title: `Deploy ${props.dispayName}`,
});
}}
>
Share
</Button>
<Button asChild variant="primary" className="gap-2">
<Link
href={`/${props.publisher}/${props.contract_id}${props.version ? `/${props.version}` : ""}/deploy${stringifiedSearchParams ? `?${stringifiedSearchParams}` : ""}`}
>
Deploy Now
<ChevronsRightIcon className="size-4" />
</Link>
</Button>
</div>
);
}
@@ -0,0 +1,45 @@
"use client";
import type { PropsWithChildren } from "react";
import type { PublishedContractWithVersion } from "../../../../components/contract-components/fetch-contracts-with-versions";
import { ModuleList } from "../[publisher]/[contract_id]/components/module-list.client";
import { DeployContractInfo } from "./contract-info";
import { DeployContractVersionSelector } from "./version-selector";
type DeployContractHeaderProps = {
publisher: string;
contract_id: string;
version?: string;
allVersions: PublishedContractWithVersion[];
activeVersion: PublishedContractWithVersion;
};
export function DeployContractHeader(
props: PropsWithChildren<DeployContractHeaderProps>,
) {
return (
<div className="py-2 flex flex-col gap-4">
<div className="flex flex-col md:flex-row justify-between gap-6 md:items-center">
<DeployContractInfo
name={props.activeVersion.name}
displayName={props.activeVersion.displayName}
description={props.activeVersion.description}
logo={props.activeVersion.logo}
/>
<div className="flex flex-col-reverse md:flex-row gap-3">
{
<DeployContractVersionSelector
availableVersions={
props.allVersions
?.map((v) => v.version)
.filter((v) => v !== undefined) || []
}
version={props.activeVersion.version || "latest"}
/>
}
{props.children}
</div>
</div>
<ModuleList />
</div>
);
}
@@ -0,0 +1,38 @@
import { thirdwebClient } from "@/constants/client";
import { resolveScheme } from "thirdweb/storage";
export function DeployContractInfo(props: {
name: string;
displayName?: string;
description?: string;
logo?: string;
}) {
const contractNameDisplay = props.displayName || props.name;
return (
<div className="flex flex-col gap-2">
<div className="flex gap-4 items-center flex-1">
{props.logo && (
<div className="rounded-xl p-2 border border-border shrink-0 items-center justify-center hidden md:flex">
{/*eslint-disable-next-line @next/next/no-img-element*/}
<img
className="size-12"
alt={props.name}
src={resolveScheme({
client: thirdwebClient,
uri: props.logo,
})}
/>
</div>
)}
<div className="flex flex-col gap-1">
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">
{contractNameDisplay}
</h1>
<p className="text-muted-foreground text-sm">{props.description}</p>
</div>
</div>
</div>
);
}
@@ -0,0 +1,89 @@
import { Separator } from "@/components/ui/separator";
import { thirdwebClient } from "@/constants/client";
import { isAddress } from "thirdweb";
import { fetchDeployMetadata } from "thirdweb/contract";
import { resolveAddress } from "thirdweb/extensions/ens";
import {
fetchPublishedContractVersion,
fetchPublishedContractVersions,
} from "../../../../components/contract-components/fetch-contracts-with-versions";
import { DeployActions } from "./contract-actions-deploy.client";
import { DeployContractHeader } from "./contract-header";
import { DeployFormForUri } from "./uri-based-deploy";
type PublishBasedDeployProps = {
publisher: string;
contract_id: string;
version?: string;
modules?: Array<{ publisher: string; moduleId: string; version?: string }>;
};
function mapThirdwebPublisher(publisher: string) {
if (publisher === "thirdweb.eth") {
return "deployer.thirdweb.eth";
}
return publisher;
}
export async function DeployFormForPublishInfo(props: PublishBasedDeployProps) {
// resolve ENS if required
const publisherAddress = isAddress(props.publisher)
? props.publisher
: await resolveAddress({
client: thirdwebClient,
name: mapThirdwebPublisher(props.publisher),
});
// get all the published versions of the contract
const [publishedContractVersions, ...modules] = await Promise.all([
fetchPublishedContractVersions(publisherAddress, props.contract_id),
...(props.modules || []).map((m) =>
fetchPublishedContractVersion(m.publisher, m.moduleId, m.version),
),
]);
// determine the "active" version of the contract based on the version that is passed
const publishedContract =
publishedContractVersions.find((v) => v.version === props.version) ||
publishedContractVersions[0];
const moduleUris = modules
.filter((m) => m !== null)
.map((m) => m.publishMetadataUri);
const [contractMetadata, ...fetchedModules] = await Promise.all([
fetchDeployMetadata({
client: thirdwebClient,
// force `ipfs://` prefix
uri: publishedContract.publishMetadataUri.startsWith("ipfs://")
? publishedContract.publishMetadataUri
: `ipfs://${publishedContract.publishMetadataUri}`,
}).catch(() => null),
...(moduleUris || []).map((uri) =>
fetchDeployMetadata({
client: thirdwebClient,
// force `ipfs://` prefix
uri: uri.startsWith("ipfs://") ? uri : `ipfs://${uri}`,
}).catch(() => null),
),
]);
return (
<>
<DeployContractHeader
{...props}
allVersions={publishedContractVersions}
activeVersion={publishedContract}
>
<DeployActions
{...props}
dispayName={publishedContract.displayName || publishedContract.name}
/>
</DeployContractHeader>
<Separator />
<DeployFormForUri
contractMetadata={contractMetadata}
modules={fetchedModules.filter((m) => m !== null)}
/>
</>
);
}
@@ -0,0 +1,26 @@
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
import { CustomContractForm } from "components/contract-components/contract-deploy-form/custom-contract";
import type { FetchDeployMetadataResult } from "thirdweb/contract";
type DeployFormForUriProps = {
contractMetadata: FetchDeployMetadataResult | null;
modules: FetchDeployMetadataResult[] | null;
};
export async function DeployFormForUri(props: DeployFormForUriProps) {
const { contractMetadata, modules } = props;
if (!contractMetadata) {
return <div>Could not fetch metadata</div>;
}
// TODO: remove the `ChakraProviderSetup` wrapper once the form is updated to no longer use chrakra
return (
<ChakraProviderSetup>
<CustomContractForm
metadata={contractMetadata}
modules={modules?.filter((m) => m !== null)}
/>
</ChakraProviderSetup>
);
}
@@ -0,0 +1,76 @@
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { usePathname, useSearchParams } from "next/navigation";
import { useMemo } from "react";
type DeployContractVersionSelectorProps = {
availableVersions: string[];
version: string;
};
export function DeployContractVersionSelector({
availableVersions,
version,
}: DeployContractVersionSelectorProps) {
const router = useDashboardRouter();
const pathname = usePathname() || "";
const searchParams = useSearchParams();
const { isDeploy, rootPathName } = useMemo(() => {
return {
isDeploy: pathname.endsWith("/deploy"),
rootPathName: pathname
.replace(/\/deploy$/, "")
.replace(`/${version}`, ""),
};
}, [pathname, version]);
return (
<Select
value={version}
onValueChange={(val) => {
if (availableVersions.includes(val)) {
const stringifiedSearchParams = searchParams?.toString();
const isLatestVersion = val === availableVersions[0];
let pathName = isLatestVersion
? rootPathName
: `${rootPathName}/${val}`;
if (isDeploy) {
// append the /deploy to the path
pathName = `${pathName}/deploy`;
}
// if any search params, add them to the path
if (stringifiedSearchParams) {
pathName = `${pathName}?${stringifiedSearchParams}`;
}
router.push(pathName);
}
}}
>
<SelectTrigger className="min-w-[180px] bg-transparent hover:bg-accent">
<SelectValue />
</SelectTrigger>
<SelectContent>
{availableVersions.map((v, idx) => {
return (
<SelectItem value={v || "latest"} key={v}>
{v}
{idx === 0 && (
<span className="text-muted-foreground"> (latest) </span>
)}
</SelectItem>
);
})}
</SelectContent>
</Select>
);
}
@@ -0,0 +1,28 @@
import { thirdwebClient } from "@/constants/client";
import { redirect } from "next/navigation";
import { fetchDeployMetadata } from "thirdweb/contract";
export default async function Page(props: {
searchParams: {
uri?: string;
};
}) {
if (!props.searchParams?.uri) {
// redirect back out if we do not have a uri
return redirect("/dashboard");
}
const contractMetadata = await fetchDeployMetadata({
client: thirdwebClient,
// force `ipfs://` prefix
uri: props.searchParams.uri.startsWith("ipfs://")
? props.searchParams.uri
: `ipfs://${props.searchParams.uri}`,
}).catch(() => null);
return (
<div className="container">
<pre>{JSON.stringify(contractMetadata, null, 2)}</pre>
</div>
);
}
@@ -0,0 +1,36 @@
type Module = {
publisher: string;
moduleId: string;
version?: string;
};
function assertModule(val: unknown): asserts val is Module {
if (
typeof val !== "object" ||
val === null ||
typeof (val as Module).publisher !== "string" ||
typeof (val as Module).moduleId !== "string" ||
((val as Module).version !== undefined &&
typeof (val as Module).version !== "string")
) {
throw new Error("Invalid module");
}
}
export function moduleFromBase64(base64: string) {
try {
const mod = JSON.parse(atob(base64));
assertModule(mod);
return {
...mod,
version: mod.version || "latest",
displayName: mod.moduleId.split("ERC")[0],
};
} catch {
return null;
}
}
export function moduleToBase64(mod: Module) {
return btoa(JSON.stringify(mod));
}
@@ -24,14 +24,6 @@ function isValidPlan(plan: string): plan is keyof typeof planToCustomerId {
return plan in planToCustomerId;
}
// const SUPPORT_EMAIL = "[email protected]";
export type CreateTicketInput = {
markdown: string;
product: string;
files?: File[];
} & Record<string, string>;
function prepareEmailTitle(
product: string,
problemArea: string,
@@ -1,12 +0,0 @@
export const PRODUCTS = [
"connect",
"engine",
"contracts",
"account",
"other",
] as const;
export type Product = (typeof PRODUCTS)[number];
export function isProduct(value: string): value is Product {
return PRODUCTS.includes(value as Product);
}
@@ -5,7 +5,7 @@ import { ChevronDown } from "lucide-react";
import { usePathname } from "next/navigation";
import type { SortBy, TimeRange } from "../../../../lib/search";
export type SortingHeaderProps = {
type SortingHeaderProps = {
sortBy: string;
title: string;
searchParams:
@@ -1,30 +0,0 @@
"use client";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import type { TimeRange } from "lib/search";
import { usePathname, useSearchParams } from "next/navigation";
export function TimeRangeSwitcher() {
const router = useDashboardRouter();
const path = usePathname();
const searchParams = useSearchParams();
const currentSort = searchParams?.get("sortBy") || "transactionCount";
return (
<ToggleGroup
type="single"
value={searchParams?.get("timeRange") || "day"}
onValueChange={async (value: TimeRange) => {
router.replace(
`${path}?timeRange=${value}${currentSort ? `&sortBy=${currentSort}` : ""}`,
{
scroll: false,
},
);
}}
>
<ToggleGroupItem value="day">Last 24h</ToggleGroupItem>
<ToggleGroupItem value="week">Last 7 Days</ToggleGroupItem>
<ToggleGroupItem value="month">Last 30 Days</ToggleGroupItem>
</ToggleGroup>
);
}
@@ -5,7 +5,8 @@ import type { Team } from "@/api/team";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
import { useAccount } from "@3rdweb-sdk/react/hooks/useApi";
import { useCallback } from "react";
import { useCallback, useState } from "react";
import { LazyCreateAPIKeyDialog } from "../../../components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog";
import {
type AccountHeaderCompProps,
AccountHeaderDesktopUI,
@@ -17,6 +18,8 @@ export function AccountHeader(props: {
}) {
const myAccountQuery = useAccount();
const router = useDashboardRouter();
const [isCreateProjectDialogOpen, setIsCreateProjectDialogOpen] =
useState(false);
const logout = useCallback(async () => {
// log out the user
@@ -35,12 +38,23 @@ export function AccountHeader(props: {
email: myAccountQuery.data?.email,
logout: logout,
connectButton: <CustomConnectWallet />,
createProject: () => setIsCreateProjectDialogOpen(true),
};
return (
<div>
<AccountHeaderDesktopUI {...headerProps} className="max-lg:hidden" />
<AccountHeaderMobileUI {...headerProps} className="lg:hidden" />
<LazyCreateAPIKeyDialog
open={isCreateProjectDialogOpen}
onOpenChange={setIsCreateProjectDialogOpen}
wording="project"
onCreateAndComplete={() => {
// refresh projects
router.refresh();
}}
/>
</div>
);
}
@@ -53,6 +53,7 @@ function Variants(props: {
email="[email protected]"
logout={() => {}}
connectButton={<ConnectButtonStub />}
createProject={() => {}}
/>
</BadgeContainer>
</div>
@@ -14,6 +14,7 @@ export type AccountHeaderCompProps = {
logout: () => void;
connectButton: React.ReactNode;
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
createProject: () => void;
};
export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
@@ -46,6 +47,7 @@ export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
currentTeam={undefined}
teamsAndProjects={props.teamsAndProjects}
focus="team-selection"
createProject={props.createProject}
/>
</div>
</div>
@@ -85,7 +85,7 @@ function AccountAvatarFormControl() {
saveButton={{
onClick: handleSave,
disabled: false,
isLoading: updateAvatarMutation.isLoading,
isLoading: updateAvatarMutation.isPending,
}}
noPermissionText={undefined}
errorText={undefined}
@@ -143,7 +143,7 @@ function AccountNameFormControl(props: {
saveButton={{
onClick: handleSave,
disabled: accountName.length === 0,
isLoading: updateAccountNameMutation.isLoading,
isLoading: updateAccountNameMutation.isPending,
}}
errorText={undefined}
noPermissionText={undefined} // TODO
@@ -191,7 +191,7 @@ function DeleteAccountCard() {
description={description}
buttonLabel={title}
buttonOnClick={handleDelete}
isLoading={deleteAccount.isLoading}
isLoading={deleteAccount.isPending}
confirmationDialog={{
title: "Are you sure you want to delete your account?",
description:
@@ -321,10 +321,10 @@ function EmailUpdateDialog(props: {
<Button variant="outline"> Cancel </Button>
</DialogClose>
<Button className="min-w-24 gap-2" type="submit">
{updateEmailMutation.isLoading && (
{updateEmailMutation.isPending && (
<Spinner className="size-4" />
)}
{updateEmailMutation.isLoading ? "Saving" : "Save"}
{updateEmailMutation.isPending ? "Saving" : "Save"}
</Button>
</DialogFooter>
</form>
@@ -0,0 +1,78 @@
import { type NextRequest, NextResponse } from "next/server";
import invariant from "tiny-invariant";
import { cacheGet, cacheSet } from "../../../lib/redis";
import type { ContactFormPayload } from "./types";
// Note: This handler cannot use "edge" runtime because of Redis usage.
export const POST = async (req: NextRequest) => {
const rateLimitedResponse = await rateLimiter(req);
if (rateLimitedResponse) {
return rateLimitedResponse;
}
const requestBody = (await req.json()) as ContactFormPayload;
const { fields } = requestBody;
invariant(process.env.HUBSPOT_ACCESS_TOKEN, "missing HUBSPOT_ACCESS_TOKEN");
const response = await fetch(
"https://api.hsforms.com/submissions/v3/integration/secure/submit/23987964/38849262-3605-4eb2-883b-b4f1aa5ad845",
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}`,
},
method: "POST",
body: JSON.stringify({ fields }),
},
);
if (!response.ok) {
const body = await response.json();
console.error("error", body);
}
return NextResponse.json(
{ status: response.statusText },
{
status: response.status,
},
);
};
async function rateLimiter(req: NextRequest) {
// Max 1 requests per minute
const rateLimitSeconds = 60;
const ipAddress =
req.headers.get("CF-Connecting-IP") ||
req.ip ||
req.headers.get("X-Forwarded-For");
if (!ipAddress) {
return NextResponse.json(
{
error: "Could not validate elligibility.",
},
{ status: 400 },
);
}
const cacheKey = `contact-us:${ipAddress}`;
const cacheValue = await cacheGet(cacheKey);
// if we have a cached value, return an error
if (cacheValue !== null) {
return NextResponse.json(
{
error: `Rate limit exceeded. Try again in ${rateLimitSeconds} seconds.`,
},
{ status: 429 },
);
}
// cache it for `rateLimitSeconds`
await cacheSet(cacheKey, "contact-us-used", rateLimitSeconds);
}
@@ -0,0 +1,6 @@
export type ContactFormPayload = {
fields: {
name: string;
value: string;
}[];
};
@@ -35,7 +35,6 @@ export function DashboardHeader() {
))}
</div>
</div>
{/* Right */}
<div className="flex items-center gap-4">
<div className="items-center gap-2 lg:flex">
@@ -4,7 +4,7 @@ import { cn } from "@/lib/utils";
import Link, { type LinkProps } from "next/link";
import { usePathname } from "next/navigation";
export type NavLinkProps = Omit<
type NavLinkProps = Omit<
React.AnchorHTMLAttributes<HTMLAnchorElement>,
keyof LinkProps
> &
+5 -1
View File
@@ -7,9 +7,13 @@ import PlausibleProvider from "next-plausible";
import dynamic from "next/dynamic";
import { Inter } from "next/font/google";
import NextTopLoader from "nextjs-toploader";
import { setOverrides } from "../lib/vercel-utils";
import { PostHogProvider } from "./components/root-providers";
import { AppRouterProviders } from "./providers";
// run this on app load
setOverrides();
const fontSans = Inter({
subsets: ["latin"],
variable: "--font-sans",
@@ -65,7 +69,7 @@ export default function RootLayout({
<PostHogProvider>
<body
className={cn(
"h-screen bg-background font-sans antialiased",
"bg-background font-sans antialiased",
fontSans.variable,
)}
>

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