Skip to content

Conversation

@0xFirekeeper
Copy link
Member

@0xFirekeeper 0xFirekeeper commented Dec 15, 2025

Introduces a new hook useWalletPortfolio to fetch and aggregate wallet token balances and USD values across multiple chains. Updates the user wallets table to include total balance and token columns, a chain selector, a fetch balances button with progress, and summary stats for funded wallets and total value. Enhances the analytics/stat component to support empty text display.

Closes BLD-520


PR-Codex overview

This PR focuses on enhancing the UserWalletsTable component to integrate portfolio fetching capabilities, improve error handling, and display wallet statistics, including funded wallets and total value. It also adds retry logic for API calls and introduces new UI elements for better user experience.

Detailed summary

  • Added emptyText prop to StatCard in stat.tsx.
  • Implemented retry logic with exponential backoff in useEmbeddedWallets.ts.
  • Introduced fetchWithRetry function for API calls in useWalletPortfolio.ts.
  • Added portfolio state and fetching logic in UserWalletsTable.
  • Displayed wallet statistics (funded wallets and total value) in UserWalletsTable.
  • Enhanced UI with loading indicators and progress bars during balance fetch.

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

Summary by CodeRabbit

  • New Features

    • Per-wallet USD balances and token columns with expandable tooltips; aggregated stats for funded wallets and total value.
    • Network selector and on-demand "Fetch All Balances" with real-time progress, badges, and loading states.
    • Stat card now supports custom empty text for clearer empty-state display.
    • New portfolio-fetching capability to batch and aggregate wallet portfolio data via a fetch hook.
  • Bug Fixes

    • More robust wallet-list fetching with bounded retries, partial-result handling, and transient-failure resilience.

✏️ Tip: You can customize this high-level summary in your review settings.

Introduces a new hook `useWalletPortfolio` to fetch and aggregate wallet token balances and USD values across multiple chains. Updates the user wallets table to include total balance and token columns, a chain selector, a fetch balances button with progress, and summary stats for funded wallets and total value. Enhances the analytics/stat component to support empty text display.
@0xFirekeeper 0xFirekeeper requested review from a team as code owners December 15, 2025 23:16
@changeset-bot
Copy link

changeset-bot bot commented Dec 15, 2025

⚠️ No Changeset found

Latest commit: bc39467

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

@vercel
Copy link

vercel bot commented Dec 15, 2025

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

Project Deployment Review Updated (UTC)
docs-v2 Ready Ready Preview, Comment Dec 16, 2025 10:40pm
nebula Ready Ready Preview, Comment Dec 16, 2025 10:40pm
thirdweb_playground Ready Ready Preview, Comment Dec 16, 2025 10:40pm
thirdweb-www Ready Ready Preview, Comment Dec 16, 2025 10:40pm
wallet-ui Ready Ready Preview, Comment Dec 16, 2025 10:40pm

@github-actions github-actions bot added the Dashboard Involves changes to the Dashboard. label Dec 15, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 15, 2025

Walkthrough

Adds portfolio fetching and display: a new wallet-portfolio hook with batched/retry fetching and progress callbacks, UI changes in the user-wallets table to select chains and fetch balances with progress and per-wallet totals/tokens, a robustness update to embedded-wallet pagination, and a small StatCard prop addition.

Changes

Cohort / File(s) Summary
StatCard Enhancement
apps/dashboard/src/@/components/analytics/stat.tsx
Adds optional emptyText?: string prop and renders emptyText in a muted span when isPending is false and emptyText is provided; preserves existing formatted/value display otherwise.
Portfolio Data Hook
apps/dashboard/src/@/hooks/useWalletPortfolio.ts
New module exporting WalletPortfolioData and a React Query mutation hook useFetchAllPortfolios() that performs batched per-address/per-chain fetching, fetchWithRetry (exponential backoff), per-chain token fetchers, aggregation to per-wallet totals/tokens, and a progress callback.
User Wallets Table UI
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
Integrates portfolio hook, adds chain/network selector and "Fetch All Balances" action, progress UI (progress bar, spinner, badge), portfolioMap state, new table columns for Total Balance and Tokens (with tooltips/truncation), aggregated stats (Funded Wallets, Total Value), and updates download/search controls to respect new loading states.
Embedded Wallets Fetch Robustness
apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
Replaces an unbounded pagination loop with a bounded, retry-aware pagination loop using per-page retries, exponential backoff, max consecutive failures, and returns partial results on repeated failures.
Manifest
package.json
Manifest updated (likely dependency or metadata adjustments related to new features).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as UserWalletsTable
    participant Hook as useFetchAllPortfolios
    participant API as Thirdweb/API
    participant State as portfolioMap

    User->>UI: Click "Fetch All Balances" (select chains)
    UI->>Hook: start mutation { addresses, chainIds, authToken, onProgress }
    Hook->>API: Batch requests -> per-address, per-chain token calls (with fetchWithRetry/backoff)
    API-->>Hook: returns token balances + price info (per address)
    Hook->>State: update portfolioMap for address
    Hook->>UI: onProgress(completed, total)
    UI->>UI: update progress bar / spinner
    alt more batches
        Hook->>API: next batch...
    end
    Hook-->>UI: mutation resolves (done)
    UI->>State: compute aggregatedStats (total USD, funded wallets)
    UI->>User: render updated rows, tokens tooltips, and stats
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Review focus:
    • apps/dashboard/src/@/hooks/useWalletPortfolio.ts: batching/concurrency, retry/backoff correctness, progress callback behavior, and exported types/hook signature.
    • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx: state lifecycle for portfolioMap and aggregatedStats, UI loading/progress transitions, CSV download gating, and large-tooltip rendering.
    • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts: pagination retry loop, partial-result semantics, and error handling.
    • apps/dashboard/src/@/components/analytics/stat.tsx: emptyText branch interaction with formatter and locale formatting.
    • package.json: verify added/updated dependencies and version consistency.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding wallet portfolio fetching functionality and displaying it in the user table, which is the core objective of this PR.
Description check ✅ Passed The description covers the main changes and references the closed issue (BLD-520), though it lacks the suggested title format and explicit testing instructions from the template.
Linked Issues check ✅ Passed The PR implements all requirements from BLD-520: fetches wallet tokens across chains (useWalletPortfolio), displays tokens with USD values in the user table, and surfaces aggregate metrics (total USD value, funded wallet counts).
Out of Scope Changes check ✅ Passed All changes are in scope: portfolio fetching hook, user table enhancements with tokens/balances, retry logic for robustness, and StatCard improvements support the main objective of displaying wallet portfolios and metrics.
✨ 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 firekeeper/aggregated-balances

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

@codecov
Copy link

codecov bot commented Dec 15, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 54.47%. Comparing base (aad3e64) to head (bc39467).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8570   +/-   ##
=======================================
  Coverage   54.47%   54.47%           
=======================================
  Files         922      922           
  Lines       61361    61361           
  Branches     4149     4149           
=======================================
  Hits        33425    33425           
  Misses      27835    27835           
  Partials      101      101           
Flag Coverage Δ
packages 54.47% <ø> (ø)
🚀 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: 0

🧹 Nitpick comments (6)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (2)

44-46: Silent failure hides API errors.

When response.ok is false, returning an empty array silently may mask issues (rate limiting, auth errors, etc.). Consider logging the error status for debugging.

         if (!response.ok) {
+          console.warn(
+            `Failed to fetch tokens for ${address} on chain ${chainId}: ${response.status}`,
+          );
           return [];
         }

61-63: Potential precision loss for large token balances.

Number(t.balance) may lose precision for balances exceeding ~15 significant digits. Since this is display-only and the value field correctly uses BigInt, this is acceptable for most cases, but be aware that very large balances (e.g., meme tokens) could display slightly incorrect values.

For higher precision, consider using a library like viem's formatUnits:

import { formatUnits } from "viem";
// ...
displayValue: formatUnits(BigInt(t.balance), t.decimals),
apps/dashboard/src/@/components/analytics/stat.tsx (1)

3-10: Missing className prop for external overrides.

Per coding guidelines, components should expose a className prop on the root element to allow external styling overrides.

 export const StatCard: React.FC<{
   label: string;
   value?: number;
   icon: React.FC<{ className?: string }>;
   formatter?: (value: number) => string;
   isPending: boolean;
   emptyText?: string;
-}> = ({ label, value, formatter, icon: Icon, isPending, emptyText }) => {
+  className?: string;
+}> = ({ label, value, formatter, icon: Icon, isPending, emptyText, className }) => {
   return (
-    <dl className="flex items-center justify-between gap-4 rounded-lg border border-border bg-card p-4 pr-6">
+    <dl className={cn("flex items-center justify-between gap-4 rounded-lg border border-border bg-card p-4 pr-6", className)}>

This would require importing cn from @/lib/utils.

apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx (3)

160-169: Unstable dependency in useCallback.

Including fetchPortfoliosMutation (the entire mutation object) in the dependency array may cause unnecessary re-renders since the object reference changes on each render. Use the stable mutateAsync function directly.

+  const { mutateAsync: fetchPortfolios, isPending: isFetchingPortfolios } =
+    useFetchAllPortfolios();
+
   const handleFetchBalances = useCallback(async () => {
     // ...
-      const results = await fetchPortfoliosMutation.mutateAsync({
+      const results = await fetchPortfolios({
         // ...
       });
     // ...
   }, [
     selectedChains,
     getAllEmbeddedWallets,
     props.projectClientId,
     props.ecosystemSlug,
     props.teamId,
     props.client,
     props.authToken,
-    fetchPortfoliosMutation,
+    fetchPortfolios,
   ]);

Then update isFetchingBalances to use isFetchingPortfolios instead of fetchPortfoliosMutation.isPending.


293-296: Key may be non-unique for native tokens.

If tokenAddress is undefined for native tokens, the key becomes "undefined-${chainId}". Consider using a fallback like "native" for clarity.

 {data.tokens.map((t) => (
   <div
-    key={`${t.tokenAddress}-${t.chainId}`}
+    key={`${t.tokenAddress || "native"}-${t.chainId}`}
     className="flex justify-between gap-4 text-xs"
   >

509-510: Unnecessary arrow function wrapper.

The arrow function wrapper is not needed since handleFetchBalances takes no arguments.

 <Button
-  onClick={() => handleFetchBalances()}
+  onClick={handleFetchBalances}
   disabled={...}
📜 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 4bab6f1 and e269cdd.

📒 Files selected for processing (3)
  • apps/dashboard/src/@/components/analytics/stat.tsx (1 hunks)
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx (7 hunks)
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each TypeScript 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 in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground-web}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/src/**/*.{ts,tsx}: Import UI component primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground
Use Tailwind CSS only – no inline styles or CSS modules in dashboard and playground
Use cn() from @/lib/utils for conditional Tailwind class merging
Use design system tokens for styling (backgrounds: bg-card, borders: border-border, muted text: text-muted-foreground)
Expose className prop on root element for component overrides

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/dashboard/src/**/*.{ts,tsx}: Use NavLink for internal navigation with automatic active states in dashboard
Start server component files with import "server-only"; in Next.js
Read cookies/headers with next/headers in server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic with redirect() from next/navigation in server components
Begin client component files with 'use client'; directive in Next.js
Handle interactive UI with React hooks (useState, useEffect, React Query, wallet hooks) in client components
Access browser APIs (localStorage, window, IntersectionObserver) in client components
Support fast transitions with prefetched data in client components
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header for API calls – never embed tokens in URLs
Return typed results (Project[], User[]) from server-side data fetches – avoid any
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys in React Query for 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 – only use analytics client-side

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*.{ts,tsx}: Always import from the central UI library under @/components/ui/* for reusable core UI components like Button, Input, Select, Tabs, Card, Sidebar, Separator, Badge
Use NavLink from @/components/ui/NavLink for internal navigation to ensure active states are handled automatically
For notices and skeletons, rely on AnnouncementBanner, GenericLoadingPage, and EmptyStateCard components
Import icons from lucide-react or the project-specific …/icons exports; never embed raw SVG
Keep components pure; fetch data outside using server components or hooks and pass it down via props
Use Tailwind CSS as the styling system; avoid inline styles or CSS modules
Merge class names with cn from @/lib/utils to keep conditional logic readable
Stick to design tokens: use bg-card, border-border, text-muted-foreground and other Tailwind variables instead of hard-coded colors
Use spacing utilities (px-*, py-*, gap-*) instead of custom margins
Follow mobile-first responsive design with Tailwind helpers (max-sm, md, lg, xl)
Never hard-code colors; always use Tailwind variables
Combine class names via cn, and expose className prop if useful in components
Use React Query (@tanstack/react-query) for all client-side data fetching with typed hooks

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

Add className prop to the root element of every component to allow external overrides

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground}/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{tsx,ts}: Import UI primitives from @/components/ui/_ (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in Dashboard and Playground apps
Use NavLink for internal navigation so active states are handled automatically
Use Tailwind CSS for styling – no inline styles or CSS modules
Merge class names with cn() from @/lib/utils to keep conditional logic readable
Stick to design tokens for styling: backgrounds (bg-card), borders (border-border), muted text (text-muted-foreground), etc.
Server Components: Read cookies/headers with next/headers, access server-only environment variables or secrets, perform heavy data fetching, implement redirect logic with redirect() from next/navigation, and start files with import 'server-only'; to prevent client bundling
Client Components: Begin files with 'use client'; before imports, handle interactive UI relying on React hooks (useState, useEffect, React Query, wallet hooks), access browser APIs (localStorage, window, IntersectionObserver, etc.), and support fast transitions with client-side data prefetching
For client-side data fetching: Wrap calls in React Query (@tanstack/react-query), use descriptive and stable queryKeys for cache hits, configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s), and keep tokens secret by calling internal API routes or server actions

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground}/**/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/components/**/*.{tsx,ts}: Group feature-specific components under feature/components/_ and expose a barrel index.ts when necessary
Expose a className prop on the root element of every component for styling overrides

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: For server-side data fetching: Always call getAuthToken() to retrieve the JWT from cookies and inject the token as an Authorization: Bearer header – never embed it in the URL. Return typed results (Project[], User[], …) – avoid any
Never import posthog-js in server components; analytics reporting is client-side only

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • apps/dashboard/src/@/components/analytics/stat.tsx
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/*use*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*use*.{ts,tsx}: Keep queryKey stable and descriptive in React Query hooks for reliable cache hits
Configure staleTime and cacheTime in React Query according to data freshness requirements

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
⏰ 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: Unit Tests
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: Size
  • GitHub Check: Build Packages
  • GitHub Check: Lint Packages
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (5)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (1)

16-92: LGTM!

The fetchWalletPortfolio function is well-structured with proper per-chain error isolation, sensible API parameters, and clean data transformation. The batching approach in fetchAllPortfolios with progress callbacks is a good pattern for handling potentially large wallet lists.

apps/dashboard/src/@/components/analytics/stat.tsx (1)

17-18: LGTM!

The emptyText conditional logic correctly provides a fallback display when the component is not pending but the data hasn't been loaded yet. The muted styling appropriately indicates a placeholder state.

apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx (3)

553-574: LGTM!

The StatCard usage correctly leverages the new emptyText prop to show a placeholder before balances are loaded, while displaying actual values after fetch completion. The isPending state properly reflects the loading status.


241-313: LGTM!

The new Total Balance and Tokens columns are well-implemented with consistent null checks, proper loading state placeholders, and good UX touches like the tooltip showing all tokens. The currency formatting using Intl.NumberFormat is appropriate.


82-91: LGTM!

The component props maintain a clean discriminated union type for either projectClientId or ecosystemSlug, ensuring type safety for the different wallet contexts.

@vercel vercel bot temporarily deployed to Preview – wallet-ui December 15, 2025 23:22 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula December 15, 2025 23:22 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground December 15, 2025 23:22 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 December 15, 2025 23:22 Inactive
@0xFirekeeper 0xFirekeeper added the DO NOT MERGE This pull request is still in progress and is not ready to be merged. label Dec 15, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Dec 15, 2025

size-limit report 📦

Path Size
@thirdweb-dev/nexus (esm) 105.66 KB (0%)
@thirdweb-dev/nexus (cjs) 319.47 KB (0%)

@linear
Copy link

linear bot commented Dec 15, 2025

Added retry logic with exponential backoff to embedded wallet and portfolio fetching to handle transient errors and rate limits. Increased batch size and used Promise.allSettled for concurrent portfolio fetches, improving throughput and resilience. Partial results are now returned on repeated failures, and error handling is more robust.
@vercel vercel bot temporarily deployed to Preview – wallet-ui December 16, 2025 12:03 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula December 16, 2025 12:03 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 December 16, 2025 12:03 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground December 16, 2025 12:03 Inactive
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

🧹 Nitpick comments (3)
apps/dashboard/src/@/hooks/useEmbeddedWallets.ts (1)

180-237: Solid retry logic with partial results fallback.

The bounded retry approach with consecutive failure tracking is well-implemented. A few observations:

  1. The loop lacks an upper page limit safeguard. If the API continuously returns hasMore: true, this could run indefinitely.

  2. Minor inconsistency: the inner retry delay (line 233-234) uses linear backoff (1000 * consecutiveFailures) while fetchQuery uses exponential. Consider aligning both to exponential for consistency.

Consider adding a max page limit as a safeguard:

       const responses: WalletUser[] = [];
       let page = 1;
       let consecutiveFailures = 0;
       const maxConsecutiveFailures = 3;
+      const maxPages = 1000; // Safety limit

-      while (consecutiveFailures < maxConsecutiveFailures) {
+      while (consecutiveFailures < maxConsecutiveFailures && page <= maxPages) {
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (2)

23-44: Consider preserving the last response status for better error context.

When all retries exhaust due to 429/5xx responses (hitting the continue branch), lastError remains null and the generic "Max retries exceeded" message lacks context about why retries failed.

 async function fetchWithRetry(
   url: string,
   options: RequestInit,
   maxRetries = 3,
 ): Promise<Response> {
   let lastError: Error | null = null;
+  let lastStatus: number | null = null;

   for (let attempt = 0; attempt < maxRetries; attempt++) {
     try {
       const response = await fetch(url, options);

       // Retry on rate limit (429) or server errors (5xx)
       if (response.status === 429 || response.status >= 500) {
+        lastStatus = response.status;
         const delay = Math.min(1000 * 2 ** attempt, 10000);
         await new Promise((resolve) => setTimeout(resolve, delay));
         continue;
       }

       return response;
     } catch (e) {
       lastError = e instanceof Error ? e : new Error(String(e));
       const delay = Math.min(1000 * 2 ** attempt, 10000);
       await new Promise((resolve) => setTimeout(resolve, delay));
     }
   }

-  throw lastError || new Error("Max retries exceeded");
+  throw lastError || new Error(`Max retries exceeded (last status: ${lastStatus})`);
 }

184-208: Consider extracting the mutation parameter type for reusability.

The inline type definition works but could be extracted for documentation and potential reuse by consumers.

+export type FetchAllPortfoliosParams = {
+  addresses: string[];
+  client: ThirdwebClient;
+  chainIds: number[];
+  authToken: string;
+  onProgress?: (completed: number, total: number) => void;
+};
+
 export function useFetchAllPortfolios() {
   return useMutation({
-    mutationFn: async ({
-      addresses,
-      client,
-      chainIds,
-      authToken,
-      onProgress,
-    }: {
-      addresses: string[];
-      client: ThirdwebClient;
-      chainIds: number[];
-      authToken: string;
-      onProgress?: (completed: number, total: number) => void;
-    }) => {
+    mutationFn: async (params: FetchAllPortfoliosParams) => {
+      const { addresses, client, chainIds, authToken, onProgress } = params;
       return fetchAllPortfolios(
📜 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 dd85e31 and 090f0d9.

📒 Files selected for processing (2)
  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts (1 hunks)
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts (1 hunks)
🧰 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 TypeScript 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 in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground-web}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/src/**/*.{ts,tsx}: Import UI component primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground
Use Tailwind CSS only – no inline styles or CSS modules in dashboard and playground
Use cn() from @/lib/utils for conditional Tailwind class merging
Use design system tokens for styling (backgrounds: bg-card, borders: border-border, muted text: text-muted-foreground)
Expose className prop on root element for component overrides

Files:

  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/dashboard/src/**/*.{ts,tsx}: Use NavLink for internal navigation with automatic active states in dashboard
Start server component files with import "server-only"; in Next.js
Read cookies/headers with next/headers in server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic with redirect() from next/navigation in server components
Begin client component files with 'use client'; directive in Next.js
Handle interactive UI with React hooks (useState, useEffect, React Query, wallet hooks) in client components
Access browser APIs (localStorage, window, IntersectionObserver) in client components
Support fast transitions with prefetched data in client components
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header for API calls – never embed tokens in URLs
Return typed results (Project[], User[]) from server-side data fetches – avoid any
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys in React Query for 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 – only use analytics client-side

Files:

  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*.{ts,tsx}: Always import from the central UI library under @/components/ui/* for reusable core UI components like Button, Input, Select, Tabs, Card, Sidebar, Separator, Badge
Use NavLink from @/components/ui/NavLink for internal navigation to ensure active states are handled automatically
For notices and skeletons, rely on AnnouncementBanner, GenericLoadingPage, and EmptyStateCard components
Import icons from lucide-react or the project-specific …/icons exports; never embed raw SVG
Keep components pure; fetch data outside using server components or hooks and pass it down via props
Use Tailwind CSS as the styling system; avoid inline styles or CSS modules
Merge class names with cn from @/lib/utils to keep conditional logic readable
Stick to design tokens: use bg-card, border-border, text-muted-foreground and other Tailwind variables instead of hard-coded colors
Use spacing utilities (px-*, py-*, gap-*) instead of custom margins
Follow mobile-first responsive design with Tailwind helpers (max-sm, md, lg, xl)
Never hard-code colors; always use Tailwind variables
Combine class names via cn, and expose className prop if useful in components
Use React Query (@tanstack/react-query) for all client-side data fetching with typed hooks

Files:

  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/*use*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*use*.{ts,tsx}: Keep queryKey stable and descriptive in React Query hooks for reliable cache hits
Configure staleTime and cacheTime in React Query according to data freshness requirements

Files:

  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground}/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{tsx,ts}: Import UI primitives from @/components/ui/_ (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in Dashboard and Playground apps
Use NavLink for internal navigation so active states are handled automatically
Use Tailwind CSS for styling – no inline styles or CSS modules
Merge class names with cn() from @/lib/utils to keep conditional logic readable
Stick to design tokens for styling: backgrounds (bg-card), borders (border-border), muted text (text-muted-foreground), etc.
Server Components: Read cookies/headers with next/headers, access server-only environment variables or secrets, perform heavy data fetching, implement redirect logic with redirect() from next/navigation, and start files with import 'server-only'; to prevent client bundling
Client Components: Begin files with 'use client'; before imports, handle interactive UI relying on React hooks (useState, useEffect, React Query, wallet hooks), access browser APIs (localStorage, window, IntersectionObserver, etc.), and support fast transitions with client-side data prefetching
For client-side data fetching: Wrap calls in React Query (@tanstack/react-query), use descriptive and stable queryKeys for cache hits, configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s), and keep tokens secret by calling internal API routes or server actions

Files:

  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: For server-side data fetching: Always call getAuthToken() to retrieve the JWT from cookies and inject the token as an Authorization: Bearer header – never embed it in the URL. Return typed results (Project[], User[], …) – avoid any
Never import posthog-js in server components; analytics reporting is client-side only

Files:

  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
🧬 Code graph analysis (2)
apps/dashboard/src/@/hooks/useEmbeddedWallets.ts (1)
packages/thirdweb/src/wallets/in-app/core/wallet/types.ts (1)
  • WalletUser (47-47)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (2)
packages/thirdweb/src/exports/extensions/erc20.ts (1)
  • GetBalanceResult (103-103)
packages/thirdweb/src/exports/thirdweb.ts (1)
  • ThirdwebClient (25-25)
⏰ 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: Unit Tests
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: Lint Packages
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: Size
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (3)

111-135: Clean implementation with appropriate concurrency.

The function correctly leverages the graceful error handling in fetchTokensForChain, allowing partial results when some chains fail. The concurrent fetching across chains is appropriate here.


137-182: Well-structured batch processing with fault tolerance.

Good use of Promise.allSettled to isolate failures per address. The batch size of 10 with 100ms inter-batch delay provides reasonable throttling.

One consideration: rejected results are silently dropped. Depending on use-case, you might want to track failed addresses for user feedback or retry capability.


65-71: Verify client.clientId property access.

The code accesses client.clientId directly. Ensure this property is guaranteed to exist on ThirdwebClient in all scenarios (e.g., when client is created via different initialization paths).

#!/bin/bash
# Verify ThirdwebClient type includes clientId property
ast-grep --pattern 'type ThirdwebClient = {
  $$$
  clientId$_
  $$$
}'

# Also check interface definition
ast-grep --pattern 'interface ThirdwebClient {
  $$$
  clientId$_
  $$$
}'

Replaces the use of the client object with explicit teamId, clientId, and ecosystemSlug parameters for fetching wallet portfolios. Updates headers and function signatures to support these changes, improving clarity and flexibility in API requests.
@vercel vercel bot temporarily deployed to Preview – wallet-ui December 16, 2025 12:24 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula December 16, 2025 12:24 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 December 16, 2025 12:24 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground December 16, 2025 12:24 Inactive
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)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (1)

95-97: Precision loss for large token balances remains unaddressed.

As noted in a previous review, BigInt(t.balance) will throw on non-integer strings (e.g., scientific notation), and Number(t.balance) loses precision for balances exceeding Number.MAX_SAFE_INTEGER. Consider defensive parsing.

🧹 Nitpick comments (4)
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx (2)

162-170: Including fetchPortfoliosMutation in dependency array may cause unnecessary re-renders.

The fetchPortfoliosMutation object reference changes on every render since useMutation returns a new object. Consider extracting mutateAsync directly or using fetchPortfoliosMutation.mutateAsync without including the whole object in deps.

- const fetchPortfoliosMutation = useFetchAllPortfolios();
+ const { mutateAsync: fetchPortfolios, isPending: isFetchingPortfolios } = useFetchAllPortfolios();

  // Then in handleFetchBalances:
- const results = await fetchPortfoliosMutation.mutateAsync({
+ const results = await fetchPortfolios({

  // And update dependency array:
- ], [
-   selectedChains,
-   getAllEmbeddedWallets,
-   ...
-   fetchPortfoliosMutation,
- ]);
+ ], [
+   selectedChains,
+   getAllEmbeddedWallets,
+   ...
+   fetchPortfolios,
+ ]);

510-527: Void return from arrow function passed to onClick.

() => handleFetchBalances() returns a Promise which is ignored. While this works, consider adding explicit void handling to avoid potential issues with React's synthetic event handling.

- onClick={() => handleFetchBalances()}
+ onClick={() => void handleFetchBalances()}
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (2)

82-84: Consider validating API response structure.

The code accesses data.result?.tokens without validating the overall response shape. If the API returns an unexpected structure, this could lead to subtle bugs.

     const data = await response.json();
-    const rawTokens = data.result?.tokens || [];
+    if (!data?.result || !Array.isArray(data.result.tokens)) {
+      console.warn(`Unexpected API response structure for ${address} on chain ${chainId}`);
+      return [];
+    }
+    const rawTokens = data.result.tokens;

4-7: Consider exporting WalletPortfolioToken type.

The WalletPortfolioToken type is used in the exported WalletPortfolioData.tokens array but isn't exported itself. Consumers may need this type for proper typing when iterating over tokens.

- type WalletPortfolioToken = GetBalanceResult & {
+ export type WalletPortfolioToken = GetBalanceResult & {
   usdValue?: number;
   priceUsd?: number;
 };
📜 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 090f0d9 and 890ff96.

📒 Files selected for processing (2)
  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx (7 hunks)
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each TypeScript 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 in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground-web}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/src/**/*.{ts,tsx}: Import UI component primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground
Use Tailwind CSS only – no inline styles or CSS modules in dashboard and playground
Use cn() from @/lib/utils for conditional Tailwind class merging
Use design system tokens for styling (backgrounds: bg-card, borders: border-border, muted text: text-muted-foreground)
Expose className prop on root element for component overrides

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/dashboard/src/**/*.{ts,tsx}: Use NavLink for internal navigation with automatic active states in dashboard
Start server component files with import "server-only"; in Next.js
Read cookies/headers with next/headers in server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic with redirect() from next/navigation in server components
Begin client component files with 'use client'; directive in Next.js
Handle interactive UI with React hooks (useState, useEffect, React Query, wallet hooks) in client components
Access browser APIs (localStorage, window, IntersectionObserver) in client components
Support fast transitions with prefetched data in client components
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header for API calls – never embed tokens in URLs
Return typed results (Project[], User[]) from server-side data fetches – avoid any
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys in React Query for 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 – only use analytics client-side

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*.{ts,tsx}: Always import from the central UI library under @/components/ui/* for reusable core UI components like Button, Input, Select, Tabs, Card, Sidebar, Separator, Badge
Use NavLink from @/components/ui/NavLink for internal navigation to ensure active states are handled automatically
For notices and skeletons, rely on AnnouncementBanner, GenericLoadingPage, and EmptyStateCard components
Import icons from lucide-react or the project-specific …/icons exports; never embed raw SVG
Keep components pure; fetch data outside using server components or hooks and pass it down via props
Use Tailwind CSS as the styling system; avoid inline styles or CSS modules
Merge class names with cn from @/lib/utils to keep conditional logic readable
Stick to design tokens: use bg-card, border-border, text-muted-foreground and other Tailwind variables instead of hard-coded colors
Use spacing utilities (px-*, py-*, gap-*) instead of custom margins
Follow mobile-first responsive design with Tailwind helpers (max-sm, md, lg, xl)
Never hard-code colors; always use Tailwind variables
Combine class names via cn, and expose className prop if useful in components
Use React Query (@tanstack/react-query) for all client-side data fetching with typed hooks

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

Add className prop to the root element of every component to allow external overrides

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
apps/dashboard/**/*use*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*use*.{ts,tsx}: Keep queryKey stable and descriptive in React Query hooks for reliable cache hits
Configure staleTime and cacheTime in React Query according to data freshness requirements

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground}/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{tsx,ts}: Import UI primitives from @/components/ui/_ (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in Dashboard and Playground apps
Use NavLink for internal navigation so active states are handled automatically
Use Tailwind CSS for styling – no inline styles or CSS modules
Merge class names with cn() from @/lib/utils to keep conditional logic readable
Stick to design tokens for styling: backgrounds (bg-card), borders (border-border), muted text (text-muted-foreground), etc.
Server Components: Read cookies/headers with next/headers, access server-only environment variables or secrets, perform heavy data fetching, implement redirect logic with redirect() from next/navigation, and start files with import 'server-only'; to prevent client bundling
Client Components: Begin files with 'use client'; before imports, handle interactive UI relying on React hooks (useState, useEffect, React Query, wallet hooks), access browser APIs (localStorage, window, IntersectionObserver, etc.), and support fast transitions with client-side data prefetching
For client-side data fetching: Wrap calls in React Query (@tanstack/react-query), use descriptive and stable queryKeys for cache hits, configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s), and keep tokens secret by calling internal API routes or server actions

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground}/**/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/components/**/*.{tsx,ts}: Group feature-specific components under feature/components/_ and expose a barrel index.ts when necessary
Expose a className prop on the root element of every component for styling overrides

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: For server-side data fetching: Always call getAuthToken() to retrieve the JWT from cookies and inject the token as an Authorization: Bearer header – never embed it in the URL. Return typed results (Project[], User[], …) – avoid any
Never import posthog-js in server components; analytics reporting is client-side only

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
🧬 Code graph analysis (2)
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx (5)
packages/thirdweb/src/wallets/in-app/core/wallet/types.ts (1)
  • WalletUser (47-47)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (2)
  • WalletPortfolioData (9-12)
  • useFetchAllPortfolios (202-232)
apps/dashboard/src/@/hooks/useEmbeddedWallets.ts (2)
  • useEmbeddedWallets (135-160)
  • useAllEmbeddedWallets (163-242)
apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx (1)
  • MultiNetworkSelector (17-150)
apps/dashboard/src/@/components/analytics/stat.tsx (1)
  • StatCard (3-30)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (2)
packages/thirdweb/src/exports/extensions/erc20.ts (1)
  • GetBalanceResult (103-103)
packages/thirdweb/test/src/test-clients.js (1)
  • clientId (3-3)
⏰ 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: E2E Tests (pnpm, webpack)
  • GitHub Check: Unit Tests
  • GitHub Check: Build Packages
  • GitHub Check: Lint Packages
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: Size
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (6)
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx (3)

97-106: Portfolio state initialization looks good.

The state setup for chain selection (defaulting to Ethereum mainnet), portfolio data map, loaded flag, and progress tracking is well-structured and follows React patterns.


242-314: New portfolio columns are well-implemented.

The Total Balance and Tokens columns handle loading states gracefully with placeholders, format currency correctly using Intl.NumberFormat, and the tooltip for tokens provides a good UX for viewing full token details. The sorting by USD value ensures the most valuable tokens appear first.


554-575: Stats section displays well with proper loading states.

The StatCard components correctly handle pending states during fetch and show empty placeholders when portfolios haven't been loaded yet. The currency formatter is consistent with the table column formatting.

apps/dashboard/src/@/hooks/useWalletPortfolio.ts (3)

14-43: Retry logic with exponential backoff is well-implemented.

The fetchWithRetry function handles rate limits (429) and server errors (5xx) appropriately with capped exponential backoff. Network errors are also retried gracefully.


151-200: Batch processing with progress callback is well-designed.

The fetchAllPortfolios function uses Promise.allSettled for resilient batch processing, reports progress after each batch, and includes a small delay between batches to avoid API throttling. The batch size of 10 is reasonable.


202-231: Hook follows React Query patterns correctly.

The useFetchAllPortfolios hook properly wraps the async function in useMutation and passes through all parameters including the progress callback. The typing is explicit and clear.

@vercel vercel bot temporarily deployed to Preview – nebula December 16, 2025 22:26 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui December 16, 2025 22:26 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 December 16, 2025 22:26 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground December 16, 2025 22:26 Inactive
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 (2)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (2)

77-79: Silent failure on non-OK response still lacks status logging.

As noted in a previous review, when response.ok is false, the function returns an empty array without logging the HTTP status. While the catch block at lines 107-110 logs network/parsing errors, 4xx client errors are not captured.

Apply the previously suggested diff:

     if (!response.ok) {
+      console.warn(
+        `API returned ${response.status} for ${address} on chain ${chainId}`,
+      );
       return [];
     }

84-104: Precision loss and error-prone BigInt parsing remain unaddressed.

As noted in a previous review, BigInt(t.balance) at line 94 will throw if the API returns a non-integer string (e.g., scientific notation), and Number(t.balance) at line 96 loses precision for balances exceeding Number.MAX_SAFE_INTEGER.

Consider implementing the defensive parsing suggested in the previous review or a similar bigint-safe approach.

🧹 Nitpick comments (1)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (1)

201-231: Add explicit return type annotation.

Per coding guidelines, all exported functions should have explicit return type annotations for better type safety and documentation.

Apply this diff:

-export function useFetchAllPortfolios() {
+export function useFetchAllPortfolios(): ReturnType<typeof useMutation<
+  Map<string, WalletPortfolioData>,
+  unknown,
+  {
+    addresses: string[];
+    chainIds: number[];
+    authToken: string;
+    teamId: string;
+    clientId?: string;
+    ecosystemSlug?: string;
+    onProgress?: (completed: number, total: number) => void;
+  },
+  unknown
+>> {
   return useMutation({

Alternatively, extract the mutation options to improve readability:

type FetchAllPortfoliosParams = {
  addresses: string[];
  chainIds: number[];
  authToken: string;
  teamId: string;
  clientId?: string;
  ecosystemSlug?: string;
  onProgress?: (completed: number, total: number) => void;
};

export function useFetchAllPortfolios() {
  return useMutation<
    Map<string, WalletPortfolioData>,
    unknown,
    FetchAllPortfoliosParams
  >({
    mutationFn: async (params) => {
      return fetchAllPortfolios(
        params.addresses,
        params.chainIds,
        params.authToken,
        params.teamId,
        params.clientId,
        params.ecosystemSlug,
        params.onProgress,
      );
    },
  });
}
📜 Review details

Configuration used: Organization 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 890ff96 and 3792da5.

📒 Files selected for processing (1)
  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts (1 hunks)
🧰 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 TypeScript 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 in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground-web}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/src/**/*.{ts,tsx}: Import UI component primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground
Use Tailwind CSS only – no inline styles or CSS modules in dashboard and playground
Use cn() from @/lib/utils for conditional Tailwind class merging
Use design system tokens for styling (backgrounds: bg-card, borders: border-border, muted text: text-muted-foreground)
Expose className prop on root element for component overrides

Files:

  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/dashboard/src/**/*.{ts,tsx}: Use NavLink for internal navigation with automatic active states in dashboard
Start server component files with import "server-only"; in Next.js
Read cookies/headers with next/headers in server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic with redirect() from next/navigation in server components
Begin client component files with 'use client'; directive in Next.js
Handle interactive UI with React hooks (useState, useEffect, React Query, wallet hooks) in client components
Access browser APIs (localStorage, window, IntersectionObserver) in client components
Support fast transitions with prefetched data in client components
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header for API calls – never embed tokens in URLs
Return typed results (Project[], User[]) from server-side data fetches – avoid any
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys in React Query for 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 – only use analytics client-side

Files:

  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*.{ts,tsx}: Always import from the central UI library under @/components/ui/* for reusable core UI components like Button, Input, Select, Tabs, Card, Sidebar, Separator, Badge
Use NavLink from @/components/ui/NavLink for internal navigation to ensure active states are handled automatically
For notices and skeletons, rely on AnnouncementBanner, GenericLoadingPage, and EmptyStateCard components
Import icons from lucide-react or the project-specific …/icons exports; never embed raw SVG
Keep components pure; fetch data outside using server components or hooks and pass it down via props
Use Tailwind CSS as the styling system; avoid inline styles or CSS modules
Merge class names with cn from @/lib/utils to keep conditional logic readable
Stick to design tokens: use bg-card, border-border, text-muted-foreground and other Tailwind variables instead of hard-coded colors
Use spacing utilities (px-*, py-*, gap-*) instead of custom margins
Follow mobile-first responsive design with Tailwind helpers (max-sm, md, lg, xl)
Never hard-code colors; always use Tailwind variables
Combine class names via cn, and expose className prop if useful in components
Use React Query (@tanstack/react-query) for all client-side data fetching with typed hooks

Files:

  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/*use*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*use*.{ts,tsx}: Keep queryKey stable and descriptive in React Query hooks for reliable cache hits
Configure staleTime and cacheTime in React Query according to data freshness requirements

Files:

  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground}/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{tsx,ts}: Import UI primitives from @/components/ui/_ (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in Dashboard and Playground apps
Use NavLink for internal navigation so active states are handled automatically
Use Tailwind CSS for styling – no inline styles or CSS modules
Merge class names with cn() from @/lib/utils to keep conditional logic readable
Stick to design tokens for styling: backgrounds (bg-card), borders (border-border), muted text (text-muted-foreground), etc.
Server Components: Read cookies/headers with next/headers, access server-only environment variables or secrets, perform heavy data fetching, implement redirect logic with redirect() from next/navigation, and start files with import 'server-only'; to prevent client bundling
Client Components: Begin files with 'use client'; before imports, handle interactive UI relying on React hooks (useState, useEffect, React Query, wallet hooks), access browser APIs (localStorage, window, IntersectionObserver, etc.), and support fast transitions with client-side data prefetching
For client-side data fetching: Wrap calls in React Query (@tanstack/react-query), use descriptive and stable queryKeys for cache hits, configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s), and keep tokens secret by calling internal API routes or server actions

Files:

  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: For server-side data fetching: Always call getAuthToken() to retrieve the JWT from cookies and inject the token as an Authorization: Bearer header – never embed it in the URL. Return typed results (Project[], User[], …) – avoid any
Never import posthog-js in server components; analytics reporting is client-side only

Files:

  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • apps/dashboard/src/@/hooks/useWalletPortfolio.ts
🧬 Code graph analysis (1)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (2)
packages/thirdweb/src/exports/extensions/erc20.ts (1)
  • GetBalanceResult (103-103)
apps/dashboard/src/@/constants/urls.ts (1)
  • THIRDWEB_API_HOST (1-2)
⏰ 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: Build Packages
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: Unit Tests
  • GitHub Check: Lint Packages
  • GitHub Check: Size
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (4)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (4)

15-20: Add explicit return type annotation.

Per coding guidelines, all functions should have explicit return type annotations.

 async function fetchWithRetry(
   url: string,
   options: RequestInit,
   maxRetries = 3,
-): Promise<Response> {
+): Promise<Response> {

Wait, the return type is already present. Let me check again... yes, line 20 has : Promise<Response>. This is correct.


46-54: Add explicit return type annotation.

Per coding guidelines, all functions should have explicit return type annotations.

Apply this diff:

 async function fetchTokensForChain(
   address: string,
   chainId: number,
   authToken: string,
   teamId: string,
   clientId?: string,
   ecosystemSlug?: string,
-): Promise<WalletPortfolioToken[]> {
+): Promise<WalletPortfolioToken[]> {

Wait, line 54 already has the return type. Let me re-check... yes, : Promise<WalletPortfolioToken[]> is present.


115-148: LGTM: Clean aggregation logic.

The concurrent fetch with Promise.all and subsequent aggregation of USD values is well-structured. Error isolation in fetchTokensForChain ensures partial failures don't abort the entire portfolio fetch.


150-199: LGTM: Robust batch processing with progress tracking.

The use of Promise.allSettled ensures individual wallet failures don't abort the entire batch, and progress callbacks provide good UX feedback. The 100ms inter-batch delay helps avoid rate limits.

Comment on lines +1 to +3
import { useMutation } from "@tanstack/react-query";
import type { GetBalanceResult } from "thirdweb/extensions/erc20";
import { THIRDWEB_API_HOST } from "@/constants/urls";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add 'use client' directive.

This file uses useMutation from React Query, which requires client-side execution. Per coding guidelines, client component files must begin with the 'use client'; directive.

Apply this diff:

+'use client';
+
 import { useMutation } from "@tanstack/react-query";
 import type { GetBalanceResult } from "thirdweb/extensions/erc20";
 import { THIRDWEB_API_HOST } from "@/constants/urls";
📝 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
import { useMutation } from "@tanstack/react-query";
import type { GetBalanceResult } from "thirdweb/extensions/erc20";
import { THIRDWEB_API_HOST } from "@/constants/urls";
'use client';
import { useMutation } from "@tanstack/react-query";
import type { GetBalanceResult } from "thirdweb/extensions/erc20";
import { THIRDWEB_API_HOST } from "@/constants/urls";
🤖 Prompt for AI Agents
In apps/dashboard/src/@/hooks/useWalletPortfolio.ts around lines 1 to 3, the
file is missing the required 'use client' directive for client-side React hooks;
add a top-of-file line: 'use client'; as the very first statement (before any
imports) so the module is treated as a client component and useMutation will run
on the client.

@0xFirekeeper 0xFirekeeper merged commit 5f0e25b into main Dec 16, 2025
21 of 23 checks passed
@0xFirekeeper 0xFirekeeper deleted the firekeeper/aggregated-balances branch December 16, 2025 22:31
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. DO NOT MERGE This pull request is still in progress and is not ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants