-
Notifications
You must be signed in to change notification settings - Fork 620
Dashboard: Fix Token Rewards UI not accounting for token decimals #8164
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
Dashboard: Fix Token Rewards UI not accounting for token decimals #8164
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
WalkthroughAdds per-token decimals and native-token handling: story and component props include token decimals; UI formats amounts with token-specific decimals; page passes chainMetadata into getUnclaimedFees; getUnclaimedFees detects native vs ERC‑20, reads symbols/decimals from chain metadata or token contracts, and returns decimals with tokens. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Page as Rewards Page
participant Util as getUnclaimedFees
participant ChainMeta as Chain Metadata
participant T0 as Token0 Contract
participant T1 as Token1 Contract
User->>Page: Open rewards view
Page->>Util: getUnclaimedFees({ positionManager, chainMetadata, reward })
Util->>ChainMeta: read native symbol & decimals (if token is native)
alt token0 is native
Util-->>Util: use chainMetadata for token0 symbol & decimals
else
Util->>T0: symbol(), decimals()
end
alt token1 is native
Util-->>Util: use chainMetadata for token1 symbol & decimals
else
Util->>T1: symbol(), decimals()
end
Util-->>Page: return unclaimedFees with token0/token1 symbol & decimals
Page-->>User: render amounts via toTokens(amount, token.decimals)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Comment |
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8164 +/- ##
=======================================
Coverage 56.28% 56.28%
=======================================
Files 906 906
Lines 59208 59208
Branches 4180 4180
=======================================
Hits 33324 33324
Misses 25779 25779
Partials 105 105
🚀 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: 1
📜 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 (4)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsx(1 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsx(4 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx(2 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx
**/*.stories.tsx
📄 CodeRabbit inference engine (CLAUDE.md)
For new UI components, add Storybook stories (
*.stories.tsx) alongside the code
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsx
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx
apps/{dashboard,playground}/**/*.stories.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Add Storybook stories (
*.stories.tsx) alongside new UI components
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts (1)
packages/thirdweb/src/exports/thirdweb.ts (3)
ThirdwebContract(44-44)getContract(43-43)NATIVE_TOKEN_ADDRESS(31-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Unit Tests
- GitHub Check: Lint Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (11)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsx (1)
27-42: LGTM! Story data aligned with new decimals requirement.The addition of
decimals: 18to both token stubs correctly matches the updated type signature expected byClaimRewardsPageUI.apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx (2)
34-34: LGTM! Extracting chain metadata for downstream use.
59-70: LGTM! Chain metadata passed to enable per-token decimals resolution.apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsx (4)
27-40: LGTM! Type extended to include token-specific decimals.
98-111: LGTM! Props type correctly extended with decimals.
277-283: LGTM! Token type extended with decimals field.
310-312: LGTM! Now using per-token decimals instead of hardcoded 18.This correctly formats the token amount using the actual decimals from the token metadata.
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts (4)
2-10: LGTM! Imports added for native token handling and decimals.
14-21: LGTM! Function signature extended with chainMetadata parameter.
67-86: LGTM! Native token detection and metadata extraction.The logic correctly identifies native tokens by comparing addresses and extracts native currency metadata from the chain.
111-124: Verify the return type after fixing the typo.Once the
token1Decimalsstypo is corrected, the return structure will correctly include decimals for both tokens.
...g]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts
Outdated
Show resolved
Hide resolved
688a55c to
52537f1
Compare
size-limit report 📦
|
52537f1 to
b54c182
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts (1)
9-16: Add explicit types and return type (per coding guidelines).Define stable shapes and annotate the function to prevent accidental breakage.
+type TokenInfo = { + address: string; + amount: bigint; + symbol: string; + decimals: number; +}; + +export type UnclaimedFees = { + token0: TokenInfo; + token1: TokenInfo; +}; + +type GetUnclaimedFeesParams = { + positionManager: ThirdwebContract; + chainMetadata: ChainMetadata; + reward: { tokenId: bigint; recipient: string }; +}; - -export async function getUnclaimedFees(params: { - positionManager: ThirdwebContract; - chainMetadata: ChainMetadata; - reward: { - tokenId: bigint; - recipient: string; - }; -}) { +export async function getUnclaimedFees(params: GetUnclaimedFeesParams): Promise<UnclaimedFees> {As per coding guidelines
🧹 Nitpick comments (4)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts (4)
2-6: Broaden native-token detection: include NATIVE_TOKEN_ADDRESS sentinel.Some libs/apps pass 0xEeeee... to represent native tokens. Consider checking both ZERO_ADDRESS and NATIVE_TOKEN_ADDRESS to avoid mislabeling.
-import { getAddress, getContract, readContract, ZERO_ADDRESS } from "thirdweb"; +import { getAddress, getContract, readContract, ZERO_ADDRESS, NATIVE_TOKEN_ADDRESS } from "thirdweb";And see the detection tweak below (Lines 74–76).
74-79: Harden native detection and avoid false negatives.Include the common 0xEeee... sentinel and normalize once.
- const isToken0Native = getAddress(token0Address) === getAddress(ZERO_ADDRESS); - const isToken1Native = getAddress(token1Address) === getAddress(ZERO_ADDRESS); + const zero = getAddress(ZERO_ADDRESS); + const eeee = getAddress(NATIVE_TOKEN_ADDRESS); + const t0 = getAddress(token0Address); + const t1 = getAddress(token1Address); + const isToken0Native = t0 === zero || t0 === eeee; + const isToken1Native = t1 === zero || t1 === eeee;
62-73: Avoid creating contract wrappers for native tokens (minor cleanup).Not harmful, but constructing wrappers for ZERO/EEEEE can be confusing. Defer wrapper creation to non‑native branches.
- const token0Contract = getContract({ - address: token0Address, - chain, - client, - }); - - const token1Contract = getContract({ - address: token1Address, - chain, - client, - }); + // Create wrappers only for non‑native tokens + const getToken0Contract = () => + getContract({ address: token0Address, chain, client }); + const getToken1Contract = () => + getContract({ address: token1Address, chain, client });And reference them lazily below:
- : symbol({ - contract: token0Contract, - }), + : symbol({ contract: getToken0Contract() }), ... - : decimals({ contract: token0Contract }), + : decimals({ contract: getToken0Contract() }), ... - : symbol({ contract: token1Contract }), + : symbol({ contract: getToken1Contract() }), ... - : decimals({ contract: token1Contract }), + : decimals({ contract: getToken1Contract() }),
80-102: Resilience: add safe fallbacks for symbol/decimals to handle non‑standard ERC‑20s.Some tokens revert on metadata; provide graceful defaults.
Add helpers near the top:
+async function safeSymbol(fn: () => Promise<string>, fallback = "UNKNOWN") { + try { return await fn(); } catch { return fallback; } +} +async function safeDecimals(fn: () => Promise<number>, fallback = 18) { + try { return await fn(); } catch { return fallback; } +}Then:
- isToken0Native ? nativeSymbol : symbol({ contract: getToken0Contract() }), + isToken0Native ? nativeSymbol : safeSymbol(() => symbol({ contract: getToken0Contract() })), ... - isToken1Native ? nativeSymbol : symbol({ contract: getToken1Contract() }), + isToken1Native ? nativeSymbol : safeSymbol(() => symbol({ contract: getToken1Contract() })), ... - isToken0Native ? nativeDecimals : decimals({ contract: getToken0Contract() }), + isToken0Native ? nativeDecimals : safeDecimals(() => decimals({ contract: getToken0Contract() })), ... - isToken1Native ? nativeDecimals : decimals({ contract: getToken1Contract() }), + isToken1Native ? nativeDecimals : safeDecimals(() => decimals({ contract: getToken1Contract() })),
📜 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 (4)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsx(1 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsx(4 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx(2 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsx
- apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx
- apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.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). (7)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Unit Tests
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Lint Packages
- GitHub Check: Build Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts (3)
17-41: VerifyreadContractusage on non‑viewcollect.
collectis non‑view on Uniswap V3; many libs support static calls, but some require setting afrom. Please confirm this path consistently succeeds in staging across chains and recipients.
104-117: LGTM on return shape update.Adding
decimalson both tokens aligns with the UI changes and fixes incorrect formatting.
11-11: Verified: All getUnclaimedFees call sites include the new chainMetadata parameter.
Merge activity
|
) <!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the handling of token rewards by adding `decimals` properties for tokens in various components and improving the retrieval of metadata related to tokens. ### Detailed summary - Added `decimals` property to `token0` and `token1` in `claim-rewards-page.stories.tsx`. - Introduced `chainMetadata` in `page.tsx`. - Updated `ClaimRewardsPageUI` to utilize `decimals` for token amounts. - Refactored `getUnclaimedFees` to fetch `decimals` and `symbol` for tokens. - Improved token contract retrieval logic in `getUnclaimedFees`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Token amounts now display using each token’s specific decimals. - Chain metadata is passed to unclaimed-fees retrieval to source native token symbol and decimals. - Bug Fixes - Removed hardcoded 18-decimal assumption so amounts render correctly for tokens with different precisions. - Tests - Story/test stubs updated to include decimals for token objects for more realistic scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
b54c182 to
64034c0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsx (1)
98-111: LGTM! Consistent type definition with decimals.The
unclaimedFeestype correctly mirrors the parent component's type withdecimalsadded to both tokens.Optional: Consider extracting the shared type.
The
unclaimedFeestype structure is duplicated betweenClaimRewardsPageandClaimRewardsPageUI. You could extract it to a shared type for maintainability:type UnclaimedFeesTokens = { token0: { address: string; amount: bigint; symbol: string; decimals: number; }; token1: { address: string; amount: bigint; symbol: string; decimals: number; }; };Then use
unclaimedFees: UnclaimedFeesTokensin both components.
📜 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 (4)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsx(1 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsx(4 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx(2 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.stories.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx
⏰ 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). (4)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Lint Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (5)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx (2)
34-34: LGTM! Clean extraction of chainMetadata.The extraction follows the existing pattern and enables passing chain-level metadata to
getUnclaimedFeesfor proper native token handling.
59-70: LGTM! chainMetadata properly passed to getUnclaimedFees.This enables the utility function to correctly identify native tokens and retrieve their symbols and decimals from chain metadata.
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsx (3)
27-40: LGTM! Type definition correctly updated with decimals.The
decimalsproperty has been properly added to bothtoken0andtoken1, enabling token-specific decimal handling.
277-287: LGTM! TokenReward props correctly include decimals.The
tokenprop type has been properly updated to include thedecimalsproperty.
310-312: Excellent fix! Now uses token-specific decimals.This change correctly formats token amounts using each token's actual
decimalsvalue instead of the hardcoded18. This fixes the bug for tokens with non-standard decimals (e.g., USDC with 6 decimals).
| const isToken0Native = getAddress(token0Address) === getAddress(ZERO_ADDRESS); | ||
| const isToken1Native = getAddress(token1Address) === getAddress(ZERO_ADDRESS); | ||
|
|
||
| const nativeSymbol = params.chainMetadata.nativeCurrency.symbol; | ||
| const nativeDecimals = params.chainMetadata.nativeCurrency.decimals; | ||
|
|
||
| const [token0Symbol, token1Symbol, token0Decimals, token1Decimals] = | ||
| await Promise.all([ | ||
| isToken0Native | ||
| ? nativeSymbol | ||
| : symbol({ | ||
| contract: token0Contract, | ||
| }), | ||
| isToken1Native | ||
| ? nativeSymbol | ||
| : symbol({ | ||
| contract: token1Contract, | ||
| }), | ||
| isToken0Native | ||
| ? nativeDecimals | ||
| : decimals({ | ||
| contract: token0Contract, | ||
| }), | ||
| isToken1Native | ||
| ? nativeDecimals | ||
| : decimals({ | ||
| contract: token1Contract, | ||
| }), |
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.
Native token detection still misses NATIVE_TOKEN_ADDRESS.
collect/positions will continue to throw for native payouts because thirdweb encodes the native asset as 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, not 0x000…000. When a reward uses that sentinel, we still call symbol()/decimals() on a non-existent ERC‑20 and the RPC returns empty data, so this function rejects and the page breaks again. Extend the check to treat both ZERO_ADDRESS and NATIVE_TOKEN_ADDRESS (or call the shared helper) as native before attempting any contract reads.
-import { getAddress, getContract, readContract, ZERO_ADDRESS } from "thirdweb";
+import {
+ getAddress,
+ getContract,
+ readContract,
+ ZERO_ADDRESS,
+ NATIVE_TOKEN_ADDRESS,
+} from "thirdweb";
…
- const isToken0Native = getAddress(token0Address) === getAddress(ZERO_ADDRESS);
- const isToken1Native = getAddress(token1Address) === getAddress(ZERO_ADDRESS);
+ const normalizedToken0 = getAddress(token0Address);
+ const normalizedToken1 = getAddress(token1Address);
+ const zero = getAddress(ZERO_ADDRESS);
+ const native = getAddress(NATIVE_TOKEN_ADDRESS);
+
+ const isToken0Native = normalizedToken0 === zero || normalizedToken0 === native;
+ const isToken1Native = normalizedToken1 === zero || normalizedToken1 === native;📝 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.
| const isToken0Native = getAddress(token0Address) === getAddress(ZERO_ADDRESS); | |
| const isToken1Native = getAddress(token1Address) === getAddress(ZERO_ADDRESS); | |
| const nativeSymbol = params.chainMetadata.nativeCurrency.symbol; | |
| const nativeDecimals = params.chainMetadata.nativeCurrency.decimals; | |
| const [token0Symbol, token1Symbol, token0Decimals, token1Decimals] = | |
| await Promise.all([ | |
| isToken0Native | |
| ? nativeSymbol | |
| : symbol({ | |
| contract: token0Contract, | |
| }), | |
| isToken1Native | |
| ? nativeSymbol | |
| : symbol({ | |
| contract: token1Contract, | |
| }), | |
| isToken0Native | |
| ? nativeDecimals | |
| : decimals({ | |
| contract: token0Contract, | |
| }), | |
| isToken1Native | |
| ? nativeDecimals | |
| : decimals({ | |
| contract: token1Contract, | |
| }), | |
| // apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/utils/unclaimed-fees.ts | |
| import { | |
| getAddress, | |
| getContract, | |
| readContract, | |
| ZERO_ADDRESS, | |
| NATIVE_TOKEN_ADDRESS, | |
| } from "thirdweb"; | |
| // … | |
| // Normalize addresses once for clearer comparisons | |
| const normalizedToken0 = getAddress(token0Address); | |
| const normalizedToken1 = getAddress(token1Address); | |
| const zero = getAddress(ZERO_ADDRESS); | |
| const native = getAddress(NATIVE_TOKEN_ADDRESS); | |
| // Treat both the zero address and the native‐token sentinel as “native” | |
| const isToken0Native = normalizedToken0 === zero || normalizedToken0 === native; | |
| const isToken1Native = normalizedToken1 === zero || normalizedToken1 === native; | |
| const nativeSymbol = params.chainMetadata.nativeCurrency.symbol; | |
| const nativeDecimals = params.chainMetadata.nativeCurrency.decimals; | |
| const [token0Symbol, token1Symbol, token0Decimals, token1Decimals] = | |
| await Promise.all([ | |
| isToken0Native | |
| ? nativeSymbol | |
| : symbol({ | |
| contract: token0Contract, | |
| }), | |
| isToken1Native | |
| ? nativeSymbol | |
| : symbol({ | |
| contract: token1Contract, | |
| }), | |
| isToken0Native | |
| ? nativeDecimals | |
| : decimals({ | |
| contract: token0Contract, | |
| }), | |
| isToken1Native | |
| ? nativeDecimals | |
| : decimals({ | |
| contract: token1Contract, | |
| }), | |
| // … |

PR-Codex overview
This PR focuses on enhancing the handling of token rewards by adding support for
decimalsin various components and functions, improving the representation of token amounts.Detailed summary
decimalsproperty to token objects inclaim-rewards-page.stories.tsx.chainMetadatainpage.tsxfor better token handling.ClaimRewardsPageUIandTokenRewardcomponents to usedecimals.getUnclaimedFeesto fetch and utilize token decimals.Summary by CodeRabbit
New Features
Bug Fixes
Tests