-
Notifications
You must be signed in to change notification settings - Fork 634
Add wallet portfolio fetching and display to user table #8570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 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
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (6)
apps/dashboard/src/@/hooks/useWalletPortfolio.ts (2)
44-46: Silent failure hides API errors.When
response.okis 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 thevaluefield correctly usesBigInt, 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'sformatUnits:import { formatUnits } from "viem"; // ... displayValue: formatUnits(BigInt(t.balance), t.decimals),apps/dashboard/src/@/components/analytics/stat.tsx (1)
3-10: MissingclassNameprop for external overrides.Per coding guidelines, components should expose a
classNameprop 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
cnfrom@/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 stablemutateAsyncfunction 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
isFetchingBalancesto useisFetchingPortfoliosinstead offetchPortfoliosMutation.isPending.
293-296: Key may be non-unique for native tokens.If
tokenAddressis 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
handleFetchBalancestakes 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.
📒 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@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoidanyandunknownin 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.tsxapps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/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
Usecn()from@/lib/utilsfor conditional Tailwind class merging
Use design system tokens for styling (backgrounds:bg-card, borders:border-border, muted text:text-muted-foreground)
ExposeclassNameprop on root element for component overrides
Files:
apps/dashboard/src/@/components/analytics/stat.tsxapps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/dashboard/src/**/*.{ts,tsx}: UseNavLinkfor internal navigation with automatic active states in dashboard
Start server component files withimport "server-only";in Next.js
Read cookies/headers withnext/headersin server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic withredirect()fromnext/navigationin 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 callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader for API calls – never embed tokens in URLs
Return typed results (Project[],User[]) from server-side data fetches – avoidany
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysin React Query for cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components – only use analytics client-side
Files:
apps/dashboard/src/@/components/analytics/stat.tsxapps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/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 likeButton,Input,Select,Tabs,Card,Sidebar,Separator,Badge
UseNavLinkfrom@/components/ui/NavLinkfor internal navigation to ensure active states are handled automatically
For notices and skeletons, rely onAnnouncementBanner,GenericLoadingPage, andEmptyStateCardcomponents
Import icons fromlucide-reactor the project-specific…/iconsexports; 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 withcnfrom@/lib/utilsto keep conditional logic readable
Stick to design tokens: usebg-card,border-border,text-muted-foregroundand 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 viacn, and exposeclassNameprop 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.tsxapps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)
Add
classNameprop to the root element of every component to allow external overrides
Files:
apps/dashboard/src/@/components/analytics/stat.tsxapps/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 lintbefore committing, ensure there are no linting errors
Files:
apps/dashboard/src/@/components/analytics/stat.tsxapps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/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.tsxapps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/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.tsxapps/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.tsxapps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/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.tsxapps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/*use*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)
apps/dashboard/**/*use*.{ts,tsx}: KeepqueryKeystable and descriptive in React Query hooks for reliable cache hits
ConfigurestaleTimeandcacheTimein React Query according to data freshness requirements
Files:
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/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
fetchWalletPortfoliofunction is well-structured with proper per-chain error isolation, sensible API parameters, and clean data transformation. The batching approach infetchAllPortfolioswith progress callbacks is a good pattern for handling potentially large wallet lists.apps/dashboard/src/@/components/analytics/stat.tsx (1)
17-18: LGTM!The
emptyTextconditional 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
StatCardusage correctly leverages the newemptyTextprop to show a placeholder before balances are loaded, while displaying actual values after fetch completion. TheisPendingstate properly reflects the loading status.
241-313: LGTM!The new
Total BalanceandTokenscolumns are well-implemented with consistent null checks, proper loading state placeholders, and good UX touches like the tooltip showing all tokens. The currency formatting usingIntl.NumberFormatis appropriate.
82-91: LGTM!The component props maintain a clean discriminated union type for either
projectClientIdorecosystemSlug, ensuring type safety for the different wallet contexts.
size-limit report 📦
|
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 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:
The loop lacks an upper page limit safeguard. If the API continuously returns
hasMore: true, this could run indefinitely.Minor inconsistency: the inner retry delay (line 233-234) uses linear backoff (
1000 * consecutiveFailures) whilefetchQueryuses 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
continuebranch),lastErrorremainsnulland 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.
📒 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@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoidanyandunknownin 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.tsapps/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
Usecn()from@/lib/utilsfor conditional Tailwind class merging
Use design system tokens for styling (backgrounds:bg-card, borders:border-border, muted text:text-muted-foreground)
ExposeclassNameprop on root element for component overrides
Files:
apps/dashboard/src/@/hooks/useEmbeddedWallets.tsapps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/dashboard/src/**/*.{ts,tsx}: UseNavLinkfor internal navigation with automatic active states in dashboard
Start server component files withimport "server-only";in Next.js
Read cookies/headers withnext/headersin server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic withredirect()fromnext/navigationin 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 callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader for API calls – never embed tokens in URLs
Return typed results (Project[],User[]) from server-side data fetches – avoidany
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysin React Query for cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components – only use analytics client-side
Files:
apps/dashboard/src/@/hooks/useEmbeddedWallets.tsapps/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 likeButton,Input,Select,Tabs,Card,Sidebar,Separator,Badge
UseNavLinkfrom@/components/ui/NavLinkfor internal navigation to ensure active states are handled automatically
For notices and skeletons, rely onAnnouncementBanner,GenericLoadingPage, andEmptyStateCardcomponents
Import icons fromlucide-reactor the project-specific…/iconsexports; 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 withcnfrom@/lib/utilsto keep conditional logic readable
Stick to design tokens: usebg-card,border-border,text-muted-foregroundand 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 viacn, and exposeclassNameprop 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.tsapps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/*use*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)
apps/dashboard/**/*use*.{ts,tsx}: KeepqueryKeystable and descriptive in React Query hooks for reliable cache hits
ConfigurestaleTimeandcacheTimein React Query according to data freshness requirements
Files:
apps/dashboard/src/@/hooks/useEmbeddedWallets.tsapps/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 lintbefore committing, ensure there are no linting errors
Files:
apps/dashboard/src/@/hooks/useEmbeddedWallets.tsapps/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.tsapps/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.tsapps/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.tsapps/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.allSettledto 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: Verifyclient.clientIdproperty access.The code accesses
client.clientIddirectly. Ensure this property is guaranteed to exist onThirdwebClientin 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
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), andNumber(t.balance)loses precision for balances exceedingNumber.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: IncludingfetchPortfoliosMutationin dependency array may cause unnecessary re-renders.The
fetchPortfoliosMutationobject reference changes on every render sinceuseMutationreturns a new object. Consider extractingmutateAsyncdirectly or usingfetchPortfoliosMutation.mutateAsyncwithout 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?.tokenswithout 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 exportingWalletPortfolioTokentype.The
WalletPortfolioTokentype is used in the exportedWalletPortfolioData.tokensarray 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.
📒 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@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoidanyandunknownin 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.tsxapps/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
Usecn()from@/lib/utilsfor conditional Tailwind class merging
Use design system tokens for styling (backgrounds:bg-card, borders:border-border, muted text:text-muted-foreground)
ExposeclassNameprop on root element for component overrides
Files:
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/dashboard/src/**/*.{ts,tsx}: UseNavLinkfor internal navigation with automatic active states in dashboard
Start server component files withimport "server-only";in Next.js
Read cookies/headers withnext/headersin server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic withredirect()fromnext/navigationin 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 callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader for API calls – never embed tokens in URLs
Return typed results (Project[],User[]) from server-side data fetches – avoidany
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysin React Query for cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components – only use analytics client-side
Files:
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/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 likeButton,Input,Select,Tabs,Card,Sidebar,Separator,Badge
UseNavLinkfrom@/components/ui/NavLinkfor internal navigation to ensure active states are handled automatically
For notices and skeletons, rely onAnnouncementBanner,GenericLoadingPage, andEmptyStateCardcomponents
Import icons fromlucide-reactor the project-specific…/iconsexports; 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 withcnfrom@/lib/utilsto keep conditional logic readable
Stick to design tokens: usebg-card,border-border,text-muted-foregroundand 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 viacn, and exposeclassNameprop 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.tsxapps/dashboard/src/@/hooks/useWalletPortfolio.ts
apps/dashboard/**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)
Add
classNameprop 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}: KeepqueryKeystable and descriptive in React Query hooks for reliable cache hits
ConfigurestaleTimeandcacheTimein React Query according to data freshness requirements
Files:
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/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 lintbefore committing, ensure there are no linting errors
Files:
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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
fetchWithRetryfunction 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
fetchAllPortfoliosfunction usesPromise.allSettledfor 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
useFetchAllPortfolioshook properly wraps the async function inuseMutationand passes through all parameters including the progress callback. The typing is explicit and clear.
apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (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.okis 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), andNumber(t.balance)at line 96 loses precision for balances exceedingNumber.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.
📒 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@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoidanyandunknownin 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
Usecn()from@/lib/utilsfor conditional Tailwind class merging
Use design system tokens for styling (backgrounds:bg-card, borders:border-border, muted text:text-muted-foreground)
ExposeclassNameprop 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}: UseNavLinkfor internal navigation with automatic active states in dashboard
Start server component files withimport "server-only";in Next.js
Read cookies/headers withnext/headersin server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic withredirect()fromnext/navigationin 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 callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader for API calls – never embed tokens in URLs
Return typed results (Project[],User[]) from server-side data fetches – avoidany
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysin React Query for cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components – 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 likeButton,Input,Select,Tabs,Card,Sidebar,Separator,Badge
UseNavLinkfrom@/components/ui/NavLinkfor internal navigation to ensure active states are handled automatically
For notices and skeletons, rely onAnnouncementBanner,GenericLoadingPage, andEmptyStateCardcomponents
Import icons fromlucide-reactor the project-specific…/iconsexports; 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 withcnfrom@/lib/utilsto keep conditional logic readable
Stick to design tokens: usebg-card,border-border,text-muted-foregroundand 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 viacn, and exposeclassNameprop 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}: KeepqueryKeystable and descriptive in React Query hooks for reliable cache hits
ConfigurestaleTimeandcacheTimein 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 lintbefore 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.alland subsequent aggregation of USD values is well-structured. Error isolation infetchTokensForChainensures partial failures don't abort the entire portfolio fetch.
150-199: LGTM: Robust batch processing with progress tracking.The use of
Promise.allSettledensures 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.
| import { useMutation } from "@tanstack/react-query"; | ||
| import type { GetBalanceResult } from "thirdweb/extensions/erc20"; | ||
| import { THIRDWEB_API_HOST } from "@/constants/urls"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
Introduces a new hook
useWalletPortfolioto 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
UserWalletsTablecomponent 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
emptyTextprop toStatCardinstat.tsx.useEmbeddedWallets.ts.fetchWithRetryfunction for API calls inuseWalletPortfolio.ts.UserWalletsTable.UserWalletsTable.Summary by CodeRabbit
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.