Compare commits

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

<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

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

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

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

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

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the content in the `page.tsx` file related to the `TeamProjectsPage` component and modifying the alert message to provide clearer information about managing engines within projects.

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

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

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

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