diff --git a/apps/main/src/dex/components/PagePool/Deposit/components/FieldsDeposit.tsx b/apps/main/src/dex/components/PagePool/Deposit/components/FieldsDeposit.tsx index 336b899623..5ef2859b33 100644 --- a/apps/main/src/dex/components/PagePool/Deposit/components/FieldsDeposit.tsx +++ b/apps/main/src/dex/components/PagePool/Deposit/components/FieldsDeposit.tsx @@ -1,6 +1,8 @@ import { BigNumber } from 'bignumber.js' import lodash from 'lodash' import { useCallback, useMemo } from 'react' +import type { Address } from 'viem' +import { useConnection } from 'wagmi' import FieldToken from '@/dex/components/PagePool/components/FieldToken' import type { FormValues, LoadMaxAmount } from '@/dex/components/PagePool/Deposit/types' import { FieldsWrapper } from '@/dex/components/PagePool/styles' @@ -12,6 +14,7 @@ import type { CurrencyReserves } from '@/dex/types/main.types' import { getChainPoolIdActiveKey } from '@/dex/utils' import Checkbox from '@ui/Checkbox' import { t } from '@ui-kit/lib/i18n' +import { useTokenBalances } from '@ui-kit/queries/token-balance.query' import { Amount } from '../../utils' /** @@ -54,6 +57,7 @@ function calculateBalancedValues( } const FieldsDeposit = ({ + chainId, formProcessing, formValues, haveSigner, @@ -63,9 +67,9 @@ const FieldsDeposit = ({ poolDataCacheOrApi, routerParams: { rChainId, rPoolIdOrAddress }, tokensMapper, - userPoolBalances, updateFormValues, }: { + chainId: number | undefined blockchainId: string formProcessing: boolean formValues: FormValues @@ -76,9 +80,8 @@ const FieldsDeposit = ({ loadMaxAmount: LoadMaxAmount | null, updatedMaxSlippage: string | null, ) => void -} & Pick) => { +} & Pick) => { const { data: network } = useNetworkByChain({ chainId: rChainId }) - const balancesLoading = useStore((state) => state.user.walletBalancesLoading) const maxLoading = useStore((state) => state.poolDeposit.maxLoading) const setPoolIsWrapped = useStore((state) => state.pools.setPoolIsWrapped) const poolId = usePoolIdByAddressOrId({ chainId: rChainId, poolIdOrAddress: rPoolIdOrAddress }) @@ -130,6 +133,13 @@ const FieldsDeposit = ({ [poolDataCacheOrApi.tokenAddresses, updateFormValues], ) + const { address: userAddress } = useConnection() + const { data: userPoolBalances, isLoading: balancesLoading } = useTokenBalances({ + chainId, + userAddress, + tokenAddresses: poolDataCacheOrApi.tokenAddresses as Address[], + }) + return ( {poolDataCacheOrApi.tokens.length === amountsInput.length && diff --git a/apps/main/src/dex/components/PagePool/Deposit/components/FormDeposit.tsx b/apps/main/src/dex/components/PagePool/Deposit/components/FormDeposit.tsx index 563f47e5b0..9688a65423 100644 --- a/apps/main/src/dex/components/PagePool/Deposit/components/FormDeposit.tsx +++ b/apps/main/src/dex/components/PagePool/Deposit/components/FormDeposit.tsx @@ -1,4 +1,5 @@ import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useConfig, type Config } from 'wagmi' import AlertFormError from '@/dex/components/AlertFormError' import AlertSlippage from '@/dex/components/AlertSlippage' import DetailInfoEstGas from '@/dex/components/DetailInfoEstGas' @@ -39,7 +40,6 @@ const FormDeposit = ({ routerParams, seed, tokensMapper, - userPoolBalances, }: TransferProps) => { const isSubscribed = useRef(false) @@ -67,6 +67,8 @@ const FormDeposit = ({ const poolId = poolData?.pool?.id const haveSigner = !!signerAddress + const config = useConfig() + const updateFormValues = useCallback( ( updatedFormValues: Partial, @@ -77,6 +79,7 @@ const FormDeposit = ({ setSlippageConfirmed(false) void setFormValues( 'DEPOSIT', + config, curve, poolDataCacheOrApi.pool.id, poolData, @@ -86,7 +89,7 @@ const FormDeposit = ({ updatedMaxSlippage || maxSlippage, ) }, - [curve, maxSlippage, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues], + [config, curve, maxSlippage, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues], ) const handleApproveClick = useCallback( @@ -100,11 +103,18 @@ const FormDeposit = ({ ) const handleDepositClick = useCallback( - async (activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string) => { + async ( + activeKey: string, + config: Config, + curve: CurveApi, + poolData: PoolData, + formValues: FormValues, + maxSlippage: string, + ) => { const tokenText = amountsDescription(formValues.amounts) const notifyMessage = t`Please confirm deposit of ${tokenText} at max ${maxSlippage}% slippage.` const { dismiss } = notify(notifyMessage, 'pending') - const resp = await fetchStepDeposit(activeKey, curve, poolData, formValues, maxSlippage) + const resp = await fetchStepDeposit(activeKey, config, curve, poolData, formValues, maxSlippage) if (isSubscribed.current && resp && resp.hash && resp.activeKey === activeKey && network) { const txDescription = t`Deposited ${tokenText}.` @@ -118,6 +128,7 @@ const FormDeposit = ({ const getSteps = useCallback( ( activeKey: string, + config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, @@ -164,13 +175,13 @@ const FormDeposit = ({ onClick: () => setSlippageConfirmed(false), }, primaryBtnProps: { - onClick: () => handleDepositClick(activeKey, curve, poolData, formValues, maxSlippage), + onClick: () => handleDepositClick(activeKey, config, curve, poolData, formValues, maxSlippage), disabled: !slippageConfirmed, }, primaryBtnLabel: 'Deposit anyway', }, } - : { onClick: () => handleDepositClick(activeKey, curve, poolData, formValues, maxSlippage) }), + : { onClick: () => handleDepositClick(activeKey, config, curve, poolData, formValues, maxSlippage) }), }, } @@ -224,6 +235,7 @@ const FormDeposit = ({ if (curve && poolData) { const updatedSteps = getSteps( activeKey, + config, curve, poolData, formValues, @@ -237,6 +249,7 @@ const FormDeposit = ({ } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ + config, curve?.chainId, poolData?.pool.id, signerAddress, @@ -261,6 +274,7 @@ const FormDeposit = ({ return ( <> @@ -308,7 +321,6 @@ const FormDeposit = ({ loading={!chainId || !steps.length || !seed.loaded} routerParams={routerParams} seed={seed} - userPoolBalances={userPoolBalances} > {formStatus.error && ( diff --git a/apps/main/src/dex/components/PagePool/Deposit/components/FormDepositStake.tsx b/apps/main/src/dex/components/PagePool/Deposit/components/FormDepositStake.tsx index de10456eaa..bed77d65da 100644 --- a/apps/main/src/dex/components/PagePool/Deposit/components/FormDepositStake.tsx +++ b/apps/main/src/dex/components/PagePool/Deposit/components/FormDepositStake.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useConfig, type Config } from 'wagmi' import AlertFormError from '@/dex/components/AlertFormError' import AlertSlippage from '@/dex/components/AlertSlippage' import DetailInfoEstGas from '@/dex/components/DetailInfoEstGas' @@ -42,7 +43,6 @@ const FormDepositStake = ({ routerParams, seed, tokensMapper, - userPoolBalances, }: TransferProps) => { const isSubscribed = useRef(false) @@ -70,6 +70,8 @@ const FormDepositStake = ({ const poolId = poolData?.pool?.id const haveSigner = !!signerAddress + const config = useConfig() + const updateFormValues = useCallback( ( updatedFormValues: Partial, @@ -80,6 +82,7 @@ const FormDepositStake = ({ setSlippageConfirmed(false) void setFormValues( 'DEPOSIT_STAKE', + config, curve, poolDataCacheOrApi.pool.id, poolData, @@ -89,7 +92,7 @@ const FormDepositStake = ({ updatedMaxSlippage || maxSlippage, ) }, - [curve, maxSlippage, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues], + [config, curve, maxSlippage, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues], ) const handleApproveClick = useCallback( @@ -103,11 +106,18 @@ const FormDepositStake = ({ ) const handleDepositStakeClick = useCallback( - async (activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string) => { + async ( + activeKey: string, + config: Config, + curve: CurveApi, + poolData: PoolData, + formValues: FormValues, + maxSlippage: string, + ) => { const tokenText = amountsDescription(formValues.amounts) const notifyMessage = t`Please confirm deposit and staking of ${tokenText} LP Tokens at max ${maxSlippage}% slippage.` const { dismiss } = notify(notifyMessage, 'pending') - const resp = await fetchStepDepositStake(activeKey, curve, poolData, formValues, maxSlippage) + const resp = await fetchStepDepositStake(activeKey, config, curve, poolData, formValues, maxSlippage) if (isSubscribed.current && resp && resp.hash && resp.activeKey === activeKey) { const TxDescription = t`Deposit and staked ${tokenText}` @@ -121,6 +131,7 @@ const FormDepositStake = ({ const getSteps = useCallback( ( activeKey: string, + config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, @@ -167,13 +178,13 @@ const FormDepositStake = ({ onClick: () => setSlippageConfirmed(false), }, primaryBtnProps: { - onClick: () => handleDepositStakeClick(activeKey, curve, poolData, formValues, maxSlippage), + onClick: () => handleDepositStakeClick(activeKey, config, curve, poolData, formValues, maxSlippage), disabled: !slippageConfirmed, }, primaryBtnLabel: 'Deposit anyway', }, } - : { onClick: () => handleDepositStakeClick(activeKey, curve, poolData, formValues, maxSlippage) }), + : { onClick: () => handleDepositStakeClick(activeKey, config, curve, poolData, formValues, maxSlippage) }), }, } @@ -227,6 +238,7 @@ const FormDepositStake = ({ if (curve && poolData) { const updatedSteps = getSteps( activeKey, + config, curve, poolData, formValues, @@ -239,7 +251,17 @@ const FormDepositStake = ({ setSteps(updatedSteps) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [chainId, poolId, signerAddress, formValues, formStatus, slippage.isHighSlippage, slippageConfirmed, maxSlippage]) + }, [ + config, + chainId, + poolId, + signerAddress, + formValues, + formStatus, + slippage.isHighSlippage, + slippageConfirmed, + maxSlippage, + ]) const activeStep = haveSigner ? getActiveStep(steps) : null const disableForm = !seed.loaded || formStatus.formProcessing @@ -255,6 +277,7 @@ const FormDepositStake = ({ return ( <> @@ -308,7 +330,6 @@ const FormDepositStake = ({ loading={!chainId || !steps.length || !seed.loaded} routerParams={routerParams} seed={seed} - userPoolBalances={userPoolBalances} > {formStatus.error && ( diff --git a/apps/main/src/dex/components/PagePool/Deposit/components/FormStake.tsx b/apps/main/src/dex/components/PagePool/Deposit/components/FormStake.tsx index 0edb8f24f6..32890e603f 100644 --- a/apps/main/src/dex/components/PagePool/Deposit/components/FormStake.tsx +++ b/apps/main/src/dex/components/PagePool/Deposit/components/FormStake.tsx @@ -1,6 +1,8 @@ import BigNumber from 'bignumber.js' import { useCallback, useEffect, useRef, useState } from 'react' import type { ReactNode } from 'react' +import { useConnection, type Config } from 'wagmi' +import { useConfig } from 'wagmi' import AlertFormError from '@/dex/components/AlertFormError' import DetailInfoEstGas from '@/dex/components/DetailInfoEstGas' import DetailInfoExpectedApy from '@/dex/components/PagePool/components/DetailInfoExpectedApy' @@ -11,6 +13,7 @@ import { FieldsWrapper } from '@/dex/components/PagePool/styles' import type { TransferProps } from '@/dex/components/PagePool/types' import { DEFAULT_ESTIMATED_GAS } from '@/dex/components/PagePool/utils' import { useNetworks } from '@/dex/entities/networks' +import { usePoolTokenDepositBalances } from '@/dex/hooks/usePoolTokenDepositBalances' import useStore from '@/dex/store/useStore' import { CurveApi, Pool, PoolData } from '@/dex/types/main.types' import AlertBox from '@ui/AlertBox' @@ -22,13 +25,12 @@ import { scanTxPath } from '@ui/utils' import { notify } from '@ui-kit/features/connect-wallet' import { t } from '@ui-kit/lib/i18n' -const FormStake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, userPoolBalances }: TransferProps) => { +const FormStake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed }: TransferProps) => { const isSubscribed = useRef(false) const { chainId, signerAddress } = curve || {} const { rChainId } = routerParams const activeKey = useStore((state) => state.poolDeposit.activeKey) - const balancesLoading = useStore((state) => state.user.walletBalancesLoading) const formEstGas = useStore((state) => state.poolDeposit.formEstGas[activeKey] ?? DEFAULT_ESTIMATED_GAS) const formStatus = useStore((state) => state.poolDeposit.formStatus) const formValues = useStore((state) => state.poolDeposit.formValues) @@ -46,12 +48,24 @@ const FormStake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us const poolId = poolData?.pool?.id const haveSigner = !!signerAddress + const config = useConfig() + const updateFormValues = useCallback( (updatedFormValues: Partial) => { setTxInfoBar(null) - void setFormValues('STAKE', curve, poolDataCacheOrApi.pool.id, poolData, updatedFormValues, null, seed.isSeed, '') + void setFormValues( + 'STAKE', + config, + curve, + poolDataCacheOrApi.pool.id, + poolData, + updatedFormValues, + null, + seed.isSeed, + '', + ) }, - [curve, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues], + [config, curve, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues], ) const handleApproveClick = useCallback( @@ -65,10 +79,10 @@ const FormStake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us ) const handleStakeClick = useCallback( - async (activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues) => { + async (activeKey: string, config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues) => { const notifyMessage = t`Please confirm staking of ${formValues.lpToken} LP Tokens` const { dismiss } = notify(notifyMessage, 'pending') - const resp = await fetchStepStake(activeKey, curve, poolData, formValues) + const resp = await fetchStepStake(activeKey, config, curve, poolData, formValues) if (isSubscribed.current && resp && resp.hash && resp.activeKey === activeKey && network) { const TxDescription = `Staked ${formValues.lpToken} LP Tokens` @@ -82,6 +96,7 @@ const FormStake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us const getSteps = useCallback( ( activeKey: string, + config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, @@ -105,7 +120,7 @@ const FormStake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us status: getStepStatus(isComplete, formStatus.step === 'STAKE', isValid && formStatus.isApproved), type: 'action', content: isComplete ? t`Stake Complete` : t`Stake`, - onClick: () => handleStakeClick(activeKey, curve, poolData, formValues), + onClick: () => handleStakeClick(activeKey, config, curve, poolData, formValues), }, } @@ -149,15 +164,21 @@ const FormStake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us // steps useEffect(() => { if (curve && poolId) { - const updatedSteps = getSteps(activeKey, curve, poolData, formValues, formStatus, steps) + const updatedSteps = getSteps(activeKey, config, curve, poolData, formValues, formStatus, steps) setSteps(updatedSteps) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [chainId, poolId, signerAddress, formValues, formStatus]) + }, [config, chainId, poolId, signerAddress, formValues, formStatus]) const activeStep = signerAddress ? getActiveStep(steps) : null const disableForm = seed.isSeed === null || formStatus.formProcessing - const balLpToken = (userPoolBalances?.lpToken as string) ?? '0' + + const { address: userAddress } = useConnection() + const { lpTokenBalance, isLoading: lpTokenBalanceLoading } = usePoolTokenDepositBalances({ + chainId, + userAddress, + poolId, + }) return ( <> @@ -165,9 +186,9 @@ const FormStake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us updateFormValues({ lpToken }), [updateFormValues])} disabled={disableForm} @@ -196,7 +217,6 @@ const FormStake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us loading={!chainId || !steps.length || !seed.loaded} routerParams={routerParams} seed={seed} - userPoolBalances={userPoolBalances} > {formStatus.error === 'lpToken-too-much' ? ( {t`Not enough LP Tokens balances.`} diff --git a/apps/main/src/dex/components/PagePool/Swap/index.tsx b/apps/main/src/dex/components/PagePool/Swap/index.tsx index 37821669b1..65ab46af90 100644 --- a/apps/main/src/dex/components/PagePool/Swap/index.tsx +++ b/apps/main/src/dex/components/PagePool/Swap/index.tsx @@ -1,7 +1,8 @@ import lodash from 'lodash' import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { styled } from 'styled-components' -import { ethAddress } from 'viem' +import { ethAddress, type Address } from 'viem' +import { useConfig, useConnection, type Config } from 'wagmi' import AlertFormError from '@/dex/components/AlertFormError' import AlertFormWarning from '@/dex/components/AlertFormWarning' import AlertSlippage from '@/dex/components/AlertSlippage' @@ -18,7 +19,7 @@ import DetailInfoExchangeRate from '@/dex/components/PageRouterSwap/components/D import DetailInfoPriceImpact from '@/dex/components/PageRouterSwap/components/DetailInfoPriceImpact' import { useNetworks } from '@/dex/entities/networks' import useStore from '@/dex/store/useStore' -import { Balances, CurveApi, PoolAlert, PoolData, TokensMapper } from '@/dex/types/main.types' +import { CurveApi, PoolAlert, PoolData, TokensMapper } from '@/dex/types/main.types' import { toTokenOption } from '@/dex/utils' import { getSlippageImpact } from '@/dex/utils/utilsSwap' import AlertBox from '@ui/AlertBox' @@ -40,6 +41,7 @@ import usePageVisibleInterval from '@ui-kit/hooks/usePageVisibleInterval' import { t } from '@ui-kit/lib/i18n' import { REFRESH_INTERVAL } from '@ui-kit/lib/model' import { useTokenUsdRate } from '@ui-kit/lib/model/entities/token-usd-rate' +import { useTokenBalance } from '@ui-kit/queries/token-balance.query' import { LargeTokenInput } from '@ui-kit/shared/ui/LargeTokenInput' import { decimal, type Decimal } from '@ui-kit/utils' @@ -55,16 +57,12 @@ const Swap = ({ routerParams, seed, tokensMapper, - userPoolBalances, - userPoolBalancesLoading, }: Pick & { chainIdPoolId: string poolAlert: PoolAlert | null maxSlippage: string seed: Seed tokensMapper: TokensMapper - userPoolBalances: Balances | undefined - userPoolBalancesLoading: boolean }) => { const isSubscribed = useRef(false) @@ -97,8 +95,19 @@ const Swap = ({ const poolId = poolData?.pool?.id const haveSigner = !!signerAddress - const userFromBalance = userPoolBalances?.[formValues.fromAddress] - const userToBalance = userPoolBalances?.[formValues.toAddress] + const config = useConfig() + const { address: userAddress } = useConnection() + const { data: userFromBalance, isLoading: userFromBalanceLoading } = useTokenBalance({ + chainId, + userAddress, + tokenAddress: (formValues.fromAddress as Address) || undefined, + }) + + const { data: userToBalance, isLoading: userToBalanceLoading } = useTokenBalance({ + chainId, + userAddress, + tokenAddress: (formValues.toAddress as Address) || undefined, + }) const { data: fromUsdRate } = useTokenUsdRate( { chainId, tokenAddress: formValues.fromAddress }, @@ -125,6 +134,7 @@ const Swap = ({ setTxInfoBar(null) void setFormValues( + config, curve, poolDataCacheOrApi.pool.id, poolData, @@ -134,12 +144,13 @@ const Swap = ({ updatedMaxSlippage || maxSlippage, ) }, - [setFormValues, curve, poolDataCacheOrApi.pool.id, poolData, seed.isSeed, maxSlippage], + [setFormValues, config, curve, poolDataCacheOrApi.pool.id, poolData, seed.isSeed, maxSlippage], ) const handleSwapClick = useCallback( async ( actionActiveKey: string, + config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, @@ -148,7 +159,7 @@ const Swap = ({ const { fromAmount, fromToken, toToken } = formValues const notifyMessage = t`Please confirm swap ${fromAmount} ${fromToken} for ${toToken} at max slippage ${maxSlippage}%.` const { dismiss } = notify(notifyMessage, 'pending') - const resp = await fetchStepSwap(actionActiveKey, curve, poolData, formValues, maxSlippage) + const resp = await fetchStepSwap(actionActiveKey, config, curve, poolData, formValues, maxSlippage) if (isSubscribed.current && resp && resp.hash && resp.activeKey === activeKey && network) { setTxInfoBar( @@ -169,6 +180,7 @@ const Swap = ({ const getSteps = useCallback( ( actionActiveKey: string, + config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, @@ -200,7 +212,7 @@ const Swap = ({ onClick: async () => { const notifyMessage = t`Please approve spending your ${formValues.fromToken}.` const { dismiss } = notify(notifyMessage, 'pending') - await fetchStepApprove(actionActiveKey, curve, poolData.pool, formValues, maxSlippage) + await fetchStepApprove(actionActiveKey, config, curve, poolData.pool, formValues, maxSlippage) if (typeof dismiss === 'function') dismiss() }, }, @@ -226,13 +238,13 @@ const Swap = ({ }, isDismissable: false, primaryBtnProps: { - onClick: () => handleSwapClick(actionActiveKey, curve, poolData, formValues, maxSlippage), + onClick: () => handleSwapClick(actionActiveKey, config, curve, poolData, formValues, maxSlippage), disabled: !confirmedLoss, }, primaryBtnLabel: 'Swap anyway', }, } - : { onClick: () => handleSwapClick(actionActiveKey, curve, poolData, formValues, maxSlippage) }), + : { onClick: () => handleSwapClick(actionActiveKey, config, curve, poolData, formValues, maxSlippage) }), }, } @@ -274,9 +286,9 @@ const Swap = ({ // get user balances useEffect(() => { if (curve && poolId && haveSigner && (isUndefined(userFromBalance) || isUndefined(userToBalance))) { - void fetchUserPoolInfo(curve, poolId, true) + void fetchUserPoolInfo(config, curve, poolId, true) } - }, [chainId, poolId, haveSigner, userFromBalance, userToBalance, curve, fetchUserPoolInfo]) + }, [chainId, poolId, haveSigner, userFromBalance, userToBalance, config, curve, fetchUserPoolInfo]) // curve state change useEffect(() => { @@ -297,6 +309,7 @@ const Swap = ({ if (curve && poolData && seed.isSeed !== null) { const updatedSteps = getSteps( activeKey, + config, curve, poolData, formValues, @@ -306,12 +319,13 @@ const Swap = ({ steps, seed.isSeed, maxSlippage, - userPoolBalancesLoading, + userFromBalanceLoading || userToBalanceLoading, ) setSteps(updatedSteps) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ + config, chainId, poolId, confirmedLoss, @@ -321,7 +335,8 @@ const Swap = ({ formValues, maxSlippage, seed.isSeed, - userPoolBalancesLoading, + userFromBalanceLoading, + userToBalanceLoading, ]) // pageVisible @@ -363,7 +378,7 @@ const Swap = ({ labelProps={ haveSigner && { label: t`Avail.`, - descriptionLoading: userPoolBalancesLoading, + descriptionLoading: userFromBalanceLoading, description: formatNumber(userFromBalance), } } @@ -458,7 +473,7 @@ const Swap = ({ disabled={isDisabled} walletBalance={{ balance: decimal(userFromBalance), - loading: userPoolBalancesLoading || isMaxLoading, + loading: userFromBalanceLoading || isMaxLoading, symbol: fromToken?.symbol, usdRate: fromUsdRate, }} @@ -507,7 +522,7 @@ const Swap = ({ labelProps={ haveSigner && { label: t`Avail.`, - descriptionLoading: userPoolBalancesLoading, + descriptionLoading: userToBalanceLoading, description: formatNumber(userToBalance), } } @@ -582,7 +597,7 @@ const Swap = ({ } walletBalance={{ balance: decimal(userToBalance), - loading: userPoolBalancesLoading, + loading: userToBalanceLoading, symbol: toToken?.symbol, usdRate: toUsdRate, }} @@ -673,7 +688,6 @@ const Swap = ({ loading={!chainId || !steps.length || !seed.loaded} routerParams={routerParams} seed={seed} - userPoolBalances={userPoolBalances} > {txInfoBar} diff --git a/apps/main/src/dex/components/PagePool/UserDetails/index.tsx b/apps/main/src/dex/components/PagePool/UserDetails/index.tsx index b184cff1f7..c392c6c8e9 100644 --- a/apps/main/src/dex/components/PagePool/UserDetails/index.tsx +++ b/apps/main/src/dex/components/PagePool/UserDetails/index.tsx @@ -1,8 +1,10 @@ import { useMemo } from 'react' import { styled } from 'styled-components' +import { useConnection } from 'wagmi' import type { TransferProps } from '@/dex/components/PagePool/types' import PoolRewardsCrv from '@/dex/components/PoolRewardsCrv' import { usePoolIdByAddressOrId } from '@/dex/hooks/usePoolIdByAddressOrId' +import { usePoolTokenDepositBalances } from '@/dex/hooks/usePoolTokenDepositBalances' import { getUserPoolActiveKey } from '@/dex/store/createUserSlice' import useStore from '@/dex/store/useStore' import Box from '@ui/Box' @@ -22,13 +24,9 @@ const MySharesStats = ({ poolDataCacheOrApi, routerParams, tokensMapper, - userPoolBalances, }: { className?: string -} & Pick< - TransferProps, - 'curve' | 'poolData' | 'poolDataCacheOrApi' | 'routerParams' | 'tokensMapper' | 'userPoolBalances' ->) => { +} & Pick) => { const { rChainId, rPoolIdOrAddress } = routerParams const poolId = usePoolIdByAddressOrId({ chainId: rChainId, poolIdOrAddress: rPoolIdOrAddress }) const userPoolActiveKey = curve && poolId ? getUserPoolActiveKey(curve, poolId) : '' @@ -63,6 +61,13 @@ const MySharesStats = ({ return '' }, [poolDataCacheOrApi, userWithdrawAmounts]) + const { address: userAddress } = useConnection() + const { lpTokenBalance, gaugeTokenBalance } = usePoolTokenDepositBalances({ + chainId: rChainId, + userAddress, + poolId, + }) + const crvRewardsTooltipText = useMemo(() => { if (haveBoosting && typeof rewardsApy?.crv?.[0] !== 'undefined' && userCrvApy?.boostApy && userCrvApy?.crvApy) { return ( @@ -99,10 +104,10 @@ const MySharesStats = ({
- {t`Staked:`} {formatNumber(userPoolBalances?.gauge, { defaultValue: '-' })} + {t`Staked:`} {formatNumber(gaugeTokenBalance, { defaultValue: '-' })}
- {t`Unstaked:`} {formatNumber(userPoolBalances?.lpToken, { defaultValue: '-' })} + {t`Unstaked:`} {formatNumber(lpTokenBalance, { defaultValue: '-' })}
{(haveCrvRewards || haveBoosting) && ( diff --git a/apps/main/src/dex/components/PagePool/Withdraw/components/FormClaim.tsx b/apps/main/src/dex/components/PagePool/Withdraw/components/FormClaim.tsx index bb37a1c154..b0fb991821 100644 --- a/apps/main/src/dex/components/PagePool/Withdraw/components/FormClaim.tsx +++ b/apps/main/src/dex/components/PagePool/Withdraw/components/FormClaim.tsx @@ -1,6 +1,7 @@ import lodash from 'lodash' import { ReactNode, useCallback, useEffect, useRef, useState } from 'react' import { styled } from 'styled-components' +import { useConfig, type Config } from 'wagmi' import AlertFormError from '@/dex/components/AlertFormError' import TransferActions from '@/dex/components/PagePool/components/TransferActions' import type { TransferProps } from '@/dex/components/PagePool/types' @@ -21,7 +22,7 @@ import { formatNumber, scanTxPath } from '@ui/utils' import { notify } from '@ui-kit/features/connect-wallet' import { t, Trans } from '@ui-kit/lib/i18n' -const FormClaim = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, userPoolBalances }: TransferProps) => { +const FormClaim = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed }: TransferProps) => { const isSubscribed = useRef(false) const { chainId, signerAddress } = curve || {} @@ -46,15 +47,18 @@ const FormClaim = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us const haveClaimableCrv = +formValues.claimableCrv > 0 const haveClaimableRewards = +formValues.claimableRewards.length > 0 + const config = useConfig() + const updateFormValues = useCallback(() => { setTxInfoBar(null) setSlippageConfirmed(false) - void setFormValues('CLAIM', curve, poolDataCacheOrApi.pool.id, poolData, {}, null, seed.isSeed, '') - }, [curve, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues]) + void setFormValues('CLAIM', config, curve, poolDataCacheOrApi.pool.id, poolData, {}, null, seed.isSeed, '') + }, [config, curve, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues]) const handleClaimClick = useCallback( async ( activeKey: string, + config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, @@ -63,7 +67,7 @@ const FormClaim = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us ) => { const notifyMessage = getClaimText(formValues, formStatus, 'notify', rewardsNeedNudging) const { dismiss } = notify(notifyMessage, 'pending') - const resp = await fetchStepClaim(activeKey, curve, poolData) + const resp = await fetchStepClaim(activeKey, config, curve, poolData) if (isSubscribed.current && resp && resp.hash && resp.activeKey === activeKey && network) { const claimedLabel = formStatus.isClaimCrv @@ -80,6 +84,7 @@ const FormClaim = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us const getSteps = useCallback( ( activeKey: string, + config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, @@ -109,7 +114,7 @@ const FormClaim = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us ? getClaimText(formValues, formStatus, 'claimCrvButton', rewardsNeedNudging) : t`Claim Rewards`, onClick: () => { - void handleClaimClick(activeKey, curve, poolData, formValues, formStatus, rewardsNeedNudging) + void handleClaimClick(activeKey, config, curve, poolData, formValues, formStatus, rewardsNeedNudging) }, }, } @@ -154,11 +159,30 @@ const FormClaim = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us // steps useEffect(() => { if (curve && poolData && seed.isSeed !== null) { - const updatedSteps = getSteps(activeKey, curve, poolData, formValues, formStatus, rewardsNeedNudging, seed.isSeed) + const updatedSteps = getSteps( + activeKey, + config, + curve, + poolData, + formValues, + formStatus, + rewardsNeedNudging, + seed.isSeed, + ) setSteps(updatedSteps) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [chainId, poolData, slippageConfirmed, signerAddress, formValues, formStatus, rewardsNeedNudging, seed.isSeed]) + }, [ + config, + chainId, + poolData, + slippageConfirmed, + signerAddress, + formValues, + formStatus, + rewardsNeedNudging, + seed.isSeed, + ]) const handleBtnClick = (isClaimCrv: boolean, isClaimRewards: boolean) => { setTxInfoBar(null) @@ -171,7 +195,7 @@ const FormClaim = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us cFormStatus.isClaimRewards = isClaimRewards setStateByKey('formStatus', cFormStatus) - void handleClaimClick(activeKey, curve, poolData, formValues, cFormStatus, rewardsNeedNudging) + void handleClaimClick(activeKey, config, curve, poolData, formValues, cFormStatus, rewardsNeedNudging) } } @@ -184,7 +208,6 @@ const FormClaim = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, us loading={!chainId || !steps.length || seed.isSeed === null} routerParams={routerParams} seed={seed} - userPoolBalances={userPoolBalances} > {haveClaimableCrv || haveClaimableRewards ? ( diff --git a/apps/main/src/dex/components/PagePool/Withdraw/components/FormUnstake.tsx b/apps/main/src/dex/components/PagePool/Withdraw/components/FormUnstake.tsx index 26c8b6ffec..27a45e02b4 100644 --- a/apps/main/src/dex/components/PagePool/Withdraw/components/FormUnstake.tsx +++ b/apps/main/src/dex/components/PagePool/Withdraw/components/FormUnstake.tsx @@ -1,4 +1,6 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from 'react' +import { useConnection, type Config } from 'wagmi' +import { useConfig } from 'wagmi' import AlertFormError from '@/dex/components/AlertFormError' import DetailInfoEstGas from '@/dex/components/DetailInfoEstGas' import FieldLpToken from '@/dex/components/PagePool/components/FieldLpToken' @@ -7,6 +9,7 @@ import type { TransferProps } from '@/dex/components/PagePool/types' import { DEFAULT_ESTIMATED_GAS } from '@/dex/components/PagePool/utils' import type { FormStatus, FormValues } from '@/dex/components/PagePool/Withdraw/types' import { useNetworks } from '@/dex/entities/networks' +import { usePoolTokenDepositBalances } from '@/dex/hooks/usePoolTokenDepositBalances' import useStore from '@/dex/store/useStore' import { CurveApi, PoolData } from '@/dex/types/main.types' import { getStepStatus } from '@ui/Stepper/helpers' @@ -17,7 +20,7 @@ import { scanTxPath } from '@ui/utils' import { notify } from '@ui-kit/features/connect-wallet' import { t } from '@ui-kit/lib/i18n' -const FormUnstake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, userPoolBalances }: TransferProps) => { +const FormUnstake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed }: TransferProps) => { const isSubscribed = useRef(false) const { chainId, signerAddress } = curve || {} @@ -38,11 +41,14 @@ const FormUnstake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, const poolId = poolData?.pool?.id const haveSigner = !!signerAddress + const config = useConfig() + const updateFormValues = useCallback( (updatedFormValues: Partial) => { setTxInfoBar(null) void setFormValues( 'UNSTAKE', + config, curve, poolDataCacheOrApi.pool.id, poolData, @@ -52,14 +58,14 @@ const FormUnstake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, '', ) }, - [curve, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues], + [config, curve, poolData, poolDataCacheOrApi.pool.id, seed.isSeed, setFormValues], ) const handleUnstakeClick = useCallback( - async (activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues) => { + async (activeKey: string, config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues) => { const notifyMessage = t`Please confirm unstaking of ${formValues.stakedLpToken} LP Tokens` const { dismiss } = notify(notifyMessage, 'pending') - const resp = await fetchStepUnstake(activeKey, curve, poolData, formValues) + const resp = await fetchStepUnstake(activeKey, config, curve, poolData, formValues) if (isSubscribed.current && resp && resp.hash && resp.activeKey === activeKey && network) { const TxDescription = t`Unstaked ${formValues.stakedLpToken} LP Tokens` @@ -73,6 +79,7 @@ const FormUnstake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, const getSteps = useCallback( ( activeKey: string, + config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, @@ -89,7 +96,7 @@ const FormUnstake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, status: getStepStatus(isComplete, step === 'UNSTAKE', isValid), type: 'action', content: isComplete ? t`Unstake Complete` : t`Unstake`, - onClick: () => handleUnstakeClick(activeKey, curve, poolData, formValues), + onClick: () => handleUnstakeClick(activeKey, config, curve, poolData, formValues), }, } @@ -125,23 +132,29 @@ const FormUnstake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, // steps useEffect(() => { if (curve && poolData && seed.isSeed !== null) { - const updatedSteps = getSteps(activeKey, curve, poolData, formValues, formStatus, seed.isSeed) + const updatedSteps = getSteps(activeKey, config, curve, poolData, formValues, formStatus, seed.isSeed) setSteps(updatedSteps) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [chainId, poolId, signerAddress, formValues, formStatus]) + }, [config, chainId, poolId, signerAddress, formValues, formStatus]) const isDisabled = seed.isSeed === null || seed.isSeed || formStatus.formProcessing - const balGauge = userPoolBalances?.gauge as string + + const { address: userAddress } = useConnection() + const { gaugeTokenBalance, isLoading: gaugeTokenLoading } = usePoolTokenDepositBalances({ + chainId, + userAddress, + poolId, + }) return ( <> {/* input fields */} +balGauge} + balanceLoading={gaugeTokenLoading} + balance={gaugeTokenBalance ?? '0'} + hasError={+formValues.stakedLpToken > +(gaugeTokenBalance ?? '0')} haveSigner={haveSigner} handleAmountChange={useCallback((stakedLpToken) => updateFormValues({ stakedLpToken }), [updateFormValues])} disabled={isDisabled} @@ -159,7 +172,6 @@ const FormUnstake = ({ curve, poolData, poolDataCacheOrApi, routerParams, seed, loading={!chainId || !steps.length || !seed.loaded} routerParams={routerParams} seed={seed} - userPoolBalances={userPoolBalances} > {formStatus.error && updateFormValues({})} />} {txInfoBar} diff --git a/apps/main/src/dex/components/PagePool/Withdraw/components/FormWithdraw.tsx b/apps/main/src/dex/components/PagePool/Withdraw/components/FormWithdraw.tsx index 645fbfba8c..9877a96a5b 100644 --- a/apps/main/src/dex/components/PagePool/Withdraw/components/FormWithdraw.tsx +++ b/apps/main/src/dex/components/PagePool/Withdraw/components/FormWithdraw.tsx @@ -1,6 +1,8 @@ import lodash from 'lodash' import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { styled, css } from 'styled-components' +import { useConnection, type Config } from 'wagmi' +import { useConfig } from 'wagmi' import AlertFormError from '@/dex/components/AlertFormError' import AlertSlippage from '@/dex/components/AlertSlippage' import DetailInfoEstGas from '@/dex/components/DetailInfoEstGas' @@ -18,6 +20,7 @@ import { amountsDescription, DEFAULT_ESTIMATED_GAS, DEFAULT_SLIPPAGE } from '@/d import type { FormStatus, FormValues, StepKey } from '@/dex/components/PagePool/Withdraw/types' import { resetFormAmounts } from '@/dex/components/PagePool/Withdraw/utils' import { useNetworks } from '@/dex/entities/networks' +import { usePoolTokenDepositBalances } from '@/dex/hooks/usePoolTokenDepositBalances' import useStore from '@/dex/store/useStore' import { CurveApi, Pool, PoolData } from '@/dex/types/main.types' import Box from '@ui/Box' @@ -43,7 +46,6 @@ const FormWithdraw = ({ routerParams, seed, tokensMapper, - userPoolBalances, }: TransferProps) => { const isSubscribed = useRef(false) @@ -69,12 +71,22 @@ const FormWithdraw = ({ const poolId = poolData?.pool?.id const haveSigner = !!signerAddress + const { address: userAddress } = useConnection() + const { lpTokenBalance, isLoading: lpTokenBalanceLoading } = usePoolTokenDepositBalances({ + chainId, + userAddress, + poolId, + }) + + const config = useConfig() + const updateFormValues = useCallback( (updatedFormValues: Partial, updatedMaxSlippage: string | null) => { setTxInfoBar(null) setSlippageConfirmed(false) void setFormValues( 'WITHDRAW', + config, curve, poolDataCacheOrApi.pool.id, poolData, @@ -84,25 +96,32 @@ const FormWithdraw = ({ updatedMaxSlippage || maxSlippage, ) }, - [setFormValues, curve, poolDataCacheOrApi.pool.id, poolData, seed.isSeed, maxSlippage], + [setFormValues, config, curve, poolDataCacheOrApi.pool.id, poolData, seed.isSeed, maxSlippage], ) const handleApproveClick = useCallback( - async (activeKey: string, curve: CurveApi, pool: Pool, formValues: FormValues) => { + async (activeKey: string, config: Config, curve: CurveApi, pool: Pool, formValues: FormValues) => { const notifyMessage = t`Please approve spending your LP Tokens.` const { dismiss } = notify(notifyMessage, 'pending') - await fetchStepApprove(activeKey, curve, 'WITHDRAW', pool, formValues) + await fetchStepApprove(activeKey, config, curve, 'WITHDRAW', pool, formValues) if (typeof dismiss === 'function') dismiss() }, [fetchStepApprove], ) const handleWithdrawClick = useCallback( - async (activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string) => { + async ( + activeKey: string, + config: Config, + curve: CurveApi, + poolData: PoolData, + formValues: FormValues, + maxSlippage: string, + ) => { const tokenText = amountsDescription(formValues.amounts) const notifyMessage = t`Please confirm withdrawal of ${formValues.lpToken} LP Tokens at max ${maxSlippage}% slippage.` const { dismiss } = notify(notifyMessage, 'pending') - const resp = await fetchStepWithdraw(activeKey, curve, poolData, formValues, maxSlippage) + const resp = await fetchStepWithdraw(activeKey, config, curve, poolData, formValues, maxSlippage) if (isSubscribed.current && resp && resp.hash && resp.activeKey === activeKey && network) { const TxDescription = t`Withdrew ${formValues.lpToken} LP Tokens for ${tokenText}` @@ -116,6 +135,7 @@ const FormWithdraw = ({ const getSteps = useCallback( ( activeKey: string, + config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, @@ -127,8 +147,8 @@ const FormWithdraw = ({ isSeed: boolean, ) => { const haveFormLpToken = +formValues.lpToken > 0 - const haveUserLpToken = typeof userPoolBalances !== 'undefined' && +userPoolBalances.lpToken > 0 - const isValidLpToken = haveUserLpToken && haveFormLpToken && +userPoolBalances.lpToken >= +formValues.lpToken + const haveUserLpToken = lpTokenBalance != null && +lpTokenBalance > 0 + const isValidLpToken = haveUserLpToken && haveFormLpToken && +lpTokenBalance >= +formValues.lpToken let isValid = haveSigner && !isSeed && isValidLpToken && !!formValues.selected && !formStatus.error if (isValid && (formValues.selected === 'token' || formValues.selected === 'imbalance')) { @@ -144,7 +164,7 @@ const FormWithdraw = ({ status: getStepStatus(isApproved, formStatus.step === 'APPROVAL', isValid), type: 'action', content: isApproved ? t`Spending Approved` : t`Approve Spending`, - onClick: () => handleApproveClick(activeKey, curve, poolData.pool, formValues), + onClick: () => handleApproveClick(activeKey, config, curve, poolData.pool, formValues), }, WITHDRAW: { key: 'WITHDRAW', @@ -170,13 +190,13 @@ const FormWithdraw = ({ onClick: () => setSlippageConfirmed(false), }, primaryBtnProps: { - onClick: () => handleWithdrawClick(activeKey, curve, poolData, formValues, maxSlippage), + onClick: () => handleWithdrawClick(activeKey, config, curve, poolData, formValues, maxSlippage), disabled: !slippageConfirmed, }, primaryBtnLabel: 'Withdraw anyway', }, } - : { onClick: () => handleWithdrawClick(activeKey, curve, poolData, formValues, maxSlippage) }), + : { onClick: () => handleWithdrawClick(activeKey, config, curve, poolData, formValues, maxSlippage) }), }, } @@ -190,7 +210,7 @@ const FormWithdraw = ({ return stepsKey.map((key) => stepsObj[key]) }, - [handleApproveClick, handleWithdrawClick, haveSigner, userPoolBalances], + [handleApproveClick, handleWithdrawClick, haveSigner, lpTokenBalance], ) // onMount @@ -230,6 +250,7 @@ const FormWithdraw = ({ if (curve && poolData && seed.isSeed !== null) { const updatedSteps = getSteps( activeKey, + config, curve, poolData, formValues, @@ -244,6 +265,7 @@ const FormWithdraw = ({ } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ + config, chainId, poolId, signerAddress, @@ -289,7 +311,6 @@ const FormWithdraw = ({ const haveSlippage = formValues.selected !== 'lpToken' const activeStep = haveSigner ? getActiveStep(steps) : null const isDisabled = seed.isSeed === null || seed.isSeed || formStatus.formProcessing - const balLpToken = (userPoolBalances?.lpToken as string) ?? '0' const handleAmountChange = useCallback( (val: string, idx: number) => { @@ -305,9 +326,9 @@ const FormWithdraw = ({ <> +balLpToken} + balance={lpTokenBalance ?? '0'} + balanceLoading={lpTokenBalanceLoading} + hasError={haveSigner && +formValues.lpToken > +(lpTokenBalance ?? '0')} haveSigner={haveSigner} handleAmountChange={useCallback( (lpToken: string) => @@ -477,7 +498,6 @@ const FormWithdraw = ({ loading={!chainId || !steps.length || !seed.loaded} routerParams={routerParams} seed={seed} - userPoolBalances={userPoolBalances} > {txInfoBar} diff --git a/apps/main/src/dex/components/PagePool/components/TransferActions.tsx b/apps/main/src/dex/components/PagePool/components/TransferActions.tsx index 19e9ce0b7f..c25f320c6d 100644 --- a/apps/main/src/dex/components/PagePool/components/TransferActions.tsx +++ b/apps/main/src/dex/components/PagePool/components/TransferActions.tsx @@ -1,9 +1,11 @@ import { ReactNode } from 'react' +import { useConnection } from 'wagmi' import FormConnectWallet from '@/dex/components/FormConnectWallet' import AlertSeedAmounts from '@/dex/components/PagePool/components/AlertSeedAmounts' import type { TransferProps } from '@/dex/components/PagePool/types' import { useSignerAddress } from '@/dex/entities/signer' import { usePoolIdByAddressOrId } from '@/dex/hooks/usePoolIdByAddressOrId' +import { usePoolTokenBalances } from '@/dex/hooks/usePoolTokenBalances' import useTokenAlert from '@/dex/hooks/useTokenAlert' import useStore from '@/dex/store/useStore' import { getChainPoolIdActiveKey } from '@/dex/utils' @@ -17,27 +19,43 @@ const TransferActions = ({ loading, poolData, routerParams, - userPoolBalances, }: { loading?: boolean children: ReactNode -} & Pick) => { +} & Pick) => { const { data: signerAddress } = useSignerAddress() const { rChainId, rPoolIdOrAddress } = routerParams const poolId = usePoolIdByAddressOrId({ chainId: rChainId, poolIdOrAddress: rPoolIdOrAddress }) const alert = useTokenAlert(poolData?.tokenAddressesAll ?? []) const { isHydrated } = useCurve() const currencyReserves = useStore((state) => state.pools.currencyReserves[getChainPoolIdActiveKey(rChainId, poolId)]) - const walletBalancesLoading = useStore((state) => state.user.walletBalancesLoading) + + const { address: userAddress } = useConnection() + const { + wrappedCoinsBalances, + underlyingCoinsBalances, + isLoading: walletBalancesLoading, + } = usePoolTokenBalances({ + chainId: rChainId, + userAddress, + poolId, + }) const isLoading = - loading || typeof poolData === 'undefined' || typeof currencyReserves === 'undefined' || !isHydrated || !seed.loaded + loading || + typeof poolData === 'undefined' || + typeof currencyReserves === 'undefined' || + !isHydrated || + !seed.loaded || + walletBalancesLoading + + const hasWalletBalances = Object.keys(wrappedCoinsBalances).length && Object.keys(underlyingCoinsBalances).length return ( <> {alert && !alert.isInformationOnly ? {alert.message} : null} - {signerAddress && !isLoading && !walletBalancesLoading && typeof userPoolBalances === 'undefined' && ( + {signerAddress && !isLoading && !hasWalletBalances && ( {t`Unable to get wallet balances`} )} {children} diff --git a/apps/main/src/dex/components/PagePool/index.tsx b/apps/main/src/dex/components/PagePool/index.tsx index 7f40d4d304..c5787e367b 100644 --- a/apps/main/src/dex/components/PagePool/index.tsx +++ b/apps/main/src/dex/components/PagePool/index.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { styled } from 'styled-components' import { type Address, isAddressEqual } from 'viem' +import { useConfig } from 'wagmi' import CampaignRewardsBanner from '@/dex/components/PagePool/components/CampaignRewardsBanner' import Deposit from '@/dex/components/PagePool/Deposit' import PoolInfoData from '@/dex/components/PagePool/PoolDetails/ChartOhlcWrapper' @@ -60,8 +61,6 @@ const Transfer = (pageTransferProps: PageTransferProps) => { const { tokensMapper } = useTokensMapper(rChainId) const userPoolActiveKey = curve && poolId ? getUserPoolActiveKey(curve, poolId) : '' const chainIdPoolId = getChainPoolIdActiveKey(rChainId, poolId) - const userPoolBalances = useStore((state) => state.user.walletBalances[userPoolActiveKey]) - const userPoolBalancesLoading = useStore((state) => state.user.walletBalancesLoading) const currencyReserves = useStore((state) => state.pools.currencyReserves[chainIdPoolId]) const isMdUp = useLayoutStore((state) => state.isMdUp) const fetchUserPoolInfo = useStore((state) => state.user.fetchUserPoolInfo) @@ -148,11 +147,12 @@ const Transfer = (pageTransferProps: PageTransferProps) => { }, [poolData?.pool?.id, currencyReserves?.total]) // fetch user pool info + const config = useConfig() useEffect(() => { if (curve && poolId && signerAddress) { - void fetchUserPoolInfo(curve, poolId) + void fetchUserPoolInfo(config, curve, poolId) } - }, [rChainId, poolId, signerAddress, curve, fetchUserPoolInfo]) + }, [rChainId, poolId, signerAddress, config, curve, fetchUserPoolInfo]) const isRewardsDistributor = useMemo( () => @@ -237,8 +237,6 @@ const Transfer = (pageTransferProps: PageTransferProps) => { maxSlippage={maxSlippage} seed={seed} tokensMapper={tokensMapper} - userPoolBalances={userPoolBalances} - userPoolBalancesLoading={userPoolBalancesLoading} /> )} @@ -252,8 +250,6 @@ const Transfer = (pageTransferProps: PageTransferProps) => { maxSlippage={maxSlippage} seed={seed} tokensMapper={tokensMapper} - userPoolBalances={userPoolBalances} - userPoolBalancesLoading={userPoolBalancesLoading} /> ) : rFormType === 'withdraw' ? ( { maxSlippage={maxSlippage} seed={seed} tokensMapper={tokensMapper} - userPoolBalances={userPoolBalances} - userPoolBalancesLoading={userPoolBalancesLoading} /> ) : rFormType === 'manage-gauge' ? ( poolData ? ( @@ -308,7 +302,6 @@ const Transfer = (pageTransferProps: PageTransferProps) => { poolDataCacheOrApi={poolDataCacheOrApi} routerParams={routerParams} tokensMapper={tokensMapper} - userPoolBalances={userPoolBalances} /> )} {poolInfoTab === 'pool' && ( diff --git a/apps/main/src/dex/components/PagePool/types.ts b/apps/main/src/dex/components/PagePool/types.ts index 62a5412674..bf42aea046 100644 --- a/apps/main/src/dex/components/PagePool/types.ts +++ b/apps/main/src/dex/components/PagePool/types.ts @@ -1,5 +1,4 @@ import { - Balances, CurveApi, ChainId, RFormType, @@ -46,7 +45,5 @@ export type TransferProps = { poolAlert: PoolAlert | null maxSlippage: string seed: Seed - userPoolBalances: Balances | undefined - userPoolBalancesLoading: boolean tokensMapper: TokensMapper } & PageTransferProps diff --git a/apps/main/src/dex/hooks/usePoolTokenBalances.ts b/apps/main/src/dex/hooks/usePoolTokenBalances.ts new file mode 100644 index 0000000000..b117650bce --- /dev/null +++ b/apps/main/src/dex/hooks/usePoolTokenBalances.ts @@ -0,0 +1,69 @@ +import type { Address } from 'viem' +import type { Config } from 'wagmi' +import { requireLib, useCurve, type CurveApi } from '@ui-kit/features/connect-wallet' +import { fetchTokenBalance, useTokenBalances } from '@ui-kit/queries/token-balance.query' + +/** Hook to get all pool token balances for underlying tokens */ +export function usePoolTokenBalances( + { + chainId, + userAddress, + poolId, + }: { + chainId: number | null | undefined + userAddress: Address | null | undefined + poolId: string | null | undefined + }, + enabled = true, +) { + const { curveApi, isHydrated } = useCurve() + const pool = chainId && userAddress && poolId && curveApi && isHydrated ? curveApi.getPool(poolId) : undefined + + const { data: wrappedCoinsBalances, isLoading: wrappedCoinsLoading } = useTokenBalances( + { + chainId, + userAddress, + tokenAddresses: pool?.wrappedCoinAddresses as Address[], + }, + enabled && pool != null, + ) + + const { data: underlyingCoinsBalances, isLoading: underlyingCoinsLoading } = useTokenBalances( + { + chainId, + userAddress, + tokenAddresses: pool?.underlyingCoinAddresses as Address[], + }, + enabled && pool != null, + ) + + const isLoading = wrappedCoinsLoading || underlyingCoinsLoading + + return { + wrappedCoinsBalances, + underlyingCoinsBalances, + isLoading, + } +} + +/** Temporary imperative function for some zustand slices to fetch all pool token balances */ +export async function fetchPoolTokenBalances(config: Config, curve: CurveApi, poolId: string) { + const pool = requireLib('curveApi').getPool(poolId) + const chainId = curve.chainId + const userAddress = curve.signerAddress as Address + + const balances = await Promise.allSettled([ + ...(pool.wrappedCoinAddresses as Address[]).map((tokenAddress) => + fetchTokenBalance(config, { chainId, userAddress, tokenAddress }).then( + (balance) => [tokenAddress, balance] as const, + ), + ), + ...(pool.underlyingCoinAddresses as Address[]).map((tokenAddress) => + fetchTokenBalance(config, { chainId, userAddress, tokenAddress }).then( + (balance) => [tokenAddress, balance] as const, + ), + ), + ]) + + return Object.fromEntries(balances.filter((x) => x.status === 'fulfilled').map((x) => x.value)) +} diff --git a/apps/main/src/dex/hooks/usePoolTokenDepositBalances.ts b/apps/main/src/dex/hooks/usePoolTokenDepositBalances.ts new file mode 100644 index 0000000000..d670cdc259 --- /dev/null +++ b/apps/main/src/dex/hooks/usePoolTokenDepositBalances.ts @@ -0,0 +1,73 @@ +import { type Address, isAddressEqual, zeroAddress } from 'viem' +import type { Config } from 'wagmi' +import { requireLib, useCurve, type CurveApi } from '@ui-kit/features/connect-wallet' +import { fetchTokenBalance, useTokenBalance } from '@ui-kit/queries/token-balance.query' +import { isValidAddress } from '../utils' + +/** Hook to get lp token and possible gauge token balances */ +export function usePoolTokenDepositBalances( + { + chainId, + userAddress, + poolId, + }: { + chainId: number | null | undefined + userAddress: Address | null | undefined + poolId: string | null | undefined + }, + enabled = true, +) { + const { curveApi, isHydrated } = useCurve() + const pool = chainId && userAddress && poolId && curveApi && isHydrated ? curveApi.getPool(poolId) : undefined + + const { data: lpTokenBalance, isLoading: lpTokenLoading } = useTokenBalance( + { + chainId, + userAddress, + tokenAddress: pool?.lpToken as Address, + }, + enabled && pool != null, + ) + + const hasGauge = pool?.gauge.address != null && !isAddressEqual(pool.gauge.address as Address, zeroAddress) + const { data: gaugeTokenBalance, isLoading: gaugeTokenLoading } = useTokenBalance( + { + chainId, + userAddress, + tokenAddress: pool?.gauge.address as Address, + }, + enabled && hasGauge, + ) + + const isLoading = lpTokenLoading || gaugeTokenLoading + + return { + isLoading, + lpTokenBalance, + hasGauge, + gaugeTokenBalance, + } +} + +/** Temporary imperative function for some zustand slices */ +export function fetchPoolLpTokenBalance(config: Config, curve: CurveApi, poolId: string) { + const pool = requireLib('curveApi').getPool(poolId) + + return fetchTokenBalance(config, { + chainId: curve?.chainId, + userAddress: curve.signerAddress as Address, + tokenAddress: pool.lpToken as Address, + }) +} + +/** Temporary imperative function for some zustand slices */ +export function fetchPoolGaugeTokenBalance(config: Config, curve: CurveApi, poolId: string) { + const pool = requireLib('curveApi').getPool(poolId) + if (!isValidAddress(pool.gauge.address)) return Promise.resolve('0') + + return fetchTokenBalance(config, { + chainId: curve?.chainId, + userAddress: curve.signerAddress as Address, + tokenAddress: pool.lpToken as Address, + }) +} diff --git a/apps/main/src/dex/lib/curvejs.ts b/apps/main/src/dex/lib/curvejs.ts index 1fc9c9b898..7a9c85070b 100644 --- a/apps/main/src/dex/lib/curvejs.ts +++ b/apps/main/src/dex/lib/curvejs.ts @@ -1222,26 +1222,6 @@ const wallet = { } return fetchedUserClaimable }, - poolWalletBalances: async (curve: CurveApi, poolId: string) => { - log('poolUserPoolBalances', curve?.signerAddress, poolId) - const p = curve.getPool(poolId) - const [wrappedCoinBalances, underlyingBalances, lpTokenBalances] = await Promise.all([ - p.wallet.wrappedCoinBalances(), - p.wallet.underlyingCoinBalances(), - p.wallet.lpTokenBalances(), - ]) - const balances: { [tokenAddress: string]: string } = {} - - Object.entries({ - ...wrappedCoinBalances, - ...underlyingBalances, - ...lpTokenBalances, - }).forEach(([address, balance]) => { - balances[address] = new BN(balance as string).toString() - }) - - return balances - }, userClaimableFees: async (curve: CurveApi, activeKey: string, walletAddress: string) => { log('userClaimableFees', activeKey, walletAddress) const resp = { activeKey, '3CRV': '', crvUSD: '', error: '' } diff --git a/apps/main/src/dex/store/createPoolDepositSlice.ts b/apps/main/src/dex/store/createPoolDepositSlice.ts index 0389ceca20..5700c9a0ee 100644 --- a/apps/main/src/dex/store/createPoolDepositSlice.ts +++ b/apps/main/src/dex/store/createPoolDepositSlice.ts @@ -1,5 +1,6 @@ import lodash from 'lodash' import { ethAddress } from 'viem' +import type { Config } from 'wagmi' import type { StoreApi } from 'zustand' import type { FormLpTokenExpected, @@ -22,10 +23,8 @@ import { } from '@/dex/components/PagePool/utils' import type { Amount } from '@/dex/components/PagePool/utils' import curvejsApi from '@/dex/lib/curvejs' -import { getUserPoolActiveKey } from '@/dex/store/createUserSlice' import type { State } from '@/dex/store/useStore' import { - Balances, ChainId, CurveApi, FnStepApproveResponse, @@ -41,6 +40,8 @@ import { t } from '@ui-kit/lib/i18n' import { fetchGasInfoAndUpdateLib } from '@ui-kit/lib/model/entities/gas-info' import { setMissingProvider } from '@ui-kit/utils/store.util' import { fetchNetworks } from '../entities/networks' +import { fetchPoolTokenBalances } from '../hooks/usePoolTokenBalances' +import { fetchPoolLpTokenBalance } from '../hooks/usePoolTokenDepositBalances' type StateKey = keyof typeof DEFAULT_STATE const { cloneDeep } = lodash @@ -62,20 +63,19 @@ const sliceKey = 'poolDeposit' // prettier-ignore export type PoolDepositSlice = { [sliceKey]: SliceState & { - fetchUserPoolWalletBalances(curve: CurveApi, poolId: string): Promise fetchExpected(activeKey: string, chainId: ChainId, formType: FormType, pool: Pool, formValues: FormValues): Promise fetchMaxAmount(activeKey: string, chainId: ChainId, pool: Pool, loadMaxAmount: LoadMaxAmount): Promise fetchSeedAmount(poolData: PoolData, formValues: FormValues): Promise> fetchSlippage(activeKey: string, chainId: ChainId, formType: FormType, pool: Pool, formValues: FormValues, maxSlippage: string): Promise - setFormValues(formType: FormType, curve: CurveApi | null, poolId: string, poolData: PoolData | undefined, formValues: Partial, loadMaxAmount: LoadMaxAmount | null, isSeed: boolean | null, maxSlippage: string): Promise + setFormValues(formType: FormType, config: Config, curve: CurveApi | null, poolId: string, poolData: PoolData | undefined, formValues: Partial, loadMaxAmount: LoadMaxAmount | null, isSeed: boolean | null, maxSlippage: string): Promise // steps fetchEstGasApproval(activeKey: string, chainId: ChainId, formType: FormType, pool: Pool): Promise fetchStepApprove(activeKey: string, curve: CurveApi, formType: FormType, pool: Pool, formValues: FormValues): Promise - fetchStepDeposit(activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string): Promise - fetchStepDepositStake(activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string): Promise + fetchStepDeposit(activeKey: string, config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string): Promise + fetchStepDepositStake(activeKey: string, config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string): Promise fetchStepStakeApprove(activeKey: string, curve: CurveApi, formType: FormType, pool: Pool, formValues: FormValues): Promise - fetchStepStake(activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues): Promise + fetchStepStake(activeKey: string, config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues): Promise setStateByActiveKey(key: StateKey, activeKey: string, value: T): void setStateByKey(key: StateKey, value: T): void @@ -103,10 +103,6 @@ const createPoolDepositSlice = ( [sliceKey]: { ...DEFAULT_STATE, - fetchUserPoolWalletBalances: async (curve, poolId) => { - const userPoolActiveKey = getUserPoolActiveKey(curve, poolId) - return get().user.walletBalances[userPoolActiveKey] ?? (await get().user.fetchUserPoolInfo(curve, poolId, true)) - }, fetchExpected: async (activeKey, chainId, formType, pool, formValues) => { const { amounts, isWrapped } = formValues const depositExpectedFn = @@ -219,7 +215,17 @@ const createPoolDepositSlice = ( }) } }, - setFormValues: async (formType, curve, poolId, poolData, updatedFormValues, loadMaxAmount, isSeed, maxSlippage) => { + setFormValues: async ( + formType, + config, + curve, + poolId, + poolData, + updatedFormValues, + loadMaxAmount, + isSeed, + maxSlippage, + ) => { // stored values const storedActiveKey = get()[sliceKey].activeKey const storedFormValues = get()[sliceKey].formValues @@ -298,7 +304,7 @@ const createPoolDepositSlice = ( if (signerAddress) { // validate input amounts with wallet - const balances = await get()[sliceKey].fetchUserPoolWalletBalances(curve, pool.id) + const balances = await fetchPoolTokenBalances(config, curve, pool.id) const amountsError = getAmountsError(cFormValues.amounts, balances) if (amountsError) { @@ -319,8 +325,8 @@ const createPoolDepositSlice = ( } else if (formType === 'STAKE') { if (!!signerAddress && +cFormValues.lpToken > 0) { // validate lpToken balances - const balances = await get()[sliceKey].fetchUserPoolWalletBalances(curve, pool.id) - const lpTokenError = +cFormValues.lpToken > +(balances.lpToken ?? '0') ? 'lpToken-too-much' : '' + const lpTokenBalance = await fetchPoolLpTokenBalance(config, curve, pool.id) + const lpTokenError = +cFormValues.lpToken > +lpTokenBalance ? 'lpToken-too-much' : '' if (lpTokenError) { get()[sliceKey].setStateByKey('formStatus', { @@ -414,7 +420,7 @@ const createPoolDepositSlice = ( return resp } }, - fetchStepDeposit: async (activeKey, curve, poolData, formValues, maxSlippage) => { + fetchStepDeposit: async (activeKey, config, curve, poolData, formValues, maxSlippage) => { const { provider } = useWallet.getState() if (!provider) return setMissingProvider(get()[sliceKey]) @@ -452,13 +458,16 @@ const createPoolDepositSlice = ( }) // re-fetch data - await Promise.all([get().user.fetchUserPoolInfo(curve, pool.id), get().pools.fetchPoolStats(curve, poolData)]) + await Promise.all([ + get().user.fetchUserPoolInfo(config, curve, pool.id), + get().pools.fetchPoolStats(curve, poolData), + ]) } return resp } }, - fetchStepDepositStake: async (activeKey, curve, poolData, formValues, maxSlippage) => { + fetchStepDepositStake: async (activeKey, config, curve, poolData, formValues, maxSlippage) => { const { provider } = useWallet.getState() if (!provider) return setMissingProvider(get()[sliceKey]) @@ -494,7 +503,10 @@ const createPoolDepositSlice = ( }) // re-fetch data - await Promise.all([get().user.fetchUserPoolInfo(curve, pool.id), get().pools.fetchPoolStats(curve, poolData)]) + await Promise.all([ + get().user.fetchUserPoolInfo(config, curve, pool.id), + get().pools.fetchPoolStats(curve, poolData), + ]) } return resp @@ -533,7 +545,7 @@ const createPoolDepositSlice = ( return resp } }, - fetchStepStake: async (activeKey, curve, poolData, formValues) => { + fetchStepStake: async (activeKey, config, curve, poolData, formValues) => { const { provider } = useWallet.getState() if (!provider) return setMissingProvider(get()[sliceKey]) @@ -562,7 +574,10 @@ const createPoolDepositSlice = ( }) // re-fetch data - await Promise.all([get().user.fetchUserPoolInfo(curve, pool.id), get().pools.fetchPoolStats(curve, poolData)]) + await Promise.all([ + get().user.fetchUserPoolInfo(config, curve, pool.id), + get().pools.fetchPoolStats(curve, poolData), + ]) } return resp diff --git a/apps/main/src/dex/store/createPoolSwapSlice.ts b/apps/main/src/dex/store/createPoolSwapSlice.ts index f4f14a9fa1..296b97d6e0 100644 --- a/apps/main/src/dex/store/createPoolSwapSlice.ts +++ b/apps/main/src/dex/store/createPoolSwapSlice.ts @@ -1,6 +1,7 @@ import { Contract, Interface, JsonRpcProvider } from 'ethers' import lodash from 'lodash' import { ethAddress } from 'viem' +import type { Config } from 'wagmi' import type { StoreApi } from 'zustand' import type { ExchangeOutput, FormStatus, FormValues, RouterSwapOutput } from '@/dex/components/PagePool/Swap/types' import { @@ -14,7 +15,6 @@ import type { RoutesAndOutput, RoutesAndOutputModal } from '@/dex/components/Pag import curvejsApi from '@/dex/lib/curvejs' import type { State } from '@/dex/store/useStore' import { - Balances, ChainId, CurrencyReserves, CurveApi, @@ -30,6 +30,7 @@ import { useWallet } from '@ui-kit/features/connect-wallet' import { fetchGasInfoAndUpdateLib } from '@ui-kit/lib/model/entities/gas-info' import { setMissingProvider } from '@ui-kit/utils/store.util' import { fetchNetworks } from '../entities/networks' +import { fetchPoolTokenBalances } from '../hooks/usePoolTokenBalances' type StateKey = keyof typeof DEFAULT_STATE const { cloneDeep } = lodash @@ -51,15 +52,14 @@ const sliceKey = 'poolSwap' export type PoolSwapSlice = { [sliceKey]: SliceState & { fetchIgnoreExchangeRateCheck(curve: CurveApi, pool: Pool): Promise - fetchExchangeOutput(activeKey: string, storedActiveKey: string, curve: CurveApi, pool: Pool, formValues: FormValues, maxSlippage: string): Promise - fetchTokenWalletBalance(curve: CurveApi, poolId: string, tokenAddress: string): Promise - fetchMaxAmount(activeKey: string, curve: CurveApi, pool: Pool, formValues: FormValues, maxSlippage: string): Promise - setFormValues(curve: CurveApi | null, poolId: string, poolData: PoolData | undefined, updatedFormValues: Partial, isGetMaxFrom: boolean | null, isSeed: boolean | null, maxSlippage: string): Promise + fetchExchangeOutput(activeKey: string, storedActiveKey: string, config: Config, curve: CurveApi, pool: Pool, formValues: FormValues, maxSlippage: string): Promise + fetchMaxAmount(activeKey: string, config: Config, curve: CurveApi, pool: Pool, formValues: FormValues, maxSlippage: string): Promise + setFormValues(config: Config, curve: CurveApi | null, poolId: string, poolData: PoolData | undefined, updatedFormValues: Partial, isGetMaxFrom: boolean | null, isSeed: boolean | null, maxSlippage: string): Promise // steps fetchEstGasApproval(activeKey: string, chainId: ChainId, pool: Pool, formValues: FormValues, maxSlippage: string): Promise - fetchStepApprove(activeKey: string, curve: CurveApi, pool: Pool, formValues: FormValues, globalMaxSlippage: string): Promise - fetchStepSwap(activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string): Promise + fetchStepApprove(activeKey: string, config: Config, curve: CurveApi, pool: Pool, formValues: FormValues, globalMaxSlippage: string): Promise + fetchStepSwap(activeKey: string, config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string): Promise setStateByActiveKey(key: StateKey, activeKey: string, value: T): void setStateByKey(key: StateKey, value: T): void @@ -119,7 +119,7 @@ const createPoolSwapSlice = (set: StoreApi['setState'], get: StoreApi { + fetchExchangeOutput: async (activeKey, storedActiveKey: string, config, curve, pool, formValues, maxSlippage) => { const state = get() const sliceState = state[sliceKey] @@ -162,7 +162,7 @@ const createPoolSwapSlice = (set: StoreApi['setState'], get: StoreApi= +cFormValues.fromAmount ? '' : 'too-much' @@ -211,19 +211,16 @@ const createPoolSwapSlice = (set: StoreApi['setState'], get: StoreApi { - const storedWalletBalance = get().user.walletBalances[tokenAddress] - return storedWalletBalance ?? (await get().user.fetchUserPoolInfo(curve, poolId, true)) - }, fetchMaxAmount: async ( activeKey: string, + config: Config, curve: CurveApi, pool: Pool, formValues: FormValues, maxSlippage: string, ) => { // stored values - const userPoolBalances = await get()[sliceKey].fetchTokenWalletBalance(curve, pool.id, formValues.fromAddress) + const userPoolBalances = await fetchPoolTokenBalances(config, curve, pool.id) const walletFromBalance = userPoolBalances[formValues.fromAddress] const networks = await fetchNetworks() const { basePlusPriority } = await fetchGasInfoAndUpdateLib({ @@ -231,7 +228,7 @@ const createPoolSwapSlice = (set: StoreApi['setState'], get: StoreApi['setState'], get: StoreApi { + setFormValues: async (config, curve, poolId, poolData, updatedFormValues, isGetMaxFrom, isSeed, maxSlippage) => { // stored values const storedActiveKey = get()[sliceKey].activeKey const storedFormStatus = get()[sliceKey].formStatus @@ -293,7 +290,14 @@ const createPoolSwapSlice = (set: StoreApi['setState'], get: StoreApi['setState'], get: StoreApi['setState'], get: StoreApi { + fetchStepApprove: async (activeKey, config, curve, pool, formValues, maxSlippage) => { const { provider } = useWallet.getState() if (!provider) return setMissingProvider(get()[sliceKey]) @@ -378,13 +390,21 @@ const createPoolSwapSlice = (set: StoreApi['setState'], get: StoreApi { + fetchStepSwap: async (activeKey, config, curve, poolData, formValues, maxSlippage) => { const { provider } = useWallet.getState() if (!provider) return setMissingProvider(get()[sliceKey]) @@ -438,7 +458,7 @@ const createPoolSwapSlice = (set: StoreApi['setState'], get: StoreApi fetchWithdrawCustom(props: FetchWithdrawProps): Promise fetchClaimable(activeKey: string, chainId: ChainId, pool: Pool): Promise - setFormValues(formType: FormType, curve: CurveApi | null, poolId: string, poolData: PoolData | undefined, updatedFormValues: Partial, loadMaxAmount: LoadMaxAmount | null, isSeed: boolean | null, maxSlippage: string): Promise + setFormValues(formType: FormType, config: Config, curve: CurveApi | null, poolId: string, poolData: PoolData | undefined, updatedFormValues: Partial, loadMaxAmount: LoadMaxAmount | null, isSeed: boolean | null, maxSlippage: string): Promise // steps - fetchEstGasApproval(activeKey: string, curve: CurveApi, formType: FormType, pool: Pool, formValues: FormValues): Promise - fetchStepApprove(activeKey: string, curve: CurveApi, formType: FormType, pool: Pool, formValues: FormValues): Promise - fetchStepWithdraw(activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string): Promise - fetchStepUnstake(activeKey: string, curve: CurveApi, poolData: PoolData, formValues: FormValues): Promise - fetchStepClaim(activeKey: string, curve: CurveApi, poolData: PoolData): Promise + fetchEstGasApproval(activeKey: string, config: Config, curve: CurveApi, formType: FormType, pool: Pool, formValues: FormValues): Promise + fetchStepApprove(activeKey: string, config: Config, curve: CurveApi, formType: FormType, pool: Pool, formValues: FormValues): Promise + fetchStepWithdraw(activeKey: string, config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues, maxSlippage: string): Promise + fetchStepUnstake(activeKey: string, config: Config, curve: CurveApi, poolData: PoolData, formValues: FormValues): Promise + fetchStepClaim(activeKey: string, config: Config, curve: CurveApi, poolData: PoolData): Promise setStateByActiveKey(key: StateKey, activeKey: string, value: T): void setStateByKey(key: StateKey, value: T): void @@ -88,7 +91,7 @@ const createPoolWithdrawSlice = ( ...DEFAULT_STATE, fetchWithdrawToken: async (props) => { - const { storedActiveKey, curve, formType, poolData, formValues, maxSlippage } = props + const { storedActiveKey, config, curve, formType, poolData, formValues, maxSlippage } = props let activeKey = props.activeKey const cFormValues = cloneDeep(formValues) const { pool } = poolData @@ -144,11 +147,11 @@ const createPoolWithdrawSlice = ( // get gas if (signerAddress) { - void get()[sliceKey].fetchEstGasApproval(activeKey, curve, formType, pool, cFormValues) + void get()[sliceKey].fetchEstGasApproval(activeKey, config, curve, formType, pool, cFormValues) } }, fetchWithdrawLpToken: async (props) => { - const { storedActiveKey, curve, formType, poolData, formValues, maxSlippage } = props + const { storedActiveKey, config, curve, formType, poolData, formValues, maxSlippage } = props let activeKey = props.activeKey const cFormValues = cloneDeep(formValues) const { signerAddress } = curve @@ -192,12 +195,12 @@ const createPoolWithdrawSlice = ( // get gas if (signerAddress) { - void get()[sliceKey].fetchEstGasApproval(activeKey, curve, formType, pool, cFormValues) + void get()[sliceKey].fetchEstGasApproval(activeKey, config, curve, formType, pool, cFormValues) } } }, fetchWithdrawCustom: async (props) => { - const { storedActiveKey, curve, formType, poolData, formValues, maxSlippage } = props + const { storedActiveKey, config, curve, formType, poolData, formValues, maxSlippage } = props let activeKey = props.activeKey const cFormValues = cloneDeep(formValues) const { pool } = poolData @@ -283,7 +286,7 @@ const createPoolWithdrawSlice = ( // get gas if (signerAddress) { - void get()[sliceKey].fetchEstGasApproval(activeKey, curve, formType, pool, cFormValues) + void get()[sliceKey].fetchEstGasApproval(activeKey, config, curve, formType, pool, cFormValues) } }, fetchClaimable: async (activeKey, chainId, pool) => { @@ -303,7 +306,17 @@ const createPoolWithdrawSlice = ( } } }, - setFormValues: async (formType, curve, poolId, poolData, updatedFormValues, loadMaxAmount, isSeed, maxSlippage) => { + setFormValues: async ( + formType, + config, + curve, + poolId, + poolData, + updatedFormValues, + loadMaxAmount, + isSeed, + maxSlippage, + ) => { if (get()[sliceKey].formType !== formType) return // stored values @@ -343,6 +356,7 @@ const createPoolWithdrawSlice = ( const props: FetchWithdrawProps = { activeKey, storedActiveKey, + config, curve, formType, poolData, @@ -357,20 +371,20 @@ const createPoolWithdrawSlice = ( void get()[sliceKey].fetchWithdrawCustom(props) } } else if (formType === 'UNSTAKE' && !!signerAddress && +cFormValues.stakedLpToken > 0) { - void get()[sliceKey].fetchEstGasApproval(activeKey, curve, formType, pool, cFormValues) + void get()[sliceKey].fetchEstGasApproval(activeKey, config, curve, formType, pool, cFormValues) } else if (formType === 'CLAIM') { void get()[sliceKey].fetchClaimable(activeKey, chainId, pool) } }, // steps - fetchEstGasApproval: async (activeKey, curve, formType, pool, formValues) => { + fetchEstGasApproval: async (activeKey, config, curve, formType, pool, formValues) => { const { chainId } = curve let resp if (formType === 'WITHDRAW') { - const walletBalances = await get().poolDeposit.fetchUserPoolWalletBalances(curve, pool.id) + const lpTokenBalance = await fetchPoolLpTokenBalance(config, curve, pool.id) - if (+formValues.lpToken > 0 && +walletBalances.lpToken > 0 && +walletBalances.lpToken >= +formValues.lpToken) { + if (+formValues.lpToken > 0 && +lpTokenBalance >= +formValues.lpToken) { resp = formValues.selected === 'lpToken' ? await curvejsApi.poolWithdraw.withdrawEstGasApproval( @@ -420,7 +434,7 @@ const createPoolWithdrawSlice = ( } return resp }, - fetchStepApprove: async (activeKey, curve, formType, pool, formValues) => { + fetchStepApprove: async (activeKey, config, curve, formType, pool, formValues) => { const { provider } = useWallet.getState() if (!provider) return setMissingProvider(get()[sliceKey]) @@ -455,13 +469,13 @@ const createPoolWithdrawSlice = ( get()[sliceKey].setStateByKey('formStatus', cFormStatus) // fetch est gas and approval - await get()[sliceKey].fetchEstGasApproval(activeKey, curve, formType, pool, formValues) + await get()[sliceKey].fetchEstGasApproval(activeKey, config, curve, formType, pool, formValues) } return resp } }, - fetchStepWithdraw: async (activeKey, curve, poolData, formValues, maxSlippage) => { + fetchStepWithdraw: async (activeKey, config, curve, poolData, formValues, maxSlippage) => { const { provider } = useWallet.getState() if (!provider) return setMissingProvider(get()[sliceKey]) @@ -508,13 +522,16 @@ const createPoolWithdrawSlice = ( }) // re-fetch data - await Promise.all([get().user.fetchUserPoolInfo(curve, pool.id), get().pools.fetchPoolStats(curve, poolData)]) + await Promise.all([ + get().user.fetchUserPoolInfo(config, curve, pool.id), + get().pools.fetchPoolStats(curve, poolData), + ]) } return resp } }, - fetchStepUnstake: async (activeKey, curve, poolData, formValues) => { + fetchStepUnstake: async (activeKey, config, curve, poolData, formValues) => { const { provider } = useWallet.getState() if (!provider) return setMissingProvider(get()[sliceKey]) @@ -542,13 +559,16 @@ const createPoolWithdrawSlice = ( }) // re-fetch data - await Promise.all([get().user.fetchUserPoolInfo(curve, pool.id), get().pools.fetchPoolStats(curve, poolData)]) + await Promise.all([ + get().user.fetchUserPoolInfo(config, curve, pool.id), + get().pools.fetchPoolStats(curve, poolData), + ]) } return resp } }, - fetchStepClaim: async (activeKey, curve, poolData) => { + fetchStepClaim: async (activeKey, config, curve, poolData) => { const { provider } = useWallet.getState() if (!provider) return setMissingProvider(get()[sliceKey]) @@ -582,7 +602,10 @@ const createPoolWithdrawSlice = ( }) // re-fetch data - await Promise.all([get().user.fetchUserPoolInfo(curve, pool.id), get().pools.fetchPoolStats(curve, poolData)]) + await Promise.all([ + get().user.fetchUserPoolInfo(config, curve, pool.id), + get().pools.fetchPoolStats(curve, poolData), + ]) } return resp diff --git a/apps/main/src/dex/store/createUserSlice.ts b/apps/main/src/dex/store/createUserSlice.ts index 0d5671f0d6..aa4ff30bbf 100644 --- a/apps/main/src/dex/store/createUserSlice.ts +++ b/apps/main/src/dex/store/createUserSlice.ts @@ -1,4 +1,5 @@ import lodash from 'lodash' +import type { Config } from 'wagmi' import type { StoreApi } from 'zustand' import curvejsApi from '@/dex/lib/curvejs' import type { State } from '@/dex/store/useStore' @@ -6,6 +7,8 @@ import { Balances, CurveApi, UserPoolListMapper } from '@/dex/types/main.types' import { fulfilledValue, isValidAddress } from '@/dex/utils' import { shortenAccount } from '@ui/utils' import { t } from '@ui-kit/lib/i18n' +import { fetchPoolTokenBalances } from '../hooks/usePoolTokenBalances' +import { fetchPoolGaugeTokenBalance, fetchPoolLpTokenBalance } from '../hooks/usePoolTokenDepositBalances' type StateKey = keyof typeof DEFAULT_STATE const { cloneDeep } = lodash @@ -18,8 +21,6 @@ type SliceState = { userLiquidityUsd: { [userPoolActiveKey: string]: string } userShare: { [userPoolActiveKey: string]: Balances | null } userWithdrawAmounts: { [userPoolActiveKey: string]: string[] } - walletBalances: { [userPoolActiveKey: string]: Balances } - walletBalancesLoading: boolean loaded: boolean loading: boolean error: string @@ -31,7 +32,7 @@ const sliceKey = 'user' export type UserSlice = { [sliceKey]: SliceState & { fetchUserPoolList(curve: CurveApi): Promise - fetchUserPoolInfo(curve: CurveApi, poolId: string, isFetchWalletBalancesOnly?: boolean): Promise + fetchUserPoolInfo(config: Config, curve: CurveApi, poolId: string, isFetchWalletBalancesOnly?: boolean): void setStateByActiveKey(key: StateKey, activeKey: string, value: T): void setStateByKey(key: StateKey, value: T): void @@ -50,8 +51,6 @@ const DEFAULT_STATE: SliceState = { userLiquidityUsd: {}, userShare: {}, userWithdrawAmounts: {}, - walletBalances: {}, - walletBalancesLoading: false, loaded: false, loading: false, error: '', @@ -94,7 +93,7 @@ const createUserSlice = (set: StoreApi['setState'], get: StoreApi[ return parsedUserPoolList } }, - fetchUserPoolInfo: async (curve, poolId, isFetchWalletBalancesOnly) => { + fetchUserPoolInfo: async (config, curve, poolId, isFetchWalletBalancesOnly) => { let fetchedWalletBalances: Balances = {} try { @@ -102,12 +101,7 @@ const createUserSlice = (set: StoreApi['setState'], get: StoreApi[ const chainId = curve.chainId const userPoolActiveKey = getUserPoolActiveKey(curve, poolId) const storedPoolData = get().pools.poolsMapper[chainId][poolId] - - // get user wallet balances for pool - get()[sliceKey].setStateByKey('walletBalancesLoading', true) - fetchedWalletBalances = await curvejsApi.wallet.poolWalletBalances(curve, poolId) - get()[sliceKey].setStateByActiveKey('walletBalances', userPoolActiveKey, fetchedWalletBalances) - get()[sliceKey].setStateByKey('walletBalancesLoading', false) + fetchedWalletBalances = await fetchPoolTokenBalances(config, curve, poolId) // set wallet balances into tokens state get().userBalances.updateUserBalancesFromPool(fetchedWalletBalances) @@ -123,7 +117,10 @@ const createUserSlice = (set: StoreApi['setState'], get: StoreApi[ let userShare = null let userWithdrawAmounts = [] as string[] - if (+fetchedWalletBalances.gauge > 0 || +fetchedWalletBalances.lpToken > 0) { + const lpTokenBalance = await fetchPoolLpTokenBalance(config, curve, poolId) + const gaugeTokenBalance = await fetchPoolGaugeTokenBalance(config, curve, poolId) + + if (+gaugeTokenBalance > 0 || +lpTokenBalance > 0) { const pool = storedPoolData.pool const [userPoolWithdrawResult, liquidityUsdResult, userShareResult, userCrvApyResult, userPoolBoostResult] = await Promise.allSettled([ @@ -147,11 +144,8 @@ const createUserSlice = (set: StoreApi['setState'], get: StoreApi[ get()[sliceKey].setStateByActiveKey('userWithdrawAmounts', userPoolActiveKey, userWithdrawAmounts) get()[sliceKey].setStateByActiveKey('userShare', userPoolActiveKey, userShare) get()[sliceKey].setStateByActiveKey('userLiquidityUsd', userPoolActiveKey, liquidityUsd) - - return fetchedWalletBalances } catch (error) { get()[sliceKey].setStateByKey('error', 'error-get-pool-wallet-balances') - return fetchedWalletBalances } }, diff --git a/packages/curve-ui-kit/src/queries/token-balance.query.ts b/packages/curve-ui-kit/src/queries/token-balance.query.ts index 4346cdf540..eb6ed2fa79 100644 --- a/packages/curve-ui-kit/src/queries/token-balance.query.ts +++ b/packages/curve-ui-kit/src/queries/token-balance.query.ts @@ -1,7 +1,9 @@ +import { useCallback, useMemo } from 'react' import { erc20Abi, ethAddress, formatUnits, isAddressEqual, type Address } from 'viem' import { useConfig } from 'wagmi' import { useBalance, useReadContracts } from 'wagmi' -import type { FieldsOf } from '@ui-kit/lib' +import { useQueries, type QueriesResults } from '@tanstack/react-query' +import { combineQueriesToObject, type FieldsOf } from '@ui-kit/lib' import { queryClient } from '@ui-kit/lib/api' import type { ChainQuery, UserQuery } from '@ui-kit/lib/model' import { Decimal } from '@ui-kit/utils' @@ -50,10 +52,13 @@ export const fetchTokenBalance = async (config: Config, query: TokenBalanceQuery .then((balance) => convertBalance({ value: balance[0], decimals: balance[1] })) /** Hook to fetch the token balance */ -export function useTokenBalance({ chainId, userAddress, tokenAddress }: FieldsOf) { +export function useTokenBalance( + { chainId, userAddress, tokenAddress }: FieldsOf, + enabled: boolean = true, +) { const config = useConfig() - const isEnabled = chainId != null && userAddress != null && tokenAddress != null + const isEnabled = enabled && chainId != null && userAddress != null && tokenAddress != null const isNativeToken = tokenAddress != null && isNative({ tokenAddress }) const nativeBalance = useBalance({ @@ -86,3 +91,48 @@ export function useTokenBalance({ chainId, userAddress, tokenAddress }: FieldsOf isLoading: erc20Balance.isLoading, } } + +/** Get query options for a token balance (handles both native and ERC-20) */ +const getTokenBalanceQueryOptions = (config: Config, query: TokenBalanceQuery) => { + if (isNative(query)) { + return { + ...getNativeBalanceQueryOptions(config, query), + select: (data: GetBalanceReturnType) => convertBalance(data), + } + } + + return { + ...readContractsQueryOptions(config, { + allowFailure: false, + contracts: getERC20QueryContracts(query), + }), + select: (data: readonly [bigint, number]) => convertBalance({ value: data[0], decimals: data[1] }), + } +} + +/** Hook to fetch balances for multiple tokens */ +export function useTokenBalances( + { chainId, userAddress, tokenAddresses = [] }: FieldsOf & { tokenAddresses?: Address[] }, + enabled: boolean = true, +) { + const config = useConfig() + + const isEnabled = enabled && chainId != null && userAddress != null + const uniqueAddresses = useMemo(() => Array.from(new Set(tokenAddresses)), [tokenAddresses]) + + return useQueries({ + queries: useMemo( + () => + uniqueAddresses.map((tokenAddress) => ({ + ...getTokenBalanceQueryOptions(config, { chainId: chainId!, userAddress: userAddress!, tokenAddress }), + enabled: isEnabled, + })), + [config, chainId, userAddress, uniqueAddresses, isEnabled], + ), + combine: useCallback( + (results: QueriesResults[]>) => + combineQueriesToObject(results, uniqueAddresses), + [uniqueAddresses], + ), + }) +}