Skip to content

Commit 9f1264e

Browse files
authored
Merge branch 'main' into feat/track-transactions
2 parents 0f711d9 + e7bbc32 commit 9f1264e

File tree

26 files changed

+665
-1918
lines changed

26 files changed

+665
-1918
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ packages/*/typedoc/*
2929
*storybook.log
3030
storybook-static
3131
.aider*
32+
33+
tsconfig.tsbuildinfo

apps/dashboard/.eslintrc.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ module.exports = {
3434
message:
3535
"Use useV5DashboardChain instead if you are using it inside a component",
3636
},
37+
{
38+
selector: "CallExpression[callee.name='resolveScheme']",
39+
message:
40+
"resolveScheme can throw error if resolution fails. Either catch the error and ignore the lint warning or Use `resolveSchemeWithErrorHandler` / `replaceIpfsUrl` utility in dashboard instead",
41+
},
3742
],
3843
"no-restricted-imports": [
3944
"error",

apps/dashboard/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"@radix-ui/react-switch": "^1.1.1",
4848
"@radix-ui/react-tooltip": "1.1.3",
4949
"@sentry/nextjs": "8.34.0",
50-
"@shazow/whatsabi": "^0.15.3",
50+
"@shazow/whatsabi": "^0.15.4",
5151
"@stripe/react-stripe-js": "^2.8.1",
5252
"@stripe/stripe-js": "^3.5.0",
5353
"@tanstack/react-query": "5.59.13",
@@ -62,7 +62,7 @@
6262
"compare-versions": "^6.1.0",
6363
"date-fns": "4.1.0",
6464
"flat": "^6.0.1",
65-
"framer-motion": "11.11.8",
65+
"framer-motion": "11.11.9",
6666
"fuse.js": "7.0.0",
6767
"input-otp": "^1.2.4",
6868
"ioredis": "^5.4.1",
@@ -118,10 +118,10 @@
118118
"@storybook/blocks": "8.3.5",
119119
"@storybook/nextjs": "8.3.5",
120120
"@storybook/react": "8.3.5",
121-
"@storybook/test": "8.3.5",
121+
"@storybook/test": "8.3.6",
122122
"@types/color": "^3.0.6",
123123
"@types/node": "20.14.9",
124-
"@types/papaparse": "^5.3.14",
124+
"@types/papaparse": "^5.3.15",
125125
"@types/pluralize": "^0.0.33",
126126
"@types/qrcode": "^1.5.5",
127127
"@types/react": "^18.3.11",

apps/dashboard/src/@/components/blocks/wallet-address.tsx

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"use client";
2-
32
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
43
import {
54
HoverCard,
65
HoverCardContent,
76
HoverCardTrigger,
87
} from "@/components/ui/hover-card";
98
import { useThirdwebClient } from "@/constants/thirdweb.client";
9+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
1010
import { useClipboard } from "hooks/useClipboard";
1111
import { Check, Copy, ExternalLinkIcon } from "lucide-react";
1212
import { useMemo } from "react";
@@ -18,7 +18,6 @@ import {
1818
type SocialProfile,
1919
useSocialProfiles,
2020
} from "thirdweb/react";
21-
import { resolveScheme } from "thirdweb/storage";
2221
import { cn } from "../../lib/utils";
2322
import { Badge } from "../ui/badge";
2423
import { Button } from "../ui/button";
@@ -113,42 +112,41 @@ export function WalletAddress(props: {
113112
) : !profiles.data?.length ? (
114113
<p className="text-muted-foreground text-sm">No profiles found</p>
115114
) : (
116-
profiles.data?.map((profile) => (
117-
<div
118-
className="flex flex-row items-center gap-2"
119-
key={profile.type + profile.name}
120-
>
121-
{profile.avatar &&
122-
(profile.avatar.startsWith("http") ||
123-
profile.avatar?.startsWith("ipfs")) && (
115+
profiles.data?.map((profile) => {
116+
const walletAvatarLink = resolveSchemeWithErrorHandler({
117+
client: thirdwebClient,
118+
uri: profile.avatar,
119+
});
120+
121+
return (
122+
<div
123+
className="flex flex-row items-center gap-2"
124+
key={profile.type + profile.name}
125+
>
126+
{walletAvatarLink && (
124127
<Avatar>
125-
<AvatarImage
126-
src={resolveScheme({
127-
client: thirdwebClient,
128-
uri: profile.avatar,
129-
})}
130-
alt={profile.name}
131-
/>
128+
<AvatarImage src={walletAvatarLink} alt={profile.name} />
132129
{profile.name && (
133130
<AvatarFallback>
134131
{profile.name.slice(0, 2)}
135132
</AvatarFallback>
136133
)}
137134
</Avatar>
138135
)}
139-
<div className="flex w-full flex-col gap-1">
140-
<div className="flex w-full flex-row items-center justify-between gap-4">
141-
<h4 className="font-semibold text-md">{profile.name}</h4>
142-
<Badge variant="outline">{profile.type}</Badge>
136+
<div className="flex w-full flex-col gap-1">
137+
<div className="flex w-full flex-row items-center justify-between gap-4">
138+
<h4 className="font-semibold text-md">{profile.name}</h4>
139+
<Badge variant="outline">{profile.type}</Badge>
140+
</div>
141+
{profile.bio && (
142+
<p className="line-clamp-1 whitespace-normal text-muted-foreground text-sm">
143+
{profile.bio}
144+
</p>
145+
)}
143146
</div>
144-
{profile.bio && (
145-
<p className="line-clamp-1 whitespace-normal text-muted-foreground text-sm">
146-
{profile.bio}
147-
</p>
148-
)}
149147
</div>
150-
</div>
151-
))
148+
);
149+
})
152150
)}
153151
<Button
154152
asChild
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { ThirdwebClient } from "thirdweb";
2+
import { resolveScheme } from "thirdweb/storage";
3+
4+
export function resolveSchemeWithErrorHandler(options: {
5+
uri: string | undefined;
6+
client: ThirdwebClient;
7+
}) {
8+
if (!options.uri) {
9+
return undefined;
10+
}
11+
try {
12+
// eslint-disable-next-line no-restricted-syntax
13+
return resolveScheme({
14+
uri: options.uri,
15+
client: options.client,
16+
});
17+
} catch (err) {
18+
console.error("error resolving ipfs url", options.uri, err);
19+
return undefined;
20+
}
21+
}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CopyTextButton } from "@/components/ui/CopyTextButton";
66
import { Spinner } from "@/components/ui/Spinner/Spinner";
77
import { useThirdwebClient } from "@/constants/thirdweb.client";
88
import { useDashboardRouter } from "@/lib/DashboardRouter";
9+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
910
import {
1011
Box,
1112
ButtonGroup,
@@ -25,7 +26,6 @@ import type { ThirdwebContract } from "thirdweb";
2526
import { getNFT as getErc721NFT } from "thirdweb/extensions/erc721";
2627
import { getNFT as getErc1155NFT } from "thirdweb/extensions/erc1155";
2728
import { useReadContract } from "thirdweb/react";
28-
import { resolveScheme } from "thirdweb/storage";
2929
import { Badge, Button, Card, CodeBlock, Heading, Text } from "tw-components";
3030
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
3131
import { shortenString } from "utils/usedapp-external";
@@ -83,6 +83,15 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
8383
},
8484
);
8585

86+
const tokenURIHttpLink = resolveSchemeWithErrorHandler({
87+
client,
88+
uri: nft?.tokenURI,
89+
});
90+
const nftImageLink = resolveSchemeWithErrorHandler({
91+
client,
92+
uri: nft?.metadata.image,
93+
});
94+
8695
if (isPending) {
8796
return (
8897
<div className="flex h-[400px] items-center justify-center">
@@ -263,14 +272,13 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
263272
tooltip="The URI of this NFT"
264273
copyIconPosition="right"
265274
/>
266-
<Button variant="ghost" size="sm">
267-
<Link
268-
href={resolveScheme({ client, uri: nft.tokenURI })}
269-
target="_blank"
270-
>
271-
<ExternalLinkIcon className="size-4" />
272-
</Link>
273-
</Button>
275+
{tokenURIHttpLink && (
276+
<Button variant="ghost" size="sm">
277+
<Link href={tokenURIHttpLink} target="_blank">
278+
<ExternalLinkIcon className="size-4" />
279+
</Link>
280+
</Button>
281+
)}
274282
</GridItem>
275283
{nft.metadata.image && (
276284
<>
@@ -287,17 +295,13 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
287295
tooltip="The media URI of this NFT"
288296
copyIconPosition="right"
289297
/>
290-
<Button variant="ghost" size="sm">
291-
<Link
292-
href={resolveScheme({
293-
client,
294-
uri: nft.metadata.image,
295-
})}
296-
target="_blank"
297-
>
298-
<ExternalLinkIcon className="size-4" />
299-
</Link>
300-
</Button>
298+
{nftImageLink && (
299+
<Button variant="ghost" size="sm">
300+
<Link href={nftImageLink} target="_blank">
301+
<ExternalLinkIcon className="size-4" />
302+
</Link>
303+
</Button>
304+
)}
301305
</GridItem>
302306
</>
303307
)}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ export const SettingsMetadata = ({
8989
let image: string | undefined = metadata.data?.image;
9090
try {
9191
image = image
92-
? resolveScheme({
92+
? // eslint-disable-next-line no-restricted-syntax
93+
resolveScheme({
9394
client: contract.client,
9495
uri: image,
9596
})

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/sources/ContractSourcesPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ interface VerificationResult {
3434
error?: string;
3535
}
3636

37-
async function verifyContract(contract: ThirdwebContract) {
37+
export async function verifyContract(contract: ThirdwebContract) {
3838
try {
3939
const response = await fetch(
4040
"https://contract.thirdweb.com/verify/contract",

apps/dashboard/src/app/(dashboard)/(chain)/components/server/chain-icon.tsx

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import "server-only";
33
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
44
import { getThirdwebClient } from "@/constants/thirdweb.server";
5+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
56
import { cn } from "@/lib/utils";
6-
import { resolveScheme } from "thirdweb/storage";
77

88
const fallbackChainIcon =
99
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTY4LjE1MTkgNzUuNzM3MkM2Mi4yOTQzIDc5Ljk5MyA1NS4yMzk3IDgyLjI4NTIgNDcuOTk5MyA4Mi4yODUyQzQwLjc1ODkgODIuMjg1MiAzMy43MDQzIDc5Ljk5MyAyNy44NDY2IDc1LjczNzJNNjMuMDI5MSAxNy4xODM3QzY5LjUzNjggMjAuMzU3NyA3NC44NzI2IDI1LjUxMDQgNzguMjcxOCAzMS45MDMzQzgxLjY3MDkgMzguMjk2MiA4Mi45NTkgNDUuNjAxMiA4MS45NTEzIDUyLjc3MTFNMTQuMDQ3NiA1Mi43NzA4QzEzLjAzOTkgNDUuNjAwOCAxNC4zMjggMzguMjk1OSAxNy43MjcxIDMxLjkwM0MyMS4xMjYzIDI1LjUxMDEgMjYuNDYyMSAyMC4zNTczIDMyLjk2OTggMTcuMTgzM000Ni4wNTk4IDI5LjM2NzVMMjkuMzY3MyA0Ni4wNkMyOC42ODg1IDQ2LjczODkgMjguMzQ5IDQ3LjA3ODMgMjguMjIxOCA0Ny40Njk3QzI4LjExIDQ3LjgxNCAyOC4xMSA0OC4xODQ5IDI4LjIyMTggNDguNTI5MkMyOC4zNDkgNDguOTIwNiAyOC42ODg1IDQ5LjI2MDEgMjkuMzY3MyA0OS45MzlMNDYuMDU5OCA2Ni42MzE0QzQ2LjczODcgNjcuMzEwMyA0Ny4wNzgxIDY3LjY0OTcgNDcuNDY5NSA2Ny43NzY5QzQ3LjgxMzggNjcuODg4OCA0OC4xODQ3IDY3Ljg4ODggNDguNTI5IDY3Ljc3NjlDNDguOTIwNCA2Ny42NDk3IDQ5LjI1OTkgNjcuMzEwMyA0OS45Mzg4IDY2LjYzMTRMNjYuNjMxMiA0OS45MzlDNjcuMzEwMSA0OS4yNjAxIDY3LjY0OTUgNDguOTIwNiA2Ny43NzY3IDQ4LjUyOTJDNjcuODg4NiA0OC4xODQ5IDY3Ljg4ODYgNDcuODE0IDY3Ljc3NjcgNDcuNDY5N0M2Ny42NDk1IDQ3LjA3ODMgNjcuMzEwMSA0Ni43Mzg5IDY2LjYzMTIgNDYuMDZMNDkuOTM4OCAyOS4zNjc1QzQ5LjI1OTkgMjguNjg4NyA0OC45MjA0IDI4LjM0OTIgNDguNTI5IDI4LjIyMkM0OC4xODQ3IDI4LjExMDIgNDcuODEzOCAyOC4xMTAyIDQ3LjQ2OTUgMjguMjIyQzQ3LjA3ODEgMjguMzQ5MiA0Ni43Mzg3IDI4LjY4ODcgNDYuMDU5OCAyOS4zNjc1WiIgc3Ryb2tlPSIjNDA0MDQwIiBzdHJva2Utd2lkdGg9IjYuODU3MTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K";
@@ -13,24 +13,35 @@ export async function ChainIcon(props: {
1313
className?: string;
1414
}) {
1515
if (props.iconUrl) {
16-
const resolved = resolveScheme({
16+
let imageLink = fallbackChainIcon;
17+
18+
const resolved = resolveSchemeWithErrorHandler({
1719
client: getThirdwebClient(),
1820
uri: props.iconUrl,
1921
});
20-
const res = await fetch(resolved, {
21-
// revalidate every hour
22-
next: { revalidate: 60 * 60 },
23-
method: "HEAD",
24-
headers: DASHBOARD_THIRDWEB_SECRET_KEY
25-
? {
26-
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
27-
}
28-
: {},
29-
}).catch(() => null);
22+
23+
if (resolved) {
24+
// check if it loads or not
25+
const res = await fetch(resolved, {
26+
// revalidate every hour
27+
next: { revalidate: 60 * 60 },
28+
method: "HEAD",
29+
headers: DASHBOARD_THIRDWEB_SECRET_KEY
30+
? {
31+
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
32+
}
33+
: {},
34+
}).catch(() => null);
35+
36+
if (res?.status === 200) {
37+
imageLink = resolved;
38+
}
39+
}
40+
3041
return (
3142
<img
3243
alt=""
33-
src={res?.status === 200 ? resolved : fallbackChainIcon}
44+
src={imageLink}
3445
className={cn("object-contain", props.className)}
3546
/>
3647
);

apps/dashboard/src/app/(dashboard)/contracts/publish/[publish_uri]/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getActiveAccountCookie, getJWTCookie } from "@/constants/cookie";
33
import { getThirdwebClient } from "@/constants/thirdweb.server";
44
import { ContractPublishForm } from "components/contract-components/contract-publish-form";
55
import { revalidatePath } from "next/cache";
6-
import { redirect } from "next/navigation";
6+
import { notFound, redirect } from "next/navigation";
77
import { fetchDeployMetadata } from "thirdweb/contract";
88
import { getPublishedContractsWithPublisherMapping } from "../../../published-contract/[publisher]/[contract_id]/utils/getPublishedContractsWithPublisherMapping";
99

@@ -47,6 +47,10 @@ export default async function PublishContractPage(
4747
contract_id: publishMetadataFromUri.name,
4848
});
4949

50+
if (!publishedContractVersions) {
51+
notFound();
52+
}
53+
5054
const publishedContract = publishedContractVersions[0];
5155

5256
if (publishedContract) {

0 commit comments

Comments
 (0)