Skip to content

Conversation

d4mr
Copy link
Member

@d4mr d4mr commented Sep 8, 2025


PR-Codex overview

This PR focuses on refactoring the wallet and token balance functionalities to utilize the @thirdweb-dev/api for fetching balances and contract metadata, improving the structure and error handling in the code.

Detailed summary

  • Added formatter configuration to biome.json.
  • Updated pnpm-lock.yaml to link workspace dependencies.
  • Refactored getTokenBalance and getWalletBalance to use API methods for fetching balances.
  • Enhanced error handling in balance fetching functions.
  • Updated tests to reflect changes in token and wallet balance retrieval.
  • Used specific test accounts and addresses for testing on Base Sepolia.

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

Summary by CodeRabbit

  • New Features

    • Enhanced contract metadata retrieval with API-backed lookup and automatic RPC fallback.
    • Unified wallet/token balance fetching via API, improving reliability for native and ERC20 balances.
  • Tests

    • Updated balance and metadata tests to use Base Sepolia and known tokens/addresses; relaxed assertions for stability.
  • Chores

    • Added @thirdweb-dev/api as a dependency.
    • Ignored .tmp directories in version control.
    • Adjusted formatting configuration (biome) and set package-level root behavior.

@d4mr d4mr requested review from a team as code owners September 8, 2025 16:34
Copy link

linear bot commented Sep 8, 2025

Copy link

changeset-bot bot commented Sep 8, 2025

⚠️ No Changeset found

Latest commit: d29c06d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Sep 8, 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 Sep 8, 2025 5:11pm
nebula Ready Ready Preview Comment Sep 8, 2025 5:11pm
thirdweb_playground Ready Ready Preview Comment Sep 8, 2025 5:11pm
thirdweb-www Ready Ready Preview Comment Sep 8, 2025 5:11pm
wallet-ui Ready Ready Preview Comment Sep 8, 2025 5:11pm

Copy link
Contributor

coderabbitai bot commented Sep 8, 2025

Walkthrough

Adds API-based implementations for wallet/contract metadata reads, updates related tests to Base Sepolia fixtures, introduces @thirdweb-dev/api dependency, and tweaks repo/tooling config (.gitignore, biome). getContractMetadata becomes API-first with RPC fallback; getWalletBalance/getTokenBalance switch to API-only.

Changes

Cohort / File(s) Summary
API migration: contract metadata
packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
Added API-first metadata retrieval via @thirdweb-dev/api with configure; derive name/symbol from ABI or RPC as fallback; preserves signature; retains RPC fallback on failures.
API migration: balances
packages/thirdweb/src/wallets/utils/getWalletBalance.ts, packages/thirdweb/src/wallets/utils/getTokenBalance.ts
Replaced RPC/contract reads with single API call (getWalletBalance); added configure; mapped API fields to return types; removed legacy imports/logic; added error checks for empty results.
Tests updated to Base Sepolia and API-backed behavior
packages/thirdweb/src/extensions/common/read/getContractMetadata.test.ts, packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts, packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
Rewrote assertions to be property/type-based; switched to Base Sepolia addresses/tokens; removed ANVIL/deployment flows; adjusted expectations (ETH/USDC).
Dependency addition
packages/thirdweb/package.json
Added dependency: @thirdweb-dev/api with version workspace:*.
Tooling config
biome.json, packages/thirdweb/biome.json
Enabled formatter overrides (indentStyle: space); set package biome root: false.
Repo ignore
.gitignore
Added rule to ignore .tmp/ directories.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Caller Code
  participant GC as getContractMetadata()
  participant API as api.thirdweb.com
  participant RPC as RPC/On-chain

  Dev->>GC: call(options: { chain, address, client })
  GC->>API: GET /contract-metadata?chainId&address (configured with clientId/secret)
  alt API returns ABI
    API-->>GC: { abi, compiler, language, docs... }
    par Resolve optional name/symbol
      GC->>RPC: name() via ERC20/721 if needed
      GC->>RPC: symbol() via ERC20/721 if needed
    end
    GC-->>Dev: { name, symbol, abi, compiler, language, devdoc, userdoc }
  else API error or no ABI
    API-->>GC: error/empty
    Note over GC,RPC: Fallback to legacy RPC-based metadata
    GC->>RPC: contractURI()/fetch metadata JSON
    GC->>RPC: name(), symbol()
    GC-->>Dev: { name, symbol, ...from RPC }
  end
Loading
sequenceDiagram
  autonumber
  actor Dev as Caller Code
  participant GWB as getWalletBalance()/getTokenBalance()
  participant API as api.thirdweb.com

  Dev->>GWB: call({ address, chain, tokenAddress?, client })
  GWB->>API: GET /wallet-balance?chainId&address[&tokenAddress]
  API-->>GWB: { result: [balanceData] }
  alt balanceData present
    GWB-->>Dev: { chainId, tokenAddress, name, symbol, decimals, value(BigInt), displayValue }
  else missing/empty
    GWB-->>Dev: throw Error("No balance data")
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Migrate common TS methods to api.thirdweb.com (BLD-263)
Migrate wallet-related methods to API (BLD-263)
Migrate additional method(s) e.g., contract metadata to API with fallback (BLD-263)
Add/update tests for migrated methods (BLD-263)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add .tmp ignore rule (.gitignore, end of file) Not related to API migration; repository housekeeping.
Biome formatter overrides (biome.json, override block) Tooling configuration; not tied to migrating TS methods to API.
Package-level biome root/formatter settings (packages/thirdweb/biome.json, top-level and overrides) Formatting configuration change; unrelated to API migration objectives.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • TEAM-0000: Entity not found: Issue - Could not find referenced Issue.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BLD-263

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

@github-actions github-actions bot added Dashboard Involves changes to the Dashboard. packages SDK Involves changes to the thirdweb SDK labels Sep 8, 2025
Copy link
Contributor

graphite-app bot commented Sep 8, 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.

Copy link
Contributor

github-actions bot commented Sep 8, 2025

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
thirdweb (esm) 63.96 KB (0%) 1.3 s (0%) 237 ms (+56.78% 🔺) 1.6 s
thirdweb (cjs) 356.86 KB (0%) 7.2 s (0%) 1.3 s (+4.82% 🔺) 8.4 s
thirdweb (minimal + tree-shaking) 5.73 KB (0%) 115 ms (0%) 94 ms (+964.36% 🔺) 209 ms
thirdweb/chains (tree-shaking) 526 B (0%) 11 ms (0%) 56 ms (+1649.38% 🔺) 67 ms
thirdweb/react (minimal + tree-shaking) 19.15 KB (0%) 383 ms (0%) 68 ms (+182.62% 🔺) 451 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: 6

🧹 Nitpick comments (16)
apps/dashboard/package.json (1)

2-130: Format-only churn: consider adding JSON files to your formatter scope

To avoid future whitespace-only diffs, include package.json in your Biome/Prettier targets (or a root format script).

packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts (4)

6-9: Misleading comment on test address

The address 0x742d… is a well-known mainnet account, not “Base deployer”. Consider neutral wording or use a local test account from test/src/test-wallets.ts.

Apply:

-// Create a mock account for testing
-const testAccount = {
-  address: "0x742d35Cc6645C0532b6C766684f4b4E99Bf87E8A", // Base deployer address
-};
+// Use a deterministic test account (no balance assumptions)
+const testAccount = {
+  address: "0x742d35Cc6645C0532b6C766684f4b4E99Bf87E8A",
+};

Or import from your shared fixtures:

+import { TEST_ACCOUNT_D as testAccount } from "~test/test-wallets.js";

19-24: Avoid brittle asserts on native token name

Hard-coding "Sepolia Ether" may break across chain metadata updates. Assert against chain metadata instead.

- expect(result.decimals).toBe(18);
- expect(result.name).toBe("Sepolia Ether");
- expect(result.symbol).toBe("ETH");
+ expect(result.decimals).toBe(baseSepolia.nativeCurrency.decimals);
+ expect(result.name).toBe(baseSepolia.nativeCurrency.name);
+ expect(result.symbol).toBe(baseSepolia.nativeCurrency.symbol);

27-36: Prefer MSW over live API in unit tests

These tests rely on network + TW_SECRET_KEY, which can flake. Use MSW to stub @thirdweb-dev/api responses and keep tests deterministic.

I can sketch MSW handlers for the wallet balance endpoint if helpful.


38-44: USDC decimals assert is fine; add minimal shape checks

Since value/displayValue types are asserted, also consider asserting symbol/name non-empty to guard empty API payloads.

- expect(result.symbol).toBeDefined();
- expect(result.name).toBeDefined();
+ expect(result.symbol).toMatch(/\S+/);
+ expect(result.name).toMatch(/\S+/);
packages/thirdweb/src/wallets/utils/getWalletBalance.ts (5)

1-4: Lazy-load @thirdweb-dev/api to keep bundles lean

Importing the API client at top-level pulls it into bundles even when unused. Dynamic import inside the function avoids upfront cost.

-import {
-  getWalletBalance as apiGetWalletBalance,
-  configure,
-} from "@thirdweb-dev/api";
+// Defer loading heavy deps until needed

Inside the function, before using it:

+  const { getWalletBalance: apiGetWalletBalance, configure } =
+    await import("@thirdweb-dev/api");

47-55: Support cancellation for better UX

Consider accepting an optional AbortSignal in options and passing it to the underlying request (if supported) to allow callers to cancel in-flight balance fetches.

Additional changes outside this hunk:

// Extend options
export type GetWalletBalanceOptions = {
  // ...
  signal?: AbortSignal;
};

And when calling:

const response = await apiGetWalletBalance(
  { path: { address }, query: { chainId: [chain.id], ...(tokenAddress && { tokenAddress }) } },
  // second param only if the API supports it; otherwise pass { signal } within the same object if that's the shape
  { signal: options.signal },
);

57-66: Improve error messages with context

Include address/chain/token to ease debugging.

-  if (!response.data?.result || response.data.result.length === 0) {
-    throw new Error("No balance data returned from API");
-  }
+  if (!response.data?.result || response.data.result.length === 0) {
+    throw new Error(
+      `No balance data returned from API for address ${address} on chain ${chain.id}${tokenAddress ? ` (token ${tokenAddress})` : ""}`,
+    );
+  }

-  if (!balanceData) {
-    throw new Error("Balance data not found for the specified chain");
-  }
+  if (!balanceData) {
+    throw new Error(
+      `Balance data not found for address ${address} on chain ${chain.id}${tokenAddress ? ` (token ${tokenAddress})` : ""}`,
+    );
+  }

68-77: Guard BigInt conversion

Defensive parsing avoids cryptic throws if the API shape changes.

-    value: BigInt(balanceData.value),
+    value:
+      (() => {
+        try {
+          return BigInt(balanceData.value);
+        } catch {
+          throw new Error(
+            `Invalid balance value "${balanceData.value}" for address ${address} on chain ${chain.id}`,
+          );
+        }
+      })(),

21-35: Add visibility tag to TSDoc

Public symbol docs require a custom tag. You already use @walletUtils; if this is experimental, add @beta/@experimental for clarity.

  /**
   * Retrieves the balance of a token or native currency for a given wallet.
+  * @beta
   * @param options - The options for retrieving the token balance.
packages/thirdweb/src/wallets/utils/getTokenBalance.ts (3)

48-56: Normalize and validate tokenAddress before sending to API

Ensure tokenAddress is well-formed to avoid API cache misses or 400s. Normalize to lowercase and optionally validate.

-    query: {
-      chainId: [chain.id],
-      ...(tokenAddress && { tokenAddress }),
-    },
+    query: {
+      chainId: [chain.id],
+      ...(tokenAddress && { tokenAddress: tokenAddress.toLowerCase() }),
+    },

Optionally, add an address guard:

// at function top (after destructure)
import { isAddress } from "viem";
if (tokenAddress && !isAddress(tokenAddress)) {
  throw new Error(`Invalid tokenAddress: ${tokenAddress}`);
}

58-67: Defensive result selection and clearer errors

  • If the API ever returns multiple results, assert the selected item matches chain.id.
  • Improve error messages with context to aid debugging.
-  if (!response.data?.result || response.data.result.length === 0) {
-    throw new Error("No balance data returned from API");
-  }
+  if (!response.data?.result || response.data.result.length === 0) {
+    throw new Error(`No balance data returned from API for chainId=${chain.id}`);
+  }

-  const balanceData = response.data.result[0];
+  const balanceData = response.data.result[0];

-  if (!balanceData) {
-    throw new Error("Balance data not found for the specified chain");
-  }
+  if (!balanceData) {
+    throw new Error(`Balance data not found for chainId=${chain.id}`);
+  }
+  if (typeof balanceData.chainId === "number" && balanceData.chainId !== chain.id) {
+    throw new Error(`Mismatched chainId: expected ${chain.id}, got ${balanceData.chainId}`);
+  }

71-76: Guard BigInt parsing

Ensure value is a stringified integer before BigInt, avoiding accidental decimal or non-numeric inputs.

-    value: BigInt(balanceData.value),
+    value: BigInt(String(balanceData.value)),
packages/thirdweb/src/extensions/common/read/getContractMetadata.test.ts (1)

10-21: Good shift to stable assertions; consider stubbing network for determinism

The partial/object and type checks are robust. To avoid dependence on external API/RPC and TW_SECRET_KEY, add an MSW stub for the metadata endpoint (and optionally the name/symbol RPC calls) so the test is deterministic.

packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (2)

58-69: Type the ABI entries to avoid any and brittle property access

Define a minimal ABI item type for functions and narrow checks instead of (item: any). This improves readability and safety.

type AbiItem = { type: string; name?: string; inputs?: unknown[] };
const isZeroArgFn = (i: AbiItem, n: "name" | "symbol") =>
  i.type === "function" && i.name === n && (i.inputs?.length ?? 0) === 0;

const nameFunc = (abi as AbiItem[]).find((i) => isZeroArgFn(i, "name"));
const symbolFunc = (abi as AbiItem[]).find((i) => isZeroArgFn(i, "symbol"));

93-95: Prefer internal logger over console.debug in library code

console.debug in library paths is noisy and hard to control. Route through an internal logger or gate with a verbose flag.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 e78a984 and 8b275fc.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • .gitignore (1 hunks)
  • apps/dashboard/package.json (1 hunks)
  • packages/thirdweb/package.json (1 hunks)
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.test.ts (1 hunks)
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (2 hunks)
  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts (1 hunks)
  • packages/thirdweb/src/wallets/utils/getTokenBalance.ts (2 hunks)
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts (1 hunks)
  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{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/common/read/getContractMetadata.test.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.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/extensions/common/read/getContractMetadata.test.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.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/extensions/common/read/getContractMetadata.test.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.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/extensions/common/read/getContractMetadata.test.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.ts
packages/thirdweb/src/wallets/**

📄 CodeRabbit inference engine (CLAUDE.md)

packages/thirdweb/src/wallets/**: Unified Wallet and Account interfaces in wallet architecture
Support for in-app wallets (social/email login)
Smart wallets with account abstraction
EIP-1193, EIP-5792, EIP-7702 standard support in wallet modules

Files:

  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.ts
**/package.json

📄 CodeRabbit inference engine (AGENTS.md)

Track bundle budgets via package.json#size-limit

Files:

  • apps/dashboard/package.json
  • packages/thirdweb/package.json
🧠 Learnings (5)
📓 Common learnings
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/wallets/** : EIP-1193, EIP-5792, EIP-7702 standard support in wallet modules
📚 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 test/src/test-wallets.ts : Predefined test accounts are in `test/src/test-wallets.ts`

Applied to files:

  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.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 **/*.test.{ts,tsx} : Use `FORKED_ETHEREUM_CHAIN` for mainnet interactions and `ANVIL_CHAIN` for isolated tests

Applied to files:

  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.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/wallets/** : EIP-1193, EIP-5792, EIP-7702 standard support in wallet modules

Applied to files:

  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.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/wallets/** : Unified `Wallet` and `Account` interfaces in wallet architecture

Applied to files:

  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts
🧬 Code graph analysis (2)
packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts (2)
packages/thirdweb/src/wallets/utils/getTokenBalance.ts (1)
  • getTokenBalance (37-77)
packages/thirdweb/src/exports/chains.ts (1)
  • baseSepolia (18-18)
packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts (2)
packages/thirdweb/src/wallets/utils/getWalletBalance.ts (1)
  • getWalletBalance (36-78)
packages/thirdweb/src/exports/chains.ts (1)
  • baseSepolia (18-18)
⏰ 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: Build Packages
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: Size
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: Unit Tests
  • GitHub Check: Lint Packages
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
.gitignore (1)

37-37: LGTM: sensible ignore for ephemeral .tmp dirs

This will ignore ".tmp" directories at any depth. No issues.

packages/thirdweb/package.json (1)

26-26: Dependency add looks good

Adding @thirdweb-dev/api aligns with the API-first utilities in this PR.

Comment on lines +1 to +4
import {
getContractMetadata as apiGetContractMetadata,
configure,
} from "@thirdweb-dev/api";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Avoid global configure() races; centralize auth similar to balance util

Same concern as in balance utils: configure() is global and can race across concurrent clients. Use a shared ensureApiConfigured(options.contract.client) helper (see suggestion in getTokenBalance.ts) to dedupe and avoid cross-request credential bleed.

-  // Configure the API client
-  configure({
-    clientId: options.contract.client.clientId,
-    secretKey: options.contract.client.secretKey,
-  });
+  ensureApiConfigured(options.contract.client);

Also applies to: 32-37

🤖 Prompt for AI Agents
In packages/thirdweb/src/extensions/common/read/getContractMetadata.ts around
lines 1-4 (and also affecting lines 32-37), the file currently calls the global
configure() from @thirdweb-dev/api which can race across concurrent clients and
leak credentials; replace direct configure() usage with a centralized helper
(e.g., ensureApiConfigured(options.contract.client)) that initializes API auth
for the given client idempotently and safely, import and call that helper before
any apiGetContractMetadata calls, and remove any other global configure()
invocations so auth setup is deduplicated and scoped to the client to prevent
cross-request credential bleed.

Comment on lines +47 to +55
if (response.data?.result?.output?.abi) {
// Extract name and symbol from ABI or devdoc/userdoc
const abi = response.data.result.output.abi;
const devdoc = response.data.result.output.devdoc;
const userdoc = response.data.result.output.userdoc;

// Try to find name and symbol from ABI functions
let contractName = "";
let contractSymbol = "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Early return can emit empty name/symbol; gate return or merge with fallback

If name()/symbol() RPC calls fail, contractName/contractSymbol remain empty, yet the function returns early with empty strings. Only return early if at least one is resolved; otherwise, let the RPC/URI fallback path run.

-      return {
-        name: contractName,
-        symbol: contractSymbol,
-        abi: abi,
-        compiler: response.data.result.compiler,
-        language: response.data.result.language,
-        devdoc: devdoc,
-        userdoc: userdoc,
-      };
+      if (contractName || contractSymbol) {
+        return {
+          name: contractName,
+          symbol: contractSymbol,
+          abi,
+          compiler: response.data.result.compiler,
+          language: response.data.result.language,
+          devdoc,
+          userdoc,
+        };
+      }
+      // No name/symbol resolvable via ABI/RPC; fall through to fallback below.

Optional improvement: capture abi/devdoc/userdoc/compiler/language into a local apiExtras and merge them into the final return after the fallback so you don’t lose the API payload when taking the fallback path.

Also applies to: 71-79, 82-92

🤖 Prompt for AI Agents
In packages/thirdweb/src/extensions/common/read/getContractMetadata.ts around
lines 47-55 (and similarly at 71-79 and 82-92), the code currently performs an
early return even when contractName/contractSymbol are empty; change the logic
so you only return early if at least one of name or symbol was successfully
resolved (non-empty) — otherwise do not return and allow the RPC/URI fallback
path to run. Additionally, capture abi, devdoc, userdoc, compiler and language
into a local apiExtras object and ensure you merge apiExtras into the final
return value after the fallback path (so the API payload is preserved when
falling back).

Comment on lines +2 to +4
getWalletBalance as apiGetWalletBalance,
configure,
} from "@thirdweb-dev/api";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Global configure in a hot path can race across clients; avoid mutable global auth

configure() mutates module-global state. Concurrent calls with different client credentials can interleave and leak headers across requests. Move auth to per-request (preferred), or at least dedupe per client and detect conflicting credentials.

Apply this change locally:

-  // Configure the API client with credentials from the thirdweb client
-  configure({
-    clientId: client.clientId,
-    secretKey: client.secretKey,
-  });
+  // Ensure API auth is set for this client (no-ops if already set for same creds)
+  ensureApiConfigured(client);

Add a shared helper (place once, e.g., src/internal/api/config.ts, then import here):

// internal/api/config.ts
import { configure } from "@thirdweb-dev/api";
import type { ThirdwebClient } from "../../client/client.js";

let activeClientId: string | undefined;
let activeSecret: string | undefined;

export function ensureApiConfigured(client: ThirdwebClient): void {
  // Fast path: same creds already applied
  if (client.clientId === activeClientId && client.secretKey === activeSecret) return;

  // If different creds are already active, prefer explicitness:
  // either throw or overwrite with a warning. Choose policy; for now, overwrite.
  if (activeClientId && (client.clientId !== activeClientId || client.secretKey !== activeSecret)) {
    // Consider routing these through a logger if available
    // console.warn("Switching @thirdweb-dev/api credentials at runtime.");
  }

  configure({ clientId: client.clientId, secretKey: client.secretKey });
  activeClientId = client.clientId;
  activeSecret = client.secretKey;
}

Also applies to: 42-47

Comment on lines +7 to 26
it("should work for native currency", async () => {
// Use a known address with ETH on Base Sepolia
// This is a public address that should have some ETH for testing
const testAddress = "0x742d35Cc6645C0532b6C766684f4b4E99Bf87E8A"; // Base deployer address

const result = await getWalletBalance({
address: TEST_ACCOUNT_A.address,
chain: ANVIL_CHAIN,
address: testAddress,
chain: baseSepolia,
client: TEST_CLIENT,
tokenAddress: erc20Address,
});
const expectedDecimal = 18;

expect(result).toBeDefined();
expect(result.decimals).toBe(expectedDecimal);
expect(result.symbol).toBeDefined();
expect(result.name).toBeDefined();
expect(result.value).toBe(BigInt(amount) * 10n ** BigInt(expectedDecimal));
expect(result.displayValue).toBe(amount.toString());
expect(result.chainId).toBe(84532); // Base Sepolia chain ID
expect(result.decimals).toBe(18);
expect(result.symbol).toBe("ETH");
expect(result.name).toBe("Sepolia Ether");
expect(result.tokenAddress).toBeDefined();
expect(result.value).toBeTypeOf("bigint");
expect(result.displayValue).toBeTypeOf("string");
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Make the native-currency test deterministic; relax brittle assertions

This test depends on live Base Sepolia state and a hard-coded address (comment also mislabels it as “Base deployer”). It will flake and can fail if the address has 0 balance or API semantics change. Also, native balances may not include tokenAddress.

  • Stub the API using MSW and assert on the contract instead of the network.
  • Relax assertions for name and tokenAddress (native may omit tokenAddress).

Minimal assertion tweak:

-    expect(result.name).toBe("Sepolia Ether");
-    expect(result.tokenAddress).toBeDefined();
+    expect(result.name).toBeTypeOf("string");
+    // For native, API may omit tokenAddress; don't assert presence.

Example MSW handler you can co-locate in this test (or shared test setup):

// at top of file
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";

const server = setupServer(
  http.get(/\/wallets\/.+\/balance$/, () =>
    HttpResponse.json({
      result: [
        {
          chainId: 84532,
          decimals: 18,
          displayValue: "1.2345",
          symbol: "ETH",
          name: "Sepolia Ether",
          tokenAddress: null,
          value: "1234500000000000000",
        },
      ],
    }),
  ),
);

beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => server.resetHandlers());

Run with TW_SECRET_KEY unset to ensure this path is used.


Stub network calls and relax brittle native-currency assertions

  • Avoid hitting live Base Sepolia with a hard-coded address (0x742d35Cc6645C…); flakes if balance changes or API omits native fields.
  • Co-locate an MSW handler to stub GET /wallets/:address/balance and assert on its JSON payload.
  • Replace strict name and tokenAddress checks with:
-    expect(result.name).toBe("Sepolia Ether");
-    expect(result.tokenAddress).toBeDefined();
+    expect(result.name).toBeTypeOf("string");
+    // native may omit tokenAddress
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("should work for native currency", async () => {
// Use a known address with ETH on Base Sepolia
// This is a public address that should have some ETH for testing
const testAddress = "0x742d35Cc6645C0532b6C766684f4b4E99Bf87E8A"; // Base deployer address
const result = await getWalletBalance({
address: TEST_ACCOUNT_A.address,
chain: ANVIL_CHAIN,
address: testAddress,
chain: baseSepolia,
client: TEST_CLIENT,
tokenAddress: erc20Address,
});
const expectedDecimal = 18;
expect(result).toBeDefined();
expect(result.decimals).toBe(expectedDecimal);
expect(result.symbol).toBeDefined();
expect(result.name).toBeDefined();
expect(result.value).toBe(BigInt(amount) * 10n ** BigInt(expectedDecimal));
expect(result.displayValue).toBe(amount.toString());
expect(result.chainId).toBe(84532); // Base Sepolia chain ID
expect(result.decimals).toBe(18);
expect(result.symbol).toBe("ETH");
expect(result.name).toBe("Sepolia Ether");
expect(result.tokenAddress).toBeDefined();
expect(result.value).toBeTypeOf("bigint");
expect(result.displayValue).toBeTypeOf("string");
});
it("should work for native currency", async () => {
// Use a known address with ETH on Base Sepolia
// This is a public address that should have some ETH for testing
const testAddress = "0x742d35Cc6645C0532b6C766684f4b4E99Bf87E8A"; // Base deployer address
const result = await getWalletBalance({
address: testAddress,
chain: baseSepolia,
client: TEST_CLIENT,
});
expect(result).toBeDefined();
expect(result.chainId).toBe(84532); // Base Sepolia chain ID
expect(result.decimals).toBe(18);
expect(result.symbol).toBe("ETH");
expect(result.name).toBeTypeOf("string");
// native may omit tokenAddress
expect(result.value).toBeTypeOf("bigint");
expect(result.displayValue).toBeTypeOf("string");
});

Comment on lines +28 to 49
it("should work for ERC20 token", async () => {
// Use a known ERC20 token on Base Sepolia
// Using the official test token: Base Sepolia USD Coin
const usdcAddress = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // USDC on Base Sepolia
const testAddress = "0x742d35Cc6645C0532b6C766684f4b4E99Bf87E8A"; // Address with potential balance

const result = await getWalletBalance({
address: TEST_ACCOUNT_A.address,
chain: ANVIL_CHAIN,
address: testAddress,
chain: baseSepolia,
client: TEST_CLIENT,
tokenAddress: usdcAddress,
});

expect(result).toBeDefined();
expect(result.decimals).toBe(18);
expect(result.chainId).toBe(84532);
expect(result.decimals).toBe(6); // USDC has 6 decimals
expect(result.symbol).toBeDefined();
expect(result.value).toBeGreaterThan(0n);
expect(result.displayValue).toBeDefined();
expect(result.name).toBeDefined();
expect(result.tokenAddress).toBe(usdcAddress);
expect(result.value).toBeTypeOf("bigint");
expect(result.displayValue).toBeTypeOf("string");
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

ERC20 test also relies on live chain and a possibly incorrect USDC address

Using a real address with unknown holdings on Base Sepolia is flaky. Keep decimals check, but stub the API and assert types and equality against the stubbed payload instead of chain state.

Suggested tweaks:

-    expect(result.symbol).toBeDefined();
-    expect(result.name).toBeDefined();
+    expect(typeof result.symbol).toBe("string");
+    expect(typeof result.name).toBe("string");

And in the MSW stub for this case, return a second handler that matches the tokenAddress query and returns a 6-decimal payload for USDC so you can keep:

expect(result.decimals).toBe(6);
expect(result.tokenAddress).toBe(usdcAddress.toLowerCase());
🤖 Prompt for AI Agents
In packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts around lines
28-49, the ERC20 test depends on a live chain and an unreliable address; replace
the real-chain call with a stubbed API response: add an MSW handler that matches
the tokenAddress query and returns a deterministic ERC20 payload (symbol, name,
decimals: 6, tokenAddress in lowercase, value/displayValue) and use TEST_CLIENT
configured to hit MSW; keep the decimals assertion but assert tokenAddress
equals the stubbed usdcAddress.toLowerCase() and assert types/values against the
stubbed payload rather than live chain state.

Comment on lines +42 to +45
configure({
clientId: client.clientId,
secretKey: client.secretKey,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Do not pass secretKey in browser; also avoid global mutable config

Leaking secretKey to client runtimes is a security risk, and global configure() is unsafe with multiple clients in the same process (race/bleed across requests).

-  configure({
-    clientId: client.clientId,
-    secretKey: client.secretKey,
-  });
+  // Only include secretKey on server; avoid leaking in browser bundles
+  const config: { clientId?: string; secretKey?: string } = {};
+  if (client.clientId) config.clientId = client.clientId;
+  if (typeof window === "undefined" && client.secretKey) {
+    config.secretKey = client.secretKey;
+  }
+  configure(config);

Run this scan to find other global configure() usages that may suffer the same issue:


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts --type=tsx -C2 '\bconfigure\s*\('

Length of output: 27


🏁 Script executed:

#!/bin/bash
rg -nP '\bconfigure\s*\(' -g '*.ts' -g '*.tsx' -C2

Length of output: 4345


Avoid leaking secretKey in browser; guard configure calls server-side

  • In getWalletBalance.ts, getTokenBalance.ts, and getContractMetadata.ts, build a local config object that always includes clientId but only adds secretKey when typeof window === 'undefined', then call configure(config) instead of passing both keys unconditionally.
  • This prevents shipping your secretKey in browser bundles and removes reliance on a global mutable config. Tests running in Node (e.g. in-app-gateway.test.ts) will still receive secretKey as expected.
  • Double-check that no other client-side code calls configure({ secretKey }).
🤖 Prompt for AI Agents
In packages/thirdweb/src/wallets/utils/getWalletBalance.ts around lines 42-45,
the code calls configure with clientId and secretKey unconditionally which can
leak secretKey into browser bundles; change to build a local config object that
always sets clientId and only adds secretKey when typeof window === 'undefined',
then call configure(config) instead of passing both keys; apply the same pattern
to getTokenBalance.ts and getContractMetadata.ts and scan for any other
client-side code calling configure({ secretKey }) to remove reliance on a global
mutable config.

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 (5)
packages/thirdweb/src/wallets/utils/getWalletBalance.ts (1)

41-45: Do not pass secretKey in browser; avoid global mutable configure across requests

Passing secretKey unconditionally risks leaking it to browser bundles; global configure() can bleed creds across concurrent requests.

-  // Configure the API client with credentials from the thirdweb client
-  configure({
-    clientId: client.clientId,
-    secretKey: client.secretKey,
-  });
+  // Configure API client safely (no secretKey in browser; minimize global state)
+  const { getWalletBalance: apiGetWalletBalance, configure } =
+    await import("@thirdweb-dev/api");
+  const cfg: { clientId?: string; secretKey?: string } = {};
+  if (client.clientId) cfg.clientId = client.clientId;
+  // Only attach secretKey on server runtimes
+  if (typeof window === "undefined" && client.secretKey) {
+    cfg.secretKey = client.secretKey;
+  }
+  configure(cfg);

Optional: if the API supports per-call config instead of global state, prefer that to eliminate race conditions.

packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts (4)

6-16: Make tests deterministic: stub the API with MSW; don’t gate on secrets

These tests hit live infra and are flaky. Stub GET /wallets/:address/balance with MSW and run unconditionally.

Add an MSW server alongside this test (minimal sketch):

import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";

const server = setupServer(
  // native
  http.get(/\/wallets\/[^/]+\/balance$/, ({ request }) => {
    const url = new URL(request.url);
    const token = url.searchParams.get("tokenAddress");
    if (!token) {
      return HttpResponse.json({
        result: [{
          chainId: 84532, decimals: 18, symbol: "ETH", name: "Sepolia Ether",
          tokenAddress: null, value: "1234500000000000000", displayValue: "1.2345",
        }],
      });
    }
    // ERC20
    return HttpResponse.json({
      result: [{
        chainId: 84532, decimals: 6, symbol: "USDC", name: "USD Coin",
        tokenAddress: token.toLowerCase(), value: "42", displayValue: "0.000042",
      }],
    });
  }),
);

beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => server.resetHandlers());

Then remove runIf(process.env.TW_SECRET_KEY) so the unit tests run offline.


19-25: Relax brittle native-asset assertions; tokenAddress may be null/omitted

API may omit tokenAddress for native. Don’t assert presence; assert types instead of exact name.

-    expect(result.decimals).toBe(18);
-    expect(result.symbol).toBe("ETH");
-    expect(result.name).toBe("Sepolia Ether");
-    expect(result.tokenAddress).toBeDefined();
+    expect(result.decimals).toBe(18);
+    expect(typeof result.symbol).toBe("string");
+    expect(typeof result.name).toBe("string");
+    // native may omit tokenAddress; don't assert presence

28-39: Stub ERC20 path too; don’t depend on live USDC at a possibly empty wallet

Mirror the native-case MSW stub with a handler that returns USDC-like payload (6 decimals) when tokenAddress is present; assert against the stub.


44-47: Compare tokenAddress case-insensitively; assert types for symbol/name

APIs often normalize to lowercase; current strict match can fail.

-    expect(result.symbol).toBeDefined();
-    expect(result.name).toBeDefined();
-    expect(result.tokenAddress).toBe(usdcAddress);
+    expect(typeof result.symbol).toBe("string");
+    expect(typeof result.name).toBe("string");
+    expect(result.tokenAddress?.toLowerCase()).toBe(usdcAddress.toLowerCase());
🧹 Nitpick comments (7)
biome.json (1)

38-41: Remove redundant formatter override in overrides

Top-level formatter already enforces indentStyle. Keeping a duplicate per-file override for package.json adds noise without benefit.

       "assist": {
         "actions": {
           "source": {
             "useSortedKeys": "off"
           }
         }
       },
-      "formatter": {
-        "enabled": true,
-        "indentStyle": "space"
-      },
       "includes": ["package.json"]
packages/thirdweb/biome.json (1)

14-17: Drop duplicate formatter block inside overrides

Indent settings are already defined globally; no special formatting is needed for package.json here.

   {
     "assist": {
       "actions": {
         "source": {
           "useSortedKeys": "off"
         }
       }
     },
-    "formatter": {
-      "enabled": true,
-      "indentStyle": "space"
-    },
     "includes": ["package.json"]
   }
packages/thirdweb/src/wallets/utils/getWalletBalance.ts (2)

2-5: Consider lazy-loading @thirdweb-dev/api to keep bundles lean

Importing the API client at top-level can bloat initial bundles; dynamically import inside the async function.

-import {
-  getWalletBalance as apiGetWalletBalance,
-  configure,
-} from "@thirdweb-dev/api";
+// lazy-load to reduce initial bundle size
+// (tree-shaken and code-split in consumer apps)

And inside the function (see other comment) dynamically import:

const { getWalletBalance: apiGetWalletBalance, configure } =
  await import("@thirdweb-dev/api");

68-77: Harden value parsing and normalize native token address

Guard against malformed values and normalize zero-address to undefined for native token to match existing GetBalanceResult expectations.

-  return {
-    chainId: balanceData.chainId,
-    decimals: balanceData.decimals,
-    displayValue: balanceData.displayValue,
-    name: balanceData.name,
-    symbol: balanceData.symbol,
-    tokenAddress: balanceData.tokenAddress,
-    value: BigInt(balanceData.value),
-  };
+  let bigValue: bigint;
+  try {
+    bigValue = BigInt(balanceData.value);
+  } catch {
+    throw new Error(
+      `Invalid balance value for address=${address} chainId=${chain.id}: ${balanceData.value}`,
+    );
+  }
+  const ZERO = "0x0000000000000000000000000000000000000000";
+  return {
+    chainId: Number(balanceData.chainId),
+    decimals: balanceData.decimals,
+    displayValue: balanceData.displayValue,
+    name: balanceData.name,
+    symbol: balanceData.symbol,
+    tokenAddress:
+      balanceData.tokenAddress &&
+      balanceData.tokenAddress.toLowerCase() !== ZERO
+        ? balanceData.tokenAddress
+        : undefined,
+    value: bigValue,
+  };
packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts (3)

3-3: Prefer central chain export to avoid deep import coupling

Import baseSepolia from the public/central export to reduce fragility if internal paths change.

-import { baseSepolia } from "../../chains/chain-definitions/base-sepolia.js";
+import { baseSepolia } from "../../exports/chains.js";

8-11: Fix misleading comment and avoid relying on a “known” live address

This isn’t the “Base deployer” and may have zero balance on Base Sepolia. With MSW, the specific address doesn’t matter—use a neutral label.

-    // Use a known address with ETH on Base Sepolia
-    // This is a public address that should have some ETH for testing
-    const testAddress = "0x742d35Cc6645C0532b6C766684f4b4E99Bf87E8A"; // Base deployer address
+    const testAddress = "0x1111111111111111111111111111111111111111"; // dummy for stubbed test

41-49: Add a negative-path test for empty API result

Cover the error branch (“No balance data returned from API”) by stubbing { result: [] } and asserting rejects.toThrow.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 8b275fc and d29c06d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • .gitignore (1 hunks)
  • biome.json (1 hunks)
  • packages/thirdweb/biome.json (2 hunks)
  • packages/thirdweb/package.json (1 hunks)
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.test.ts (1 hunks)
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts (2 hunks)
  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts (1 hunks)
  • packages/thirdweb/src/wallets/utils/getTokenBalance.ts (2 hunks)
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts (1 hunks)
  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • .gitignore
  • packages/thirdweb/package.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.test.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.ts
  • packages/thirdweb/src/wallets/utils/getTokenBalance.test.ts
  • packages/thirdweb/src/extensions/common/read/getContractMetadata.ts
🧰 Additional context used
📓 Path-based instructions (6)
biome.json

📄 CodeRabbit inference engine (CLAUDE.md)

Biome is the primary linter/formatter; rules are defined in biome.json

Biome governs formatting and linting; project rules live in biome.json

Files:

  • biome.json
**/*.{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/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.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/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts
packages/thirdweb/src/wallets/**

📄 CodeRabbit inference engine (CLAUDE.md)

packages/thirdweb/src/wallets/**: Unified Wallet and Account interfaces in wallet architecture
Support for in-app wallets (social/email login)
Smart wallets with account abstraction
EIP-1193, EIP-5792, EIP-7702 standard support in wallet modules

Files:

  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.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/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.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/wallets/utils/getWalletBalance.test.ts
🧠 Learnings (7)
📓 Common learnings
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/wallets/** : EIP-1193, EIP-5792, EIP-7702 standard support in wallet modules
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/wallets/** : Unified `Wallet` and `Account` interfaces in wallet architecture
📚 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 biome.json : Biome is the primary linter/formatter; rules are defined in `biome.json`

Applied to files:

  • biome.json
  • packages/thirdweb/biome.json
📚 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 biome.json : Biome governs formatting and linting; project rules live in biome.json

Applied to files:

  • biome.json
  • packages/thirdweb/biome.json
📚 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/wallets/** : Unified `Wallet` and `Account` interfaces in wallet architecture

Applied to files:

  • packages/thirdweb/src/wallets/utils/getWalletBalance.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/wallets/** : EIP-1193, EIP-5792, EIP-7702 standard support in wallet modules

Applied to files:

  • packages/thirdweb/src/wallets/utils/getWalletBalance.ts
  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.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 test/src/test-wallets.ts : Predefined test accounts are in `test/src/test-wallets.ts`

Applied to files:

  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.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 **/*.test.{ts,tsx} : Use `FORKED_ETHEREUM_CHAIN` for mainnet interactions and `ANVIL_CHAIN` for isolated tests

Applied to files:

  • packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts
🧬 Code graph analysis (1)
packages/thirdweb/src/wallets/utils/getWalletBalance.test.ts (2)
packages/thirdweb/src/wallets/utils/getWalletBalance.ts (1)
  • getWalletBalance (36-78)
packages/thirdweb/src/exports/chains.ts (1)
  • baseSepolia (18-18)
⏰ 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, vite)
  • GitHub Check: Size
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: Build Packages
  • GitHub Check: Unit Tests
  • GitHub Check: Lint Packages
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/thirdweb/biome.json (2)

4-4: Setting "root": false here is correct for a workspace package

Prevents this package config from shadowing the repo root rules.


3-5: Ignore invalid extends suggestion The "extends": "//" microsyntax in Biome v2 marks the config as nested (root: false) and extends the root configuration in monorepos (biomejs.dev). No changes required.

Likely an incorrect or invalid review comment.

Comment on lines +57 to 67
if (!response.data?.result || response.data.result.length === 0) {
throw new Error("No balance data returned from API");
}
// native token case
const rpcRequest = getRpcClient({ chain, client });

const [nativeSymbol, nativeDecimals, nativeName, nativeBalance] =
await Promise.all([
getChainSymbol(chain),
getChainDecimals(chain),
getChainNativeCurrencyName(chain),
eth_getBalance(rpcRequest, { address }),
]);
// Get the first result (should match our chain)
const balanceData = response.data.result[0];

if (!balanceData) {
throw new Error("Balance data not found for the specified chain");
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Don’t assume result[0]; filter by chainId (and tokenAddress) to avoid mismatches

API may return multiple results; picking the first is brittle. Filter to the requested chainId and tokenAddress.

-  if (!response.data?.result || response.data.result.length === 0) {
+  if (!response.data?.result || response.data.result.length === 0) {
     throw new Error("No balance data returned from API");
   }
-
-  // Get the first result (should match our chain)
-  const balanceData = response.data.result[0];
-
-  if (!balanceData) {
-    throw new Error("Balance data not found for the specified chain");
-  }
+  const chainIdStr = String(chain.id);
+  const balanceData = response.data.result.find((r) => String(r.chainId) === chainIdStr
+    && (tokenAddress ? r.tokenAddress?.toLowerCase() === tokenAddress.toLowerCase() : true));
+  if (!balanceData) {
+    throw new Error(
+      `Balance not found for address=${address} chainId=${chain.id}` +
+      (tokenAddress ? ` token=${tokenAddress}` : ""),
+    );
+  }

Please confirm the API guarantees single-element results when query.chainId contains one item; if guaranteed, we can keep a fast-path but still assert chainId equality before using [0].


🌐 Web query:

Does @thirdweb-dev/api getWalletBalance return a single result when query.chainId has a single id, and is the order guaranteed?

💡 Result:

Short answer: Yes — when you call getWalletBalance for a single chain the SDK returns a single balance object (not an array). For multichain calls the Insight-backed endpoints return a data array with per-item chain_id and a meta.chain_ids list, but you should not rely on the array index for mapping — use the chain_id (or meta.chain_ids) to match results to requested chains. [1][2]

Sources:

  • getWalletBalance return type (single object with chainId). [1]
  • Insight multichain response format (meta.chain_ids + per-item chain_id; use those fields to identify chain). [2]

Handle single-chain vs multi-chain responses explicitly
The SDK returns a single object (not an array) when query.chainId has one entry; for multi-chain calls it returns an array in arbitrary order. Update the logic so that:

  • If response.data.result is an object, assert its chainId matches before using it.
  • If it’s an array, filter by chainId (and tokenAddress) instead of using result[0].
🤖 Prompt for AI Agents
In packages/thirdweb/src/wallets/utils/getWalletBalance.ts around lines 57 to
67, the current code blindly uses response.data.result[0] and throws when
missing; change it to handle both object and array shapes: if
response.data.result is an object, verify result.chainId (and tokenAddress when
provided) matches the requested chainId/tokenAddress and use it; if it is an
array, filter the array for entries matching chainId (and tokenAddress if
present) and pick the matching entry (throw if none); keep existing error
messages when no matching balance is found.

@@ -1,6 +1,7 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
"extends": "//",
"root": false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think that extends: "//" is wrong no? that's prob why its not inheriting the root rules?

@@ -25,6 +29,72 @@ export async function getContractMetadata(
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix any
[key: string]: any;
}> {
// Configure the API client
configure({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of calling configure, pass clientFetch as a custom fetch impl to each api call. Check how i do it for insight/engine hey-api packages. Also make sure we respect the thirdweb domains value for dev/prod

"../../extensions/erc20/read/getBalance.js"
);
return getBalance({

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we use this function in gateway ? gotta make sure there's no infinite loop here

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