Skip to content

Commit

Permalink
fix: add fox benefits rfox asset selectors (#8553)
Browse files Browse the repository at this point in the history
  • Loading branch information
NeOMakinG authored Jan 16, 2025
1 parent b1c4f9c commit fda8993
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 77 deletions.
2 changes: 1 addition & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2770,7 +2770,7 @@
"24hrPriceChange": "24h Price Change",
"rfox": {
"whatIs": "What is rFOX Staking?",
"totalFoxBurn": "Total FOX Burn",
"totalSymbolBurn": "Total %{symbol} Burn",
"simulateTitle": "rFOX Simulator",
"simulateSubtle": "Input number of deposit something",
"estimatedRewards": "Estimated Rewards",
Expand Down
25 changes: 20 additions & 5 deletions src/pages/Fox/components/FoxTokenFilterButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Button, useColorModeValue } from '@chakra-ui/react'
import type { AssetId } from '@shapeshiftoss/caip'
import type { Asset } from '@shapeshiftoss/types'
import { useMemo } from 'react'
import { AssetIcon } from 'components/AssetIcon'
import { selectFeeAssetByChainId } from 'state/slices/selectors'
import { useAppSelector } from 'state/store'
Expand All @@ -8,34 +10,47 @@ export type Filter = {
label: string
chainId?: string
assetId?: AssetId
asset?: Asset
}

type FoxTokenFilterButtonProps = {
filter: Filter
onFilterClick: (filter: Filter) => void
isSelected: boolean
asset?: Asset
}

const buttonsHover = {
opacity: '.6',
}

const pairProps = {
showFirst: true,
}

export const FoxTokenFilterButton = ({
filter,
onFilterClick,
isSelected,
asset,
}: FoxTokenFilterButtonProps) => {
const buttonsBgColor = useColorModeValue('gray.100', 'white')
const buttonsColor = useColorModeValue('gray.500', 'white')
const feeAsset = useAppSelector(state => selectFeeAssetByChainId(state, filter.chainId ?? ''))

const iconSrc = feeAsset?.networkIcon

const networkIcon = iconSrc ? (
<AssetIcon src={iconSrc} size='xs' />
) : (
<AssetIcon assetId={feeAsset?.assetId ?? ''} size='xs' />
)
const networkIcon = useMemo(() => {
if (asset) {
return <AssetIcon assetId={asset.assetId} pairProps={pairProps} size='xs' />
}

if (iconSrc) {
return <AssetIcon src={iconSrc} size='xs' />
}

return <AssetIcon assetId={feeAsset?.assetId ?? ''} size='xs' />
}, [asset, feeAsset, iconSrc])

return (
<Button
Expand Down
162 changes: 96 additions & 66 deletions src/pages/Fox/components/RFOXSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import type { FlexProps, StackProps } from '@chakra-ui/react'
import {
Box,
Button,
ButtonGroup,
Card,
CardBody,
Divider,
Flex,
Heading,
HStack,
SimpleGrid,
Skeleton,
Stack,
Expand All @@ -18,8 +20,9 @@ import {
fromAccountId,
fromAssetId,
thorchainAssetId,
uniV2EthFoxArbitrumAssetId,
} from '@shapeshiftoss/caip'
import { useCallback, useEffect, useMemo } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslate } from 'react-polyglot'
import { useHistory } from 'react-router'
import { Amount } from 'components/Amount/Amount'
Expand All @@ -29,7 +32,7 @@ import { useFeatureFlag } from 'hooks/useFeatureFlag/useFeatureFlag'
import { bn } from 'lib/bignumber/bignumber'
import { fromBaseUnit } from 'lib/math'
import { formatSecondsToDuration } from 'lib/utils/time'
import { selectStakingBalance } from 'pages/RFOX/helpers'
import { getStakingContract, selectStakingBalance } from 'pages/RFOX/helpers'
import { useAffiliateRevenueQuery } from 'pages/RFOX/hooks/useAffiliateRevenueQuery'
import { useCurrentApyQuery } from 'pages/RFOX/hooks/useCurrentApyQuery'
import { useCurrentEpochMetadataQuery } from 'pages/RFOX/hooks/useCurrentEpochMetadataQuery'
Expand All @@ -48,8 +51,17 @@ import {
import { useAppDispatch, useAppSelector } from 'state/store'

import { useFoxPageContext } from '../hooks/useFoxPageContext'
import type { Filter } from './FoxTokenFilterButton'
import { FoxTokenFilterButton } from './FoxTokenFilterButton'
import { RFOXSimulator } from './RFOXSimulator'

const hstackProps: StackProps = {
flexWrap: {
base: 'wrap',
md: 'nowrap',
},
}

const containerPaddingX = { base: 4, xl: 0 }
const columnsProps = {
base: 1,
Expand Down Expand Up @@ -83,26 +95,19 @@ export const RFOXSection = () => {
const translate = useTranslate()
const history = useHistory()
const isRFOXEnabled = useFeatureFlag('FoxPageRFOX')
const isRFOXLPEnabled = useFeatureFlag('RFOX_LP')
const { assetAccountNumber } = useFoxPageContext()
const { setStakingAssetAccountId } = useRFOXContext()
const stakingAssetId = foxOnArbitrumOneAssetId
const appDispatch = useAppDispatch()
const [stakingAssetId, setStakingAssetId] = useState(foxOnArbitrumOneAssetId)

const runeAsset = useAppSelector(state => selectAssetById(state, thorchainAssetId))

useEffect(() => {
appDispatch(marketApi.endpoints.findByAssetId.initiate(stakingAssetId))
}, [appDispatch, stakingAssetId])

const {
data: apy,
isLoading: isApyQueryLoading,
isFetching: isApyFetching,
} = useCurrentApyQuery({ stakingAssetId })

const isApyLoading = useMemo(() => {
return isApyQueryLoading || isApyFetching
}, [isApyQueryLoading, isApyFetching])
const currentApyQuery = useCurrentApyQuery({ stakingAssetId })

const accountIdsByAccountNumberAndChainId = useAppSelector(
selectAccountIdByAccountNumberAndChainId,
Expand All @@ -113,6 +118,26 @@ export const RFOXSection = () => {
selectMarketDataByAssetIdUserCurrency(state, thorchainAssetId),
)

const foxOnArbAsset = useAppSelector(state => selectAssetById(state, foxOnArbitrumOneAssetId))
const foxLpAsset = useAppSelector(state => selectAssetById(state, uniV2EthFoxArbitrumAssetId))

const filters = useMemo<Filter[]>(() => {
return [
{
label: foxOnArbAsset?.symbol ?? '',
chainId: foxOnArbAsset?.chainId,
assetId: foxOnArbitrumOneAssetId,
asset: foxOnArbAsset,
},
{
label: foxLpAsset?.symbol ?? '',
chainId: foxLpAsset?.chainId,
assetId: uniV2EthFoxArbitrumAssetId,
asset: foxLpAsset,
},
]
}, [foxLpAsset, foxOnArbAsset])

const stakingAssetAccountId = useMemo(() => {
const accountNumberAccountIds = accountIdsByAccountNumberAndChainId[assetAccountNumber]
const matchingAccountId = accountNumberAccountIds?.[fromAssetId(stakingAssetId).chainId]
Expand All @@ -139,56 +164,30 @@ export const RFOXSection = () => {
[stakingAsset],
)

const {
data: stakingBalanceCryptoPrecision,
isLoading: isStakingBalanceCryptoPrecisionQueryLoading,
isFetching: isStakingBalanceCryptoPrecisionFetching,
} = useStakingInfoQuery({
const stakingBalanceCryptoPrecisionQuery = useStakingInfoQuery({
stakingAssetId,
stakingAssetAccountAddress,
select: selectStakingBalanceCryptoPrecision,
})

const isStakingBalanceCryptoBaseUnitLoading = useMemo(() => {
return isStakingBalanceCryptoPrecisionQueryLoading || isStakingBalanceCryptoPrecisionFetching
}, [isStakingBalanceCryptoPrecisionQueryLoading, isStakingBalanceCryptoPrecisionFetching])
const currentEpochMetadataQuery = useCurrentEpochMetadataQuery()

const currentEpochMetadataResult = useCurrentEpochMetadataQuery()

const {
data: currentEpochRewardsCryptoBaseUnit,
isLoading: isCurrentEpochRewardsCryptoBaseUnitQueryLoading,
isFetching: isCurrentEpochRewardsCryptoBaseUnitFetching,
} = useCurrentEpochRewardsQuery({
const currentEpochRewardsQuery = useCurrentEpochRewardsQuery({
stakingAssetId,
stakingAssetAccountAddress,
currentEpochMetadata: currentEpochMetadataResult.data,
currentEpochMetadata: currentEpochMetadataQuery.data,
})

const currentEpochRewardsCryptoPrecision = useMemo(
() => fromBaseUnit(currentEpochRewardsCryptoBaseUnit?.toString(), runeAsset?.precision ?? 0),
[currentEpochRewardsCryptoBaseUnit, runeAsset?.precision],
() => fromBaseUnit(currentEpochRewardsQuery.data?.toString(), runeAsset?.precision ?? 0),
[currentEpochRewardsQuery.data, runeAsset?.precision],
)

const isCurrentEpochRewardsCryptoBaseUnitLoading = useMemo(() => {
return (
isCurrentEpochRewardsCryptoBaseUnitQueryLoading || isCurrentEpochRewardsCryptoBaseUnitFetching
)
}, [isCurrentEpochRewardsCryptoBaseUnitQueryLoading, isCurrentEpochRewardsCryptoBaseUnitFetching])

const {
data: lifetimeRewards,
isLoading: isLifetimeRewardsQueryLoading,
isFetching: isLifetimeRewardsFetching,
} = useLifetimeRewardsQuery({
const lifetimeRewardsQuery = useLifetimeRewardsQuery({
stakingAssetId,
stakingAssetAccountAddress,
})

const isLifetimeRewardsLoading = useMemo(() => {
return isLifetimeRewardsQueryLoading || isLifetimeRewardsFetching
}, [isLifetimeRewardsQueryLoading, isLifetimeRewardsFetching])

const {
data: timeInPoolHuman,
isLoading: isTimeInPoolQueryLoading,
Expand All @@ -200,28 +199,35 @@ export const RFOXSection = () => {
timeInPoolSeconds === 0n ? 'N/A' : formatSecondsToDuration(Number(timeInPoolSeconds)),
})

const handleSelectAssetId = useCallback((filter: Filter) => {
setStakingAssetId(filter.assetId ?? foxOnArbitrumOneAssetId)
}, [])

const isTimeInPoolLoading = useMemo(() => {
return isTimeInPoolQueryLoading || isTimeInPoolFetching
}, [isTimeInPoolQueryLoading, isTimeInPoolFetching])

const affiliateRevenueResult = useAffiliateRevenueQuery<string>({
startTimestamp: currentEpochMetadataResult.data?.epochStartTimestamp,
endTimestamp: currentEpochMetadataResult.data?.epochEndTimestamp,
const affiliateRevenueQuery = useAffiliateRevenueQuery<string>({
startTimestamp: currentEpochMetadataQuery.data?.epochStartTimestamp,
endTimestamp: currentEpochMetadataQuery.data?.epochEndTimestamp,
select: (totalRevenue: bigint) => {
return bn(fromBaseUnit(totalRevenue.toString(), runeAsset?.precision ?? 0))
.times(runeMarketData.price)
.toFixed(2)
},
})

const burn = useMemo(() => {
if (!affiliateRevenueResult.data) return
if (!currentEpochMetadataResult.data) return
const emissionsPoolUserCurrency = useMemo(() => {
if (!affiliateRevenueQuery.data) return
if (!currentEpochMetadataQuery.data) return

const distributionRate =
currentEpochMetadataQuery.data.distributionRateByStakingContract[
getStakingContract(stakingAssetId)
]

return bn(affiliateRevenueResult.data)
.times(currentEpochMetadataResult.data.burnRate)
.toFixed(2)
}, [affiliateRevenueResult.data, currentEpochMetadataResult.data])
return bn(affiliateRevenueQuery.data).times(distributionRate).toFixed(2)
}, [affiliateRevenueQuery, currentEpochMetadataQuery, stakingAssetId])

if (!(stakingAsset && runeAsset)) return null

Expand All @@ -236,20 +242,34 @@ export const RFOXSection = () => {
<Heading as='h2' fontSize='2xl' display='flex' alignItems='center'>
<RFOXIcon me={2} boxSize='32px' sx={rfoxIconStyles} />
{translate('RFOX.staking')}
<Skeleton isLoaded={!Boolean(isApyLoading)} ml={2}>
<Skeleton isLoaded={!currentApyQuery.isFetching} ml={2}>
<Tag colorScheme='green' verticalAlign='middle'>
<Amount.Percent value={apy ?? 0} suffix='APY' />
<Amount.Percent value={currentApyQuery.data ?? 0} suffix='APY' />
</Tag>
</Skeleton>
</Heading>
<Text fontSize='md' color='text.subtle' mt={2} translation='foxPage.rfox.whatIs' />
{isRFOXLPEnabled ? (
<ButtonGroup variant='transparent' mb={4} spacing={0} mt={2}>
<HStack spacing={1} p={1} borderRadius='md' {...hstackProps}>
{filters.map(filter => (
<FoxTokenFilterButton
key={filter.label}
onFilterClick={handleSelectAssetId}
filter={filter}
isSelected={stakingAssetId === filter.assetId}
asset={filter.asset}
/>
))}
</HStack>
</ButtonGroup>
) : null}
</Box>

<Card width='100%' maxWidth='400px'>
<CardBody py={4} px={4}>
<Text fontSize='md' color='text.subtle' translation='RFOX.pendingRewardsBalance' />

<Skeleton isLoaded={!Boolean(isCurrentEpochRewardsCryptoBaseUnitLoading)}>
<Skeleton isLoaded={!currentEpochRewardsQuery.isFetching}>
<Amount.Crypto
value={currentEpochRewardsCryptoPrecision}
symbol={runeAsset.symbol ?? ''}
Expand All @@ -269,10 +289,10 @@ export const RFOXSection = () => {
translation='defi.stakingBalance'
mb={1}
/>
<Skeleton isLoaded={!isStakingBalanceCryptoBaseUnitLoading}>
<Skeleton isLoaded={!stakingBalanceCryptoPrecisionQuery.isFetching}>
<Amount.Crypto
fontSize='2xl'
value={stakingBalanceCryptoPrecision}
value={stakingBalanceCryptoPrecisionQuery.data}
symbol={stakingAsset.symbol ?? ''}
/>
</Skeleton>
Expand All @@ -290,10 +310,13 @@ export const RFOXSection = () => {
translation='RFOX.lifetimeRewards'
mb={1}
/>
<Skeleton isLoaded={!Boolean(isLifetimeRewardsLoading)}>
<Skeleton isLoaded={!Boolean(lifetimeRewardsQuery.isFetching)}>
<Amount.Crypto
fontSize='2xl'
value={fromBaseUnit(lifetimeRewards?.toString(), runeAsset.precision ?? 0)}
value={fromBaseUnit(
lifetimeRewardsQuery.data?.toString(),
runeAsset.precision ?? 0,
)}
symbol={runeAsset.symbol ?? ''}
/>
</Skeleton>
Expand All @@ -317,11 +340,18 @@ export const RFOXSection = () => {
fontSize='md'
color='text.subtle'
fontWeight='medium'
translation='foxPage.rfox.totalFoxBurn'
// we need to pass a local scope arg here, so we need an anonymous function wrapper
// eslint-disable-next-line react-memo/require-usememo
translation={[
'foxPage.rfox.totalSymbolBurn',
{
symbol: stakingAsset.symbol,
},
]}
mb={1}
/>
<Skeleton isLoaded={Boolean(burn)}>
<Amount.Fiat fontSize='2xl' value={burn} suffix='🔥 _ 🔥' />
<Skeleton isLoaded={Boolean(emissionsPoolUserCurrency)}>
<Amount.Fiat fontSize='2xl' value={emissionsPoolUserCurrency} suffix='🔥 _ 🔥' />
</Skeleton>
</Stack>
</SimpleGrid>
Expand Down
Loading

0 comments on commit fda8993

Please sign in to comment.