-
Notifications
You must be signed in to change notification settings - Fork 618
Change getContractMetadata() return type from any to unknown #8289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change getContractMetadata() return type from any to unknown #8289
Conversation
🦋 Changeset detectedLatest commit: 5084400 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
WalkthroughContract metadata typings tightened: index signatures moved from Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant getContractMetadata
participant contractURI as contractURI / fetch
participant resolveOnChain as on-chain name/symbol
participant ConsumerUI
Client->>getContractMetadata: request metadata
par parallel resolution
getContractMetadata->>contractURI: fetch/resolve URI (may decode base64)
getContractMetadata->>resolveOnChain: resolve on-chain name/symbol
end
contractURI-->>getContractMetadata: resolvedMetadata (unknown-typed)
resolveOnChain-->>getContractMetadata: resolvedName / resolvedSymbol
getContractMetadata->>getContractMetadata: runtime guards (isString/isRecord)
getContractMetadata-->>Client: metadata (name/symbol: string|null, index: unknown)
Client->>ConsumerUI: passes metadata
ConsumerUI->>ConsumerUI: guard fields (isString/isRecord) or use fallbacks
alt valid strings
ConsumerUI->>UI: render features (images, BuyEmbed, transfers)
else missing/invalid
ConsumerUI->>UI: apply fallbacks or skip rendering
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts (1)
27-27: Fix typo in TSDoc example.The example contains a typo:
getContracctshould begetContract. This prevents the example from compiling.Apply this diff:
- * const NFT = getContracct({ + * const NFT = getContract({apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx (3)
107-109: Fix type inconsistency forcollectionMetadata.name.The
FetchAndRenderTokenInfocomponent definescollectionMetadata.nameasstring(line 108), butTokenViewerSheet(line 40) declares it asstring | nulland passes it to this component (line 139). Similarly,TokenInfoUI(line 155) expectsstring, creating a type safety gap.Apply this diff to align the types:
function FetchAndRenderTokenInfo(props: { clientContract: ThirdwebContract; chainMetadata: ChainMetadata; type: "erc1155" | "erc721"; tokenId: bigint; tokenByIndexSupported: boolean; collectionMetadata: { - name: string; + name: string | null; }; }) {
154-157: Fix type inconsistency forcollectionMetadata.name.The
TokenInfoUIcomponent definescollectionMetadata.nameasstring(line 155), but receives it fromTokenViewerSheetwhere it's typed asstring | null(line 40, passed at lines 88-94).Apply this diff to align the types:
function TokenInfoUI(props: { data: NFT | undefined; contract: ThirdwebContract; type: "erc1155" | "erc721"; chainMetadata: ChainMetadata; tokenId: bigint; collectionMetadata: { - name: string; + name: string | null; image?: string; }; }) {
240-258: Add fallback for nullablecollectionMetadata.name.The
altattribute (line 242) and rendered text (line 257) useprops.collectionMetadata.name, which can now benull. Thealtattribute requires a string, and renderingnullprovides poor UX.Apply this diff to add fallbacks:
<Img - alt={props.collectionMetadata.name} + alt={props.collectionMetadata.name ?? "Collection"} className="size-3.5 rounded-lg" fallback={ <BoxIcon className="size-3.5 text-muted-foreground" /> } src={ props.collectionMetadata.image ? resolveSchemeWithErrorHandler({ client: props.contract.client, uri: props.collectionMetadata.image, }) || "" : "" } /> <span className="text-foreground text-sm "> - {props.collectionMetadata.name} + {props.collectionMetadata.name ?? "Collection"} </span>
🧹 Nitpick comments (3)
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts (1)
57-58: Consider a type guard instead of a type assertion.The type assertion bypasses compile-time safety. If
metadata.merklehas an unexpected shape at runtime (e.g., not a record, or values aren't strings), this could lead to silent failures or runtime errors when accessingmerkleData[merkleRoot].A type guard would validate the structure and align with the coding guideline to "narrow generics when possible":
+function isMerkleData(value: unknown): value is Record<string, string> { + return ( + typeof value === "object" && + value !== null && + Object.values(value).every((v) => typeof v === "string") + ); +} + export async function fetchProofsERC1155(options: { contract: ThirdwebContract; recipient: string; merkleRoot: string; }): Promise<ClaimProofERC1155 | null> { const { contract, merkleRoot, recipient } = options; const metadata = await getContractMetadata({ contract, }); - // TODO: fix the type assertion later - const merkleData = (metadata.merkle as Record<string, string>) || {}; + const merkleData = isMerkleData(metadata.merkle) ? metadata.merkle : {};Since the AI summary indicates this pattern appears in
fetch-proofs-erc20.tsandfetch-proofs-erc721.tsas well, you might want to extract the type guard into a shared utility to keep the validation consistent across all airdrop modules.Would you like me to help create a shared type guard helper that can be reused across all three airdrop fetch-proofs implementations?
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx (1)
231-238: Type guards correctly narrow unknown to string.The implementation properly handles the updated
getContractMetadata()return type. The type guards safely narrowunknowntostringbefore use.The current pattern using
&&has an implicit side effect: it filters out empty strings. Consider this simpler, more explicit alternative:return { - image: - contractMetadata?.image && typeof contractMetadata.image === "string" - ? contractMetadata.image - : undefined, - name: - contractMetadata?.name && typeof contractMetadata.name === "string" - ? contractMetadata.name - : undefined, + image: typeof contractMetadata?.image === "string" + ? contractMetadata.image + : undefined, + name: typeof contractMetadata?.name === "string" + ? contractMetadata.name + : undefined, };This approach:
- Handles null/undefined via optional chaining
- Preserves empty strings if they exist (though likely edge case for URLs/names)
- More direct and clearer intent
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx (1)
56-62: Consider refining the fallback strategy.The fallback approach is pragmatic and prevents rendering issues, but as the FIXME comment notes, using the contract address as a name fallback and an empty string for symbol may not provide the best UX.
Consider either:
- Rendering a more explicit placeholder like
"Unnamed Collection"for null names, or- Conditionally hiding UI elements that depend on these fields when they're unavailable
For now, the current approach is acceptable given it prevents breakage, but the FIXME should be addressed in a follow-up.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (18)
.changeset/witty-plums-read.md(1 hunks)apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx(4 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx(1 hunks)apps/dashboard/src/app/(app)/drops/[slug]/page.tsx(1 hunks)packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts(1 hunks)packages/thirdweb/src/extensions/common/read/getContractMetadata.ts(2 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx(1 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx(1 hunks)packages/thirdweb/src/utils/contract/fetchContractMetadata.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts(1 hunks)packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts(1 hunks)packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxpackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.tspackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxpackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/utils/contract/fetchContractMetadata.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tspackages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxpackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.tspackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxpackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/utils/contract/fetchContractMetadata.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tspackages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
packages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.tspackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.tspackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/utils/contract/fetchContractMetadata.tspackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tspackages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
.changeset/*.md
📄 CodeRabbit inference engine (AGENTS.md)
.changeset/*.md: Each change inpackages/*must include a changeset for the appropriate package
Version bump rules: patch for non‑API changes; minor for new/modified public API
Files:
.changeset/witty-plums-read.md
🧬 Code graph analysis (1)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (2)
apps/dashboard/src/@/components/blocks/grid-pattern-embed-container.tsx (1)
GridPatternEmbedContainer(3-23)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/RecentTransfers.tsx (1)
RecentTransfers(279-313)
🪛 LanguageTool
.changeset/witty-plums-read.md
[style] ~24-~24: This phrase is redundant. Consider using “outside”.
Context: ...is not (and was never) strictly defined outside of name and symbol and may contain any...
(OUTSIDE_OF)
🪛 markdownlint-cli2 (0.18.1)
.changeset/witty-plums-read.md
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Lint Packages
- GitHub Check: Unit Tests
- GitHub Check: Build Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (10)
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx (1)
194-201: LGTM: Proper type guards for metadata fields.The defensive type checks ensure
contractMetadata.imageandcontractMetadata.nameare strings before use, correctly handling the updatedunknownreturn type fromgetContractMetadata.apps/dashboard/src/app/(app)/drops/[slug]/page.tsx (1)
78-81: LGTM: Appropriate type guard for thumbnail selection.The type check for
contractMetadata.imageproperly handles theunknowntype, with a clear fallback chain to ensure a valid thumbnail value.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx (1)
50-53: LGTM: Proper type guard for logo parameter.The type check ensures only string values are passed as the logo, correctly handling the
unknowntype from contract metadata.packages/thirdweb/src/utils/contract/fetchContractMetadata.ts (1)
21-21: LGTM: Improved type safety.Changing from
anytounknownenforces type safety by requiring consumers to validate types before use, aligning with the coding guidelines to avoidanyunless unavoidable.packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (2)
23-25: LGTM: Improved type safety for public API.The return type change to
string | nullfornameandsymbol, plusunknownfor dynamic properties, correctly enforces type safety and prevents unsafe assumptions about metadata structure.
46-53: LGTM: Proper type guards with fallback.The type guards ensure
nameandsymbolfrom metadata are verified as strings before use, with appropriate fallbacks to individually resolved values. This defensive pattern handles theunknowntype correctly.apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx (1)
62-65: LGTM: Appropriate type guard for image field.The type check ensures only string values are assigned to the image field, correctly handling the
unknowntype from contract metadata with a sensible fallback..changeset/witty-plums-read.md (1)
1-44: LGTM! Clear changeset documentation.The changeset accurately documents the breaking change from
anytounknown, provides helpful before/after examples, and offers both recommended and quick-fix approaches for consumers.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (2)
92-116: LGTM! Proper type guards for metadata fields.The type guards correctly narrow
unknownmetadata fields tostringbefore passing to components. The fallback to the contract address forname(line 103) ensures a reasonable default when metadata is incomplete.
161-197: LGTM! Components properly gated on metadata availability.Both
BuyEmbedandRecentTransfersare now conditionally rendered only when required metadata fields (name,symbol) are available as strings, preventing runtime errors when metadata is incomplete.
...in)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
Outdated
Show resolved
Hide resolved
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
Outdated
Show resolved
Hide resolved
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
Outdated
Show resolved
Hide resolved
packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
Outdated
Show resolved
Hide resolved
...s/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
Outdated
Show resolved
Hide resolved
size-limit report 📦
|
27190a0 to
75903ae
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts (1)
28-28: Fix typo in the example code.The example contains a typo:
getContracctshould begetContract.Apply this diff:
-const TOKEN = getContracct({ +const TOKEN = getContract({
♻️ Duplicate comments (3)
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts (1)
59-60: Replace type assertion with proper type narrowing.The type assertion bypasses the type safety benefits of using
unknownoverany. As noted in the TODO comment, this needs to be fixed with proper runtime validation before casting.The past review comment on these lines provides detailed solutions using type guards to safely narrow the
unknowntype. Please implement one of those approaches.As per coding guidelines: "Avoid
anyandunknownunless unavoidable; narrow generics when possible."packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts (1)
83-86: The unsafe type assertion remains unaddressed.The type assertion
as Record<string, string>on line 83 still bypasses runtime validation. Sincemetadata.merkleisunknown, this assertion could fail if the metadata contains non-string values, is null, or has a different shape, leading to runtime errors downstream.Apply the previously suggested type guard to validate at runtime:
- const merkleInfo = (metadata.merkle as Record<string, string>)[key]; - if (merkleInfo) { + const merkleValue = (metadata.merkle as Record<string, unknown>)?.[key]; + if (typeof merkleValue === "string" && merkleValue) { - merkleInfos[key] = merkleInfo; + merkleInfos[key] = merkleValue; }This ensures only validated string values are assigned to
merkleInfos, aligning with the coding guideline to narrow types rather than using unsafe assertions.Based on coding guidelines.
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx (1)
231-238: Type guards correctly implemented.The type guards here correctly narrow
contractMetadata.imageandcontractMetadata.nametostring | undefined, consistent with the changes inClaimButton/index.tsx. This improves type safety as intended by the PR.Note: The code duplication issue between this file and
ClaimButton/index.tsxhas been flagged in the previous file's review.
🧹 Nitpick comments (2)
.changeset/witty-plums-read.md (2)
24-24: Minor: Simplify phrasing.Consider "outside" instead of "outside of" for more concise wording.
-Metadata is not (and was never) strictly defined outside of `name` and `symbol` and may contain any type of data in the record. +Metadata is not (and was never) strictly defined outside `name` and `symbol` and may contain any type of data in the record.
38-38: Minor: Use consistent emphasis style.Use asterisks for emphasis to match the markdown style throughout the file.
-_This is as "unsafe" as the type was before._ +*This is as "unsafe" as the type was before.*
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (18)
.changeset/witty-plums-read.md(1 hunks)apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx(4 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx(3 hunks)apps/dashboard/src/app/(app)/drops/[slug]/page.tsx(1 hunks)packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts(1 hunks)packages/thirdweb/src/extensions/common/read/getContractMetadata.ts(2 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx(1 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx(1 hunks)packages/thirdweb/src/utils/contract/fetchContractMetadata.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts(1 hunks)packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts(1 hunks)packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
- apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
- packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
- packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
- apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
.changeset/*.md
📄 CodeRabbit inference engine (AGENTS.md)
.changeset/*.md: Each change inpackages/*must include a changeset for the appropriate package
Version bump rules: patch for non‑API changes; minor for new/modified public API
Files:
.changeset/witty-plums-read.md
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to **/*.{ts,tsx} : Avoid `any` and `unknown` unless unavoidable; narrow generics when possible
Applied to files:
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
🪛 LanguageTool
.changeset/witty-plums-read.md
[style] ~24-~24: This phrase is redundant. Consider using “outside”.
Context: ...is not (and was never) strictly defined outside of name and symbol and may contain any...
(OUTSIDE_OF)
🪛 markdownlint-cli2 (0.18.1)
.changeset/witty-plums-read.md
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Unit Tests
- GitHub Check: Size
- GitHub Check: Lint Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (6)
packages/thirdweb/src/utils/contract/fetchContractMetadata.ts (1)
21-21: LGTM! Type safety improvement.Changing from
anytounknownproperly reflects that contract metadata can contain arbitrary data and forces callers to perform runtime type checks before accessing properties..changeset/witty-plums-read.md (1)
1-44: Excellent migration guidance.The changeset clearly documents the breaking change with helpful before/after examples and provides both recommended (type guards) and quick-fix (type assertions) approaches for migration.
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx (2)
40-40: Proper handling of nullable metadata.The type change correctly reflects that
namecan be null fromgetContractMetadata().
60-63: Excellent defensive fallback pattern.Creating
collectionMetadataWithNameFallbackensures child components always receive a valid string name by falling back to the contract address when metadata name is missing. This prevents UI rendering issues while maintaining type safety.packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (2)
23-25: Type safety improvement aligns with coding guidelines.The updated return type properly reflects the uncertain nature of contract metadata beyond
nameandsymbol, forcing callers to perform runtime checks. As per coding guidelines: "Avoidanyandunknownunless unavoidable."
46-53: Robust type guards ensure safe metadata handling.The implementation correctly validates that
nameandsymbolfrom metadata are strings before using them, falling back to the direct contract method results otherwise. The logic properly handles all cases:
- Valid string in metadata → use it
- Missing, null, or non-string in metadata → use fallback from contract methods
- Other metadata properties → preserved as unknown
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
Outdated
Show resolved
Hide resolved
Codecov Report❌ Patch coverage is ❌ Your patch status has failed because the patch coverage (68.42%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #8289 +/- ##
=======================================
Coverage 54.89% 54.90%
=======================================
Files 919 919
Lines 60623 60665 +42
Branches 4116 4128 +12
=======================================
+ Hits 33282 33309 +27
- Misses 27240 27254 +14
- Partials 101 102 +1
🚀 New features to boost your workflow:
|
75903ae to
9f52d84
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx (1)
109-109: Add type guard fornamefield to matchimagehandling.While the
imagefield (lines 87-90) has an explicittypeof === "string"check,nameis accessed without a type guard. For consistency with the PR's pattern of adding type guards throughout, consider:- name: metadata.data?.name || "", + name: typeof metadata.data?.name === "string" ? metadata.data.name : "",This ensures consistent handling of
unknown-typed metadata fields and aligns with the PR's type-safety improvements.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx (1)
408-410: Type inconsistency: PageLoadTokenViewerSheet requires non-null name while parent accepts null.
PageLoadTokenViewerSheetexpectscollectionMetadata.name: string, butTokenViewerSheetnow acceptsstring | nulland handles the null case internally with a fallback (lines 60-63). This creates an inconsistent API where callers ofPageLoadTokenViewerSheetmust pre-handle null names, while direct callers ofTokenViewerSheetcan pass null.For consistency, consider accepting
string | nullhere as well:export function PageLoadTokenViewerSheet(props: { clientContract: ThirdwebContract; chainMetadata: ChainMetadata; type: "erc1155" | "erc721"; tokenId: bigint; tokenByIndexSupported: boolean; collectionMetadata: { - name: string; + name: string | null; }; }) {This allows all entry points to have the same type contract and lets
TokenViewerSheetcentralize the fallback logic.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts (1)
284-297: SnapshotEntry type mismatch and potential runtime parsing issues.
SnapshotEntry.maxClaimableand.priceare declared asstring, but you returnbigint. JSON cannot carry BigInt;download(...).json()will yield string/number. Convert to strings and relax the temporaryclaimtyping to match JSON.- return raw.claims.map( - (claim: { - address: string; - maxClaimable: bigint; - price: bigint; - currencyAddress: string; - }) => ({ - address: claim.address, - currencyAddress: claim.currencyAddress, - maxClaimable: claim.maxClaimable, - price: claim.price, - }), - ); + if (!Array.isArray(raw.claims)) return null; + return raw.claims.map( + (claim: { + address: string; + maxClaimable: string | number; + price?: string | number; + currencyAddress?: string; + }) => ({ + address: String(claim.address), + currencyAddress: claim.currencyAddress, + maxClaimable: String(claim.maxClaimable), + price: + claim.price === undefined ? undefined : String(claim.price), + }), + );Optionally add a zod schema here to validate
rawbefore mapping.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx (1)
1-1: Mark as a Server Component.Add
import "server-only";at the top to comply with the dashboard server-component guideline.
As per coding guidelines.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (1)
1-1: Mark as a Server Component.Add
import "server-only";at the top.
As per coding guidelines.
♻️ Duplicate comments (8)
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx (1)
194-201: Type guards correctly narrow metadata fields to string.The implementation properly validates that
imageandnameare strings before using them, which aligns with the PR's goal of improving type safety.Note: The code duplication with
CreateDirectListingButtonhas already been flagged in previous review comments.packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx (1)
231-238: Type guards correctly implemented.The runtime type checks properly narrow
imageandnametostring | undefined, ensuring type safety when accessing metadata fields.Note: The duplication of this pattern with
ClaimButton/index.tsxhas already been flagged in previous review comments suggesting extraction to a shared utility.packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts (1)
59-60: Replace type assertion with runtime type guard.The type assertion bypasses the type safety benefits of moving from
anytounknown. As noted in the TODO, this needs proper runtime validation to ensuremetadata.merkleis actually aRecord<string, string>.Based on coding guidelines: "Avoid
anyandunknownunless unavoidable; narrow generics when possible."packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts (1)
83-86: Add runtime validation for string values.The type assertion assumes
metadata.merkle[key]returns a string without validation. While the truthy check prevents undefined/null values from being assigned, it doesn't verify the value is actually a string.Based on coding guidelines: "Avoid
anyandunknownunless unavoidable; narrow generics when possible."packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts (1)
57-58: Replace type assertion with runtime type guard.The type assertion bypasses type safety. This is the same unsafe pattern found in other airdrop fetch-proofs files.
Based on coding guidelines: "Avoid
anyandunknownunless unavoidable; narrow generics when possible."packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts (1)
121-122: Address the type assertion technical debt.The type assertion bypasses runtime validation as acknowledged by the TODO comment.
Based on coding guidelines: "Avoid
anyandunknownunless unavoidable; narrow generics when possible."packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts (1)
57-58: Replace type assertion with runtime type guard.The type assertion bypasses type safety without validating the structure of
metadata.merkle.Based on coding guidelines: "Avoid
anyandunknownunless unavoidable; narrow generics when possible."apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts (1)
89-90: Replace unsafe merkle cast with a runtime type guard.The
as Record<string, string> | undefineddefeats theunknownhardening and risks runtime errors when indexingmerkleMetadata[merkleRoot]. Use a narrow guard and fall back toundefined.Apply:
+function isMerkleMetadata(v: unknown): v is Record<string, string> { + return !!v && typeof v === "object" && Object.entries(v).every( + ([k, val]) => typeof k === "string" && typeof val === "string", + ); +} @@ - // TODO: fix the type assertion - contractMetadata.merkle as Record<string, string> | undefined, + isMerkleMetadata(contractMetadata.merkle) + ? contractMetadata.merkle + : undefined,Based on learnings.
🧹 Nitpick comments (6)
packages/thirdweb/src/utils/contract/fetchContractMetadata.ts (2)
12-18: JSDoc nit: “token” → “contract”; add failure behavior note.This helper fetches contract metadata; also document that invalid base64/failed downloads return
undefined.- * Fetches the metadata for a token. + * Fetches the metadata for a contract. @@ - * @returns The token metadata. + * @returns The contract metadata, or `undefined` if decoding/downloading fails.
38-42: Handle download/JSON failures to keep semantics consistent with base64 branch.
download(...).json()can throw. Catch and returnundefinedlike the base64 path.- const { download } = await import("../../storage/download.js"); - return await (await download({ client, uri })).json(); + const { download } = await import("../../storage/download.js"); + try { + const res = await download({ client, uri }); + return await res.json(); + } catch (e) { + console.error("Failed to fetch contract metadata", { uri }, e); + return undefined; + }apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx (1)
56-62: Harden fallbacks with explicit string guards.Avoid truthy checks that treat
""as missing and guard against non-strings.- const contractMetadataWithNameAndSymbolFallback = { - ...contractMetadata, - // fall back to the contract address if the name is not set - name: contractMetadata.name || props.clientContract.address, - symbol: contractMetadata.symbol || "", - }; + const contractMetadataWithNameAndSymbolFallback = { + ...contractMetadata, + name: + typeof contractMetadata.name === "string" && + contractMetadata.name.trim().length > 0 + ? contractMetadata.name + : props.clientContract.address, + symbol: + typeof contractMetadata.symbol === "string" + ? contractMetadata.symbol + : "", + };apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (2)
92-97: Guards look good; consider extracting safe fields and avoid mutating metadata.Precompute
safeImage,safeName,safeSymbolonce and pass them through; this also lets you drop the earlier mutation ofcontractMetadata.image.Outside this range, add:
const safeImage = typeof contractMetadata.image === "string" ? contractMetadata.image : tokenInfoFromUB?.iconUri; const safeName = typeof contractMetadata.name === "string" && contractMetadata.name ? contractMetadata.name : props.clientContract.address; const safeSymbol = typeof contractMetadata.symbol === "string" ? contractMetadata.symbol : undefined;Then use
image={safeImage},name={safeName},symbol={safeSymbol}.Also applies to: 100-104, 111-116
161-179: EnsureshowBuyEmbedis a boolean, notboolean | object.Currently
const showBuyEmbed = isUBSupported || claimConditionMeta;infers a union. Coerce to boolean for clarity and type hygiene.Outside this range:
- const showBuyEmbed = isUBSupported || claimConditionMeta; + const showBuyEmbed = Boolean(isUBSupported || claimConditionMeta);packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (1)
46-54: Name/symbol guards LGTM; consider documenting nullability and removing the TODO.
- Keep the guards; they correctly prefer on-chain metadata when valid.
- Update TSDoc to note
name/symbolmay benullif neither source yields a string.- Replace
// TODO: basic parsing?with a tracked issue or implement a minimal parser if needed.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (19)
.changeset/witty-plums-read.md(1 hunks)apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx(4 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx(1 hunks)apps/dashboard/src/app/(app)/drops/[slug]/page.tsx(1 hunks)packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts(1 hunks)packages/thirdweb/src/extensions/common/read/getContractMetadata.ts(2 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx(1 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx(1 hunks)packages/thirdweb/src/utils/contract/fetchContractMetadata.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts(1 hunks)packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts(1 hunks)packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
- packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
- apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
- apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxpackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxpackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.tspackages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxpackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxpackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.tspackages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
.changeset/*.md
📄 CodeRabbit inference engine (AGENTS.md)
.changeset/*.md: Each change inpackages/*must include a changeset for the appropriate package
Version bump rules: patch for non‑API changes; minor for new/modified public API
Files:
.changeset/witty-plums-read.md
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxpackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.tspackages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
🧠 Learnings (1)
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to **/*.{ts,tsx} : Avoid `any` and `unknown` unless unavoidable; narrow generics when possible
Applied to files:
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
🧬 Code graph analysis (1)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (2)
apps/dashboard/src/@/components/blocks/grid-pattern-embed-container.tsx (1)
GridPatternEmbedContainer(3-23)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/RecentTransfers.tsx (1)
RecentTransfers(279-313)
🪛 GitHub Check: codecov/patch
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
[warning] 60-60: packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts#L60
Added line #L60 was not covered by tests
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
[warning] 201-201: packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx#L201
Added line #L201 was not covered by tests
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
[warning] 58-58: packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts#L58
Added line #L58 was not covered by tests
packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
[warning] 83-86: packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts#L83-L86
Added lines #L83 - L86 were not covered by tests
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
[warning] 231-231: packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx#L231
Added line #L231 was not covered by tests
[warning] 234-234: packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx#L234
Added line #L234 was not covered by tests
[warning] 238-238: packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx#L238
Added line #L238 was not covered by tests
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
[warning] 58-58: packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts#L58
Added line #L58 was not covered by tests
🪛 LanguageTool
.changeset/witty-plums-read.md
[style] ~24-~24: This phrase is redundant. Consider using “outside”.
Context: ...is not (and was never) strictly defined outside of name and symbol and may contain any...
(OUTSIDE_OF)
🪛 markdownlint-cli2 (0.18.1)
.changeset/witty-plums-read.md
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Build Packages
- GitHub Check: Lint Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (7)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx (1)
87-90: LGTM! Type guard correctly narrowsimagefromunknown.The defensive type check ensures
imageis a string before assignment, properly handling the stricterunknownreturn type fromgetContractMetadata().apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx (1)
60-63: Verify empty string handling in the fallback pattern.The
||operator will replace bothnulland empty string""with the contract address. If an empty collection name should be preserved (i.e., displayed as empty rather than showing the address), use nullish coalescing instead.Consider this alternative if empty strings should be preserved:
const collectionMetadataWithNameFallback = { ...props.collectionMetadata, - name: props.collectionMetadata.name || props.clientContract.address, + name: props.collectionMetadata.name ?? props.clientContract.address, };If replacing empty strings is intentional (which seems reasonable for UX), the current code is correct.
.changeset/witty-plums-read.md (1)
1-44: Well-documented breaking change with clear migration guidance.The changeset accurately describes the type changes and provides both recommended (type guards) and quick-fix (type assertions) migration paths. The examples are clear and actionable.
Note: The static analysis style hints (emphasis style, "outside of" wording) are pedantic and can be safely ignored.
packages/thirdweb/src/utils/contract/fetchContractMetadata.ts (1)
21-21: Type tightening tounknownis good.Return type change improves safety and forces callers to narrow before use.
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx (1)
83-84: LGTM on consistent fallback propagation.Passing
contractMetadataWithNameAndSymbolFallbackto all consumers keeps downstream components simple and typed.Also applies to: 120-121, 149-149
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (1)
189-197: Conditional render for RecentTransfers is correct.Rendering only when
symbolis a string avoids UI glitches and ensures token metadata is present.packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (1)
23-26: Public return type tightened — good upgrade.
name/symbol: string | nulland[key: string]: unknownalign consumers with safer access patterns.
9f52d84 to
b2844f6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts (1)
77-84: Consider explicit string validation for merkle values.The
isRecordguard ensuresmetadata.merkleis aRecord<string, string>, but for maximum clarity and to fully address the previous review's technical debt, consider explicitly validating thatmerkleInfois a string:- const merkleInfo = isRecord(metadata.merkle) - ? metadata.merkle[key] - : undefined; - if (merkleInfo) { - merkleInfos[key] = merkleInfo; - } + if (isRecord(metadata.merkle)) { + const merkleInfo = metadata.merkle[key]; + if (typeof merkleInfo === "string") { + merkleInfos[key] = merkleInfo; + } + }This makes the string validation explicit and filters out any edge cases where the value might not be a string (though
isRecordshould already ensure this).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (24)
.changeset/witty-plums-read.md(1 hunks)apps/dashboard/src/@/api/universal-bridge/token-list.ts(1 hunks)apps/dashboard/src/@/api/universal-bridge/tokens.ts(1 hunks)apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts(2 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx(4 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx(1 hunks)apps/dashboard/src/app/(app)/drops/[slug]/page.tsx(1 hunks)packages/service-utils/src/core/get-auth-headers.ts(1 hunks)packages/thirdweb/src/exports/utils.ts(1 hunks)packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts(2 hunks)packages/thirdweb/src/extensions/common/read/getContractMetadata.ts(2 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx(1 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx(1 hunks)packages/thirdweb/src/utils/contract/fetchContractMetadata.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts(2 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts(2 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts(2 hunks)packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts(2 hunks)packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts(2 hunks)packages/thirdweb/src/utils/type-guards.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
- packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
- packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
- packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
- packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
- packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/@/api/universal-bridge/tokens.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxpackages/thirdweb/src/utils/type-guards.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/exports/utils.tsapps/dashboard/src/@/api/universal-bridge/token-list.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsxpackages/service-utils/src/core/get-auth-headers.tspackages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/@/api/universal-bridge/tokens.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxpackages/thirdweb/src/utils/type-guards.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/exports/utils.tsapps/dashboard/src/@/api/universal-bridge/token-list.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsxpackages/service-utils/src/core/get-auth-headers.tspackages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/@/api/universal-bridge/tokens.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/@/api/universal-bridge/token-list.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/@/api/universal-bridge/tokens.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/@/api/universal-bridge/token-list.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsx
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/app/(app)/drops/[slug]/page.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
packages/thirdweb/src/utils/type-guards.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/exports/utils.tspackages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
packages/thirdweb/src/exports/**
📄 CodeRabbit inference engine (CLAUDE.md)
packages/thirdweb/src/exports/**: Export everything viaexports/directory, grouped by feature in the SDK public API
Every public symbol must have comprehensive TSDoc with at least one@exampleblock that compiles and custom annotation tags (@beta,@internal,@experimental)
Files:
packages/thirdweb/src/exports/utils.ts
.changeset/*.md
📄 CodeRabbit inference engine (AGENTS.md)
.changeset/*.md: Each change inpackages/*must include a changeset for the appropriate package
Version bump rules: patch for non‑API changes; minor for new/modified public API
Files:
.changeset/witty-plums-read.md
🧠 Learnings (2)
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to packages/thirdweb/exports/** : Export all public API via `packages/thirdweb/exports/`, grouped by feature
Applied to files:
packages/thirdweb/src/exports/utils.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to packages/thirdweb/src/exports/** : Export everything via `exports/` directory, grouped by feature in the SDK public API
Applied to files:
packages/thirdweb/src/exports/utils.ts
🧬 Code graph analysis (2)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (2)
apps/dashboard/src/@/components/blocks/grid-pattern-embed-container.tsx (1)
GridPatternEmbedContainer(3-23)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/RecentTransfers.tsx (1)
RecentTransfers(279-313)
packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts (1)
packages/thirdweb/src/utils/type-guards.ts (1)
isRecord(35-53)
🪛 LanguageTool
.changeset/witty-plums-read.md
[style] ~24-~24: This phrase is redundant. Consider using “outside”.
Context: ...is not (and was never) strictly defined outside of name and symbol and may contain any...
(OUTSIDE_OF)
🪛 markdownlint-cli2 (0.18.1)
.changeset/witty-plums-read.md
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: Unit Tests
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Lint Packages
- GitHub Check: Build Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (14)
apps/dashboard/src/app/(app)/drops/[slug]/page.tsx (1)
78-81: LGTM! Clean type guard for metadata.image.The thumbnail fallback logic correctly guards
contractMetadata.imageby verifying it's a string before use, preventing runtime errors with the newunknowntyping.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx (1)
50-53: LGTM! Proper type guard for OpenGraph image.The double-check (
contractMetadata.image && typeof contractMetadata.image === "string") ensures type safety before passing the logo URL to the template.packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (1)
44-54: LGTM! Type guards correctly handle nullable metadata.The implementation properly guards
resolvedMetadata.nameandresolvedMetadata.symbolbefore use, falling back to resolved values from direct contract calls. The spread ofresolvedMetadataat line 45 is safe—contrary to the previous review comment, spreadingnullorundefinedin JavaScript evaluates to an empty object{}and does not throw.packages/thirdweb/src/utils/type-guards.ts (2)
11-13: LGTM! Clean string type guard.Simple and effective implementation for runtime string validation.
35-53: LGTM! Well-designed generic record validator.The
isRecordimplementation is excellent:
- Flexible generics with sensible defaults (
Record<string, string>)- Optional custom key/value guards for specialized validation
- Properly excludes arrays and validates all entries
This provides a robust foundation for safe metadata access across the codebase.
packages/thirdweb/src/exports/utils.ts (1)
201-204: LGTM! Proper public API exposure.The type guard exports follow the established pattern for the SDK's public API, making
isRecord,isString, and other guards available to consumers for safe metadata handling.As per coding guidelines
.changeset/witty-plums-read.md (1)
1-43: LGTM! Excellent migration documentation.The changeset clearly communicates:
- The breaking change (type signatures)
- Before/after behavior with examples
- Recommended best-practice fix using type guards
- Pragmatic quick-fix alternative
The static analysis hints about style are minor and can be safely ignored—the content is clear and helpful for users upgrading.
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx (1)
62-65: LGTM! Consistent metadata image handling.The type guard correctly narrows
contractMetadata.imagetostringbefore assignment, aligning with the type-safety improvements throughout the dashboard.apps/dashboard/src/@/api/universal-bridge/token-list.ts (1)
24-24: LGTM! Redundant type assertion removed.TypeScript correctly infers the headers object type from the literal, making the explicit
as Record<string, string>assertion unnecessary.packages/service-utils/src/core/get-auth-headers.ts (1)
21-21: LGTM! Redundant type assertions removed.Since the function signature explicitly declares
Record<string, string>as the return type (line 13), TypeScript automatically validates that all return statements match this type. The explicitasassertions on lines 21, 27, and 33 were unnecessary.apps/dashboard/src/@/api/universal-bridge/tokens.ts (1)
24-24: LGTM! Redundant type assertion removed.Consistent with the cleanup in
token-list.ts, the explicit type assertion is unnecessary here as TypeScript infers the correct type from the object literal.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (3)
92-116: Excellent defensive type handling for contract metadata.The type guards correctly narrow
unknownmetadata fields to strings before passing them toContractHeaderUI. The fallback fornametoprops.clientContract.addressensures a meaningful display value even when metadata is unavailable.
161-179: Type guards correctly prevent rendering with invalid metadata.The compound condition ensures
BuyEmbedonly renders when both functionality is available (showBuyEmbed) and required metadata (name,symbol) are valid strings. This prevents runtime errors but means buy/swap functionality may be hidden if metadata is incomplete.
189-197: LGTM! Conditional rendering prevents errors.The type guard ensures
RecentTransfersonly renders whensymbolis a valid string, preventing undefined from reaching the component'stokenSymbolprop.
b2844f6 to
febf713
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx (1)
194-201: Deduplicate metadata string extraction.The
image/nameguards are correct but duplicated in other components. Extract a shared helper (e.g.,getMetadataStrings(metadata, ['image','name'])) and reuse.packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (1)
45-54: Bug: spreading possibly null/undefined metadata will throw.
resolvedMetadatamay benull(no URI/error). Guard the spread.- return { - ...resolvedMetadata, + return { + ...(resolvedMetadata ?? {}), name: resolvedMetadata?.name && typeof resolvedMetadata.name === "string" ? resolvedMetadata.name : resolvedName, symbol: resolvedMetadata?.symbol && typeof resolvedMetadata.symbol === "string" ? resolvedMetadata.symbol : resolvedSymbol, };
🧹 Nitpick comments (5)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx (1)
56-62: Consider using nullish coalescing (??) for more precise null handling.The
||operator treats empty strings as falsy, which meanscontractMetadata.name || props.clientContract.addresswill fall back to the address even whennameis an empty string. Since the type changed fromstringtostring | null, using the nullish coalescing operator (??) would more precisely handle onlynull/undefined:const contractMetadataWithNameAndSymbolFallback = { ...contractMetadata, - name: contractMetadata.name || props.clientContract.address, - symbol: contractMetadata.symbol || "", + name: contractMetadata.name ?? props.clientContract.address, + symbol: contractMetadata.symbol ?? "", };This preserves empty strings as valid values if that's the intended behavior. If empty strings should trigger fallbacks (current behavior), the existing code is correct but the distinction is worth noting.
.changeset/witty-plums-read.md (2)
24-26: Tighten phrasing per linter.-Metadata is not (and was never) strictly defined outside of `name` and `symbol` and may contain any type of data in the record. +Metadata is not (and was never) strictly defined outside `name` and `symbol` and may contain any type of data in the record.
36-43: Use asterisk emphasis to satisfy markdownlint (MD049).-**Quick fix:** +**Quick fix:** @@ -_This is as "unsafe" as the type was before._ +*This is as "unsafe" as the type was before.*Optional: add a note that consumers can use
isRecord/isStringfromthirdweb/utils.packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts (1)
77-84: Pre‑guardmetadata.merkleonce; iterate only when it’s a record.Avoid computing keys from non-objects and repeating the guard in the loop.
- // keep the old merkle roots from other tokenIds - for (const key of Object.keys(metadata.merkle || {})) { - const merkleInfo = isRecord(metadata.merkle) - ? metadata.merkle[key] - : undefined; - if (merkleInfo) { - merkleInfos[key] = merkleInfo; - } - } + // keep the old merkle roots from other tokenIds + const merkleData = isRecord(metadata.merkle) ? metadata.merkle : undefined; + if (merkleData) { + for (const [key, value] of Object.entries(merkleData)) { + if (value) { + merkleInfos[key] = value; + } + } + }packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx (1)
231-238: Type guards correctly enforce runtime safety for contract metadata.The guards properly check both existence and string type before returning
imageandname. This aligns well with the PR's goal of moving fromanytounknownfor contract metadata.Optional: Consider using the
isStringtype guard utility for consistency.Based on the PR's AI summary, type guard utilities like
isStringwere introduced inpackages/thirdweb/src/utils/type-guards.ts. Using a shared utility would improve consistency across the codebase:import { isString } from "../../../../../../utils/type-guards.js"; return { image: contractMetadata?.image && isString(contractMetadata.image) ? contractMetadata.image : undefined, name: contractMetadata?.name && isString(contractMetadata.name) ? contractMetadata.name : undefined, };However, the inline
typeofchecks are also clear and valid, so this is purely a consistency consideration.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (25)
.changeset/witty-plums-read.md(1 hunks)apps/dashboard/src/@/api/universal-bridge/token-list.ts(1 hunks)apps/dashboard/src/@/api/universal-bridge/tokens.ts(1 hunks)apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts(2 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx(4 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx(1 hunks)apps/dashboard/src/app/(app)/drops/[slug]/page.tsx(1 hunks)packages/service-utils/src/core/get-auth-headers.ts(1 hunks)packages/thirdweb/src/exports/utils.ts(1 hunks)packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts(2 hunks)packages/thirdweb/src/extensions/common/read/getContractMetadata.ts(2 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx(1 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx(1 hunks)packages/thirdweb/src/utils/contract/fetchContractMetadata.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts(2 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts(2 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts(2 hunks)packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts(2 hunks)packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts(2 hunks)packages/thirdweb/src/utils/type-guard.test.ts(1 hunks)packages/thirdweb/src/utils/type-guards.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
- apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
- packages/service-utils/src/core/get-auth-headers.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
- packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
- packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
- apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
- packages/thirdweb/src/utils/type-guards.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
packages/thirdweb/src/utils/type-guard.test.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxapps/dashboard/src/@/api/universal-bridge/tokens.tsapps/dashboard/src/@/api/universal-bridge/token-list.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxpackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/exports/utils.ts
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.{ts,tsx}: Place tests alongside code:foo.ts↔foo.test.ts
Use real function invocations with stub data in tests; avoid brittle mocks
Use Mock Service Worker (MSW) for fetch/HTTP call interception in tests
Keep tests deterministic and side-effect free
UseFORKED_ETHEREUM_CHAINfor mainnet interactions andANVIL_CHAINfor isolated tests
**/*.test.{ts,tsx}: Co‑locate tests asfoo.test.ts(x)next to the implementation
Use real function invocations with stub data; avoid brittle mocks
Use MSW to intercept HTTP calls for network interactions; mock only hard‑to‑reproduce scenarios
Keep tests deterministic and side‑effect free; use Vitest
Files:
packages/thirdweb/src/utils/type-guard.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
packages/thirdweb/src/utils/type-guard.test.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxapps/dashboard/src/@/api/universal-bridge/tokens.tsapps/dashboard/src/@/api/universal-bridge/token-list.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxpackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/exports/utils.ts
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
packages/thirdweb/src/utils/type-guard.test.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsxpackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/exports/utils.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/@/api/universal-bridge/tokens.tsapps/dashboard/src/@/api/universal-bridge/token-list.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/@/api/universal-bridge/tokens.tsapps/dashboard/src/@/api/universal-bridge/token-list.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
packages/thirdweb/src/exports/**
📄 CodeRabbit inference engine (CLAUDE.md)
packages/thirdweb/src/exports/**: Export everything viaexports/directory, grouped by feature in the SDK public API
Every public symbol must have comprehensive TSDoc with at least one@exampleblock that compiles and custom annotation tags (@beta,@internal,@experimental)
Files:
packages/thirdweb/src/exports/utils.ts
.changeset/*.md
📄 CodeRabbit inference engine (AGENTS.md)
.changeset/*.md: Each change inpackages/*must include a changeset for the appropriate package
Version bump rules: patch for non‑API changes; minor for new/modified public API
Files:
.changeset/witty-plums-read.md
🧠 Learnings (2)
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to packages/thirdweb/exports/** : Export all public API via `packages/thirdweb/exports/`, grouped by feature
Applied to files:
packages/thirdweb/src/exports/utils.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to packages/thirdweb/src/exports/** : Export everything via `exports/` directory, grouped by feature in the SDK public API
Applied to files:
packages/thirdweb/src/exports/utils.ts
🧬 Code graph analysis (4)
packages/thirdweb/src/utils/type-guard.test.ts (1)
packages/thirdweb/src/utils/type-guards.ts (4)
isObject(7-9)isString(11-13)isObjectWithKeys(22-27)isRecord(35-53)
packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts (1)
packages/thirdweb/src/utils/type-guards.ts (1)
isRecord(35-53)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (2)
apps/dashboard/src/@/components/blocks/grid-pattern-embed-container.tsx (1)
GridPatternEmbedContainer(3-23)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/RecentTransfers.tsx (1)
RecentTransfers(279-313)
packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts (1)
packages/thirdweb/src/utils/type-guards.ts (1)
isRecord(35-53)
🪛 LanguageTool
.changeset/witty-plums-read.md
[style] ~24-~24: This phrase is redundant. Consider using “outside”.
Context: ...is not (and was never) strictly defined outside of name and symbol and may contain any...
(OUTSIDE_OF)
🪛 markdownlint-cli2 (0.18.1)
.changeset/witty-plums-read.md
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Unit Tests
- GitHub Check: Build Packages
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Size
- GitHub Check: Lint Packages
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (11)
apps/dashboard/src/@/api/universal-bridge/tokens.ts (1)
24-24: LGTM: Redundant type assertion removed.TypeScript correctly infers this object literal as compatible with
Record<string, string>since all values are strings. The explicit assertion was unnecessary.apps/dashboard/src/@/api/universal-bridge/token-list.ts (1)
24-24: LGTM: Redundant type assertion removed.Consistent with the cleanup in
tokens.ts, this removes an unnecessary type assertion. TypeScript's inference correctly types this object literal for thefetchheaders.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx (1)
83-83: LGTM: Fallback object properly propagated to downstream components.The changes correctly pass
contractMetadataWithNameAndSymbolFallbackto all components that previously receivedcontractMetadata. This ensures downstream components receive guaranteed non-nullnameandsymbolvalues, which aligns with the PR's goal of improving type safety.Also applies to: 120-120, 148-148
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (5)
92-97: LGTM! Type guard correctly handles optional image.The type guard properly checks that
imageexists and is a string before passing it to the component, with an appropriateundefinedfallback.
99-104: LGTM! Excellent fallback strategy for missing name.The type guard ensures
nameis a valid string, and the fallback to the contract address provides a reasonable display value when metadata is unavailable or invalid.
111-116: LGTM! Type guard correctly handles optional symbol.The type guard properly validates
symbolbefore use, with an appropriateundefinedfallback for cases where the metadata is unavailable.
161-179: LGTM! Defensive rendering guards ensure metadata availability.The type guards correctly ensure both
nameandsymbolare non-empty strings before rendering theBuyEmbedcomponent. This defensive approach prevents rendering the embed when essential token metadata is unavailable, which aligns with the PR's goal of improving type safety.
189-197: LGTM! Rendering guard ensures valid symbol.The type guard correctly validates that
symbolis a non-empty string before renderingRecentTransfers, preventing the component from rendering with invalid or missing metadata.packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (1)
23-26: Return type tightening is correct.
name/symbolasstring | nulland[key: string]: unknownalign with the PR goals and downstream guards.packages/thirdweb/src/utils/type-guard.test.ts (1)
1-115: Solid guard coverage.Tests exercise key paths and edge cases; behavior matches current guard semantics.
packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts (1)
123-124: Good replacement for unsafe cast.Guarding
metadata.merklewithisRecordeliminates the assertion and ensures only string values are used.
2888368 to
44d2b8e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/thirdweb/src/exports/utils.ts (1)
201-204: Re‑export OK; ensure public docs with examples for new guards.These functions are now public via exports/. Confirm each has required TSDoc with a compiling @example per package rules, or adjust tags.
#!/bin/bash set -euo pipefail f="packages/thirdweb/src/utils/type-guards.ts" echo "Checking docblocks and @example for exported guards..." rg -nP 'export\\s+function\\s+(isObject|isString|isObjectWithKeys|isRecord)\\b' "$f" echo "--- Docblocks above functions ---" awk ' /export function (isObject|isString|isObjectWithKeys|isRecord)\(/{ if (doc=="") print "No docblock for " $3; doc=""; } /^\\s*\\/\\*\\*/{doc="start"} /^\\s*\\*\\/@/{if(doc=="start") doc=""} ' "$f" || true echo "--- @example counts ---" rg -n '@example' "$f" || true
🧹 Nitpick comments (9)
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx (2)
62-65: Good guard on image; consider using shared isString for consistency.Current typeof check is fine; to stay consistent with new guards exported from utils, you can use isString(contractMetadata.image). Purely optional.
15-19: Type the query and add cache settings (≥60s).Per guidelines, set explicit generics/return type and sensible staleTime/cacheTime.
export function useDashboardContractMetadata(contract: ThirdwebContract) { - return useQuery({ - queryFn: () => fetchDashboardContractMetadata(contract), + return useQuery<DashboardContractMetadata>({ + queryFn: () => fetchDashboardContractMetadata(contract), queryKey: ["contract-metadata-header", contract.chain.id, contract.address], - refetchOnWindowFocus: false, + refetchOnWindowFocus: false, + staleTime: 60_000, + cacheTime: 5 * 60_000, }); }Based on learnings.
apps/dashboard/src/app/(app)/drops/[slug]/page.tsx (2)
79-81: Image guard looks right; also guard nft.metadata.image.If nft.metadata.image can be non-string, add a typeof check to avoid leaking unknowns.
- const thumbnail = - project.thumbnail || - nft.metadata.image || - (typeof contractMetadata.image === "string" ? contractMetadata.image : ""); + const thumbnail = + project.thumbnail || + (typeof nft.metadata.image === "string" ? nft.metadata.image : "") || + (typeof contractMetadata.image === "string" ? contractMetadata.image : "");
1-1: Mark as server-only.This is a server component; add server-only sentinel at the top.
+import "server-only"; import type { Metadata } from "next";apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx (1)
40-43: Solid name fallback; minor API consistency nits.
- Good: accept
string | nulland provide address fallback.- Ensure exported components expose a root
classNameprop per app guidelines (optional here).export function TokenViewerSheet( - props: { + props: { + className?: string; @@ <DialogContent - className="lg:!max-w-6xl !rounded-xl flex flex-col gap-0 p-0 max-sm:max-h-[calc(100dvh-60px)] max-sm:overflow-y-auto" + className={cn( + "lg:!max-w-6xl !rounded-xl flex flex-col gap-0 p-0 max-sm:max-h-[calc(100dvh-60px)] max-sm:overflow-y-auto", + props.className, + )}Also import
cnfrom "@/lib/utils".Also applies to: 60-63, 86-87, 94-95
packages/thirdweb/src/utils/contract/fetchContractMetadata.ts (1)
21-21: Return object only when JSON is an object.To align with downstream expectations (spreading/record access), coerce non-object JSON to
undefined.export async function fetchContractMetadata( options: FetchContractMetadata, ): Promise<{ [key: string]: unknown } | undefined> { @@ - return await (await download({ client, uri })).json(); + const json = await (await download({ client, uri })).json(); + return typeof json === "object" && json !== null ? json : undefined;packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (1)
23-26: Safer spread and explicit object check.Spreading primitives yields odd keys; guard with an object check before spreading.
+import { isObject } from "../../../utils/type-guards.js"; @@ - return { - ...(resolvedMetadata ?? {}), + const base = isObject(resolvedMetadata) ? resolvedMetadata : {}; + return { + ...base, name: resolvedMetadata?.name && typeof resolvedMetadata.name === "string" ? resolvedMetadata.name : resolvedName, symbol: resolvedMetadata?.symbol && typeof resolvedMetadata.symbol === "string" ? resolvedMetadata.symbol : resolvedSymbol, };Also applies to: 45-54
packages/thirdweb/src/utils/type-guards.ts (1)
35-59: Add public‑facing examples and tags to guards (now exported).Provide concise @example blocks and swap
@internalfor a public tag (e.g.,@utils) to satisfy docs rules.-/** - * Checks if a value is a record with string values. - * @param value - The value to check. - * @returns True if the value is a record with string values, false otherwise. - * @internal - */ +/** + * Checks if a value is a record. Defaults to string keys and string values. + * Pass custom guards for keys/values to narrow precisely. + * @param value - The value to check. + * @param guards - Optional key/value guards. + * @returns True if `value` is a record satisfying the guards. + * @example + * ```ts + * const v: unknown = { a: "1", b: "2" }; + * if (isRecord(v)) { + * // v: Record<string, string> + * v.a.toUpperCase(); + * } + * // Custom value guard + * const w: unknown = { a: 1, b: 2 }; + * if (isRecord<number, number>(w, { value: (x): x is number => typeof x === "number" })) { + * w.a.toFixed(0); + * } + * ``` + * @utils + */ export function isRecord<Apply similar @example/@utils updates for
isObjectandisObjectWithKeys..changeset/witty-plums-read.md (1)
24-24: Consider minor style improvements.Static analysis flags two optional style improvements:
- Line 24: "outside of" could be simplified to "outside"
- Line 38: Consider using asterisk emphasis (
*unsafe*) instead of underscore (_unsafe_) for consistencyThese are purely cosmetic and don't affect the technical accuracy.
Also applies to: 38-38
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (25)
.changeset/witty-plums-read.md(1 hunks)apps/dashboard/src/@/api/universal-bridge/token-list.ts(1 hunks)apps/dashboard/src/@/api/universal-bridge/tokens.ts(1 hunks)apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts(2 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx(4 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx(1 hunks)apps/dashboard/src/app/(app)/drops/[slug]/page.tsx(1 hunks)packages/service-utils/src/core/get-auth-headers.ts(1 hunks)packages/thirdweb/src/exports/utils.ts(1 hunks)packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts(2 hunks)packages/thirdweb/src/extensions/common/read/getContractMetadata.ts(2 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx(2 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx(2 hunks)packages/thirdweb/src/utils/contract/fetchContractMetadata.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts(2 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts(2 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts(2 hunks)packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts(2 hunks)packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts(2 hunks)packages/thirdweb/src/utils/type-guard.test.ts(1 hunks)packages/thirdweb/src/utils/type-guards.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (14)
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
- apps/dashboard/src/@/api/universal-bridge/token-list.ts
- apps/dashboard/src/@/api/universal-bridge/tokens.ts
- packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
- packages/thirdweb/src/utils/type-guard.test.ts
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
- packages/service-utils/src/core/get-auth-headers.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
- packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
- packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
- packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tspackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tsapps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxpackages/thirdweb/src/exports/utils.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxpackages/thirdweb/src/utils/type-guards.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tspackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tsapps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxpackages/thirdweb/src/exports/utils.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxpackages/thirdweb/src/utils/type-guards.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.tspackages/thirdweb/src/utils/extensions/drops/get-claim-params.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tspackages/thirdweb/src/exports/utils.tspackages/thirdweb/src/extensions/common/read/getContractMetadata.tspackages/thirdweb/src/utils/type-guards.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxapps/dashboard/src/app/(app)/drops/[slug]/page.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxapps/dashboard/src/app/(app)/drops/[slug]/page.tsx
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsxapps/dashboard/src/app/(app)/drops/[slug]/page.tsx
packages/thirdweb/src/exports/**
📄 CodeRabbit inference engine (CLAUDE.md)
packages/thirdweb/src/exports/**: Export everything viaexports/directory, grouped by feature in the SDK public API
Every public symbol must have comprehensive TSDoc with at least one@exampleblock that compiles and custom annotation tags (@beta,@internal,@experimental)
Files:
packages/thirdweb/src/exports/utils.ts
.changeset/*.md
📄 CodeRabbit inference engine (AGENTS.md)
.changeset/*.md: Each change inpackages/*must include a changeset for the appropriate package
Version bump rules: patch for non‑API changes; minor for new/modified public API
Files:
.changeset/witty-plums-read.md
🧠 Learnings (5)
📚 Learning: 2025-05-27T19:54:55.885Z
Learnt from: MananTank
PR: thirdweb-dev/js#7177
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx:15-17
Timestamp: 2025-05-27T19:54:55.885Z
Learning: The `fetchDashboardContractMetadata` function from "3rdweb-sdk/react/hooks/useDashboardContractMetadata" has internal error handlers for all promises and cannot throw errors, so external error handling is not needed when calling this function.
Applied to files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to packages/thirdweb/exports/** : Export all public API via `packages/thirdweb/exports/`, grouped by feature
Applied to files:
packages/thirdweb/src/exports/utils.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to packages/thirdweb/src/exports/** : Export everything via `exports/` directory, grouped by feature in the SDK public API
Applied to files:
packages/thirdweb/src/exports/utils.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to packages/thirdweb/src/exports/** : Every public symbol must have comprehensive TSDoc with at least one `example` block that compiles and custom annotation tags (`beta`, `internal`, `experimental`)
Applied to files:
packages/thirdweb/src/exports/utils.ts
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to packages/thirdweb/**/*.{ts,tsx} : Every public symbol must have comprehensive TSDoc with at least one compiling `example` and a custom tag (`beta`, `internal`, `experimental`, etc.)
Applied to files:
packages/thirdweb/src/exports/utils.ts
🧬 Code graph analysis (2)
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts (1)
packages/thirdweb/src/utils/type-guards.ts (1)
isRecord(41-59)
packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts (1)
packages/thirdweb/src/utils/type-guards.ts (1)
isRecord(41-59)
🪛 LanguageTool
.changeset/witty-plums-read.md
[style] ~24-~24: This phrase is redundant. Consider using “outside”.
Context: ...is not (and was never) strictly defined outside of name and symbol and may contain any...
(OUTSIDE_OF)
🪛 markdownlint-cli2 (0.18.1)
.changeset/witty-plums-read.md
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Unit Tests
- GitHub Check: Size
- GitHub Check: Analyze (javascript)
🔇 Additional comments (4)
packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts (1)
9-9: LGTM! Type assertion replaced with type guard.The unsafe type assertion has been properly replaced with the
isRecordguard, which provides runtime type safety. The guard defaults to checkingRecord<string, string>, which is appropriate for the merkle data structure wheremerkleData[cc.merkleRoot]is accessed to retrieve the snapshot URI.This change addresses the technical debt flagged in the previous review.
Also applies to: 123-123
.changeset/witty-plums-read.md (1)
1-44: LGTM! Clear migration guidance provided.The changeset accurately documents the breaking type changes and provides helpful migration guidance with both recommended (type guards) and quick (type assertion) fixes. The before/after examples make the impact clear.
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx (2)
56-62: Pragmatic fallback for null name/symbol handling.The fallback object provides sensible defaults (contract address for name, empty string for symbol) to handle the new nullable types. This ensures downstream components receive non-null values.
The FIXME comment indicates awareness that this is technical debt. Consider whether it would be better to update the consuming components (NFTsGrid, NFTPublicPageLayout, PageLoadTokenViewerSheet) to properly handle null name/symbol values instead of masking them at this level.
Do you want me to generate an issue to track updating the downstream components to properly handle nullable metadata fields?
83-83: Consistent use of fallback across all consumers.Good that the fallback is applied consistently to all components that consume the contract metadata. This ensures uniform behavior across NFTsGrid, NFTPublicPageLayout, and PageLoadTokenViewerSheet.
Also applies to: 120-120, 148-148
44d2b8e to
5084400
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
.changeset/witty-plums-read.md (2)
24-24: Optional: Simplify phrase for clarity.The phrase "outside of" can be simplified to "outside" as suggested by the style checker.
As per static analysis hints
Apply this diff:
-Metadata is not (and was never) strictly defined outside of `name` and `symbol` and may contain any type of data in the record. +Metadata is not (and was never) strictly defined outside `name` and `symbol` and may contain any type of data in the record.
38-38: Optional: Use consistent emphasis style.The emphasis style should use asterisks instead of underscores for consistency with Markdown best practices.
As per static analysis hints
Apply this diff:
-_This is as "unsafe" as the type was before._ +*This is as "unsafe" as the type was before.*apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (1)
92-116: Type guards correctly implement runtime safety.The guards ensure
image,name, andsymbolare strings before use, with appropriate fallbacks. The logic is sound and aligns with the type safety improvements.Consider extracting the repeated guard pattern into a helper function:
function getMetadataString( value: unknown, fallback?: string ): string | undefined { return value && typeof value === "string" ? value : fallback; }Then use it:
image: getMetadataString(contractMetadata.image), name: getMetadataString(contractMetadata.name, props.clientContract.address), symbol: getMetadataString(contractMetadata.symbol),packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts (1)
84-89: Consider adding explicit string type check for defense-in-depth.The
isRecordguard validates thatmetadata.merkleis aRecord<string, string>, and the truthy check handles undefined/falsy values. However, for complete runtime safety, consider adding an explicittypeofcheck.Apply this diff for more defensive type checking:
const merkleInfo = isRecord(metadata.merkle) ? metadata.merkle[key] : undefined; -if (merkleInfo) { +if (typeof merkleInfo === "string" && merkleInfo) { merkleInfos[key] = merkleInfo; }This ensures that even if
isRecordpasses, we verify the individual value is a string before assignment, providing defense-in-depth against unexpected runtime values.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts (1)
88-88: Consider usingundefinedas the fallback instead of{}.The
isRecordguard correctly validates the shape at runtime, which addresses the type safety concern. However, passing an empty object{}when the guard fails is less semantically clear thanundefined. While both values result infetchSnapshotreturningnull, usingundefinedbetter expresses "no merkle metadata available" and aligns with the function signature that explicitly acceptsRecord<string, string> | undefined.Apply this diff:
- isRecord(contractMetadata.merkle) ? contractMetadata.merkle : {}, + isRecord(contractMetadata.merkle) ? contractMetadata.merkle : undefined,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (25)
.changeset/witty-plums-read.md(1 hunks)apps/dashboard/src/@/api/universal-bridge/token-list.ts(1 hunks)apps/dashboard/src/@/api/universal-bridge/tokens.ts(1 hunks)apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts(2 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx(4 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx(1 hunks)apps/dashboard/src/app/(app)/drops/[slug]/page.tsx(1 hunks)packages/service-utils/src/core/get-auth-headers.ts(1 hunks)packages/thirdweb/src/exports/utils.ts(1 hunks)packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts(2 hunks)packages/thirdweb/src/extensions/common/read/getContractMetadata.ts(2 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx(2 hunks)packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx(2 hunks)packages/thirdweb/src/utils/contract/fetchContractMetadata.ts(1 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts(2 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts(2 hunks)packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts(2 hunks)packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts(2 hunks)packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts(2 hunks)packages/thirdweb/src/utils/type-guard.test.ts(1 hunks)packages/thirdweb/src/utils/type-guards.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
- packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
- packages/thirdweb/src/exports/utils.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
- packages/service-utils/src/core/get-auth-headers.ts
- packages/thirdweb/src/utils/type-guard.test.ts
- packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
- apps/dashboard/src/@/api/universal-bridge/tokens.ts
- packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
- packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
- packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.tspackages/thirdweb/src/utils/type-guards.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/@/api/universal-bridge/token-list.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxpackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tsapps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.tspackages/thirdweb/src/utils/type-guards.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/@/api/universal-bridge/token-list.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxpackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tsapps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxpackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/@/api/universal-bridge/token-list.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.tsapps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/@/api/universal-bridge/token-list.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
packages/thirdweb/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/thirdweb/**/*.{ts,tsx}: Every public symbol must have comprehensive TSDoc with at least one compiling@exampleand a custom tag (@beta,@internal,@experimental, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Lazy‑load heavy dependencies inside async paths (e.g.,const { jsPDF } = await import("jspdf"))
Files:
packages/thirdweb/src/utils/type-guards.tspackages/thirdweb/src/utils/contract/fetchContractMetadata.tspackages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsxpackages/thirdweb/src/extensions/airdrop/write/saveSnapshot.tspackages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/app/(app)/drops/[slug]/page.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsxapps/dashboard/src/@/hooks/useDashboardContractMetadata.tsxapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
.changeset/*.md
📄 CodeRabbit inference engine (AGENTS.md)
.changeset/*.md: Each change inpackages/*must include a changeset for the appropriate package
Version bump rules: patch for non‑API changes; minor for new/modified public API
Files:
.changeset/witty-plums-read.md
🧠 Learnings (1)
📚 Learning: 2025-05-27T19:54:55.885Z
Learnt from: MananTank
PR: thirdweb-dev/js#7177
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx:15-17
Timestamp: 2025-05-27T19:54:55.885Z
Learning: The `fetchDashboardContractMetadata` function from "3rdweb-sdk/react/hooks/useDashboardContractMetadata" has internal error handlers for all promises and cannot throw errors, so external error handling is not needed when calling this function.
Applied to files:
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
🧬 Code graph analysis (5)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts (1)
packages/thirdweb/src/utils/type-guards.ts (1)
isRecord(41-59)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (2)
apps/dashboard/src/@/components/blocks/grid-pattern-embed-container.tsx (1)
GridPatternEmbedContainer(3-23)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/RecentTransfers.tsx (1)
RecentTransfers(279-313)
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx (1)
packages/thirdweb/src/utils/type-guards.ts (1)
isString(17-19)
packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts (1)
packages/thirdweb/src/utils/type-guards.ts (1)
isRecord(41-59)
packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts (1)
packages/thirdweb/src/utils/type-guards.ts (1)
isRecord(41-59)
🪛 LanguageTool
.changeset/witty-plums-read.md
[style] ~24-~24: This phrase is redundant. Consider using “outside”.
Context: ...is not (and was never) strictly defined outside of name and symbol and may contain any...
(OUTSIDE_OF)
🪛 markdownlint-cli2 (0.18.1)
.changeset/witty-plums-read.md
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
38-38: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Unit Tests
- GitHub Check: Build Packages
- GitHub Check: Lint Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (11)
apps/dashboard/src/@/api/universal-bridge/token-list.ts (1)
20-26: Approve the type assertion removal—type safety is properly maintained.The constant
NEXT_PUBLIC_DASHBOARD_CLIENT_IDis guaranteed to be typestring(defaults to""viaprocess.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID || ""), so removing the type assertion is both type-safe and correct. The headers object will be properly typed without needing an explicit assertion.apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx (1)
62-65: LGTM! Type guard correctly handles unknown metadata type.The guard checks both existence and string type before using
contractMetadata.image, with an appropriate fallback to empty string. This aligns with the updated return type fromgetContractMetadata.packages/thirdweb/src/utils/contract/fetchContractMetadata.ts (1)
21-21: LGTM! Type annotation correctly updated.The return type change from
anytounknownimproves type safety without affecting runtime behavior. This is a core part of the type safety improvements in this PR.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx (2)
161-179: Conditional rendering correctly guards against invalid metadata.The BuyEmbed only renders when
showBuyEmbedis true AND bothnameandsymbolexist as non-empty strings. This prevents UI errors when metadata is missing or has the wrong type.
189-197: LGTM! RecentTransfers guarded correctly.The component only renders when
symbolexists as a string, preventing type errors in the child component.apps/dashboard/src/app/(app)/drops/[slug]/page.tsx (1)
78-81: LGTM! Image guard correctly implements type safety.The type guard ensures
contractMetadata.imageis a string before using it, with a fallback to empty string. The fallback chain prioritizes project thumbnail and NFT metadata before contract metadata.packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts (1)
59-59: LGTM! Runtime type guard correctly implemented.The
isRecordguard validatesmetadata.merkleis a proper record before accessing it, with an appropriate fallback to an empty object. This addresses the past review comment about replacing type assertions with runtime validation.packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx (1)
232-235: LGTM! Type guards correctly narrow unknown metadata.The
isStringguards safely check and narrow the types before returning metadata values, with appropriate fallbacks toundefinedfor optional fields. This aligns with the type safety improvements across the PR.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx (1)
40-40: LGTM! Well-structured fallback pattern.The nullable
nametype and the fallback toclientContract.addressensure that downstream components always receive a non-null name value. This defensive approach aligns with the PR's goal of improving type safety for contract metadata, and the implementation correctly propagates the fallback version to bothFetchAndRenderTokenInfoandTokenInfoUI.Also applies to: 60-63, 86-86, 94-94
packages/thirdweb/src/utils/type-guards.ts (2)
11-19: LGTM! Non-generic signature is sound.The
isStringimplementation now uses a non-generic signature that correctly narrows tostringrather than an arbitrary string literal type. This addresses the previous review concern about unsound type narrowing and provides safe, straightforward type checking.
35-59: LGTM! Well-designed runtime guard with flexible validation.The
isRecordimplementation provides configurable key and value guards that default to string checking. The generic signature allows callers to specify stricter types while the runtime validation ensures structural conformance. The implementation correctly checks that the value is a non-array object with all entries passing the specified guards. This pattern is now used throughout the codebase for safely handlingcontractMetadata.merkleand other unknown-typed data.

TL;DR
Updated
getContractMetadata()to returnRecord<string, unknown>instead ofRecord<string, any>for improved type safety.What changed?
getContractMetadata()to returnRecord<string, unknown>instead ofRecord<string, any>nameandsymbolreturn types to bestring | nullinstead of juststringimage,name, andsymbolHow to test?
Why make this change?
This change improves type safety by making it explicit that metadata properties outside of
nameandsymbolare not strictly defined and may contain any type of data. The previousanytype was unsafe and could lead to runtime errors if developers assumed properties were of a specific type. The newunknowntype forces developers to add proper type guards before accessing properties, resulting in more robust code.Summary by CodeRabbit
PR-Codex overview
This PR focuses on improving type safety by introducing type guards and refining return types in various functions, particularly in relation to metadata handling. It also standardizes the way contract metadata is processed and validated throughout the codebase.
Detailed summary
fetchContractMetadatato returnunknowninstead ofany.isRecordandisStringtype guards.