Skip to content

Commit b30369f

Browse files
authored
Change getContractMetadata() return type from any to unknown (#8289)
1 parent f2121e9 commit b30369f

File tree

25 files changed

+335
-62
lines changed

25 files changed

+335
-62
lines changed

.changeset/witty-plums-read.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
### `getContractMetadata()` now returns a record with `unknown` values instead of `any`.
6+
7+
before:
8+
```ts
9+
const metadata = await getContractMetadata({ contract });
10+
metadata // Record<string, any>
11+
metadata.name; // string
12+
metadata.symbol; // string
13+
```
14+
15+
after:
16+
```ts
17+
const metadata = await getContractMetadata({ contract });
18+
metadata // Record<string, unknown>
19+
metadata.name; // string | null
20+
metadata.symbol; // string | null
21+
```
22+
23+
24+
Metadata is not (and was never) strictly defined outside of `name` and `symbol` and may contain any type of data in the record.
25+
This is not a runtime change but it may break type inference in existing apps that relied on the previous return type.
26+
27+
**Recommended fix:**
28+
You *should* type-guard any key you access from "metadata".
29+
```ts
30+
const metadata = await getContractMetadata({ contract });
31+
if ("foo" in metadata && typeof metadata.foo === "string") {
32+
metadata.foo; // string
33+
}
34+
```
35+
36+
**Quick fix:**
37+
If adding type assertions is not something you can do in the short term you can also assert the type directly.
38+
_This is as "unsafe" as the type was before._
39+
40+
```ts
41+
const metadata = await getContractMetadata({ contract });
42+
const foo = metadata.foo as string;
43+
```
44+

apps/dashboard/src/@/api/universal-bridge/token-list.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export async function getUniversalBridgeTokens(props: {
2121
headers: {
2222
"Content-Type": "application/json",
2323
"x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID,
24-
} as Record<string, string>,
24+
},
2525
method: "GET",
2626
});
2727

apps/dashboard/src/@/api/universal-bridge/tokens.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export async function addUniversalBridgeTokenRoute(props: {
2121
Authorization: `Bearer ${authToken}`,
2222
"Content-Type": "application/json",
2323
"x-client-id": props.project.publishableKey,
24-
} as Record<string, string>,
24+
},
2525
method: "POST",
2626
});
2727

apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ export async function fetchDashboardContractMetadata(
5959

6060
return {
6161
contractType: compilerMetadata?.name || "",
62-
image: contractMetadata.image || "",
62+
image:
63+
contractMetadata.image && typeof contractMetadata.image === "string"
64+
? contractMetadata.image
65+
: "",
6366
name: contractName,
6467
symbol: contractSymbol,
6568
};

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import * as ERC20Ext from "thirdweb/extensions/erc20";
1010
import * as ERC721Ext from "thirdweb/extensions/erc721";
1111
import * as ERC1155Ext from "thirdweb/extensions/erc1155";
1212
import { download } from "thirdweb/storage";
13-
import type { OverrideEntry } from "thirdweb/utils";
14-
import { maxUint256 } from "thirdweb/utils";
13+
import { isRecord, maxUint256, type OverrideEntry } from "thirdweb/utils";
1514
import type { z } from "zod";
1615
import type {
1716
ClaimCondition as LegacyClaimCondition,
@@ -86,7 +85,7 @@ export async function getClaimPhasesInLegacyFormat(
8685
]);
8786
const snapshot = await fetchSnapshot(
8887
condition.merkleRoot,
89-
contractMetadata.merkle,
88+
isRecord(contractMetadata.merkle) ? contractMetadata.merkle : {},
9089
options.contract.client,
9190
);
9291
return {

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ export default async function Image({
4747
chainName: info.chainMetadata.name,
4848
contractAddress: info.serverContract.address,
4949
displayName: contractDisplayName,
50-
logo: contractMetadata.image,
50+
logo:
51+
contractMetadata.image && typeof contractMetadata.image === "string"
52+
? contractMetadata.image
53+
: undefined,
5154
});
5255
} catch {
5356
return contractOGImageTemplate({

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,31 @@ export async function ERC20PublicPage(props: {
8989
chainMetadata={props.chainMetadata}
9090
clientContract={props.clientContract}
9191
contractCreator={contractCreator}
92-
image={contractMetadata.image}
92+
image={
93+
contractMetadata.image &&
94+
typeof contractMetadata.image === "string"
95+
? contractMetadata.image
96+
: undefined
97+
}
9398
isDashboardUser={isDashboardUser}
94-
name={contractMetadata.name}
99+
name={
100+
contractMetadata.name && typeof contractMetadata.name === "string"
101+
? contractMetadata.name
102+
: // if we do not have a contract name fall back to the address
103+
props.clientContract.address
104+
}
95105
socialUrls={
96106
typeof contractMetadata.social_urls === "object" &&
97107
contractMetadata.social_urls !== null
98108
? contractMetadata.social_urls
99109
: {}
100110
}
101-
symbol={contractMetadata.symbol}
111+
symbol={
112+
contractMetadata.symbol &&
113+
typeof contractMetadata.symbol === "string"
114+
? contractMetadata.symbol
115+
: undefined
116+
}
102117
/>
103118
</div>
104119
</div>
@@ -143,21 +158,25 @@ export async function ERC20PublicPage(props: {
143158
</div>
144159
)}
145160

146-
{showBuyEmbed && (
147-
<div className="container max-w-7xl pb-10">
148-
<GridPatternEmbedContainer>
149-
<BuyEmbed
150-
chainMetadata={props.chainMetadata}
151-
claimConditionMeta={claimConditionMeta}
152-
clientContract={props.clientContract}
153-
tokenAddress={props.clientContract.address}
154-
tokenDecimals={tokenDecimals}
155-
tokenName={contractMetadata.name}
156-
tokenSymbol={contractMetadata.symbol}
157-
/>
158-
</GridPatternEmbedContainer>
159-
</div>
160-
)}
161+
{showBuyEmbed &&
162+
contractMetadata.name &&
163+
typeof contractMetadata.name === "string" &&
164+
contractMetadata.symbol &&
165+
typeof contractMetadata.symbol === "string" && (
166+
<div className="container max-w-7xl pb-10">
167+
<GridPatternEmbedContainer>
168+
<BuyEmbed
169+
chainMetadata={props.chainMetadata}
170+
claimConditionMeta={claimConditionMeta}
171+
clientContract={props.clientContract}
172+
tokenAddress={props.clientContract.address}
173+
tokenDecimals={tokenDecimals}
174+
tokenName={contractMetadata.name}
175+
tokenSymbol={contractMetadata.symbol}
176+
/>
177+
</GridPatternEmbedContainer>
178+
</div>
179+
)}
161180

162181
<div className="container flex max-w-7xl grow flex-col pb-10">
163182
<div className="flex grow flex-col gap-8">
@@ -167,12 +186,15 @@ export async function ERC20PublicPage(props: {
167186
contractAddress={props.clientContract.address}
168187
/>
169188

170-
<RecentTransfers
171-
chainMetadata={props.chainMetadata}
172-
clientContract={props.clientContract}
173-
decimals={tokenDecimals}
174-
tokenSymbol={contractMetadata.symbol}
175-
/>
189+
{contractMetadata.symbol &&
190+
typeof contractMetadata.symbol === "string" && (
191+
<RecentTransfers
192+
chainMetadata={props.chainMetadata}
193+
clientContract={props.clientContract}
194+
decimals={tokenDecimals}
195+
tokenSymbol={contractMetadata.symbol}
196+
/>
197+
)}
176198
</div>
177199
</div>
178200
</div>

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ export async function NFTPublicPage(props: {
5353
const _isTokenByIndexSupported =
5454
props.type === "erc721" && isTokenByIndexSupported(functionSelectors);
5555

56+
// FIXME: this is technically a bad fallback but we gotta do what we gotta do
57+
const contractMetadataWithNameAndSymbolFallback = {
58+
...contractMetadata,
59+
// fall back to the contract address if the name is not set
60+
name: contractMetadata.name || props.clientContract.address,
61+
symbol: contractMetadata.symbol || "",
62+
};
5663
const buyNFTDropCard = nftDropClaimParams ? (
5764
<BuyNFTDropCardServer
5865
chainMetadata={props.chainMetadata}
@@ -73,7 +80,7 @@ export async function NFTPublicPage(props: {
7380
<NFTsGrid
7481
chainMetadata={props.chainMetadata}
7582
clientContract={props.clientContract}
76-
collectionMetadata={contractMetadata}
83+
collectionMetadata={contractMetadataWithNameAndSymbolFallback}
7784
gridClassName={
7885
buyNFTDropCard
7986
? "grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
@@ -110,7 +117,7 @@ export async function NFTPublicPage(props: {
110117
chainMetadata={props.chainMetadata}
111118
clientContract={props.clientContract}
112119
contractCreator={contractCreator}
113-
contractMetadata={contractMetadata}
120+
contractMetadata={contractMetadataWithNameAndSymbolFallback}
114121
isDashboardUser={isDashboardUser}
115122
>
116123
<ResponsiveLayout
@@ -138,7 +145,7 @@ export async function NFTPublicPage(props: {
138145
<PageLoadTokenViewerSheet
139146
chainMetadata={props.chainMetadata}
140147
clientContract={props.clientContract}
141-
collectionMetadata={contractMetadata}
148+
collectionMetadata={contractMetadataWithNameAndSymbolFallback}
142149
tokenByIndexSupported={_isTokenByIndexSupported}
143150
tokenId={BigInt(props.tokenId)}
144151
type={props.type}

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function TokenViewerSheet(
3737
clientContract: ThirdwebContract;
3838
chainMetadata: ChainMetadata;
3939
collectionMetadata: {
40-
name: string;
40+
name: string | null;
4141
image?: string;
4242
};
4343
type: "erc1155" | "erc721";
@@ -57,6 +57,11 @@ export function TokenViewerSheet(
5757
) {
5858
const tokenId = props.variant === "fetch-data" ? props.tokenId : props.nft.id;
5959

60+
const collectionMetadataWithNameFallback = {
61+
...props.collectionMetadata,
62+
name: props.collectionMetadata.name || props.clientContract.address,
63+
};
64+
6065
return (
6166
<Dialog
6267
onOpenChange={(open) => {
@@ -78,15 +83,15 @@ export function TokenViewerSheet(
7883
<FetchAndRenderTokenInfo
7984
chainMetadata={props.chainMetadata}
8085
clientContract={props.clientContract}
81-
collectionMetadata={props.collectionMetadata}
86+
collectionMetadata={collectionMetadataWithNameFallback}
8287
tokenByIndexSupported={props.tokenByIndexSupported}
8388
tokenId={props.tokenId}
8489
type={props.type}
8590
/>
8691
) : (
8792
<TokenInfoUI
8893
chainMetadata={props.chainMetadata}
89-
collectionMetadata={props.collectionMetadata}
94+
collectionMetadata={collectionMetadataWithNameFallback}
9095
contract={props.clientContract}
9196
data={props.nft}
9297
tokenId={tokenId}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@ export const SettingsMetadata = ({
8484
console.error(err);
8585
}
8686
}
87-
let image: string | undefined = metadata.data?.image;
87+
let image: string | undefined =
88+
metadata.data?.image && typeof metadata.data.image === "string"
89+
? metadata.data.image
90+
: undefined;
8891
try {
8992
image = image
9093
? // eslint-disable-next-line no-restricted-syntax

0 commit comments

Comments
 (0)