Skip to content

Conversation

@jnsdls
Copy link
Member

@jnsdls jnsdls commented Oct 20, 2025

TL;DR

Updated getContractMetadata() to return Record<string, unknown> instead of Record<string, any> for improved type safety.

What changed?

  • Modified getContractMetadata() to return Record<string, unknown> instead of Record<string, any>
  • Updated name and symbol return types to be string | null instead of just string
  • Added type guards throughout the codebase where contract metadata is accessed
  • Fixed various places in the dashboard and UI components that were relying on the previous return type
  • Added proper type checking for metadata properties like image, name, and symbol
  • Added a changeset explaining the change and providing migration guidance

How to test?

  1. Verify that all contract metadata access points work correctly
  2. Test accessing metadata properties in applications that use the SDK
  3. Ensure that the dashboard correctly displays contract information
  4. Confirm that UI components like ClaimButton and CreateDirectListingButton work properly

Why make this change?

This change improves type safety by making it explicit that metadata properties outside of name and symbol are not strictly defined and may contain any type of data. The previous any type was unsafe and could lead to runtime errors if developers assumed properties were of a specific type. The new unknown type forces developers to add proper type guards before accessing properties, resulting in more robust code.

Summary by CodeRabbit

  • Bug Fixes
    • Stricter runtime validation of contract metadata (name, symbol, image) with UI fallbacks to prevent rendering broken embeds, pages, and allowlist/snapshot errors.
  • New Features
    • Public type-guard utilities exported for safer metadata checks across the UI.
  • Tests
    • Added unit tests for the new type-guard utilities.
  • Documentation
    • Changelog entry documenting a minor type-level change to contract metadata typings (name/symbol may be null).

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

  • Changed fetchContractMetadata to return unknown instead of any.
  • Introduced isRecord and isString type guards.
  • Updated contract metadata handling to check types before accessing properties.
  • Refined fallback logic for metadata properties to ensure type safety.
  • Enhanced tests for type guards to validate functionality.

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

@changeset-bot
Copy link

changeset-bot bot commented Oct 20, 2025

🦋 Changeset detected

Latest commit: 5084400

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
thirdweb Minor
@thirdweb-dev/nebula Patch
@thirdweb-dev/wagmi-adapter Patch
wagmi-inapp Patch

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

@vercel
Copy link

vercel bot commented Oct 20, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
docs-v2 Ready Ready Preview Comment Oct 21, 2025 11:49pm
nebula Ready Ready Preview Comment Oct 21, 2025 11:49pm
thirdweb_playground Ready Ready Preview Comment Oct 21, 2025 11:49pm
thirdweb-www Ready Ready Preview Comment Oct 21, 2025 11:49pm
wallet-ui Ready Ready Preview Comment Oct 21, 2025 11:49pm

@jnsdls jnsdls marked this pull request as ready for review October 20, 2025 23:56
@jnsdls jnsdls requested review from a team as code owners October 20, 2025 23:56
@github-actions github-actions bot added Dashboard Involves changes to the Dashboard. packages SDK Involves changes to the thirdweb SDK labels Oct 20, 2025
Copy link
Member Author

jnsdls commented Oct 20, 2025


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 20, 2025

Walkthrough

Contract metadata typings tightened: index signatures moved from any to unknown, and name/symbol now string | null. Callers and UI components add runtime guards and local fallbacks for image, name, and symbol. New type-guards exported and used to safely handle metadata.merkle and other fields.

Changes

Cohort / File(s) Summary
Core metadata types
packages/thirdweb/src/extensions/common/read/getContractMetadata.ts, packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
Return types tightened from Record<string, any>Record<string, unknown>; name and symbol now `string
Type guards & exports
packages/thirdweb/src/utils/type-guards.ts, packages/thirdweb/src/exports/utils.ts
New/updated exports: isString, isRecord, isObject (exported) and related guards; added export * from "../utils/type-guards.js" to public exports.
Airdrop / drops utilities
packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts, packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts, packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts, packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts, packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts, packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
Added runtime isRecord guards for metadata.merkle and only merge/assign truthy merkle entries (avoid overwriting with undefined).
Dashboard hooks & consumers
apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx, apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx, apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx, apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx, apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.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]/settings/components/metadata.tsx, apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
Consumers now guard image, name, and symbol (e.g., typeof === "string" or isString), add fallbacks (name → contract address, symbol → ""), and tighten conditional rendering (e.g., BuyEmbed/RecentTransfers require valid strings).
Prebuilt UI components
packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx, packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
getPayMetadata now returns image and name only when they are strings (guarded via isString); non-string values become undefined.
Type assertion removals
apps/dashboard/src/@/api/universal-bridge/token-list.ts, apps/dashboard/src/@/api/universal-bridge/tokens.ts, packages/service-utils/src/core/get-auth-headers.ts
Removed explicit as Record<string, string> type assertions on header/return objects; no runtime change.
Changelog
.changeset/witty-plums-read.md
Adds changelog entry documenting type changes for getContractMetadata and recommended fixes for callers.
Tests
packages/thirdweb/src/utils/type-guard.test.ts
New tests for isObject, isString, isObjectWithKeys, and isRecord covering default and custom guard behaviors.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "Change getContractMetadata() return type from any to unknown" directly and specifically describes the primary API change that is central to this pull request. This function signature update in packages/thirdweb/src/extensions/common/read/getContractMetadata.ts is the root cause that triggers all subsequent changes throughout the codebase, including the addition of type guards, updates to dashboard components, and refinements to metadata handling. While the PR includes extensive follow-up implementations to accommodate this API change, the title accurately captures the core modification.
Description Check ✅ Passed The PR description is comprehensive and well-structured, including a clear TL;DR, detailed "What changed?" section explaining the modifications and their scope, specific testing instructions covering verification points for contract metadata access and UI components, and a "Why make this change?" section providing the rationale for improving type safety. The description exceeds the basic template requirements by providing thorough context and actionable guidance. While it does not include an explicit "Notes for the reviewer" section, the quality and completeness of the other sections adequately compensate for this omission, making the description suitable for reviewer comprehension.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Change_getContractMetadata_return_type_from_any_to_unknown

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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: getContracct should be getContract. 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 for collectionMetadata.name.

The FetchAndRenderTokenInfo component defines collectionMetadata.name as string (line 108), but TokenViewerSheet (line 40) declares it as string | null and passes it to this component (line 139). Similarly, TokenInfoUI (line 155) expects string, 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 for collectionMetadata.name.

The TokenInfoUI component defines collectionMetadata.name as string (line 155), but receives it from TokenViewerSheet where it's typed as string | 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 nullable collectionMetadata.name.

The alt attribute (line 242) and rendered text (line 257) use props.collectionMetadata.name, which can now be null. The alt attribute requires a string, and rendering null provides 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.merkle has 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 accessing merkleData[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.ts and fetch-proofs-erc721.ts as 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 narrow unknown to string before 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:

  1. Rendering a more explicit placeholder like "Unnamed Collection" for null names, or
  2. 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 29d05a5 and 27190a0.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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.tsx
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
  • packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.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
**/*.{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.tsx
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
  • packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.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,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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • apps/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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • apps/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 className prop on the root element of every component

Files:

  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • apps/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 @example and 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.ts
  • packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
  • packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc1155.ts
.changeset/*.md

📄 CodeRabbit inference engine (AGENTS.md)

.changeset/*.md: Each change in packages/* 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.image and contractMetadata.name are strings before use, correctly handling the updated unknown return type from getContractMetadata.

apps/dashboard/src/app/(app)/drops/[slug]/page.tsx (1)

78-81: LGTM: Appropriate type guard for thumbnail selection.

The type check for contractMetadata.image properly handles the unknown type, 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 unknown type from contract metadata.

packages/thirdweb/src/utils/contract/fetchContractMetadata.ts (1)

21-21: LGTM: Improved type safety.

Changing from any to unknown enforces type safety by requiring consumers to validate types before use, aligning with the coding guidelines to avoid any unless 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 | null for name and symbol, plus unknown for 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 name and symbol from metadata are verified as strings before use, with appropriate fallbacks to individually resolved values. This defensive pattern handles the unknown type 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 unknown type 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 any to unknown, 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 unknown metadata fields to string before passing to components. The fallback to the contract address for name (line 103) ensures a reasonable default when metadata is incomplete.


161-197: LGTM! Components properly gated on metadata availability.

Both BuyEmbed and RecentTransfers are now conditionally rendered only when required metadata fields (name, symbol) are available as strings, preventing runtime errors when metadata is incomplete.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 21, 2025

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
thirdweb (esm) 64.55 KB (+0.07% 🔺) 1.3 s (+0.07% 🔺) 208 ms (+30.57% 🔺) 1.5 s
thirdweb (cjs) 365.82 KB (+0.04% 🔺) 7.4 s (+0.04% 🔺) 1.6 s (+4.53% 🔺) 8.9 s
thirdweb (minimal + tree-shaking) 5.73 KB (0%) 115 ms (0%) 90 ms (+691.49% 🔺) 204 ms
thirdweb/chains (tree-shaking) 526 B (0%) 11 ms (0%) 55 ms (+609.9% 🔺) 65 ms
thirdweb/react (minimal + tree-shaking) 19.09 KB (-0.18% 🔽) 382 ms (-0.18% 🔽) 65 ms (+572.54% 🔺) 447 ms

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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: getContracct should be getContract.

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 unknown over any. 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 unknown type. Please implement one of those approaches.

As per coding guidelines: "Avoid any and unknown unless 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. Since metadata.merkle is unknown, 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.image and contractMetadata.name to string | undefined, consistent with the changes in ClaimButton/index.tsx. This improves type safety as intended by the PR.

Note: The code duplication issue between this file and ClaimButton/index.tsx has 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 27190a0 and 75903ae.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
  • apps/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.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
  • apps/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 @example and 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.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc20.ts
.changeset/*.md

📄 CodeRabbit inference engine (AGENTS.md)

.changeset/*.md: Each change in packages/* 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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in 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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in 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 className prop 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 any to unknown properly 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 name can be null from getContractMetadata().


60-63: Excellent defensive fallback pattern.

Creating collectionMetadataWithNameFallback ensures 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 name and symbol, forcing callers to perform runtime checks. As per coding guidelines: "Avoid any and unknown unless unavoidable."


46-53: Robust type guards ensure safe metadata handling.

The implementation correctly validates that name and symbol from 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

@codecov
Copy link

codecov bot commented Oct 21, 2025

Codecov Report

❌ Patch coverage is 68.42105% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.90%. Comparing base (29d05a5) to head (5084400).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...rdweb/src/extensions/airdrop/write/saveSnapshot.ts 14.28% 6 Missing ⚠️
...built/thirdweb/CreateDirectListingButton/index.tsx 20.00% 4 Missing ⚠️
...act/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx 60.00% 1 Missing and 1 partial ⚠️
...multicall-set-claim-claim-conditon-transactions.ts 71.42% 2 Missing ⚠️
...c/utils/extensions/airdrop/fetch-proofs-erc1155.ts 50.00% 1 Missing ⚠️
...src/utils/extensions/airdrop/fetch-proofs-erc20.ts 50.00% 1 Missing ⚠️
...rc/utils/extensions/airdrop/fetch-proofs-erc721.ts 50.00% 1 Missing ⚠️
...web/src/utils/extensions/drops/get-claim-params.ts 50.00% 1 Missing ⚠️

❌ 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     
Flag Coverage Δ
packages 54.90% <68.42%> (+<0.01%) ⬆️
Files with missing lines Coverage Δ
.../src/extensions/common/read/getContractMetadata.ts 100.00% <100.00%> (ø)
...irdweb/src/utils/contract/fetchContractMetadata.ts 100.00% <100.00%> (ø)
packages/thirdweb/src/utils/type-guards.ts 100.00% <100.00%> (ø)
...c/utils/extensions/airdrop/fetch-proofs-erc1155.ts 7.81% <50.00%> (+1.46%) ⬆️
...src/utils/extensions/airdrop/fetch-proofs-erc20.ts 8.57% <50.00%> (+1.32%) ⬆️
...rc/utils/extensions/airdrop/fetch-proofs-erc721.ts 7.93% <50.00%> (+1.48%) ⬆️
...web/src/utils/extensions/drops/get-claim-params.ts 91.37% <50.00%> (+0.07%) ⬆️
...act/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx 63.63% <60.00%> (-0.57%) ⬇️
...multicall-set-claim-claim-conditon-transactions.ts 87.96% <71.42%> (-1.01%) ⬇️
...built/thirdweb/CreateDirectListingButton/index.tsx 43.60% <20.00%> (-0.19%) ⬇️
... and 1 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 for name field to match image handling.

While the image field (lines 87-90) has an explicit typeof === "string" check, name is 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.

PageLoadTokenViewerSheet expects collectionMetadata.name: string, but TokenViewerSheet now accepts string | null and handles the null case internally with a fallback (lines 60-63). This creates an inconsistent API where callers of PageLoadTokenViewerSheet must pre-handle null names, while direct callers of TokenViewerSheet can pass null.

For consistency, consider accepting string | null here 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 TokenViewerSheet centralize 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.maxClaimable and .price are declared as string, but you return bigint. JSON cannot carry BigInt; download(...).json() will yield string/number. Convert to strings and relax the temporary claim typing 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 raw before 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 image and name are strings before using them, which aligns with the PR's goal of improving type safety.

Note: The code duplication with CreateDirectListingButton has 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 image and name to string | undefined, ensuring type safety when accessing metadata fields.

Note: The duplication of this pattern with ClaimButton/index.tsx has 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 any to unknown. As noted in the TODO, this needs proper runtime validation to ensure metadata.merkle is actually a Record<string, string>.

Based on coding guidelines: "Avoid any and unknown unless 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 any and unknown unless 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 any and unknown unless 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 any and unknown unless 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 any and unknown unless 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> | undefined defeats the unknown hardening and risks runtime errors when indexing merkleMetadata[merkleRoot]. Use a narrow guard and fall back to undefined.

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 return undefined like 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, safeSymbol once and pass them through; this also lets you drop the earlier mutation of contractMetadata.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: Ensure showBuyEmbed is a boolean, not boolean | 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/symbol may be null if 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 75903ae and 9f52d84.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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.tsx
  • 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]/public-pages/nft/token-viewer/token-viewer.tsx
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
  • packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/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.tsx
  • 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]/public-pages/nft/token-viewer/token-viewer.tsx
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
  • packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • 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/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.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
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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • 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/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.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
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • 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/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • apps/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 in packages/* 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 @example and 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.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
  • packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/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 narrows image from unknown.

The defensive type check ensures image is a string before assignment, properly handling the stricter unknown return type from getContractMetadata().

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 both null and 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 to unknown is 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 contractMetadataWithNameAndSymbolFallback to 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 symbol is 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 | null and [key: string]: unknown align consumers with safer access patterns.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 isRecord guard ensures metadata.merkle is a Record<string, string>, but for maximum clarity and to fully address the previous review's technical debt, consider explicitly validating that merkleInfo is 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 isRecord should 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 9f52d84 and b2844f6.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/@/api/universal-bridge/tokens.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • packages/thirdweb/src/utils/type-guards.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/exports/utils.ts
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • packages/service-utils/src/core/get-auth-headers.ts
  • packages/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.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/@/api/universal-bridge/tokens.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • packages/thirdweb/src/utils/type-guards.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/exports/utils.ts
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • packages/service-utils/src/core/get-auth-headers.ts
  • packages/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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/@/api/universal-bridge/tokens.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/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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/@/api/universal-bridge/tokens.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/app/(app)/drops/[slug]/page.tsx
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/opengraph-image.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/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 @example and 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.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/exports/utils.ts
  • packages/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 via exports/ directory, grouped by feature in the SDK public API
Every public symbol must have comprehensive TSDoc with at least one @example block 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 in packages/* 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.image by verifying it's a string before use, preventing runtime errors with the new unknown typing.

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.name and resolvedMetadata.symbol before use, falling back to resolved values from direct contract calls. The spread of resolvedMetadata at line 45 is safe—contrary to the previous review comment, spreading null or undefined in 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 isRecord implementation 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.image to string before 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 explicit as assertions 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 unknown metadata fields to strings before passing them to ContractHeaderUI. The fallback for name to props.clientContract.address ensures a meaningful display value even when metadata is unavailable.


161-179: Type guards correctly prevent rendering with invalid metadata.

The compound condition ensures BuyEmbed only 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 RecentTransfers only renders when symbol is a valid string, preventing undefined from reaching the component's tokenSymbol prop.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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/name guards 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.

resolvedMetadata may be null (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 means contractMetadata.name || props.clientContract.address will fall back to the address even when name is an empty string. Since the type changed from string to string | null, using the nullish coalescing operator (??) would more precisely handle only null/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/isString from thirdweb/utils.

packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts (1)

77-84: Pre‑guard metadata.merkle once; 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 image and name. This aligns well with the PR's goal of moving from any to unknown for contract metadata.

Optional: Consider using the isString type guard utility for consistency.

Based on the PR's AI summary, type guard utilities like isString were introduced in packages/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 typeof checks 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.

📥 Commits

Reviewing files that changed from the base of the PR and between b2844f6 and febf713.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • apps/dashboard/src/@/api/universal-bridge/tokens.ts
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/exports/utils.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx}: Place tests alongside code: foo.tsfoo.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
Use FORKED_ETHEREUM_CHAIN for mainnet interactions and ANVIL_CHAIN for isolated tests

**/*.test.{ts,tsx}: Co‑locate tests as foo.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.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • apps/dashboard/src/@/api/universal-bridge/tokens.ts
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/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 @example and 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.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/ClaimButton/index.tsx
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/@/api/universal-bridge/tokens.ts
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/@/api/universal-bridge/tokens.ts
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/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 className prop on the root element of every component

Files:

  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/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 via exports/ directory, grouped by feature in the SDK public API
Every public symbol must have comprehensive TSDoc with at least one @example block 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 in packages/* 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 the fetch headers.

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 contractMetadataWithNameAndSymbolFallback to all components that previously received contractMetadata. This ensures downstream components receive guaranteed non-null name and symbol values, 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 image exists and is a string before passing it to the component, with an appropriate undefined fallback.


99-104: LGTM! Excellent fallback strategy for missing name.

The type guard ensures name is 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 symbol before use, with an appropriate undefined fallback for cases where the metadata is unavailable.


161-179: LGTM! Defensive rendering guards ensure metadata availability.

The type guards correctly ensure both name and symbol are non-empty strings before rendering the BuyEmbed component. 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 symbol is a non-empty string before rendering RecentTransfers, 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/symbol as string | null and [key: string]: unknown align 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.merkle with isRecord eliminates the assertion and ensures only string values are used.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 | null and provide address fallback.
  • Ensure exported components expose a root className prop 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 cn from "@/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 @internal for 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 isObject and isObjectWithKeys.

.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 consistency

These 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.

📥 Commits

Reviewing files that changed from the base of the PR and between febf713 and 44d2b8e.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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.ts
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • packages/thirdweb/src/exports/utils.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • packages/thirdweb/src/utils/type-guards.ts
  • apps/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.ts
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • packages/thirdweb/src/exports/utils.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • packages/thirdweb/src/utils/type-guards.ts
  • apps/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 @example and 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.ts
  • packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • packages/thirdweb/src/exports/utils.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • apps/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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
packages/thirdweb/src/exports/**

📄 CodeRabbit inference engine (CLAUDE.md)

packages/thirdweb/src/exports/**: Export everything via exports/ directory, grouped by feature in the SDK public API
Every public symbol must have comprehensive TSDoc with at least one @example block 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 in packages/* 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 isRecord guard, which provides runtime type safety. The guard defaults to checking Record<string, string>, which is appropriate for the merkle data structure where merkleData[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

@jnsdls jnsdls force-pushed the Change_getContractMetadata_return_type_from_any_to_unknown branch from 44d2b8e to 5084400 Compare October 21, 2025 23:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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, and symbol are 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 isRecord guard validates that metadata.merkle is a Record<string, string>, and the truthy check handles undefined/falsy values. However, for complete runtime safety, consider adding an explicit typeof check.

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 isRecord passes, 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 using undefined as the fallback instead of {}.

The isRecord guard 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 than undefined. While both values result in fetchSnapshot returning null, using undefined better expresses "no merkle metadata available" and aligns with the function signature that explicitly accepts Record<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.

📥 Commits

Reviewing files that changed from the base of the PR and between 44d2b8e and 5084400.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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.ts
  • packages/thirdweb/src/utils/type-guards.ts
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
  • apps/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.ts
  • packages/thirdweb/src/utils/type-guards.ts
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
  • apps/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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • 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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/hooks.ts
  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/@/api/universal-bridge/token-list.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/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 @example and 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.ts
  • packages/thirdweb/src/utils/contract/fetchContractMetadata.ts
  • packages/thirdweb/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx
  • packages/thirdweb/src/extensions/airdrop/write/saveSnapshot.ts
  • packages/thirdweb/src/utils/extensions/airdrop/fetch-proofs-erc721.ts
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/app/(app)/drops/[slug]/page.tsx
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx
  • apps/dashboard/src/@/hooks/useDashboardContractMetadata.tsx
  • apps/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 in packages/* 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_ID is guaranteed to be type string (defaults to "" via process.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 from getContractMetadata.

packages/thirdweb/src/utils/contract/fetchContractMetadata.ts (1)

21-21: LGTM! Type annotation correctly updated.

The return type change from any to unknown improves 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 showBuyEmbed is true AND both name and symbol exist 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 symbol exists 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.image is 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 isRecord guard validates metadata.merkle is 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 isString guards safely check and narrow the types before returning metadata values, with appropriate fallbacks to undefined for 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 name type and the fallback to clientContract.address ensure 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 both FetchAndRenderTokenInfo and TokenInfoUI.

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 isString implementation now uses a non-generic signature that correctly narrows to string rather 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 isRecord implementation 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 handling contractMetadata.merkle and other unknown-typed data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Dashboard Involves changes to the Dashboard. packages SDK Involves changes to the thirdweb SDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants