Skip to content

Commit 97683d2

Browse files
committed
merge main
2 parents 8196f90 + 8e6651e commit 97683d2

File tree

252 files changed

+11191
-4885
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

252 files changed

+11191
-4885
lines changed

.github/workflows/typedoc.yml

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,3 @@ jobs:
2828

2929
- name: Run TypeDoc
3030
run: pnpm typedoc
31-
32-
- name: Update Gist
33-
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
34-
with:
35-
github-token: ${{ secrets.GIST_TOKEN }}
36-
script: |
37-
const fs = require('fs');
38-
const content = fs.readFileSync('./packages/thirdweb/typedoc/parsed.json', 'utf8');
39-
const gistId = '678fe1f331a01270bb002fee660f285d';
40-
41-
await github.rest.gists.update({
42-
gist_id: gistId,
43-
files: {
44-
'data.json': {
45-
content: content
46-
}
47-
}
48-
});
49-
50-
console.log(`Permalink: https://gist.githubusercontent.com/raw/${gistId}/data.json`);

apps/dashboard/package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@
6767
"input-otp": "^1.4.1",
6868
"ioredis": "^5.6.0",
6969
"ipaddr.js": "^2.2.0",
70-
"lucide-react": "0.479.0",
71-
"next": "15.2.2",
70+
"lucide-react": "0.483.0",
71+
"next": "15.2.3",
7272
"next-plausible": "^3.12.4",
7373
"next-themes": "^0.4.6",
7474
"nextjs-toploader": "^1.6.12",
@@ -105,10 +105,10 @@
105105
},
106106
"devDependencies": {
107107
"@chakra-ui/cli": "^2.4.1",
108-
"@chromatic-com/storybook": "3.2.5",
109-
"@next/bundle-analyzer": "15.2.2",
110-
"@next/eslint-plugin-next": "15.2.2",
111-
"@playwright/test": "1.51.0",
108+
"@chromatic-com/storybook": "3.2.6",
109+
"@next/bundle-analyzer": "15.2.3",
110+
"@next/eslint-plugin-next": "15.2.3",
111+
"@playwright/test": "1.51.1",
112112
"@storybook/addon-essentials": "8.6.4",
113113
"@storybook/addon-interactions": "8.6.4",
114114
"@storybook/addon-links": "8.6.4",
@@ -135,7 +135,7 @@
135135
"eslint": "8.57.0",
136136
"eslint-config-biome": "1.9.4",
137137
"eslint-plugin-react-compiler": "19.0.0-beta-40c6c23-20250301",
138-
"eslint-plugin-storybook": "0.11.4",
138+
"eslint-plugin-storybook": "0.11.6",
139139
"knip": "5.46.0",
140140
"next-sitemap": "^4.2.3",
141141
"postcss": "8.5.3",

apps/dashboard/src/@/actions/getWalletNFTs.ts

Lines changed: 120 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,9 @@ import {
1010
isMoralisSupported,
1111
transformMoralisResponseToNFT,
1212
} from "lib/wallet/nfts/moralis";
13-
import {
14-
generateSimpleHashUrl,
15-
isSimpleHashSupported,
16-
transformSimpleHashResponseToNFT,
17-
} from "lib/wallet/nfts/simpleHash";
1813
import type { WalletNFT } from "lib/wallet/nfts/types";
14+
import { getVercelEnv } from "../../lib/vercel-utils";
15+
import { DASHBOARD_THIRDWEB_CLIENT_ID } from "../constants/env";
1916

2017
type WalletNFTApiReturn =
2118
| { result: WalletNFT[]; error?: undefined }
@@ -24,40 +21,20 @@ type WalletNFTApiReturn =
2421
export async function getWalletNFTs(params: {
2522
chainId: number;
2623
owner: string;
24+
isInsightSupported: boolean;
2725
}): Promise<WalletNFTApiReturn> {
2826
const { chainId, owner } = params;
29-
const supportedChainSlug = await isSimpleHashSupported(chainId);
3027

31-
if (supportedChainSlug && process.env.SIMPLEHASH_API_KEY) {
32-
const url = generateSimpleHashUrl({ chainSlug: supportedChainSlug, owner });
28+
if (params.isInsightSupported) {
29+
const response = await getWalletNFTsFromInsight({ chainId, owner });
3330

34-
const response = await fetch(url, {
35-
method: "GET",
36-
headers: {
37-
"X-API-KEY": process.env.SIMPLEHASH_API_KEY,
38-
},
39-
next: {
40-
revalidate: 10, // cache for 10 seconds
41-
},
42-
});
43-
44-
if (response.status >= 400) {
31+
if (!response.ok) {
4532
return {
46-
error: response.statusText,
33+
error: response.error,
4734
};
4835
}
4936

50-
try {
51-
const parsedResponse = await response.json();
52-
const result = await transformSimpleHashResponseToNFT(
53-
parsedResponse,
54-
owner,
55-
);
56-
57-
return { result };
58-
} catch {
59-
return { error: "error parsing response" };
60-
}
37+
return { result: response.data };
6138
}
6239

6340
if (isAlchemySupported(chainId)) {
@@ -115,3 +92,115 @@ export async function getWalletNFTs(params: {
11592

11693
return { error: "unsupported chain" };
11794
}
95+
96+
type OwnedNFTInsightResponse = {
97+
name: string;
98+
description: string;
99+
image_url: string;
100+
background_color: string;
101+
external_url: string;
102+
metadata_url: string;
103+
extra_metadata: {
104+
customImage?: string;
105+
customAnimationUrl?: string;
106+
animation_original_url?: string;
107+
image_original_url?: string;
108+
};
109+
collection: {
110+
name: string;
111+
description: string;
112+
extra_metadata: Record<string, string>;
113+
};
114+
contract: {
115+
chain_id: number;
116+
address: string;
117+
type: "erc1155" | "erc721";
118+
name: string;
119+
};
120+
owner_addresses: string[];
121+
token_id: string;
122+
balance: string;
123+
token_type: "erc1155" | "erc721";
124+
};
125+
126+
async function getWalletNFTsFromInsight(params: {
127+
chainId: number;
128+
owner: string;
129+
}): Promise<
130+
| {
131+
data: WalletNFT[];
132+
ok: true;
133+
}
134+
| {
135+
ok: false;
136+
error: string;
137+
}
138+
> {
139+
const { chainId, owner } = params;
140+
141+
const thirdwebDomain =
142+
getVercelEnv() === "production" ? "thirdweb" : "thirdweb-dev";
143+
const url = new URL(`https://insight.${thirdwebDomain}.com/v1/nfts`);
144+
url.searchParams.append("chain", chainId.toString());
145+
url.searchParams.append("limit", "10");
146+
url.searchParams.append("owner_address", owner);
147+
148+
const response = await fetch(url, {
149+
headers: {
150+
"x-client-id": DASHBOARD_THIRDWEB_CLIENT_ID,
151+
},
152+
});
153+
154+
if (!response.ok) {
155+
const errorMessage = await response.text();
156+
return {
157+
ok: false,
158+
error: errorMessage,
159+
};
160+
}
161+
162+
const nftsResponse = (await response.json()) as {
163+
data: OwnedNFTInsightResponse[];
164+
};
165+
166+
const isDev = getVercelEnv() !== "production";
167+
168+
// NOTE: ipfscdn.io/ to thirdwebstorage-dev.com/ replacement is temporary
169+
// This should be fixed in the insight dev endpoint
170+
171+
const walletNFTs = nftsResponse.data.map((nft) => {
172+
const walletNFT: WalletNFT = {
173+
id: nft.token_id,
174+
contractAddress: nft.contract.address,
175+
metadata: {
176+
uri: isDev
177+
? nft.metadata_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
178+
: nft.metadata_url,
179+
name: nft.name,
180+
description: nft.description,
181+
image: isDev
182+
? nft.image_url.replace("ipfscdn.io/", "thirdwebstorage-dev.com/")
183+
: nft.image_url,
184+
animation_url: isDev
185+
? nft.extra_metadata.animation_original_url?.replace(
186+
"ipfscdn.io/",
187+
"thirdwebstorage-dev.com/",
188+
)
189+
: nft.extra_metadata.animation_original_url,
190+
external_url: nft.external_url,
191+
background_color: nft.background_color,
192+
},
193+
owner: params.owner,
194+
tokenURI: nft.metadata_url,
195+
type: nft.token_type === "erc721" ? "ERC721" : "ERC1155",
196+
supply: nft.balance,
197+
};
198+
199+
return walletNFT;
200+
});
201+
202+
return {
203+
data: walletNFTs,
204+
ok: true,
205+
};
206+
}

apps/dashboard/src/@/api/sms.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "../constants/env";
2+
3+
export type SMSCountryTiers = {
4+
tier1: string[];
5+
tier2: string[];
6+
tier3: string[];
7+
tier4: string[];
8+
tier5: string[];
9+
};
10+
11+
export async function getSMSCountryTiers() {
12+
if (!THIRDWEB_API_SECRET) {
13+
throw new Error("API_SERVER_SECRET is not set");
14+
}
15+
const res = await fetch(`${API_SERVER_URL}/v1/sms/list-country-tiers`, {
16+
headers: {
17+
"Content-Type": "application/json",
18+
"x-service-api-key": THIRDWEB_API_SECRET,
19+
},
20+
next: {
21+
revalidate: 15 * 60, //15 minutes
22+
},
23+
});
24+
25+
if (!res.ok) {
26+
console.error(
27+
"Failed to fetch sms country tiers",
28+
res.status,
29+
res.statusText,
30+
);
31+
res.body?.cancel();
32+
return {
33+
tier1: [],
34+
tier2: [],
35+
tier3: [],
36+
tier4: [],
37+
tier5: [],
38+
};
39+
}
40+
41+
try {
42+
return (await res.json()).data as SMSCountryTiers;
43+
} catch (e) {
44+
console.error("Failed to parse sms country tiers", e);
45+
return {
46+
tier1: [],
47+
tier2: [],
48+
tier3: [],
49+
tier4: [],
50+
tier5: [],
51+
};
52+
}
53+
}

apps/dashboard/src/@/components/ui/code/getCodeHtml.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as parserBabel from "prettier/plugins/babel";
2-
import * as estree from "prettier/plugins/estree";
32
import { format } from "prettier/standalone";
43
import { type BundledLanguage, codeToHtml } from "shiki";
54

@@ -21,10 +20,11 @@ export async function getCodeHtml(
2120
ignoreFormattingErrors?: boolean;
2221
},
2322
) {
23+
const estreePlugin = await import("prettier/plugins/estree");
2424
const formattedCode = isPrettierSupportedLang(lang)
2525
? await format(code, {
2626
parser: "babel-ts",
27-
plugins: [parserBabel, estree],
27+
plugins: [parserBabel, estreePlugin.default],
2828
printWidth: 60,
2929
}).catch((e) => {
3030
if (!options?.ignoreFormattingErrors) {

apps/dashboard/src/@3rdweb-sdk/react/hooks/useWalletNFTs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import invariant from "tiny-invariant";
55
export function useWalletNFTs(params: {
66
chainId: number;
77
walletAddress?: string;
8+
isInsightSupported: boolean;
89
}) {
910
return useQuery({
1011
queryKey: ["walletNfts", params.chainId, params.walletAddress],
@@ -13,6 +14,7 @@ export function useWalletNFTs(params: {
1314
return getWalletNFTs({
1415
chainId: params.chainId,
1516
owner: params.walletAddress,
17+
isInsightSupported: params.isInsightSupported,
1618
});
1719
},
1820
enabled: !!params.walletAddress,

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-button.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { ListerOnly } from "@3rdweb-sdk/react/components/roles/lister-only";
1313
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
1414
import { isAlchemySupported } from "lib/wallet/nfts/alchemy";
1515
import { isMoralisSupported } from "lib/wallet/nfts/moralis";
16-
import { useSimplehashSupport } from "lib/wallet/nfts/simpleHash";
1716
import { PlusIcon } from "lucide-react";
1817
import { useState } from "react";
1918
import type { ThirdwebContract } from "thirdweb";
@@ -25,6 +24,7 @@ interface CreateListingButtonProps {
2524
createText?: string;
2625
type?: "direct-listings" | "english-auctions";
2726
twAccount: Account | undefined;
27+
isInsightSupported: boolean;
2828
}
2929

3030
const LISTING_MODES = ["Select NFT", "Manual"] as const;
@@ -34,20 +34,20 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
3434
type,
3535
contract,
3636
twAccount,
37+
isInsightSupported,
3738
...restButtonProps
3839
}) => {
3940
const address = useActiveAccount()?.address;
4041
const [open, setOpen] = useState(false);
4142
const [listingMode, setListingMode] =
4243
useState<(typeof LISTING_MODES)[number]>("Select NFT");
4344

44-
const simplehashQuery = useSimplehashSupport(contract.chain.id);
45-
4645
const isSupportedChain =
4746
contract.chain.id &&
48-
(simplehashQuery.data ||
47+
(isInsightSupported ||
4948
isAlchemySupported(contract.chain.id) ||
5049
isMoralisSupported(contract.chain.id));
50+
5151
return (
5252
<ListerOnly contract={contract}>
5353
<Sheet open={open} onOpenChange={setOpen}>
@@ -83,6 +83,7 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
8383
actionText={createText}
8484
setOpen={setOpen}
8585
mode={listingMode === "Select NFT" ? "automatic" : "manual"}
86+
isInsightSupported={isInsightSupported}
8687
/>
8788
</div>
8889
</>
@@ -95,6 +96,7 @@ export const CreateListingButton: React.FC<CreateListingButtonProps> = ({
9596
actionText={createText}
9697
setOpen={setOpen}
9798
mode="manual"
99+
isInsightSupported={isInsightSupported}
98100
/>
99101
</div>
100102
)}

0 commit comments

Comments
 (0)