diff --git a/.env.base b/.env.base index 82afa800b1c..331de5c7eee 100644 --- a/.env.base +++ b/.env.base @@ -42,6 +42,7 @@ REACT_APP_FEATURE_THORCHAIN_LP_WITHDRAW=true REACT_APP_FEATURE_ACCOUNT_MANAGEMENT=true REACT_APP_FEATURE_ACCOUNT_MANAGEMENT_LEDGER=true REACT_APP_FEATURE_RFOX=false +REACT_APP_FEATURE_RFOX_REWARDS_TAB=false REACT_APP_FEATURE_ARBITRUM_BRIDGE=false # absolute URL prefix diff --git a/.env.dev b/.env.dev index 635390ee530..a331dd2d1ef 100644 --- a/.env.dev +++ b/.env.dev @@ -1,5 +1,6 @@ # feature flags REACT_APP_FEATURE_RFOX=true +REACT_APP_FEATURE_RFOX_REWARDS_TAB=true REACT_APP_FEATURE_ARBITRUM_BRIDGE=true # logging diff --git a/.env.develop b/.env.develop index bfa977dda43..65a59c83015 100644 --- a/.env.develop +++ b/.env.develop @@ -1,6 +1,7 @@ # feature flags REACT_APP_FEATURE_CHATWOOT=true REACT_APP_FEATURE_RFOX=true +REACT_APP_FEATURE_RFOX_REWARDS_TAB=true REACT_APP_FEATURE_ARBITRUM_BRIDGE=true # mixpanel diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index 45c38e33939..3bc6bee024d 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -2430,12 +2430,15 @@ } }, "RFOX": { + "staking": "rFOX Staking", "bridge": "Bridge", "bridgeFunds": "Bridge Funds", "bridgeSuccess": "Your funds have been successfully bridged. You can now continue to staking.", "stake": "Stake", "unstake": "Unstake", "claim": "Claim", + "rewards": "Rewards", + "claims": "Claims", "changeAddress": "Change address", "sameAddressNotAllowed": "Same address not allowed", "stakingDetails": "Staking Details", @@ -2461,7 +2464,7 @@ "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.", - "unstakePendingCooldown": "Claim available %{cooldownPeriodHuman}", + "unstakePendingCooldown": "Claim available in %{cooldownPeriodHuman}", "cooldownComplete": "Cooldown finished %{cooldownPeriodHuman}" }, "stakeSuccess": "You have successfully staked %{amount} %{symbol}", @@ -2490,6 +2493,36 @@ "bridgeAndConfirm": "Bridge & Confirm", "noSupportedChains": "Chain not supported", "noSupportedChainsDescription": "Add %{chainLabel} or connect to a wallet that supports it.", - "confirmAndBridge": "Confirm & Bridge" + "confirmAndBridge": "Confirm & Bridge", + "myRewardBalance": "My Reward Balance", + "myRewardBalanceHelper": "My Reward Balance", + "pendingRewardsBalance": "Pending Rewards Balance", + "pendingRewardsBalanceHelper": "Pending Rewards Balance", + "lifetimeRewards": "Lifetime Rewards", + "lifetimeRewardsHelper": "Lifetime Rewards", + "timeInPool": "Time in Pool", + "timeInPoolHelper": "Time in Pool", + "myPosition": "My Position", + "faq": { + "title": "FAQ", + "what": { + "title": "What is rFOX?", + "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + }, + "why": { + "title": "Why stake your FOX?", + "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + }, + "cooldown": { + "title": "What is the cooldown period?", + "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + } + }, + "totals": "rFOX Totals", + "totalStaked": "Total FOX Staked", + "totalFeesCollected": "Total Fees Collected (This Epoch)", + "emissionsPool": "Emissions Pool (This Epoch)", + "emissionsPoolHelper": "Emissions Pool (This Epoch)", + "foxBurnAmount": "FOX Burn Amount" } } diff --git a/src/components/Layout/Header/NavBar/Notifications.tsx b/src/components/Layout/Header/NavBar/Notifications.tsx index 2efdd064528..56c3c844b7a 100644 --- a/src/components/Layout/Header/NavBar/Notifications.tsx +++ b/src/components/Layout/Header/NavBar/Notifications.tsx @@ -83,7 +83,7 @@ export const Notifications = memo(() => { chainId: ethChainId, wallet, isSnapInstalled, - chainAccountIds: ethAccountIds, + checkConnectedAccountIds: ethAccountIds, }), [ethAccountIds, isSnapInstalled, wallet], ) diff --git a/src/components/MultiHopTrade/components/VerifyAddresses/VerifyAddresses.tsx b/src/components/MultiHopTrade/components/VerifyAddresses/VerifyAddresses.tsx index 9a4c8295a6b..2570917073a 100644 --- a/src/components/MultiHopTrade/components/VerifyAddresses/VerifyAddresses.tsx +++ b/src/components/MultiHopTrade/components/VerifyAddresses/VerifyAddresses.tsx @@ -92,7 +92,7 @@ export const VerifyAddresses = () => { chainId: buyAsset.chainId, wallet, isSnapInstalled: false, - chainAccountIds: buyAccountIds, + checkConnectedAccountIds: buyAccountIds, })), [ maybeManualReceiveAddress, diff --git a/src/components/MultiHopTrade/hooks/useSupportedAssets.tsx b/src/components/MultiHopTrade/hooks/useSupportedAssets.tsx index 305eb6c1021..d92f78b250e 100644 --- a/src/components/MultiHopTrade/hooks/useSupportedAssets.tsx +++ b/src/components/MultiHopTrade/hooks/useSupportedAssets.tsx @@ -20,7 +20,12 @@ export const useSupportedAssets = () => { return { walletSupportedChainIds: knownChainIds.filter(chainId => { const chainAccountIds = accountIdsByChainId[chainId] ?? [] - return walletSupportsChain({ chainId, wallet, isSnapInstalled, chainAccountIds }) + return walletSupportsChain({ + chainId, + wallet, + isSnapInstalled, + checkConnectedAccountIds: chainAccountIds, + }) }), sortedAssetIds: sortedAssets.map(asset => asset.assetId), } diff --git a/src/components/StakingVaults/PositionTable.tsx b/src/components/StakingVaults/PositionTable.tsx index 1f6df51ac7d..61c57a3b3a4 100644 --- a/src/components/StakingVaults/PositionTable.tsx +++ b/src/components/StakingVaults/PositionTable.tsx @@ -102,7 +102,7 @@ export const PositionTable: React.FC = ({ positions.filter(position => { const chainAccountIds = accountIdsByChainId[fromAssetId(position.assetId).chainId] ?? [] return walletSupportsChain({ - chainAccountIds, + checkConnectedAccountIds: chainAccountIds, chainId: fromAssetId(position.assetId).chainId, wallet, isSnapInstalled, diff --git a/src/components/StakingVaults/ProviderCard.tsx b/src/components/StakingVaults/ProviderCard.tsx index 1135a1cc222..006521418fe 100644 --- a/src/components/StakingVaults/ProviderCard.tsx +++ b/src/components/StakingVaults/ProviderCard.tsx @@ -74,7 +74,12 @@ export const ProviderCard: React.FC = ({ return ( staking.includes(e.id as OpportunityId) && - walletSupportsChain({ chainId: e.chainId, wallet, isSnapInstalled, chainAccountIds }) + walletSupportsChain({ + chainId: e.chainId, + wallet, + isSnapInstalled, + checkConnectedAccountIds: chainAccountIds, + }) ) }), [accountIdsByChainId, isSnapInstalled, staking, stakingOpportunities, wallet], @@ -89,7 +94,12 @@ export const ProviderCard: React.FC = ({ return ( lp.includes(e.assetId as OpportunityId) && - walletSupportsChain({ chainId: e.chainId, wallet, isSnapInstalled, chainAccountIds }) + walletSupportsChain({ + chainId: e.chainId, + wallet, + isSnapInstalled, + checkConnectedAccountIds: chainAccountIds, + }) ) }), [accountIdsByChainId, isSnapInstalled, lp, lpOpportunities, wallet], diff --git a/src/config.ts b/src/config.ts index df19a8f9e8b..0b7776913d3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -176,6 +176,7 @@ const validators = { REACT_APP_FEATURE_ACCOUNT_MANAGEMENT: bool({ default: false }), REACT_APP_FEATURE_ACCOUNT_MANAGEMENT_LEDGER: bool({ default: false }), REACT_APP_FEATURE_RFOX: bool({ default: false }), + REACT_APP_FEATURE_RFOX_REWARDS_TAB: bool({ default: false }), } function reporter({ errors }: envalid.ReporterOptions) { diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index c04ed1da975..017869c2f49 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -17,7 +17,7 @@ import { useIsSnapInstalled } from 'hooks/useIsSnapInstalled/useIsSnapInstalled' import { useMixpanelPortfolioTracking } from 'hooks/useMixpanelPortfolioTracking/useMixpanelPortfolioTracking' import { useRouteAssetId } from 'hooks/useRouteAssetId/useRouteAssetId' import { useWallet } from 'hooks/useWallet/useWallet' -import { walletDeviceSupportsChain } from 'hooks/useWalletSupportsChain/useWalletSupportsChain' +import { walletSupportsChain } from 'hooks/useWalletSupportsChain/useWalletSupportsChain' import { deriveAccountIdsAndMetadata } from 'lib/account/account' import { isUtxoChainId } from 'lib/utils/utxo' import { snapshotApi } from 'state/apis/snapshot/snapshot' @@ -92,7 +92,12 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { useEffect(() => { if (!wallet) return const walletSupportedChainIds = supportedChains.filter(chainId => { - return walletDeviceSupportsChain({ chainId, wallet, isSnapInstalled }) + return walletSupportsChain({ + chainId, + wallet, + isSnapInstalled, + checkConnectedAccountIds: false, // don't check connected account ids, we're detecting initial runtime support for chains + }) }) dispatch(portfolio.actions.setWalletSupportedChainIds(walletSupportedChainIds)) }, [accountIdsByChainId, dispatch, isSnapInstalled, wallet, supportedChains]) @@ -102,8 +107,13 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { // Skip if wallet is ledger, or if wallet is already connected to accounts - prevents this overriding user selection in account management if (!wallet || isLedger(wallet) || requestedAccountIds.length > 0) return ;(async () => { - let chainIds = Array.from(supportedChains).filter(chainId => { - return walletDeviceSupportsChain({ chainId, wallet, isSnapInstalled }) + let chainIds = supportedChains.filter(chainId => { + return walletSupportsChain({ + chainId, + wallet, + isSnapInstalled, + checkConnectedAccountIds: false, // don't check connected account ids, we're detecting runtime support for chains + }) }) const accountMetadataByAccountId: AccountMetadataById = {} diff --git a/src/features/defi/providers/thorchain-savers/components/ThorchainSaversManager/Overview/ThorchainSaversOverview.tsx b/src/features/defi/providers/thorchain-savers/components/ThorchainSaversManager/Overview/ThorchainSaversOverview.tsx index ca56131408e..c206c008ad7 100644 --- a/src/features/defi/providers/thorchain-savers/components/ThorchainSaversManager/Overview/ThorchainSaversOverview.tsx +++ b/src/features/defi/providers/thorchain-savers/components/ThorchainSaversManager/Overview/ThorchainSaversOverview.tsx @@ -375,7 +375,7 @@ export const ThorchainSaversOverview: React.FC = ({ const handleThorchainSaversEmptyClick = useCallback(() => setHideEmptyState(true), []) - if ((!earnOpportunityData && maybeAccountId) || isTradingActiveLoading) { + if ((!earnOpportunityData?.isLoaded && maybeAccountId) || isTradingActiveLoading) { return (
diff --git a/src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts b/src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts index 152b421790e..4488948a50f 100644 --- a/src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts +++ b/src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts @@ -39,22 +39,25 @@ import { selectAccountIdsByChainIdFilter } from 'state/slices/portfolioSlice/sel import { selectFeatureFlag } from 'state/slices/selectors' import { store, useAppSelector } from 'state/store' -type WalletDeviceSupportsChainArgs = { +type WalletSupportsChainArgs = { isSnapInstalled: boolean | null chainId: ChainId wallet: HDWallet | null + // The connected account ids to check against. If set to false, the currently connected account + // ids will not be checked (used for initial boot) + checkConnectedAccountIds: AccountId[] | false } -type WalletSupportsChainArgs = { - chainAccountIds: AccountId[] // allows dynamic chain-support detection for Ledger -} & WalletDeviceSupportsChainArgs - // use outside react -export const walletDeviceSupportsChain = ({ +export const walletSupportsChain = ({ chainId, wallet, isSnapInstalled, -}: WalletDeviceSupportsChainArgs): boolean => { + checkConnectedAccountIds, +}: WalletSupportsChainArgs): boolean => { + // If the user has no connected chain account ids, the user can't use it to interact with the chain + if (checkConnectedAccountIds !== false && !checkConnectedAccountIds.length) return false + if (!wallet) return false // A wallet may have feature-capabilities for a chain, but not have runtime support for it // e.g MM without snaps installed @@ -107,22 +110,6 @@ export const walletDeviceSupportsChain = ({ } } -export const walletSupportsChain = ({ - chainId, - chainAccountIds, - wallet, - isSnapInstalled, -}: WalletSupportsChainArgs): boolean => { - // If the user has no connected chain account ids, the user can't use it to interact with the chain - if (!chainAccountIds.length) return false - - return walletDeviceSupportsChain({ - chainId, - wallet, - isSnapInstalled, - }) -} - export const useWalletSupportsChain = ( chainId: ChainId, wallet: HDWallet | null, @@ -140,7 +127,12 @@ export const useWalletSupportsChain = ( ) const result = useMemo(() => { - return walletSupportsChain({ isSnapInstalled, chainId, wallet, chainAccountIds }) + return walletSupportsChain({ + isSnapInstalled, + chainId, + wallet, + checkConnectedAccountIds: chainAccountIds, + }) }, [chainAccountIds, chainId, isSnapInstalled, wallet]) return result diff --git a/src/pages/Lending/hooks/useLendingSupportedAssets/index.ts b/src/pages/Lending/hooks/useLendingSupportedAssets/index.ts index e5f864d9051..764a77d5b43 100644 --- a/src/pages/Lending/hooks/useLendingSupportedAssets/index.ts +++ b/src/pages/Lending/hooks/useLendingSupportedAssets/index.ts @@ -39,7 +39,12 @@ export const useLendingSupportedAssets = ({ type }: { type: 'collateral' | 'borr () => knownChainIds.filter(chainId => { const chainAccountIds = accountIdsByChainId[chainId] ?? [] - return walletSupportsChain({ chainId, wallet, isSnapInstalled, chainAccountIds }) + return walletSupportsChain({ + chainId, + wallet, + isSnapInstalled, + checkConnectedAccountIds: chainAccountIds, + }) }), [accountIdsByChainId, isSnapInstalled, wallet], ) diff --git a/src/pages/RFOX/RFOX.tsx b/src/pages/RFOX/RFOX.tsx index b138056c0a8..e8ea1486a06 100644 --- a/src/pages/RFOX/RFOX.tsx +++ b/src/pages/RFOX/RFOX.tsx @@ -1,120 +1,34 @@ -import { Button, Card, Center, Flex, TabPanel, TabPanels, Tabs } from '@chakra-ui/react' -import type { PropsWithChildren } from 'react' -import { useCallback, useMemo, useState } from 'react' +import type { StackDirection } from '@chakra-ui/react' +import { Heading, Stack } from '@chakra-ui/react' import { useTranslate } from 'react-polyglot' import { Main } from 'components/Layout/Main' -import { ChangeAddress } from './components/ChangeAddress/ChangeAddress' -import { Claim } from './components/Claim/Claim' -import { Stake } from './components/Stake/Stake' -import { Unstake } from './components/Unstake/Unstake' +import { Faq } from './components/Faq/Faq' +import { Overview } from './components/Overview/Overview' +import { RewardsAndClaims } from './components/RewardsAndClaims/RewardsAndClaims' +import { Widget } from './Widget' -type FormHeaderTabProps = { - index: number - onClick: (index: number) => void - isActive?: boolean -} & PropsWithChildren - -const activeStyle = { color: 'text.base' } - -export const RfoxTabIndex = { - Stake: 0, - Unstake: 1, - Claim: 2, - ChangeAddress: 3, -} - -const FormHeaderTab: React.FC = ({ index, onClick, isActive, children }) => { - const handleClick = useCallback(() => { - onClick(index) - }, [index, onClick]) - return ( - - ) -} -type FormHeaderProps = { - setStepIndex: (index: number) => void - activeIndex: number -} -const FormHeader: React.FC = ({ setStepIndex, activeIndex }) => { - const translate = useTranslate() - const handleClick = useCallback( - (index: number) => { - setStepIndex(index) - }, - [setStepIndex], - ) - return ( - - - {translate('RFOX.stake')} - - - {translate('RFOX.unstake')} - - - {translate('RFOX.claim')} - - - {translate('RFOX.changeAddress')} - - - ) -} +const direction: StackDirection = { base: 'column-reverse', xl: 'row' } +const maxWidth = { base: 'full', lg: 'full', xl: 'sm' } +const paddingVerticalResponsiveProps = { base: 8, md: 16 } export const RFOX: React.FC = () => { - const [stepIndex, setStepIndex] = useState(RfoxTabIndex.Stake) + const translate = useTranslate() - const TabHeader = useMemo( - () => , - [stepIndex], - ) return ( -
-
- - - - - - - - - - - - - - - - - - -
+
+ {translate('RFOX.staking')} + + + + + + + + + + +
) } diff --git a/src/pages/RFOX/Widget.tsx b/src/pages/RFOX/Widget.tsx new file mode 100644 index 00000000000..cdb86ce36d9 --- /dev/null +++ b/src/pages/RFOX/Widget.tsx @@ -0,0 +1,115 @@ +import { Button, Card, Flex, TabPanel, TabPanels, Tabs } from '@chakra-ui/react' +import type { PropsWithChildren } from 'react' +import { useCallback, useMemo, useState } from 'react' +import { useTranslate } from 'react-polyglot' + +import { ChangeAddress } from './components/ChangeAddress/ChangeAddress' +import { Claim } from './components/Claim/Claim' +import { Stake } from './components/Stake/Stake' +import { Unstake } from './components/Unstake/Unstake' + +type FormHeaderTabProps = { + index: number + onClick: (index: number) => void + isActive?: boolean +} & PropsWithChildren + +const activeStyle = { color: 'text.base' } + +export const RfoxTabIndex = { + Stake: 0, + Unstake: 1, + Claim: 2, + ChangeAddress: 3, +} + +const FormHeaderTab: React.FC = ({ index, onClick, isActive, children }) => { + const handleClick = useCallback(() => { + onClick(index) + }, [index, onClick]) + return ( + + ) +} +type FormHeaderProps = { + setStepIndex: (index: number) => void + activeIndex: number +} +const FormHeader: React.FC = ({ setStepIndex, activeIndex }) => { + const translate = useTranslate() + const handleClick = useCallback( + (index: number) => { + setStepIndex(index) + }, + [setStepIndex], + ) + return ( + + + {translate('RFOX.stake')} + + + {translate('RFOX.unstake')} + + + {translate('RFOX.claim')} + + + {translate('RFOX.changeAddress')} + + + ) +} + +export const Widget: React.FC = () => { + const [stepIndex, setStepIndex] = useState(RfoxTabIndex.Stake) + + const TabHeader = useMemo( + () => , + [stepIndex], + ) + return ( + + + + + + + + + + + + + + + + + + + ) +} diff --git a/src/pages/RFOX/components/Claim/ClaimConfirm.tsx b/src/pages/RFOX/components/Claim/ClaimConfirm.tsx index d56b342f466..016b6c2b350 100644 --- a/src/pages/RFOX/components/Claim/ClaimConfirm.tsx +++ b/src/pages/RFOX/components/Claim/ClaimConfirm.tsx @@ -37,7 +37,6 @@ import { import { selectAccountNumberByAccountId, selectAssetById, - selectFeeAssetByChainId, selectMarketDataByAssetIdUserCurrency, selectTxById, } from 'state/slices/selectors' @@ -65,14 +64,6 @@ export const ClaimConfirm: FC & ClaimCo const translate = useTranslate() const wallet = useWallet().state.wallet - const feeAsset = useAppSelector(state => - selectFeeAssetByChainId(state, fromAssetId(claimQuote.stakingAssetId).chainId), - ) - - const feeAssetMarketDataUserCurrency = useAppSelector(state => - selectMarketDataByAssetIdUserCurrency(state, feeAsset?.assetId ?? ''), - ) - const handleGoBack = useCallback(() => { history.push(ClaimRoutePaths.Select) }, [history]) @@ -157,27 +148,7 @@ export const ClaimConfirm: FC & ClaimCo }, }) - const isGetClaimFeesEnabled = useMemo( - () => - Boolean( - isClaimMutationIdle && - stakingAssetAccountNumber !== undefined && - wallet && - stakingAsset && - callData && - feeAsset && - feeAssetMarketDataUserCurrency, - ), - [ - isClaimMutationIdle, - stakingAssetAccountNumber, - wallet, - stakingAsset, - callData, - feeAsset, - feeAssetMarketDataUserCurrency, - ], - ) + const isGetClaimFeesEnabled = useMemo(() => Boolean(isClaimMutationIdle), [isClaimMutationIdle]) const claimFeesQueryInput = useMemo( () => ({ diff --git a/src/pages/RFOX/components/Claim/ClaimRow.tsx b/src/pages/RFOX/components/Claim/ClaimRow.tsx index 387b5c93a61..08fa1e80028 100644 --- a/src/pages/RFOX/components/Claim/ClaimRow.tsx +++ b/src/pages/RFOX/components/Claim/ClaimRow.tsx @@ -1,8 +1,8 @@ -import { Box, Button, Flex, Tooltip } from '@chakra-ui/react' +import { Box, Button, Flex, Tooltip, useMediaQuery } from '@chakra-ui/react' import { type AssetId, foxAssetId, foxOnArbitrumOneAssetId } from '@shapeshiftoss/caip' import { bnOrZero } from '@shapeshiftoss/chain-adapters' import { TransferType } from '@shapeshiftoss/unchained-client' -import { type FC, useCallback } from 'react' +import { type FC, useCallback, useMemo } from 'react' import { useTranslate } from 'react-polyglot' import { useHistory } from 'react-router' import { Amount } from 'components/Amount/Amount' @@ -12,6 +12,7 @@ import { TransactionTypeIcon } from 'components/TransactionHistory/TransactionTy import { toBaseUnit } from 'lib/math' import { selectAssetById, selectFirstAccountIdByChainId } from 'state/slices/selectors' import { useAppSelector } from 'state/store' +import { breakpoints } from 'theme/theme' import { ClaimRoutePaths, ClaimStatus, type RfoxClaimQuote } from './types' @@ -22,7 +23,16 @@ type ClaimRowProps = { setConfirmedQuote: (quote: RfoxClaimQuote) => void cooldownPeriodHuman: string index: number -} +} & ( + | { + displayClaimButton: boolean + actionDescription: string + } + | { + displayClaimButton?: never + actionDescription?: never + } +) const hoverProps = { bg: 'gray.700' } @@ -33,9 +43,12 @@ export const ClaimRow: FC = ({ setConfirmedQuote, cooldownPeriodHuman, index, + displayClaimButton, + actionDescription, }) => { const translate = useTranslate() const history = useHistory() + const [isLargerThanMd] = useMediaQuery(`(min-width: ${breakpoints['md']})`) const stakingAsset = useAppSelector(state => selectAssetById(state, stakingAssetId)) const stakingAssetSymbol = stakingAsset?.symbol @@ -61,6 +74,32 @@ export const ClaimRow: FC = ({ history.push(ClaimRoutePaths.Confirm) }, [history, index, setConfirmedQuote, stakingAmountCryptoBaseUnit, stakingAssetAccountId]) + const parentProps = useMemo(() => { + if (displayClaimButton) return {} + + return { + variant: 'unstyled', + as: Button, + isDisabled: status !== ClaimStatus.Available, + onClick: handleClaimClick, + _hover: hoverProps, + } + }, [displayClaimButton, status, handleClaimClick]) + + const statusText = useMemo(() => { + if (displayClaimButton && status === ClaimStatus.CoolingDown) + return isLargerThanMd + ? translate('RFOX.tooltips.unstakePendingCooldown', { cooldownPeriodHuman }) + : cooldownPeriodHuman + return translate('RFOX.tooltips.cooldownComplete', { cooldownPeriodHuman }) + }, [cooldownPeriodHuman, isLargerThanMd, displayClaimButton, status, translate]) + + const actionTranslation = useMemo(() => { + if (!stakingAssetSymbol) return + + return [displayClaimButton ? actionDescription : 'RFOX.claim', { stakingAssetSymbol }] + }, [displayClaimButton, actionDescription, stakingAssetSymbol]) + return ( = ({ : 'RFOX.tooltips.unstakePendingCooldown', { cooldownPeriodHuman }, )} + isDisabled={displayClaimButton} > - + - + - - {translate('RFOX.claim', { assetSymbol: stakingAssetSymbol })} - + {actionTranslation && ( + + {translate(...actionTranslation)} + + )} {stakingAssetSymbol} - + = ({ color={status === ClaimStatus.Available ? 'green.300' : 'yellow.300'} align={'end'} > - {status} + {statusText} + {displayClaimButton && ( + + )} diff --git a/src/pages/RFOX/components/Claim/ClaimSelect.tsx b/src/pages/RFOX/components/Claim/ClaimSelect.tsx index 2bab595d767..50ce1b439b5 100644 --- a/src/pages/RFOX/components/Claim/ClaimSelect.tsx +++ b/src/pages/RFOX/components/Claim/ClaimSelect.tsx @@ -10,16 +10,16 @@ import { RFOX_PROXY_CONTRACT_ADDRESS } from 'contracts/constants' import dayjs from 'dayjs' import { type FC, useCallback, useEffect, useMemo } from 'react' import { useTranslate } from 'react-polyglot' -import type { Address } from 'viem' import { getAddress } from 'viem' import { arbitrum } from 'viem/chains' -import { useReadContract, useReadContracts } from 'wagmi' +import { useReadContracts } from 'wagmi' import { AssetIcon } from 'components/AssetIcon' import { SlideTransition } from 'components/SlideTransition' import { RawText, Text } from 'components/Text' import { fromBaseUnit } from 'lib/math' import { chainIdToChainDisplayName } from 'lib/utils' -import { RfoxTabIndex } from 'pages/RFOX/RFOX' +import { useGetUnstakingRequestCountQuery } from 'pages/RFOX/hooks/useGetUnstakingRequestCountQuery' +import { RfoxTabIndex } from 'pages/RFOX/Widget' import { selectAssetById, selectFirstAccountIdByChainId } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -99,16 +99,7 @@ export const ClaimSelect: FC = ({ isSuccess: isUnstakingRequestCountSuccess, isLoading: isUnstakingRequestCountLoading, refetch: refetchUnstakingRequestCount, - } = 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), - }, - }) + } = useGetUnstakingRequestCountQuery({ stakingAssetAccountAddress }) const hasClaims = useMemo( () => diff --git a/src/pages/RFOX/components/Faq/Faq.tsx b/src/pages/RFOX/components/Faq/Faq.tsx new file mode 100644 index 00000000000..a27a238a776 --- /dev/null +++ b/src/pages/RFOX/components/Faq/Faq.tsx @@ -0,0 +1,47 @@ +import { + Accordion, + AccordionButton, + AccordionItem, + AccordionPanel, + Card, + CardBody, +} from '@chakra-ui/react' +import { useMemo } from 'react' +import { Text } from 'components/Text' + +export const Faq = () => { + const defaultIndex = useMemo(() => [0], []) + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/src/pages/RFOX/components/Overview/Overview.tsx b/src/pages/RFOX/components/Overview/Overview.tsx new file mode 100644 index 00000000000..3e47890ab8d --- /dev/null +++ b/src/pages/RFOX/components/Overview/Overview.tsx @@ -0,0 +1,35 @@ +import { Card, CardBody, CardHeader, Flex } from '@chakra-ui/react' +import { foxAssetId } from '@shapeshiftoss/caip' +import { AssetIcon } from 'components/AssetIcon' +import { RawText } from 'components/Text' + +import { StakingInfo } from './StakingInfo' +import { Stats } from './Stats' + +export const Overview: React.FC = () => { + const stakingAssetId = foxAssetId + + return ( + + + + + + + 0.00 FOX + + + + + + + + + + ) +} diff --git a/src/pages/RFOX/components/Overview/StakingInfo.tsx b/src/pages/RFOX/components/Overview/StakingInfo.tsx new file mode 100644 index 00000000000..2a40ed2d230 --- /dev/null +++ b/src/pages/RFOX/components/Overview/StakingInfo.tsx @@ -0,0 +1,43 @@ +import { Box, SimpleGrid } from '@chakra-ui/react' +import { thorchainAssetId } from '@shapeshiftoss/caip' +import { Text } from 'components/Text' + +import { StakingInfoItem } from './StakingInfoItem' + +const gridColumns = { base: 1, md: 2 } + +export const StakingInfo: React.FC = () => { + const assetId = thorchainAssetId + + return ( + + + + + + + + + + + ) +} diff --git a/src/pages/RFOX/components/Overview/StakingInfoItem.tsx b/src/pages/RFOX/components/Overview/StakingInfoItem.tsx new file mode 100644 index 00000000000..31b0ca59377 --- /dev/null +++ b/src/pages/RFOX/components/Overview/StakingInfoItem.tsx @@ -0,0 +1,87 @@ +import { Flex, Skeleton, Stack } from '@chakra-ui/react' +import { type AssetId } from '@shapeshiftoss/caip' +import { bnOrZero } from '@shapeshiftoss/chain-adapters' +import { useMemo } from 'react' +import { useTranslate } from 'react-polyglot' +import { Amount } from 'components/Amount/Amount' +import { HelperTooltip } from 'components/HelperTooltip/HelperTooltip' +import { Text } from 'components/Text' +import { fromBaseUnit } from 'lib/math' +import { selectAssetById, selectMarketDataByAssetIdUserCurrency } from 'state/slices/selectors' +import { useAppSelector } from 'state/store' + +type StakingInfoItemProps = { + informationDescription: string + helperTranslation?: string + value?: string +} & ( + | { + assetId: AssetId + amountCryptoBaseUnit: string + } + | { + assetId?: never + amountCryptoBaseUnit?: never + } +) + +export const StakingInfoItem = ({ + informationDescription, + assetId, + helperTranslation, + value, + amountCryptoBaseUnit, +}: StakingInfoItemProps) => { + const translate = useTranslate() + const asset = useAppSelector(state => selectAssetById(state, assetId ?? '')) + + const marketDataUserCurrency = useAppSelector(state => + selectMarketDataByAssetIdUserCurrency(state, assetId ?? ''), + ) + + const amountUserCurrency = useMemo( + () => + amountCryptoBaseUnit && marketDataUserCurrency && asset + ? bnOrZero(fromBaseUnit(amountCryptoBaseUnit, asset.precision)) + .times(marketDataUserCurrency.price) + .toFixed(2) + : undefined, + [amountCryptoBaseUnit, marketDataUserCurrency, asset], + ) + + const helperIconProps = useMemo(() => { + return { boxSize: !helperTranslation ? 0 : undefined } + }, [helperTranslation]) + + return ( + + + + + + + {asset && amountCryptoBaseUnit && ( + + )} + {value && } + + + + {amountUserCurrency && ( + + + + )} + + ) +} diff --git a/src/pages/RFOX/components/Overview/StatItem.tsx b/src/pages/RFOX/components/Overview/StatItem.tsx new file mode 100644 index 00000000000..40b2730e77e --- /dev/null +++ b/src/pages/RFOX/components/Overview/StatItem.tsx @@ -0,0 +1,76 @@ +import { ArrowDownIcon, ArrowUpIcon } from '@chakra-ui/icons' +import { Skeleton, Stack, Tag } from '@chakra-ui/react' +import { bnOrZero } from '@shapeshiftoss/chain-adapters' +import { useMemo } from 'react' +import { useTranslate } from 'react-polyglot' +import { Amount } from 'components/Amount/Amount' +import { HelperTooltip } from 'components/HelperTooltip/HelperTooltip' +import { Text } from 'components/Text' + +type StatItemProps = { + description: string + amountUserCurrency?: string + percentChangeDecimal?: string + helperTranslation?: string +} + +type ChangeTagProps = { + value: string +} + +// @TODO: This is used in both pool and here, make it reusable +const ChangeTag: React.FC = ({ value }) => { + const icon = bnOrZero(value).isGreaterThanOrEqualTo(0) ? : + const color = bnOrZero(value).isGreaterThanOrEqualTo(0) ? 'green.500' : 'red.500' + const colorScheme = bnOrZero(value).isGreaterThanOrEqualTo(0) ? 'green' : 'red' + return ( + + + {icon} + + + + ) +} + +const helperToolTipFlexProps = { + alignItems: 'center', + gap: 2, +} + +export const StatItem = ({ + helperTranslation, + description, + amountUserCurrency, + percentChangeDecimal, +}: StatItemProps) => { + const translate = useTranslate() + + const helperIconProps = useMemo(() => { + return { boxSize: !helperTranslation ? 0 : undefined } + }, [helperTranslation]) + + const valueChangeTag: JSX.Element | null = useMemo(() => { + if (!percentChangeDecimal) return null + + return + }, [percentChangeDecimal]) + + return ( + + + + {amountUserCurrency && ( + + )} + {valueChangeTag} + + + + + ) +} diff --git a/src/pages/RFOX/components/Overview/Stats.tsx b/src/pages/RFOX/components/Overview/Stats.tsx new file mode 100644 index 00000000000..aaf14489ab7 --- /dev/null +++ b/src/pages/RFOX/components/Overview/Stats.tsx @@ -0,0 +1,43 @@ +import { Box, Flex, SimpleGrid, Skeleton, Tag } from '@chakra-ui/react' +import { Amount } from 'components/Amount/Amount' +import { Text } from 'components/Text' + +import { StatItem } from './StatItem' + +const gridColumns = { base: 1, md: 2 } + +export const Stats: React.FC = () => { + return ( + + + + + + ~ + + + + + + + + + + + + + ) +} diff --git a/src/pages/RFOX/components/RewardsAndClaims/Claims.tsx b/src/pages/RFOX/components/RewardsAndClaims/Claims.tsx new file mode 100644 index 00000000000..15af270c29d --- /dev/null +++ b/src/pages/RFOX/components/RewardsAndClaims/Claims.tsx @@ -0,0 +1,50 @@ +import { Box, CardBody } from '@chakra-ui/react' +import { foxAssetId } from '@shapeshiftoss/caip' +import { useCallback } from 'react' +import { useTranslate } from 'react-polyglot' + +import { ClaimRow } from '../Claim/ClaimRow' +import { ClaimStatus } from '../Claim/types' + +type ClaimsProps = { + headerComponent: JSX.Element +} + +export const Claims = ({ headerComponent }: ClaimsProps) => { + const translate = useTranslate() + const setConfirmedQuote = useCallback(() => {}, []) + + // @TODO: to be removed when wiring it up + const staticKey = '1' + + return ( + + {headerComponent} + + + + + + + ) +} diff --git a/src/pages/RFOX/components/RewardsAndClaims/Rewards.tsx b/src/pages/RFOX/components/RewardsAndClaims/Rewards.tsx new file mode 100644 index 00000000000..ecdf9271c9d --- /dev/null +++ b/src/pages/RFOX/components/RewardsAndClaims/Rewards.tsx @@ -0,0 +1,33 @@ +import { Box, CardBody } from '@chakra-ui/react' +import { thorchainAssetId } from '@shapeshiftoss/caip' +import { TxStatus } from '@shapeshiftoss/unchained-client' +import { useMemo } from 'react' +import { TransactionsGroupByDate } from 'components/TransactionHistory/TransactionsGroupByDate' +import { selectTxIdsByFilter } from 'state/slices/selectors' +import { useAppSelector } from 'state/store' + +type RewardsProps = { + headerComponent: JSX.Element +} + +export const Rewards = ({ headerComponent }: RewardsProps) => { + const filter = useMemo( + () => ({ txStatus: TxStatus.Confirmed, assetIdFilter: thorchainAssetId }), + [], + ) + const txIds = useAppSelector(state => selectTxIdsByFilter(state, filter)) + const txIdsFilter = useMemo(() => { + // @TODO: Remove this slice when we have pagination in place, if we ever need a pagination + return txIds.slice(0, Number(5)) + }, [txIds]) + + return ( + + {headerComponent} + + + + + + ) +} diff --git a/src/pages/RFOX/components/RewardsAndClaims/RewardsAndClaims.tsx b/src/pages/RFOX/components/RewardsAndClaims/RewardsAndClaims.tsx new file mode 100644 index 00000000000..f125c4e04d3 --- /dev/null +++ b/src/pages/RFOX/components/RewardsAndClaims/RewardsAndClaims.tsx @@ -0,0 +1,105 @@ +import { Button, Card, Flex, TabPanel, TabPanels, Tabs } from '@chakra-ui/react' +import type { PropsWithChildren } from 'react' +import { useCallback, useMemo, useState } from 'react' +import { useTranslate } from 'react-polyglot' +import { useFeatureFlag } from 'hooks/useFeatureFlag/useFeatureFlag' + +import { Claims } from './Claims' +import { Rewards } from './Rewards' + +type FormHeaderTabProps = { + index: number + onClick: (index: number) => void + isActive?: boolean +} & PropsWithChildren + +const activeStyle = { color: 'text.base' } + +export const RewardsAndClaimsTabIndex = { + Rewards: 0, + Claims: 1, +} + +const RewardsAndClaimsTab: React.FC = ({ + index, + onClick, + isActive, + children, +}) => { + const handleClick = useCallback(() => { + onClick(index) + }, [index, onClick]) + return ( + + ) +} + +type FormHeaderProps = { + setStepIndex: (index: number) => void + activeIndex: number +} + +const RewardsAndClaimsHeader: React.FC = ({ setStepIndex, activeIndex }) => { + const isRewardsTabEnabled = useFeatureFlag('RFOXRewardsTab') + const translate = useTranslate() + const handleClick = useCallback( + (index: number) => { + setStepIndex(index) + }, + [setStepIndex], + ) + if (!isRewardsTabEnabled) return null + + return ( + + + {translate('RFOX.rewards')} + + + {translate('RFOX.claims')} + + + ) +} + +export const RewardsAndClaims: React.FC = () => { + const isRewardsTabEnabled = useFeatureFlag('RFOXRewardsTab') + const [stepIndex, setStepIndex] = useState(RewardsAndClaimsTabIndex.Rewards) + + const TabHeader = useMemo( + () => , + [stepIndex], + ) + return ( + + + + {isRewardsTabEnabled && ( + + + + )} + + + + + + + ) +} diff --git a/src/pages/RFOX/hooks/useGetUnstakingRequestCountQuery.ts b/src/pages/RFOX/hooks/useGetUnstakingRequestCountQuery.ts new file mode 100644 index 00000000000..67f31c6ade4 --- /dev/null +++ b/src/pages/RFOX/hooks/useGetUnstakingRequestCountQuery.ts @@ -0,0 +1,71 @@ +import { skipToken } from '@tanstack/react-query' +import { foxStakingV1Abi } from 'contracts/abis/FoxStakingV1' +import { RFOX_PROXY_CONTRACT_ADDRESS } from 'contracts/constants' +import { useMemo } from 'react' +import type { Address, ReadContractReturnType } from 'viem' +import { getAddress } from 'viem' +import { readContract } from 'viem/actions' +import { arbitrum } from 'viem/chains' +import type { Config } from 'wagmi' +import { type ReadContractQueryKey, useQuery } from 'wagmi/query' +import { viemClientByNetworkId } from 'lib/viem-client' + +type GetUnstakingRequestCountQueryKey = ReadContractQueryKey< + typeof foxStakingV1Abi, + 'getUnstakingRequestCount', + readonly [Address], + Config +> +type UnstakingRequestCount = ReadContractReturnType< + typeof foxStakingV1Abi, + 'getUnstakingRequestCount', + readonly [Address] +> +type UseGetUnstakingRequestCountQueryProps = { + stakingAssetAccountAddress: string | undefined + select?: (unstakingRequestCount: UnstakingRequestCount) => SelectData +} +const client = viemClientByNetworkId[arbitrum.id] + +export const useGetUnstakingRequestCountQuery = ({ + stakingAssetAccountAddress, + select, +}: UseGetUnstakingRequestCountQueryProps) => { + // wagmi doesn't expose queryFn, so we reconstruct the queryKey and queryFn ourselves to leverage skipToken type safety + const queryKey: GetUnstakingRequestCountQueryKey = useMemo( + () => [ + 'readContract', + { + address: RFOX_PROXY_CONTRACT_ADDRESS, + functionName: 'getUnstakingRequestCount', + args: [ + stakingAssetAccountAddress ? getAddress(stakingAssetAccountAddress) : ('' as Address), + ], + chainId: arbitrum.id, + }, + ], + [stakingAssetAccountAddress], + ) + + const getUnstakingRequestCountQueryFn = useMemo( + () => + stakingAssetAccountAddress + ? () => + readContract(client, { + abi: foxStakingV1Abi, + address: RFOX_PROXY_CONTRACT_ADDRESS, + functionName: 'getUnstakingRequestCount', + args: [getAddress(stakingAssetAccountAddress)], + }) + : skipToken, + [stakingAssetAccountAddress], + ) + + const unstakingRequestCountQuery = useQuery({ + queryKey, + queryFn: getUnstakingRequestCountQueryFn, + select, + }) + + return unstakingRequestCountQuery +} diff --git a/src/pages/ThorChainLP/components/AddLiquidity/AddLiquidityInput.tsx b/src/pages/ThorChainLP/components/AddLiquidity/AddLiquidityInput.tsx index 9c642b17165..8efe3cc6a26 100644 --- a/src/pages/ThorChainLP/components/AddLiquidity/AddLiquidityInput.tsx +++ b/src/pages/ThorChainLP/components/AddLiquidity/AddLiquidityInput.tsx @@ -242,14 +242,14 @@ export const AddLiquidityInput: React.FC = ({ const getDefaultOpportunityType = useCallback( (assetId: AssetId) => { const walletSupportsRune = walletSupportsChain({ - chainAccountIds: accountIdsByChainId[thorchainChainId] ?? [], + checkConnectedAccountIds: accountIdsByChainId[thorchainChainId] ?? [], chainId: thorchainChainId, wallet, isSnapInstalled, }) const walletSupportsAsset = walletSupportsChain({ - chainAccountIds: accountIdsByChainId[fromAssetId(assetId).chainId] ?? [], + checkConnectedAccountIds: accountIdsByChainId[fromAssetId(assetId).chainId] ?? [], chainId: fromAssetId(assetId).chainId, wallet, isSnapInstalled, @@ -289,7 +289,12 @@ export const AddLiquidityInput: React.FC = ({ const walletSupportedOpportunity = pools.find(pool => { const { chainId } = fromAssetId(pool.assetId) const chainAccountIds = accountIdsByChainId[chainId] ?? [] - return walletSupportsChain({ chainAccountIds, chainId, wallet, isSnapInstalled }) + return walletSupportsChain({ + checkConnectedAccountIds: chainAccountIds, + chainId, + wallet, + isSnapInstalled, + }) }) const opportunityType = getDefaultOpportunityType( @@ -399,7 +404,12 @@ export const AddLiquidityInput: React.FC = ({ const walletSupportsRune = useMemo(() => { const chainId = thorchainChainId const chainAccountIds = accountIdsByChainId[chainId] ?? [] - const walletSupport = walletSupportsChain({ chainAccountIds, chainId, wallet, isSnapInstalled }) + const walletSupport = walletSupportsChain({ + checkConnectedAccountIds: chainAccountIds, + chainId, + wallet, + isSnapInstalled, + }) return walletSupport && runeAccountIds.length > 0 }, [accountIdsByChainId, isSnapInstalled, runeAccountIds.length, wallet]) @@ -407,7 +417,12 @@ export const AddLiquidityInput: React.FC = ({ if (!assetId) return false const chainId = fromAssetId(assetId).chainId const chainAccountIds = accountIdsByChainId[chainId] ?? [] - const walletSupport = walletSupportsChain({ chainAccountIds, chainId, wallet, isSnapInstalled }) + const walletSupport = walletSupportsChain({ + checkConnectedAccountIds: chainAccountIds, + chainId, + wallet, + isSnapInstalled, + }) return walletSupport && poolAssetAccountIds.length > 0 }, [assetId, accountIdsByChainId, wallet, isSnapInstalled, poolAssetAccountIds.length]) diff --git a/src/pages/ThorChainLP/components/RemoveLiquidity/RemoveLiquidityInput.tsx b/src/pages/ThorChainLP/components/RemoveLiquidity/RemoveLiquidityInput.tsx index 17268bed295..d13c3d0c380 100644 --- a/src/pages/ThorChainLP/components/RemoveLiquidity/RemoveLiquidityInput.tsx +++ b/src/pages/ThorChainLP/components/RemoveLiquidity/RemoveLiquidityInput.tsx @@ -806,7 +806,7 @@ export const RemoveLiquidityInput: React.FC = ({ chainId: thorchainChainId, wallet, isSnapInstalled, - chainAccountIds: runeAccountIds, + checkConnectedAccountIds: runeAccountIds, }), [isSnapInstalled, runeAccountIds, wallet], ) diff --git a/src/plugins/walletConnectToDapps/components/modals/SessionProposal.tsx b/src/plugins/walletConnectToDapps/components/modals/SessionProposal.tsx index a07a6563bae..a9bc4289e31 100644 --- a/src/plugins/walletConnectToDapps/components/modals/SessionProposal.tsx +++ b/src/plugins/walletConnectToDapps/components/modals/SessionProposal.tsx @@ -125,7 +125,7 @@ const SessionProposal = forwardRef { const payload = { byId: { [userStakingId]: { + isLoaded: true, userStakingId, stakedAmountCryptoBaseUnit: '42000', rewardsCryptoBaseUnit: { @@ -229,6 +230,7 @@ describe('opportunitiesSlice', () => { const insertPayloadOne = { byId: { [userStakingIdOne]: { + isLoaded: true, userStakingId: userStakingIdOne, stakedAmountCryptoBaseUnit: '42000', rewardsCryptoBaseUnit: { @@ -251,6 +253,7 @@ describe('opportunitiesSlice', () => { const insertPayloadTwo = { byId: { [userStakingIdTwo]: { + isLoaded: true, userStakingId: userStakingIdTwo, stakedAmountCryptoBaseUnit: '42000', rewardsCryptoBaseUnit: { diff --git a/src/state/slices/opportunitiesSlice/resolvers/cosmosSdk/utils.ts b/src/state/slices/opportunitiesSlice/resolvers/cosmosSdk/utils.ts index 7fcdced2208..73a659eceb9 100644 --- a/src/state/slices/opportunitiesSlice/resolvers/cosmosSdk/utils.ts +++ b/src/state/slices/opportunitiesSlice/resolvers/cosmosSdk/utils.ts @@ -104,6 +104,7 @@ export const makeAccountUserData = ({ maybeValidatorUndelegations.length ) { acc[userStakingId] = { + isLoaded: true, userStakingId, stakedAmountCryptoBaseUnit: maybeValidatorDelegations.toFixed(), rewardsCryptoBaseUnit: { diff --git a/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/index.ts b/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/index.ts index 8fa58e41a06..7dd3a9c3c0b 100644 --- a/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/index.ts +++ b/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/index.ts @@ -154,6 +154,7 @@ export const ethFoxStakingUserDataResolver = async ({ const data = { byId: { [userStakingId]: { + isLoaded: true, userStakingId, stakedAmountCryptoBaseUnit, rewardsCryptoBaseUnit, diff --git a/src/state/slices/opportunitiesSlice/resolvers/foxy/index.ts b/src/state/slices/opportunitiesSlice/resolvers/foxy/index.ts index 0531d09f1d0..7edce032714 100644 --- a/src/state/slices/opportunitiesSlice/resolvers/foxy/index.ts +++ b/src/state/slices/opportunitiesSlice/resolvers/foxy/index.ts @@ -154,6 +154,7 @@ export const foxyStakingOpportunitiesUserDataResolver = async ({ ] stakingOpportunitiesUserDataByUserStakingId[userStakingId] = { + isLoaded: true, userStakingId, stakedAmountCryptoBaseUnit: balance, rewardsCryptoBaseUnit: { amounts: rewardsAmountsCryptoBaseUnit, claimable: true }, diff --git a/src/state/slices/opportunitiesSlice/resolvers/thorchainsavers/index.ts b/src/state/slices/opportunitiesSlice/resolvers/thorchainsavers/index.ts index 81473908dd6..33a1db12e9d 100644 --- a/src/state/slices/opportunitiesSlice/resolvers/thorchainsavers/index.ts +++ b/src/state/slices/opportunitiesSlice/resolvers/thorchainsavers/index.ts @@ -201,6 +201,7 @@ export const thorchainSaversStakingOpportunitiesUserDataResolver = async ({ // No position on that pool - either it was never staked in, or fully withdrawn if (!accountPosition) { stakingOpportunitiesUserDataByUserStakingId[userStakingId] = { + isLoaded: true, userStakingId, stakedAmountCryptoBaseUnit: '0', rewardsCryptoBaseUnit: { amounts: ['0'], claimable: false }, @@ -223,6 +224,7 @@ export const thorchainSaversStakingOpportunitiesUserDataResolver = async ({ ] stakingOpportunitiesUserDataByUserStakingId[userStakingId] = { + isLoaded: true, userStakingId, stakedAmountCryptoBaseUnit: stakedAmountCryptoBaseUnit.toFixed(), rewardsCryptoBaseUnit: { amounts: rewardsAmountsCryptoBaseUnit, claimable: false }, diff --git a/src/state/slices/opportunitiesSlice/selectors/selectors.test.ts b/src/state/slices/opportunitiesSlice/selectors/selectors.test.ts index f1e0b271b9c..83e828d1901 100644 --- a/src/state/slices/opportunitiesSlice/selectors/selectors.test.ts +++ b/src/state/slices/opportunitiesSlice/selectors/selectors.test.ts @@ -92,6 +92,7 @@ describe('opportunitiesSlice selectors', () => { ], byId: { [serializeUserStakingId(gomesAccountId, mockStakingContractTwo)]: { + isLoaded: true, userStakingId: serializeUserStakingId(gomesAccountId, mockStakingContractTwo), stakedAmountCryptoBaseUnit: '1337', rewardsCryptoBaseUnit: { @@ -100,11 +101,13 @@ describe('opportunitiesSlice selectors', () => { }, }, [serializeUserStakingId(gomesAccountId, mockStakingContractOne)]: { + isLoaded: true, userStakingId: serializeUserStakingId(gomesAccountId, mockStakingContractOne), stakedAmountCryptoBaseUnit: '4', rewardsCryptoBaseUnit: { amounts: ['3000000000000000000'] as [string], claimable: true }, }, [serializeUserStakingId(fauxmesAccountId, mockStakingContractOne)]: { + isLoaded: true, userStakingId: serializeUserStakingId(fauxmesAccountId, mockStakingContractOne), stakedAmountCryptoBaseUnit: '9000', rewardsCryptoBaseUnit: { amounts: ['1000000000000000000'] as [string], claimable: true }, @@ -205,6 +208,7 @@ describe('opportunitiesSlice selectors', () => { ], byId: { [serializeUserStakingId(gomesAccountId, mockStakingContractTwo)]: { + isLoaded: true, userStakingId: serializeUserStakingId(gomesAccountId, mockStakingContractTwo), stakedAmountCryptoBaseUnit: '1337', rewardsCryptoBaseUnit: { @@ -213,6 +217,7 @@ describe('opportunitiesSlice selectors', () => { }, }, [serializeUserStakingId(gomesAccountId, mockStakingContractOne)]: { + isLoaded: true, userStakingId: serializeUserStakingId(gomesAccountId, mockStakingContractOne), stakedAmountCryptoBaseUnit: '4', rewardsCryptoBaseUnit: { amounts: ['3000000000000000000'] as [string], claimable: true }, @@ -235,6 +240,7 @@ describe('opportunitiesSlice selectors', () => { userStakingId: serializeUserStakingId(gomesAccountId, mockStakingContractTwo), }), ).toEqual({ + isLoaded: true, apy: '0.42', assetId: foxEthStakingAssetIdV5, id: foxEthStakingAssetIdV5, @@ -259,6 +265,7 @@ describe('opportunitiesSlice selectors', () => { userStakingId: serializeUserStakingId(gomesAccountId, mockStakingContractOne), }), ).toEqual({ + isLoaded: true, apy: '0.42', assetId: 'eip155:1/erc20:0x470e8de2ebaef52014a47cb5e6af86884947f08c', id: 'eip155:1/erc20:0x470e8de2ebaef52014a47cb5e6af86884947f08c', @@ -313,6 +320,7 @@ describe('opportunitiesSlice selectors', () => { ], byId: { [serializeUserStakingId(gomesAccountId, mockStakingContractTwo)]: { + isLoaded: true, userStakingId: serializeUserStakingId(gomesAccountId, mockStakingContractTwo), stakedAmountCryptoBaseUnit: '1337', rewardsCryptoBaseUnit: { @@ -321,11 +329,13 @@ describe('opportunitiesSlice selectors', () => { }, }, [serializeUserStakingId(catpuccinoAccountId, mockStakingContractTwo)]: { + isLoaded: true, userStakingId: serializeUserStakingId(catpuccinoAccountId, mockStakingContractTwo), stakedAmountCryptoBaseUnit: '100', rewardsCryptoBaseUnit: { amounts: ['1000000000000000000'] as [string], claimable: true }, }, [serializeUserStakingId(gomesAccountId, mockStakingContractOne)]: { + isLoaded: true, userStakingId: serializeUserStakingId(gomesAccountId, mockStakingContractOne), stakedAmountCryptoBaseUnit: '4', rewardsCryptoBaseUnit: { amounts: ['3000000000000000000'] as [string], claimable: true }, @@ -348,6 +358,7 @@ describe('opportunitiesSlice selectors', () => { stakingId: mockStakingContractTwo, }) expect(result).toEqual({ + isLoaded: true, apy: '1000', assetId: mockStakingContractTwo, id: mockStakingContractTwo, diff --git a/src/state/slices/opportunitiesSlice/selectors/stakingSelectors.ts b/src/state/slices/opportunitiesSlice/selectors/stakingSelectors.ts index ef002b56f3e..770d0b8302d 100644 --- a/src/state/slices/opportunitiesSlice/selectors/stakingSelectors.ts +++ b/src/state/slices/opportunitiesSlice/selectors/stakingSelectors.ts @@ -215,6 +215,7 @@ export const selectUserStakingOpportunityByUserStakingId = createDeepEqualOutput | [string], claimable: false, }, + isLoaded: false, ...userOpportunity, ...opportunityMetadata, userStakingId, @@ -592,7 +593,7 @@ export const selectEarnUserStakingOpportunityByUserStakingId = createDeepEqualOu const earnUserStakingOpportunity: StakingEarnOpportunityType = { ...userStakingOpportunity, - isLoaded: true, + isLoaded: userStakingOpportunity.isLoaded, chainId: fromAssetId(userStakingOpportunity.assetId).chainId, cryptoAmountBaseUnit: userStakingOpportunity.stakedAmountCryptoBaseUnit ?? '0', cryptoAmountPrecision: bnOrZero(userStakingOpportunity.stakedAmountCryptoBaseUnit) diff --git a/src/state/slices/opportunitiesSlice/types.ts b/src/state/slices/opportunitiesSlice/types.ts index b99752b7cd7..5efc040c7b3 100644 --- a/src/state/slices/opportunitiesSlice/types.ts +++ b/src/state/slices/opportunitiesSlice/types.ts @@ -83,6 +83,7 @@ export type OpportunityMetadata = OpportunityMetadataBase | ThorchainSaversStaki // User-specific values for this opportunity export type UserStakingOpportunityBase = { + isLoaded: boolean userStakingId: UserStakingId // The amount of farmed LP tokens stakedAmountCryptoBaseUnit: string diff --git a/src/state/slices/preferencesSlice/preferencesSlice.ts b/src/state/slices/preferencesSlice/preferencesSlice.ts index 4dda3a71bc4..29d3e688df3 100644 --- a/src/state/slices/preferencesSlice/preferencesSlice.ts +++ b/src/state/slices/preferencesSlice/preferencesSlice.ts @@ -62,6 +62,7 @@ export type FeatureFlags = { AccountManagement: boolean AccountManagementLedger: boolean RFOX: boolean + RFOXRewardsTab: boolean } export type Flag = keyof FeatureFlags @@ -149,6 +150,7 @@ const initialState: Preferences = { AccountManagement: getConfig().REACT_APP_FEATURE_ACCOUNT_MANAGEMENT, AccountManagementLedger: getConfig().REACT_APP_FEATURE_ACCOUNT_MANAGEMENT_LEDGER, RFOX: getConfig().REACT_APP_FEATURE_RFOX, + RFOXRewardsTab: getConfig().REACT_APP_FEATURE_RFOX_REWARDS_TAB, }, selectedLocale: simpleLocale(), balanceThreshold: '0', diff --git a/src/test/mocks/store.ts b/src/test/mocks/store.ts index af458cd7e95..fd795790d67 100644 --- a/src/test/mocks/store.ts +++ b/src/test/mocks/store.ts @@ -114,6 +114,7 @@ export const mockStore: ReduxState = { AccountManagement: false, AccountManagementLedger: false, RFOX: false, + RFOXRewardsTab: false, }, selectedLocale: 'en', balanceThreshold: '0',