Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -65,11 +75,7 @@ export const ClaimDelegationRewardsButton = ({
splitData,
tokenAddress!,
beneficiary as Address,
{
rollupBalance,
splitContractBalance,
warehouseBalance
}
balances
)

// Call onSuccess callback when claim completes
Expand All @@ -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])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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)

Expand Down Expand Up @@ -165,23 +161,6 @@ export const VestingGraph = ({ globalLock, registryAddress, className = "" }: Ve

return (
<div className={`bg-gradient-to-br from-ink/40 to-ink/20 border border-parchment/10 rounded-lg p-6 ${className}`}>
{/* TGE Notice for Auction ATP */}
{isAuctionATP && (
<div className="mb-6 p-4 bg-chartreuse/10 border border-chartreuse/30 text-left">
<div className="text-sm text-parchment font-oracle-standard">
<strong className="text-chartreuse">TGE Notice:</strong> Tokens become available at TGE. TGE is decided by governance. Earliest anticipated in 90 days from start date. Latest is{' '}
<strong>
{new Date(Number(globalLock.endTime) * 1000).toLocaleDateString('en-US', {
day: 'numeric',
month: 'short',
year: 'numeric'
})}
</strong>
{' '}as shown in the graph below.
</div>
</div>
)}

<svg
className="w-full h-full"
viewBox={`0 0 ${graphData.width} ${graphData.height}`}
Expand Down
26 changes: 16 additions & 10 deletions staking-dashboard/src/hooks/rewards/useClaimAllRewards.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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()
Expand Down
26 changes: 16 additions & 10 deletions staking-dashboard/src/hooks/splits/useClaimAllSplitRewards.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand Down
83 changes: 73 additions & 10 deletions staking-dashboard/src/hooks/splits/useClaimSplitRewards.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -10,6 +10,9 @@ interface BalanceData {
rollupBalance?: bigint
splitContractBalance?: bigint
warehouseBalance?: bigint
refetchRollup?: () => Promise<any>
refetchSplitContract?: () => Promise<any>
refetchWarehouse?: () => Promise<any>
}

type QueueStep = 'claiming' | 'distributing' | 'withdrawing'
Expand All @@ -31,6 +34,10 @@ export const useClaimSplitRewards = (
const [skipMessage, setSkipMessage] = useState<string | null>(null)
const [completedMessage, setCompletedMessage] = useState<string | null>(null)
const [isProcessing, setIsProcessing] = useState(false)
const [refetchError, setRefetchError] = useState<Error | null>(null)

// Track which step is currently completing to prevent duplicate timeout scheduling
const completingStepRef = useRef<QueueStep | null>(null)

// Get warehouse address from split contract
const { warehouseAddress, isLoading: isLoadingWarehouse } = useSplitsWarehouse(splitContractAddress)
Expand Down Expand Up @@ -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

Expand All @@ -135,14 +142,64 @@ 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<any>[] = []

if (stepToRemove === 'claiming') {
// Next step is 'distributing', which checks splitContractBalance
if (balances?.refetchSplitContract) {
refetchPromises.push(balances.refetchSplitContract())
}
} else if (stepToRemove === 'distributing') {
// 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())
}
}

// 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(() => {
Expand All @@ -151,6 +208,8 @@ export const useClaimSplitRewards = (
setQueue([])
setClaimStep('idle')
setIsProcessing(false)
setRefetchError(null)
completingStepRef.current = null
claimHook.reset()
distributeHook.reset()
withdrawHook.reset()
Expand All @@ -160,7 +219,9 @@ export const useClaimSplitRewards = (
const claim = () => {
if (!warehouseAddress) return
setSkipMessage(null)
setRefetchError(null)
setIsProcessing(false)
completingStepRef.current = null
setQueue(['claiming', 'distributing', 'withdrawing'])
}

Expand All @@ -176,8 +237,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,
Expand All @@ -187,6 +248,8 @@ export const useClaimSplitRewards = (
setSkipMessage(null)
setCompletedMessage(null)
setIsProcessing(false)
setRefetchError(null)
completingStepRef.current = null
claimHook.reset()
distributeHook.reset()
withdrawHook.reset()
Expand Down
Loading
Loading