Feat/1909 add claim rewards in space treasury view#1967
Feat/1909 add claim rewards in space treasury view#1967
Conversation
📝 WalkthroughWalkthroughAdds a new SpacePendingRewardsSection component (space-scoped pending rewards + claim flow), adjusts PendingRewardsSection rendering/loading/auth checks, exports the new component, and integrates it into the Treasury page. Changes
Sequence DiagramsequenceDiagram
participant User as User
participant UI as SpacePendingRewardsSection
participant Hooks as Data Hooks
participant Contract as Smart Contract
participant Chain as Blockchain
User->>UI: Open Treasury page
UI->>Hooks: useSpace(), useMembership(), useSpaceAssets(), usePendingRewards()
Hooks->>Contract: Query space & pending rewards
Contract->>Chain: Read on-chain state
Chain-->>Contract: Return data
Contract-->>Hooks: Return aggregated results
Hooks-->>UI: Provide data -> render cards
alt Authenticated & rewards > threshold
User->>UI: Click "Claim"
UI->>Contract: Submit claim transaction
Contract->>Chain: Broadcast tx
Chain-->>Contract: Return tx receipt
UI->>Hooks: Mutate pendingRewards & refresh space assets
Hooks-->>UI: Update UI (hasClaimed / balances)
UI-->>User: Show success/updated balances
else Not authenticated or no rewards
UI-->>User: Show Empty or disabled claim
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/epics/src/treasury/components/assets/pending-rewards-section.tsx (1)
70-88:⚠️ Potential issue | 🟠 Major
hasNoRewardshides the section for fractional rewards due to BigInt truncation.
parsedRewardValue(line 50) uses integer division (pendingRewards / 10n ** 18n), which truncates all sub-token fractions to 0. For example, if a user has 0.5 tokens pending (i.e.500000000000000000n),parsedRewardValuebecomes0, triggeringhasNoRewards = true, and the section is completely hidden—even though claimable rewards exist.This also makes
MIN_REWARD_CLAIM_VALUE = 0.01unreachable sinceparsedRewardValuecan only ever be a whole number.Consider using a higher-precision conversion, e.g.:
Proposed fix for precision
const parsedRewardValue = - pendingRewards !== undefined ? Number(pendingRewards / 10n ** 18n) : 0; + pendingRewards !== undefined + ? Number(pendingRewards) / Number(10n ** 18n) + : 0;Note:
Number(pendingRewards)is safe as long as the value doesn't exceedNumber.MAX_SAFE_INTEGER(~9 × 10¹⁵ wei ≈ ~9000 tokens), which should be fine for typical reward amounts. For very large values, consider a library likeethers.formatUnits.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/treasury/components/assets/pending-rewards-section.tsx` around lines 70 - 88, parsedRewardValue is computed with integer division and loses fractional wei, causing hasNoRewards to hide when there are fractional rewards; change parsedRewardValue to a floating value (e.g. parsedRewardValue = Number(pendingRewards) / 1e18 or use ethers.formatUnits) so fractions are preserved, and update hasNoRewards to base the “no rewards” check on the raw pendingRewards or a numeric threshold (e.g. hasNoRewards = !isLoading && pendingRewards !== undefined && (pendingRewards === 0n || parsedRewardValue < MIN_REWARD_CLAIM_VALUE)) so MIN_REWARD_CLAIM_VALUE is respected; update any imports if you use ethers.formatUnits and ensure parsedRewardValue stays a number for comparisons.
🧹 Nitpick comments (1)
packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx (1)
26-148: Significant logic duplication withpending-rewards-section.tsx.This component shares ~80% of its logic with
PendingRewardsSection: reward parsing, asset lookup, claim flow,hasNoRewardsguard,disableClaimButtoncomputation, and the entire UI structure. Consider extracting the shared behavior into a custom hook (e.g.,usePendingRewardsFlow) that accepts the user address, asset fetcher, and post-claim updater, returning all the computed state and handlers. The two section components would then only differ in how they source their inputs and render authorization messages.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx` around lines 26 - 148, The SpacePendingRewardsSection duplicates most logic from PendingRewardsSection; extract the shared behavior into a custom hook (suggested name usePendingRewardsFlow) that encapsulates pendingRewards parsing (pendingRewards, parsedRewardValue), asset lookup (useAssetsSection/originalAsset -> hyphaTokenAsset), claim flow (usePendingRewards, claim, waitForClaimReceipt, updatePendingRewards), computed booleans (disableClaimButton, hasNoRewards, canClaim), and the updateSpaceAssets logic, and return state and handlers (isLoading, isClaiming, hasClaimed, setHasClaimed, onHandleClaim) so SpacePendingRewardsSection and PendingRewardsSection only call usePendingRewardsFlow with their specific inputs (executor/user address, space-specific updater) and render differing auth messages; move references to functions/vars like usePendingRewards, useAssetsSection, updateSpaceAssets, onHandleClaim, pendingRewards, parsedRewardValue, disableClaimButton and hasNoRewards into the hook to remove duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/epics/src/treasury/components/assets/pending-rewards-section.tsx`:
- Around line 110-122: The Empty state message is misleading when
isAuthenticated is false but pendingRewards (fetched via person.address) is
greater than 0; update the render logic in pending-rewards-section.tsx to either
skip fetching/displaying pending rewards when not authenticated or change the
branch that currently renders <Empty><p>No rewards found for this
user</p></Empty> to show an auth-required message. Concretely, adjust the
conditional around isLoading / isAuthenticated to also check pendingRewards (or
a boolean like hasPendingRewards) and, when !isAuthenticated && pendingRewards >
0, render a prompt to connect/authenticate (or hide the rewards) instead of
saying “No rewards found”; touch the same JSX where AssetCard, hyphaTokenAsset,
and isLoadingAssets are used.
In
`@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx`:
- Around line 53-54: parsedRewardValue currently uses integer BigInt division
(Number(pendingRewards / 10n ** 18n)) which truncates fractional token amounts
and makes hasNoRewards treat fractional rewards as zero; change the computation
to perform the division in floating point by converting the BigInt to a Number
first (e.g., Number(pendingRewards) / 1e18) or otherwise compute a decimal
string, so parsedRewardValue retains fractions; update the assignment referenced
by parsedRewardValue and ensure hasNoRewards (the boolean that hides the
section) uses the new parsedRewardValue logic.
- Around line 19-20: Extract the duplicated HYPHA_TOKEN_ADDRESS and
MIN_REWARD_CLAIM_VALUE into a shared module (e.g., create a constants.ts next to
these components), export both constants from that module, and replace the
inline definitions in space-pending-rewards-section.tsx and
pending-rewards-section.tsx with imports from the new constants module; ensure
the exported names match HYPHA_TOKEN_ADDRESS and MIN_REWARD_CLAIM_VALUE so
existing usages in those components remain unchanged.
---
Outside diff comments:
In `@packages/epics/src/treasury/components/assets/pending-rewards-section.tsx`:
- Around line 70-88: parsedRewardValue is computed with integer division and
loses fractional wei, causing hasNoRewards to hide when there are fractional
rewards; change parsedRewardValue to a floating value (e.g. parsedRewardValue =
Number(pendingRewards) / 1e18 or use ethers.formatUnits) so fractions are
preserved, and update hasNoRewards to base the “no rewards” check on the raw
pendingRewards or a numeric threshold (e.g. hasNoRewards = !isLoading &&
pendingRewards !== undefined && (pendingRewards === 0n || parsedRewardValue <
MIN_REWARD_CLAIM_VALUE)) so MIN_REWARD_CLAIM_VALUE is respected; update any
imports if you use ethers.formatUnits and ensure parsedRewardValue stays a
number for comparisons.
---
Nitpick comments:
In
`@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx`:
- Around line 26-148: The SpacePendingRewardsSection duplicates most logic from
PendingRewardsSection; extract the shared behavior into a custom hook (suggested
name usePendingRewardsFlow) that encapsulates pendingRewards parsing
(pendingRewards, parsedRewardValue), asset lookup
(useAssetsSection/originalAsset -> hyphaTokenAsset), claim flow
(usePendingRewards, claim, waitForClaimReceipt, updatePendingRewards), computed
booleans (disableClaimButton, hasNoRewards, canClaim), and the updateSpaceAssets
logic, and return state and handlers (isLoading, isClaiming, hasClaimed,
setHasClaimed, onHandleClaim) so SpacePendingRewardsSection and
PendingRewardsSection only call usePendingRewardsFlow with their specific inputs
(executor/user address, space-specific updater) and render differing auth
messages; move references to functions/vars like usePendingRewards,
useAssetsSection, updateSpaceAssets, onHandleClaim, pendingRewards,
parsedRewardValue, disableClaimButton and hasNoRewards into the hook to remove
duplication.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsxpackages/epics/src/treasury/components/assets/pending-rewards-section.tsxpackages/epics/src/treasury/components/assets/space-pending-rewards-section.tsxpackages/epics/src/treasury/components/index.ts
| {isLoading ? ( | ||
| <div className="w-full grid grid-cols-1 sm:grid-cols-3 gap-2 mt-2"> | ||
| <AssetCard isLoading /> | ||
| </div> | ||
| ) : !isAuthenticated ? ( | ||
| <Empty> | ||
| <p>No rewards found for this user</p> | ||
| </Empty> | ||
| ) : ( | ||
| <> | ||
| <div className="w-full grid grid-cols-1 sm:grid-cols-3 gap-2 mt-2"> | ||
| <AssetCard {...hyphaTokenAsset} isLoading={isLoadingAssets} /> | ||
| </div> | ||
| {isLoading && <AssetCard isLoading />} | ||
| </> | ||
| <div className="w-full grid grid-cols-1 sm:grid-cols-3 gap-2 mt-2"> | ||
| <AssetCard {...hyphaTokenAsset} isLoading={isLoadingAssets} /> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Misleading empty state when not authenticated.
When isAuthenticated is false but pendingRewards > 0 (fetched via person.address), the section renders "No rewards found for this user" even though rewards do exist. Consider adjusting the message to indicate an auth requirement, or skipping the fetch/display when not authenticated.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/epics/src/treasury/components/assets/pending-rewards-section.tsx`
around lines 110 - 122, The Empty state message is misleading when
isAuthenticated is false but pendingRewards (fetched via person.address) is
greater than 0; update the render logic in pending-rewards-section.tsx to either
skip fetching/displaying pending rewards when not authenticated or change the
branch that currently renders <Empty><p>No rewards found for this
user</p></Empty> to show an auth-required message. Concretely, adjust the
conditional around isLoading / isAuthenticated to also check pendingRewards (or
a boolean like hasPendingRewards) and, when !isAuthenticated && pendingRewards >
0, render a prompt to connect/authenticate (or hide the rewards) instead of
saying “No rewards found”; touch the same JSX where AssetCard, hyphaTokenAsset,
and isLoadingAssets are used.
| const HYPHA_TOKEN_ADDRESS = '0x8b93862835C36e9689E9bb1Ab21De3982e266CD3'; | ||
| const MIN_REWARD_CLAIM_VALUE = 0.01; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for all occurrences of HYPHA_TOKEN_ADDRESS and MIN_REWARD_CLAIM_VALUE
rg -n "HYPHA_TOKEN_ADDRESS|MIN_REWARD_CLAIM_VALUE" --type=ts --type=tsx -g '!node_modules'Repository: hypha-dao/hypha-web
Length of output: 90
🏁 Script executed:
#!/bin/bash
# Search for constants using glob patterns instead of file type
rg "HYPHA_TOKEN_ADDRESS|MIN_REWARD_CLAIM_VALUE" -g "*.ts" -g "*.tsx" -nRepository: hypha-dao/hypha-web
Length of output: 1685
🏁 Script executed:
#!/bin/bash
# Check what files exist in the assets directory
ls -la packages/epics/src/treasury/components/assets/Repository: hypha-dao/hypha-web
Length of output: 604
Extract duplicated constants into a shared module.
HYPHA_TOKEN_ADDRESS and MIN_REWARD_CLAIM_VALUE are duplicated verbatim in both pending-rewards-section.tsx (lines 13–14) and space-pending-rewards-section.tsx (lines 19–20). Extract them into a shared constants file (e.g., constants.ts in the same directory) to avoid drift and improve maintainability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx`
around lines 19 - 20, Extract the duplicated HYPHA_TOKEN_ADDRESS and
MIN_REWARD_CLAIM_VALUE into a shared module (e.g., create a constants.ts next to
these components), export both constants from that module, and replace the
inline definitions in space-pending-rewards-section.tsx and
pending-rewards-section.tsx with imports from the new constants module; ensure
the exported names match HYPHA_TOKEN_ADDRESS and MIN_REWARD_CLAIM_VALUE so
existing usages in those components remain unchanged.
| const parsedRewardValue = | ||
| pendingRewards !== undefined ? Number(pendingRewards / 10n ** 18n) : 0; |
There was a problem hiding this comment.
Same BigInt truncation issue as pending-rewards-section.tsx.
Number(pendingRewards / 10n ** 18n) truncates all sub-token fractions to 0, which will cause hasNoRewards (line 84-85) to hide the section when fractional rewards exist. See the detailed comment on pending-rewards-section.tsx line 70-88.
Proposed fix
const parsedRewardValue =
- pendingRewards !== undefined ? Number(pendingRewards / 10n ** 18n) : 0;
+ pendingRewards !== undefined
+ ? Number(pendingRewards) / Number(10n ** 18n)
+ : 0;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx`
around lines 53 - 54, parsedRewardValue currently uses integer BigInt division
(Number(pendingRewards / 10n ** 18n)) which truncates fractional token amounts
and makes hasNoRewards treat fractional rewards as zero; change the computation
to perform the division in floating point by converting the BigInt to a Number
first (e.g., Number(pendingRewards) / 1e18) or otherwise compute a decimal
string, so parsedRewardValue retains fractions; update the assignment referenced
by parsedRewardValue and ensure hasNoRewards (the boolean that hides the
section) uses the new parsedRewardValue logic.
96c473a to
b47f0b1
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
packages/epics/src/treasury/components/assets/pending-rewards-section.tsx (1)
114-117:⚠️ Potential issue | 🟡 MinorUse an auth-required message instead of a “no rewards” empty state.
At Line 116, the copy is still misleading for signed-out users with existing rewards; it should prompt sign-in rather than stating no rewards were found.
Suggested tweak
- ) : !isAuthenticated ? ( + ) : !isAuthenticated ? ( <Empty> - <p>No rewards found for this user</p> + <p>Sign in to view rewards for this user</p> </Empty> ) : (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/treasury/components/assets/pending-rewards-section.tsx` around lines 114 - 117, The Empty state shown when !isAuthenticated in PendingRewardsSection currently says "No rewards found for this user" which is misleading for signed-out users; change the copy to an auth-required prompt (e.g., "Please sign in to view your rewards" or similar) and, if appropriate, surface a sign-in CTA or link when isAuthenticated is false; update the JSX branch that checks isAuthenticated in PendingRewardsSection to render this auth message instead of the no-rewards copy.packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx (2)
19-20: Extract reward constants into a shared module to avoid drift.
HYPHA_TOKEN_ADDRESSandMIN_REWARD_CLAIM_VALUEare duplicated withpackages/epics/src/treasury/components/assets/pending-rewards-section.tsx. Centralizing them will keep both claim flows aligned.Based on learnings: DSanich prefers extracting repeated type declarations into shared modules for reusability and maintainability, particularly for token types that are used across multiple files in the codebase.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx` around lines 19 - 20, Extract the duplicated constants HYPHA_TOKEN_ADDRESS and MIN_REWARD_CLAIM_VALUE into a new shared module that exports these values (e.g., export const HYPHA_TOKEN_ADDRESS = ...; export const MIN_REWARD_CLAIM_VALUE = ...), then update both components (the file containing space-pending-rewards-section and the file containing pending-rewards-section) to import these constants from the shared module instead of declaring them locally; remove the local constant declarations so there is a single source of truth and ensure any existing usages still reference the imported HYPHA_TOKEN_ADDRESS and MIN_REWARD_CLAIM_VALUE identifiers.
64-65:⚠️ Potential issue | 🟠 MajorFix fractional reward truncation in reward parsing.
At Line 64,
Number(pendingRewards / 10n ** 18n)performs integer BigInt division and drops sub-token fractions (e.g.,0.5becomes0), which can incorrectly disable claiming and display.Proposed fix
- const parsedRewardValue = - pendingRewards !== undefined ? Number(pendingRewards / 10n ** 18n) : 0; + const parsedRewardValue = + pendingRewards !== undefined + ? Number(pendingRewards) / Number(10n ** 18n) + : 0;#!/bin/bash # Verify truncating pattern exists and locate all occurrences. rg -n "Number\\(pendingRewards / 10n \\*\\* 18n\\)" -g "*.ts" -g "*.tsx"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx` around lines 64 - 65, The current parsedRewardValue uses BigInt integer division (pendingRewards / 10n ** 18n) which truncates fractional tokens; change the calculation to perform a floating-point division instead (e.g., convert pendingRewards and the divisor to Number or parseFloat from toString and divide by 1e18) so fractions are preserved; update the expression that sets parsedRewardValue (referencing parsedRewardValue and pendingRewards in the space-pending-rewards-section component) to use Number(pendingRewards) / Number(10n ** 18n) or parseFloat(pendingRewards.toString()) / 1e18, ensuring pendingRewards undefined still yields 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx`:
- Around line 130-132: The tooltip text currently says "The reward value must be
greater than 0" while the guard uses MIN_REWARD_CLAIM_VALUE = 0.01; update the
disabled tooltip (the ternary that sets the message when disableClaimButton is
true) to reflect the actual threshold (e.g., "The reward value must be at least
{MIN_REWARD_CLAIM_VALUE}") and interpolate the MIN_REWARD_CLAIM_VALUE constant
instead of hardcoding a number so the message stays in sync with the guard
(refer to disableClaimButton and MIN_REWARD_CLAIM_VALUE in
space-pending-rewards-section.tsx).
---
Duplicate comments:
In `@packages/epics/src/treasury/components/assets/pending-rewards-section.tsx`:
- Around line 114-117: The Empty state shown when !isAuthenticated in
PendingRewardsSection currently says "No rewards found for this user" which is
misleading for signed-out users; change the copy to an auth-required prompt
(e.g., "Please sign in to view your rewards" or similar) and, if appropriate,
surface a sign-in CTA or link when isAuthenticated is false; update the JSX
branch that checks isAuthenticated in PendingRewardsSection to render this auth
message instead of the no-rewards copy.
In
`@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx`:
- Around line 19-20: Extract the duplicated constants HYPHA_TOKEN_ADDRESS and
MIN_REWARD_CLAIM_VALUE into a new shared module that exports these values (e.g.,
export const HYPHA_TOKEN_ADDRESS = ...; export const MIN_REWARD_CLAIM_VALUE =
...), then update both components (the file containing
space-pending-rewards-section and the file containing pending-rewards-section)
to import these constants from the shared module instead of declaring them
locally; remove the local constant declarations so there is a single source of
truth and ensure any existing usages still reference the imported
HYPHA_TOKEN_ADDRESS and MIN_REWARD_CLAIM_VALUE identifiers.
- Around line 64-65: The current parsedRewardValue uses BigInt integer division
(pendingRewards / 10n ** 18n) which truncates fractional tokens; change the
calculation to perform a floating-point division instead (e.g., convert
pendingRewards and the divisor to Number or parseFloat from toString and divide
by 1e18) so fractions are preserved; update the expression that sets
parsedRewardValue (referencing parsedRewardValue and pendingRewards in the
space-pending-rewards-section component) to use Number(pendingRewards) /
Number(10n ** 18n) or parseFloat(pendingRewards.toString()) / 1e18, ensuring
pendingRewards undefined still yields 0.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsxpackages/epics/src/treasury/components/assets/pending-rewards-section.tsxpackages/epics/src/treasury/components/assets/space-pending-rewards-section.tsxpackages/epics/src/treasury/components/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsx
| : disableClaimButton | ||
| ? 'The reward value must be greater than 0' | ||
| : '' |
There was a problem hiding this comment.
Align disabled tooltip copy with the actual minimum claim threshold.
At Line 131, the message says “greater than 0” but the guard uses MIN_REWARD_CLAIM_VALUE = 0.01, which is inconsistent for users.
Suggested copy fix
- ? 'The reward value must be greater than 0'
+ ? 'The reward value must be at least 0.01'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx`
around lines 130 - 132, The tooltip text currently says "The reward value must
be greater than 0" while the guard uses MIN_REWARD_CLAIM_VALUE = 0.01; update
the disabled tooltip (the ternary that sets the message when disableClaimButton
is true) to reflect the actual threshold (e.g., "The reward value must be at
least {MIN_REWARD_CLAIM_VALUE}") and interpolate the MIN_REWARD_CLAIM_VALUE
constant instead of hardcoding a number so the message stays in sync with the
guard (refer to disableClaimButton and MIN_REWARD_CLAIM_VALUE in
space-pending-rewards-section.tsx).
Summary by CodeRabbit
New Features
Improvements