Skip to content

Commit

Permalink
feat: fetch claims from rFOX contract (#7021)
Browse files Browse the repository at this point in the history
* feat: fetch claims from rFOX contract

* feat: add cooldown logic

* chore: add refetch

* Update src/assets/translations/en/main.json

Co-authored-by: woody <[email protected]>

* chore: memoize contracts const

---------

Co-authored-by: woody <[email protected]>
  • Loading branch information
0xApotheosis and woodenfurniture authored May 31, 2024
1 parent ad2301d commit d8ae167
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 57 deletions.
4 changes: 3 additions & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2452,7 +2452,9 @@
"stakeAmount": "This is the amount of FOX you will stake",
"unstakeAmount": "This is amount of FOX you will unstake",
"lockupPeriod": "This is how long you will have to wait after unstaking to claim your FOX",
"shareOfPool": "This is your percentage of the fees, you'll earn."
"shareOfPool": "This is your percentage of the fees you'll earn.",
"unstakePendingCooldown": "Claim available %{cooldownPeriodHuman}",
"cooldownComplete": "Cooldown finished %{cooldownPeriodHuman}"
},
"stakeSuccess": "You have successfully staked %{amount} %{symbol}",
"stakePending": "Staking %{amount} %{symbol}...",
Expand Down
236 changes: 180 additions & 56 deletions src/pages/RFOX/components/Claim/ClaimSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
import { Box, Button, CardBody, Center, Flex, Stack } from '@chakra-ui/react'
import { type AssetId, foxAssetId } from '@shapeshiftoss/caip'
import { Box, Button, CardBody, Center, Flex, Skeleton, Stack, Tooltip } from '@chakra-ui/react'
import {
type AssetId,
foxAssetId,
foxOnArbitrumOneAssetId,
fromAccountId,
} from '@shapeshiftoss/caip'
import { bnOrZero } from '@shapeshiftoss/chain-adapters'
import { TransferType } from '@shapeshiftoss/unchained-client'
import { foxStakingV1Abi } from 'contracts/abis/FoxStakingV1'
import { RFOX_PROXY_CONTRACT_ADDRESS } from 'contracts/constants'
import { formatDistanceToNow } from 'date-fns'
import { type FC, useCallback, useMemo } from 'react'
import { useTranslate } from 'react-polyglot'
import { useHistory } from 'react-router'
import type { Address } from 'viem'
import { getAddress } from 'viem'
import { arbitrum } from 'viem/chains'
import { useReadContract, useReadContracts } from 'wagmi'
import { Amount } from 'components/Amount/Amount'
import { AssetIcon } from 'components/AssetIcon'
import { AssetIconWithBadge } from 'components/AssetIconWithBadge'
import { SlideTransition } from 'components/SlideTransition'
import { RawText, Text } from 'components/Text'
import { TransactionTypeIcon } from 'components/TransactionHistory/TransactionTypeIcon'
import { toBaseUnit } from 'lib/math'
import { fromBaseUnit, toBaseUnit } from 'lib/math'
import { TabIndex } from 'pages/RFOX/RFOX'
import { selectAssetById, selectFirstAccountIdByChainId } from 'state/slices/selectors'
import { useAppSelector } from 'state/store'

import type { RfoxClaimQuote } from './types'
import { ClaimRoutePaths, type ClaimRouteProps } from './types'

enum ClaimStatus {
Available = 'Available',
CoolingDown = 'Cooling down',
}

type ClaimRowProps = {
stakingAssetId: AssetId
amountCryptoPrecision: string
status: string
status: ClaimStatus
setConfirmedQuote: (quote: RfoxClaimQuote) => void
cooldownPeriodHuman: string
}

const hoverProps = { bg: 'gray.700' }
Expand All @@ -33,6 +51,7 @@ const ClaimRow: FC<ClaimRowProps> = ({
amountCryptoPrecision,
status,
setConfirmedQuote,
cooldownPeriodHuman,
}) => {
const translate = useTranslate()
const history = useHistory()
Expand Down Expand Up @@ -64,38 +83,59 @@ const ClaimRow: FC<ClaimRowProps> = ({
}, [claimQuote, history, setConfirmedQuote])

return (
<Flex
as={Button}
align='center'
variant='unstyled'
p={8}
borderRadius='md'
width='100%'
onClick={handleClick}
_hover={hoverProps}
<Tooltip
label={translate(
status === ClaimStatus.Available
? 'RFOX.tooltips.cooldownComplete'
: 'RFOX.tooltips.unstakePendingCooldown',
{ cooldownPeriodHuman },
)}
>
<Box mr={4}>
<AssetIconWithBadge assetId={foxAssetId}>
<TransactionTypeIcon type={TransferType.Receive} />
</AssetIconWithBadge>
</Box>
<Box mr={4}>
<RawText fontSize='sm' color='gray.400' align={'start'}>
{translate('RFOX.unstakeFrom', { assetSymbol: stakingAssetSymbol })}
</RawText>
<RawText fontSize='xl' fontWeight='bold' color='white' align={'start'}>
{stakingAssetSymbol}
</RawText>
</Box>
<Box flex='1' alignItems={'end'}>
<RawText fontSize='sm' fontWeight='bold' color='green.300' align={'end'}>
{status}
</RawText>
<RawText fontSize='xl' fontWeight='bold' color='white' align={'end'}>
<Amount.Crypto value={amountCryptoPrecision} symbol={stakingAssetSymbol ?? ''} />
</RawText>
</Box>
</Flex>
<Flex
as={Button}
justifyContent={'space-between'}
mt={2}
align='center'
variant='unstyled'
p={8}
borderRadius='md'
width='100%'
onClick={handleClick}
isDisabled={status !== ClaimStatus.Available}
_hover={hoverProps}
>
<Flex>
<Box mr={4}>
<AssetIconWithBadge assetId={foxAssetId}>
<TransactionTypeIcon type={TransferType.Receive} />
</AssetIconWithBadge>
</Box>
<Box mr={4}>
<RawText fontSize='sm' color='gray.400' align={'start'}>
{translate('RFOX.unstakeFrom', { assetSymbol: stakingAssetSymbol })}
</RawText>
<RawText fontSize='xl' fontWeight='bold' color='white' align={'start'}>
{stakingAssetSymbol}
</RawText>
</Box>
</Flex>
<Flex justifyContent={'flex-end'}>
<Box flexGrow={1} alignItems={'end'}>
<RawText
fontSize='sm'
fontWeight='bold'
color={status === ClaimStatus.Available ? 'green.300' : 'yellow.300'}
align={'end'}
>
{status}
</RawText>
<RawText fontSize='xl' fontWeight='bold' color='white' align={'end'}>
<Amount.Crypto value={amountCryptoPrecision} symbol={stakingAssetSymbol ?? ''} />
</RawText>
</Box>
</Flex>
</Flex>
</Tooltip>
)
}

Expand Down Expand Up @@ -131,31 +171,115 @@ export const ClaimSelect: FC<ClaimSelectProps & ClaimRouteProps> = ({
setConfirmedQuote,
setStepIndex,
}) => {
const hasClaims = false // Just a placeholder for now
const stakingAssetId = foxOnArbitrumOneAssetId
const stakingAsset = useAppSelector(state => selectAssetById(state, stakingAssetId))

// TODO(gomes): make this programmatic when we implement multi-account
const stakingAssetAccountId = useAppSelector(state =>
selectFirstAccountIdByChainId(state, stakingAsset?.chainId ?? ''),
)

const stakingAssetAccountAddress = useMemo(
() => (stakingAssetAccountId ? fromAccountId(stakingAssetAccountId).account : undefined),
[stakingAssetAccountId],
)

const {
data: unstakingRequestCountResponse,
isSuccess: isUnstakingRequestCountSuccess,
isLoading: isUnstakingRequestCountLoading,
} = useReadContract({
abi: foxStakingV1Abi,
address: RFOX_PROXY_CONTRACT_ADDRESS,
functionName: 'getUnstakingRequestCount',
args: [stakingAssetAccountAddress ? getAddress(stakingAssetAccountAddress) : ('' as Address)], // actually defined, see enabled below
chainId: arbitrum.id,
query: {
enabled: Boolean(stakingAssetAccountAddress),
},
})

const hasClaims = useMemo(
() =>
isUnstakingRequestCountSuccess &&
unstakingRequestCountResponse &&
unstakingRequestCountResponse > 0n,
[isUnstakingRequestCountSuccess, unstakingRequestCountResponse],
)

const contracts = useMemo(
() =>
Array.from(
{ length: Number(unstakingRequestCountResponse) },
(_, index) =>
({
abi: foxStakingV1Abi,
address: RFOX_PROXY_CONTRACT_ADDRESS,
functionName: 'getUnstakingRequest',
args: [
stakingAssetAccountAddress
? getAddress(stakingAssetAccountAddress)
: ('' as Address, BigInt(index)),
index,
],
chainId: arbitrum.id,
}) as const,
),
[stakingAssetAccountAddress, unstakingRequestCountResponse],
)

const {
data: unstakingRequestResponse,
isSuccess: isUnstakingRequestSuccess,
isLoading: isUnstakingRequestLoading,
} = useReadContracts({
contracts,
allowFailure: false,
query: {
refetchOnMount: true,
refetchOnWindowFocus: true,
refetchInterval: 60000, // 1 minute
},
})

if (!stakingAssetAccountAddress) return null

return (
<SlideTransition>
<Stack>{headerComponent}</Stack>
<CardBody py={12}>
<Flex flexDir='column' gap={4}>
{hasClaims ? (
<>
<ClaimRow
stakingAssetId={foxAssetId}
amountCryptoPrecision={'1500'}
status={'Available'}
setConfirmedQuote={setConfirmedQuote}
/>
<ClaimRow
stakingAssetId={foxAssetId}
amountCryptoPrecision={'200'}
status={'Available'}
setConfirmedQuote={setConfirmedQuote}
/>
</>
) : (
<NoClaimsAvailable setStepIndex={setStepIndex} />
)}
</Flex>
<Skeleton isLoaded={!isUnstakingRequestCountLoading && !isUnstakingRequestLoading}>
<Flex flexDir='column' gap={4}>
{hasClaims && isUnstakingRequestSuccess ? (
unstakingRequestResponse?.map(unstakingRequest => {
const amountCryptoPrecision = fromBaseUnit(
unstakingRequest.unstakingBalance.toString() ?? '',
stakingAsset?.precision ?? 0,
)
const currentTimestampMs: number = Date.now()
const unstakingTimestampMs: number = Number(unstakingRequest.cooldownExpiry) * 1000
const isAvailable = currentTimestampMs >= unstakingTimestampMs
const cooldownDeltaMs = unstakingTimestampMs - currentTimestampMs
const cooldownPeriodHuman = formatDistanceToNow(Date.now() + cooldownDeltaMs, {
addSuffix: true,
})
const status = isAvailable ? ClaimStatus.Available : ClaimStatus.CoolingDown
return (
<ClaimRow
key={unstakingRequest.cooldownExpiry.toString()}
stakingAssetId={foxAssetId}
amountCryptoPrecision={amountCryptoPrecision?.toString() ?? ''}
status={status}
setConfirmedQuote={setConfirmedQuote}
cooldownPeriodHuman={cooldownPeriodHuman}
/>
)
})
) : (
<NoClaimsAvailable setStepIndex={setStepIndex} />
)}
</Flex>
</Skeleton>
</CardBody>
</SlideTransition>
)
Expand Down

0 comments on commit d8ae167

Please sign in to comment.