From 75f2411a7212c2e5d630abfc9342b6322c314730 Mon Sep 17 00:00:00 2001 From: Robert Brada Date: Fri, 13 Feb 2026 11:27:42 +0100 Subject: [PATCH 1/6] fix: include rollup rewards in ATP Details Modal delegation rewards The ATP Details Modal was showing 0 AZTEC rewards for all delegations because useMultipleStakeWithProviderRewards only queried split contract balances, missing rewards still in the rollup contract. Updated the hook to query both rollup.getSequencerRewards() and token.balanceOf() for each delegation, matching the correct implementation in useAggregatedStakingData. Now uses calculateTotalUserShareFromSplitRewards to combine both sources. This fix ensures consistency between the Overview Breakdown Section and ATP Details Modal, making the "Claim Rewards" button properly enabled when rewards are available. Co-Authored-By: Claude Sonnet 4.5 --- .../useMultipleStakeWithProviderRewards.ts | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/staking-dashboard/src/hooks/staker/useMultipleStakeWithProviderRewards.ts b/staking-dashboard/src/hooks/staker/useMultipleStakeWithProviderRewards.ts index 05164005a..7a225bde8 100644 --- a/staking-dashboard/src/hooks/staker/useMultipleStakeWithProviderRewards.ts +++ b/staking-dashboard/src/hooks/staker/useMultipleStakeWithProviderRewards.ts @@ -1,7 +1,8 @@ import { useReadContracts } from 'wagmi' import { ERC20Abi } from '@/contracts/abis/ERC20' -import { calculateUserShareFromTakeRate } from '@/utils/rewardCalculations' +import { calculateTotalUserShareFromSplitRewards } from '@/utils/rewardCalculations' import { useStakingAssetTokenDetails } from '@/hooks/stakingRegistry' +import { contracts } from '@/contracts' import type { Delegation } from '@/hooks/atp' import type { StakeWithProviderReward } from './types' @@ -14,8 +15,9 @@ interface MultipleStakeWithProviderRewardsParams { * Hook to calculate rewards for multiple delegations (stakeWithProvider method) * * Reward Calculation Logic: - * 1. Get total rewards for each: totalRewards = stakingToken.balanceOf(splitContract) - * 2. Apply user's share: userRewards = totalRewards * (10000 - providerTakeRate) / 10000 + * 1. Get rollup rewards: rollup.getSequencerRewards(splitContract) + * 2. Get split contract balance: stakingToken.balanceOf(splitContract) + * 3. Calculate user's share from both sources using take rate */ export const useMultipleStakeWithProviderRewards = ({ delegations, @@ -23,19 +25,29 @@ export const useMultipleStakeWithProviderRewards = ({ }: MultipleStakeWithProviderRewardsParams) => { const { stakingAssetAddress: tokenAddress } = useStakingAssetTokenDetails() - // Build contracts array for all split contract balance queries - const balanceContracts = tokenAddress && delegations.length > 0 - ? delegations.map(delegation => ({ - address: tokenAddress as `0x${string}`, - abi: ERC20Abi, - functionName: 'balanceOf', - args: [delegation.splitContract as `0x${string}`], - })) + // Build contracts array for both rollup rewards and split balance queries + const rewardContracts = tokenAddress && delegations.length > 0 + ? delegations.flatMap(delegation => [ + // Query rollup rewards + { + address: contracts.rollup.address, + abi: contracts.rollup.abi, + functionName: 'getSequencerRewards', + args: [delegation.splitContract as `0x${string}`], + }, + // Query split contract balance + { + address: tokenAddress as `0x${string}`, + abi: ERC20Abi, + functionName: 'balanceOf', + args: [delegation.splitContract as `0x${string}`], + }, + ]) : [] - // Get balances for all split contracts - const { data: balancesData, isLoading, error, refetch } = useReadContracts({ - contracts: balanceContracts, + // Get rewards from both rollup and split contracts + const { data: rewardData, isLoading, error, refetch } = useReadContracts({ + contracts: rewardContracts, query: { enabled: !!tokenAddress && delegations.length > 0 && enabled, }, @@ -43,8 +55,16 @@ export const useMultipleStakeWithProviderRewards = ({ // Calculate user rewards for each delegation const delegationRewards: StakeWithProviderReward[] = delegations.map((delegation, index) => { - const totalRewards = (balancesData?.[index]?.result as bigint) || 0n - const userRewards = calculateUserShareFromTakeRate(totalRewards, delegation.providerTakeRate) + const rollupRewards = (rewardData?.[index * 2]?.result as bigint) || 0n + const splitBalance = (rewardData?.[index * 2 + 1]?.result as bigint) || 0n + + const totalRewards = rollupRewards + splitBalance + const userRewards = calculateTotalUserShareFromSplitRewards( + rollupRewards, + splitBalance, + 0n, // warehouse balance (omitted for this flow) + delegation.providerTakeRate + ) return { providerId: delegation.providerId, @@ -63,7 +83,7 @@ export const useMultipleStakeWithProviderRewards = ({ totalUserRewards, isLoading, error, - isSuccess: !!balancesData, + isSuccess: !!rewardData, refetch } } From 864dbf667bb9a52ce705fdeba60166baae0933ca Mon Sep 17 00:00:00 2001 From: Robert Brada Date: Fri, 13 Feb 2026 12:01:58 +0100 Subject: [PATCH 2/6] fix: rewards refetch --- .../ClaimDelegationRewardsButton.tsx | 31 +++++--- .../src/hooks/rewards/useClaimAllRewards.ts | 26 +++--- .../hooks/splits/useClaimAllSplitRewards.ts | 26 +++--- .../src/hooks/splits/useClaimSplitRewards.ts | 79 ++++++++++++++++--- 4 files changed, 121 insertions(+), 41 deletions(-) diff --git a/staking-dashboard/src/components/ClaimDelegationRewardsButton/ClaimDelegationRewardsButton.tsx b/staking-dashboard/src/components/ClaimDelegationRewardsButton/ClaimDelegationRewardsButton.tsx index ab5996546..738e34c8e 100644 --- a/staking-dashboard/src/components/ClaimDelegationRewardsButton/ClaimDelegationRewardsButton.tsx +++ b/staking-dashboard/src/components/ClaimDelegationRewardsButton/ClaimDelegationRewardsButton.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react" +import { useEffect, useMemo } from "react" import { useAccount } from "wagmi" import { useClaimSplitRewards } from "@/hooks/splits" import { useStakingAssetTokenDetails } from "@/hooks/stakingRegistry" @@ -33,11 +33,11 @@ export const ClaimDelegationRewardsButton = ({ const { stakingAssetAddress: tokenAddress } = useStakingAssetTokenDetails() const { showAlert } = useAlert() - // Fetch balances for skip logic + // Fetch balances for skip logic - extract refetch functions const { warehouseAddress } = useSplitsWarehouse(splitContract) - const { rewards: rollupBalance } = useSequencerRewards(splitContract) - const { balance: splitContractBalance } = useERC20Balance(tokenAddress!, splitContract) - const { balance: warehouseBalance } = useWarehouseBalance(warehouseAddress, beneficiary, tokenAddress) + const { rewards: rollupBalance, refetch: refetchRollup } = useSequencerRewards(splitContract) + const { balance: splitContractBalance, refetch: refetchSplitContract } = useERC20Balance(tokenAddress!, splitContract) + const { balance: warehouseBalance, refetch: refetchWarehouse } = useWarehouseBalance(warehouseAddress, beneficiary, tokenAddress) // Calculate split allocations based on provider take rate const totalAllocation = 10000n @@ -52,6 +52,16 @@ export const ClaimDelegationRewardsButton = ({ distributionIncentive: 0 } + // Memoize balances object to prevent effect re-runs on every render + const balances = useMemo(() => ({ + rollupBalance, + splitContractBalance, + warehouseBalance, + refetchRollup, + refetchSplitContract, + refetchWarehouse + }), [rollupBalance, splitContractBalance, warehouseBalance, refetchRollup, refetchSplitContract, refetchWarehouse]) + const { claim, claimStep, @@ -65,11 +75,7 @@ export const ClaimDelegationRewardsButton = ({ splitData, tokenAddress!, beneficiary as Address, - { - rollupBalance, - splitContractBalance, - warehouseBalance - } + balances ) // Call onSuccess callback when claim completes @@ -79,12 +85,15 @@ export const ClaimDelegationRewardsButton = ({ } }, [isSuccess, onSuccess]) - // Handle errors + // Handle errors - show all errors, not just rejections useEffect(() => { if (error) { const errorMessage = error.message if (errorMessage.includes('User rejected') || errorMessage.includes('rejected')) { showAlert('warning', 'Transaction was cancelled') + } else { + // Show error for all other failures + showAlert('error', `Claim failed: ${errorMessage}`) } } }, [error, showAlert]) diff --git a/staking-dashboard/src/hooks/rewards/useClaimAllRewards.ts b/staking-dashboard/src/hooks/rewards/useClaimAllRewards.ts index c65ac432a..4b9c90aa1 100644 --- a/staking-dashboard/src/hooks/rewards/useClaimAllRewards.ts +++ b/staking-dashboard/src/hooks/rewards/useClaimAllRewards.ts @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback, useRef } from "react" +import { useState, useEffect, useCallback, useRef, useMemo } from "react" import { useAccount } from "wagmi" import { useClaimSplitRewards } from "@/hooks/splits/useClaimSplitRewards" import { useClaimSequencerRewards } from "@/hooks/rollup/useClaimSequencerRewards" @@ -79,27 +79,33 @@ export const useClaimAllRewards = (): UseClaimAllRewardsReturn => { const currentSplitContract = currentTask?.type === 'delegation' ? currentTask.splitContract : undefined const currentCoinbase = currentTask?.type === 'coinbase' ? currentTask.coinbaseAddress : undefined - // Fetch balances for current task (for delegations) + // Fetch balances for current task (for delegations) - extract refetch functions const { warehouseAddress, isLoading: isLoadingWarehouse } = useSplitsWarehouse(currentSplitContract) - const { rewards: rollupBalance, isLoading: isLoadingRollup } = useSequencerRewards(currentSplitContract || currentCoinbase || '') - const { balance: splitContractBalance, isLoading: isLoadingSplitBalance } = useERC20Balance(tokenAddress, currentSplitContract) - const { balance: warehouseBalance, isLoading: isLoadingWarehouseBalance } = useWarehouseBalance(warehouseAddress, userAddress, tokenAddress) + const { rewards: rollupBalance, isLoading: isLoadingRollup, refetch: refetchRollup } = useSequencerRewards(currentSplitContract || currentCoinbase || '') + const { balance: splitContractBalance, isLoading: isLoadingSplitBalance, refetch: refetchSplitContract } = useERC20Balance(tokenAddress, currentSplitContract) + const { balance: warehouseBalance, isLoading: isLoadingWarehouseBalance, refetch: refetchWarehouse } = useWarehouseBalance(warehouseAddress, userAddress, tokenAddress) const isLoadingBalances = currentTask?.type === 'delegation' ? (isLoadingWarehouse || isLoadingRollup || isLoadingSplitBalance || isLoadingWarehouseBalance) : isLoadingRollup + // Memoize balances object to prevent effect re-runs on every render + const balances = useMemo(() => ({ + rollupBalance, + splitContractBalance, + warehouseBalance, + refetchRollup, + refetchSplitContract, + refetchWarehouse + }), [rollupBalance, splitContractBalance, warehouseBalance, refetchRollup, refetchSplitContract, refetchWarehouse]) + // Use existing hooks for claiming const delegationClaimHook = useClaimSplitRewards( currentSplitContract, currentTask?.splitData || { recipients: [], allocations: [], totalAllocation: 0n, distributionIncentive: 0 }, tokenAddress, userAddress, - { - rollupBalance, - splitContractBalance, - warehouseBalance - } + balances ) const coinbaseClaimHook = useClaimSequencerRewards() diff --git a/staking-dashboard/src/hooks/splits/useClaimAllSplitRewards.ts b/staking-dashboard/src/hooks/splits/useClaimAllSplitRewards.ts index 29af5b135..c7a87fef7 100644 --- a/staking-dashboard/src/hooks/splits/useClaimAllSplitRewards.ts +++ b/staking-dashboard/src/hooks/splits/useClaimAllSplitRewards.ts @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from "react" +import { useState, useEffect, useCallback, useMemo } from "react" import { useAccount } from "wagmi" import { useClaimSplitRewards } from "./useClaimSplitRewards" import { useSequencerRewards } from "@/hooks/rollup/useSequencerRewards" @@ -39,25 +39,31 @@ export const useClaimAllSplitRewards = () => { // Get token address for balance queries const { stakingAssetAddress: tokenAddress } = useStakingAssetTokenDetails() - // Fetch balances for current task + // Fetch balances for current task - extract refetch functions const { warehouseAddress, isLoading: isLoadingWarehouse } = useSplitsWarehouse(currentTask?.splitContract) - const { rewards: rollupBalance, isLoading: isLoadingRollupBalance } = useSequencerRewards(currentTask?.splitContract || '') - const { balance: splitContractBalance, isLoading: isLoadingSplitContractBalance } = useERC20Balance(tokenAddress, currentTask?.splitContract) - const { balance: warehouseBalance, isLoading: isLoadingWarehouseBalance } = useWarehouseBalance(warehouseAddress, beneficiary, tokenAddress) + const { rewards: rollupBalance, isLoading: isLoadingRollupBalance, refetch: refetchRollup } = useSequencerRewards(currentTask?.splitContract || '') + const { balance: splitContractBalance, isLoading: isLoadingSplitContractBalance, refetch: refetchSplitContract } = useERC20Balance(tokenAddress, currentTask?.splitContract) + const { balance: warehouseBalance, isLoading: isLoadingWarehouseBalance, refetch: refetchWarehouse } = useWarehouseBalance(warehouseAddress, beneficiary, tokenAddress) const isLoading = isLoadingWarehouse || isLoadingRollupBalance || isLoadingSplitContractBalance || isLoadingWarehouseBalance + // Memoize balances object to prevent effect re-runs on every render + const balances = useMemo(() => ({ + rollupBalance, + splitContractBalance, + warehouseBalance, + refetchRollup, + refetchSplitContract, + refetchWarehouse + }), [rollupBalance, splitContractBalance, warehouseBalance, refetchRollup, refetchSplitContract, refetchWarehouse]) + // Use the single claim hook for the current task const claimHook = useClaimSplitRewards( currentTask?.splitContract, currentTask?.splitData || { recipients: [], allocations: [], totalAllocation: 0n, distributionIncentive: 0 }, currentTask?.tokenAddress, currentTask?.userAddress, - { - rollupBalance, - splitContractBalance, - warehouseBalance - } + balances ) // Monitor claim completion and move to next task diff --git a/staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts b/staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts index f20564669..dcaa39fce 100644 --- a/staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts +++ b/staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react" +import { useState, useEffect, useRef } from "react" import { useDistributeRewards } from "./useDistributeRewards" import { useWithdrawRewards } from "./useWithdrawRewards" import { useSplitsWarehouse } from "./useSplitsWarehouse" @@ -10,6 +10,9 @@ interface BalanceData { rollupBalance?: bigint splitContractBalance?: bigint warehouseBalance?: bigint + refetchRollup?: () => Promise + refetchSplitContract?: () => Promise + refetchWarehouse?: () => Promise } type QueueStep = 'claiming' | 'distributing' | 'withdrawing' @@ -31,6 +34,10 @@ export const useClaimSplitRewards = ( const [skipMessage, setSkipMessage] = useState(null) const [completedMessage, setCompletedMessage] = useState(null) const [isProcessing, setIsProcessing] = useState(false) + const [refetchError, setRefetchError] = useState(null) + + // Track which step is currently completing to prevent duplicate timeout scheduling + const completingStepRef = useRef(null) // Get warehouse address from split contract const { warehouseAddress, isLoading: isLoadingWarehouse } = useSplitsWarehouse(splitContractAddress) @@ -111,7 +118,7 @@ export const useClaimSplitRewards = ( } }, [queue, balances]) - // Handle transaction success - show completion message then remove from queue + // Handle transaction success - show completion message, refetch balances, then remove from queue useEffect(() => { if (!isProcessing || queue.length === 0) return @@ -135,14 +142,60 @@ export const useClaimSplitRewards = ( } if (stepCompleted && stepToRemove) { + // Guard: prevent duplicate timeout scheduling if already completing this step + if (completingStepRef.current === stepToRemove) return + + completingStepRef.current = stepToRemove setCompletedMessage(message) - setTimeout(() => { - setCompletedMessage(null) - setIsProcessing(false) - setQueue(prev => prev.filter(step => step !== stepToRemove)) - }, 1000) + + // Determine which balances need refetching for the NEXT step + const refetchPromises: Promise[] = [] + + if (stepToRemove === 'claiming') { + // Next step is 'distributing', which checks splitContractBalance + if (balances?.refetchSplitContract) { + refetchPromises.push(balances.refetchSplitContract()) + } + } else if (stepToRemove === 'distributing') { + // Next step is 'withdrawing', which checks warehouseBalance (CRITICAL) + if (balances?.refetchWarehouse) { + refetchPromises.push(balances.refetchWarehouse()) + } + } + + // Wait for refetch to complete before advancing + if (refetchPromises.length > 0) { + Promise.all(refetchPromises) + .then(() => { + // Refetch succeeded - advance to next step after delay + setTimeout(() => { + setCompletedMessage(null) + setIsProcessing(false) + setQueue(prev => prev.filter(step => step !== stepToRemove)) + completingStepRef.current = null // Clear ref after advancing + }, 500) + }) + .catch(err => { + console.error('Balance refetch failed:', err) + // Treat refetch failure as an error - halt the flow completely + setRefetchError(err instanceof Error ? err : new Error('Balance refetch failed')) + setCompletedMessage(null) + setQueue([]) + setClaimStep('idle') + setIsProcessing(false) + completingStepRef.current = null // Clear ref on error + }) + } else { + // No refetch needed (e.g., last step) - advance immediately + setTimeout(() => { + setCompletedMessage(null) + setIsProcessing(false) + setQueue(prev => prev.filter(step => step !== stepToRemove)) + completingStepRef.current = null // Clear ref after advancing + }, 500) + } } - }, [queue, claimHook.isSuccess, distributeHook.isSuccess, withdrawHook.isSuccess, isProcessing]) + }, [queue, claimHook.isSuccess, distributeHook.isSuccess, withdrawHook.isSuccess, isProcessing, balances]) // Handle errors - reset queue useEffect(() => { @@ -151,6 +204,8 @@ export const useClaimSplitRewards = ( setQueue([]) setClaimStep('idle') setIsProcessing(false) + setRefetchError(null) + completingStepRef.current = null claimHook.reset() distributeHook.reset() withdrawHook.reset() @@ -160,7 +215,9 @@ export const useClaimSplitRewards = ( const claim = () => { if (!warehouseAddress) return setSkipMessage(null) + setRefetchError(null) setIsProcessing(false) + completingStepRef.current = null setQueue(['claiming', 'distributing', 'withdrawing']) } @@ -176,8 +233,8 @@ export const useClaimSplitRewards = ( isLoading: isLoadingWarehouse, isClaiming, isSuccess, - isError: claimHook.isError || distributeHook.isError || withdrawHook.isError, - error: claimHook.error || distributeHook.error || withdrawHook.error, + isError: claimHook.isError || distributeHook.isError || withdrawHook.isError || !!refetchError, + error: refetchError || claimHook.error || distributeHook.error || withdrawHook.error, claimTxHash: claimHook.txHash, distributeTxHash: distributeHook.txHash, withdrawTxHash: withdrawHook.txHash, @@ -187,6 +244,8 @@ export const useClaimSplitRewards = ( setSkipMessage(null) setCompletedMessage(null) setIsProcessing(false) + setRefetchError(null) + completingStepRef.current = null claimHook.reset() distributeHook.reset() withdrawHook.reset() From 49116961ee77a2a4db95f1a7c3b60d937c6a77f6 Mon Sep 17 00:00:00 2001 From: Robert Brada Date: Fri, 13 Feb 2026 12:04:56 +0100 Subject: [PATCH 3/6] chore: remove TGE notice --- .../components/VestingSchedule/VestingGraph.tsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/staking-dashboard/src/components/VestingSchedule/VestingGraph.tsx b/staking-dashboard/src/components/VestingSchedule/VestingGraph.tsx index 054db07c4..2943b585d 100644 --- a/staking-dashboard/src/components/VestingSchedule/VestingGraph.tsx +++ b/staking-dashboard/src/components/VestingSchedule/VestingGraph.tsx @@ -165,23 +165,6 @@ export const VestingGraph = ({ globalLock, registryAddress, className = "" }: Ve return (
- {/* TGE Notice for Auction ATP */} - {isAuctionATP && ( -
-
- TGE Notice: Tokens become available at TGE. TGE is decided by governance. Earliest anticipated in 90 days from start date. Latest is{' '} - - {new Date(Number(globalLock.endTime) * 1000).toLocaleDateString('en-US', { - day: 'numeric', - month: 'short', - year: 'numeric' - })} - - {' '}as shown in the graph below. -
-
- )} - Date: Fri, 13 Feb 2026 12:09:41 +0100 Subject: [PATCH 4/6] chore: ui dust --- staking-dashboard/src/pages/ATP/MyPositionPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/staking-dashboard/src/pages/ATP/MyPositionPage.tsx b/staking-dashboard/src/pages/ATP/MyPositionPage.tsx index ebd4beed2..22b2f7fa9 100644 --- a/staking-dashboard/src/pages/ATP/MyPositionPage.tsx +++ b/staking-dashboard/src/pages/ATP/MyPositionPage.tsx @@ -201,7 +201,7 @@ export default function MyPositionPage() { )} {/* Progress bar - only show when user can stake */} - {canStake && ( + {canStake ? (
{stakedPercent > 0 && (
)}
- )} + ): null}
Date: Fri, 13 Feb 2026 12:30:05 +0100 Subject: [PATCH 5/6] chore: remove unused vars --- .../src/components/VestingSchedule/VestingGraph.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/staking-dashboard/src/components/VestingSchedule/VestingGraph.tsx b/staking-dashboard/src/components/VestingSchedule/VestingGraph.tsx index 2943b585d..169530181 100644 --- a/staking-dashboard/src/components/VestingSchedule/VestingGraph.tsx +++ b/staking-dashboard/src/components/VestingSchedule/VestingGraph.tsx @@ -3,7 +3,6 @@ import type { Address } from "viem" import { formatTokenAmount } from "@/utils/atpFormatters" import { useStakingAssetTokenDetails } from "@/hooks/stakingRegistry" import { useVestingCalculation } from "@/hooks/atp" -import { isAuctionRegistry } from "@/hooks/atpRegistry" interface VestingGraphProps { globalLock: { @@ -20,12 +19,9 @@ interface VestingGraphProps { /** * SVG vector graph showing cliff vesting pattern */ -export const VestingGraph = ({ globalLock, registryAddress, className = "" }: VestingGraphProps) => { +export const VestingGraph = ({ globalLock, className = "" }: VestingGraphProps) => { const { symbol, decimals } = useStakingAssetTokenDetails() - // Check if this is an ATP from auction registry - const isAuctionATP = isAuctionRegistry(registryAddress) - // Check for invalid time range const hasInvalidTimeRange = Number(globalLock.endTime) < Number(globalLock.startTime) From 04bdef4d5feda6c6992518d7e69d8f368123ca3b Mon Sep 17 00:00:00 2001 From: Amin Sammara Date: Fri, 13 Feb 2026 09:52:54 -0500 Subject: [PATCH 6/6] Refetch split contract balance after the first distribute call --- staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts b/staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts index dcaa39fce..f60159f9b 100644 --- a/staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts +++ b/staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts @@ -157,7 +157,11 @@ export const useClaimSplitRewards = ( refetchPromises.push(balances.refetchSplitContract()) } } else if (stepToRemove === 'distributing') { - // Next step is 'withdrawing', which checks warehouseBalance (CRITICAL) + // After distributing, tokens move from split contract to warehouse + // Refetch BOTH balances to keep the UI accurate + if (balances?.refetchSplitContract) { + refetchPromises.push(balances.refetchSplitContract()) + } if (balances?.refetchWarehouse) { refetchPromises.push(balances.refetchWarehouse()) }