diff --git a/wormhole-connect/src/components/TokensModal.tsx b/wormhole-connect/src/components/TokensModal.tsx index d64fb838d..6cdfb3c9a 100644 --- a/wormhole-connect/src/components/TokensModal.tsx +++ b/wormhole-connect/src/components/TokensModal.tsx @@ -15,7 +15,7 @@ import { BigNumber } from 'ethers'; import { RootState } from 'store'; import { CHAINS } from 'config'; import { TokenConfig } from 'config/types'; -import { setBalance, formatBalance, clearBalances } from 'store/transferInput'; +import { setBalances, formatBalance, ChainBalances } from 'store/transferInput'; import { displayAddress } from 'utils'; import { wh } from 'utils/sdk'; import { CENTER, NO_INPUT } from 'utils/style'; @@ -266,13 +266,8 @@ function TokensModal(props: Props) { const [tokens, setTokens] = useState([]); const [search, setSearch] = useState(''); - const { - sourceBalances, - destBalances, - supportedSourceTokens, - supportedDestTokens, - route, - } = useSelector((state: RootState) => state.transferInput); + const { balances, supportedSourceTokens, supportedDestTokens, route } = + useSelector((state: RootState) => state.transferInput); const supportedTokens = useMemo(() => { const supported = @@ -280,10 +275,10 @@ function TokensModal(props: Props) { return supported.filter((t) => !isCosmosNativeToken(t)); }, [type, supportedSourceTokens, supportedDestTokens]); - const tokenBalances = useMemo( - () => (type === 'source' ? sourceBalances : destBalances), - [type, sourceBalances, destBalances], - ); + const chainBalancesCache: ChainBalances | undefined = useMemo(() => { + if (!chain || !balances[chain]) return undefined; + return balances[chain]; + }, [chain, balances]); // search tokens const handleSearch = ( @@ -329,6 +324,15 @@ function TokensModal(props: Props) { const getBalances = useCallback(async () => { if (!walletAddress || !chain) return; + const fiveMinutesAgo = Date.now() - 60 * 1000 * 5; + if ( + chainBalancesCache && + chainBalancesCache.balances && + chainBalancesCache.lastUpdated! > fiveMinutesAgo + ) { + setBalancesLoaded(true); + return; + } // fetch all N tokens and trigger a single update action const balancesArr = await Promise.all( supportedTokens.map(async (t) => { @@ -371,12 +375,12 @@ function TokensModal(props: Props) { }, {}); dispatch( - setBalance({ - type, + setBalances({ + chain, balances, }), ); - }, [walletAddress, chain, dispatch, type, supportedTokens, route]); + }, [walletAddress, chain, dispatch, type, supportedTokens, route, open]); // fetch token balances and set in store useEffect(() => { @@ -387,7 +391,6 @@ function TokensModal(props: Props) { } if (!balancesLoaded) { - dispatch(clearBalances(type)); setLoading(true); getBalances().finally(() => { if (active) { @@ -417,12 +420,13 @@ function TokensModal(props: Props) { if (!t.tokenId && t.nativeChain !== chain) return false; if (t.symbol === 'USDC' && t.nativeChain !== chain) return false; if (type === 'dest') return true; - const b = tokenBalances[t.key]; + if (!chainBalancesCache) return true; + const b = chainBalancesCache.balances[t.key]; const isNullBalance = b !== null && b !== '0'; return isNullBalance; }); setTokens(filtered); - }, [tokenBalances, chain, supportedTokens, type]); + }, [chainBalancesCache, chain, supportedTokens, type]); const tabs = [ { @@ -430,7 +434,7 @@ function TokensModal(props: Props) { panel: ( { + if (!chain || !balances) return null; + const chainBalances = balances[chain]; + if (!chainBalances) return null; + return chainBalances.balances[token]; +}; + export type ValidationErr = string; export type TransferValidations = { @@ -49,8 +65,7 @@ export interface TransferInputState { amount: string; receiveAmount: string; route: Route | undefined; - sourceBalances: Balances; - destBalances: Balances; + balances: BalancesCache; foreignAsset: string; associatedTokenAddress: string; gasEst: { @@ -86,8 +101,7 @@ const initialState: TransferInputState = { amount: '', receiveAmount: '', route: undefined, - sourceBalances: {}, - destBalances: {}, + balances: {}, foreignAsset: '', associatedTokenAddress: '', gasEst: { @@ -141,8 +155,6 @@ export const transferInputSlice = createSlice({ { payload }: PayloadAction, ) => { state.fromChain = payload; - // clear balances if the chain changes; - state.sourceBalances = {}; const { fromChain, token } = state; @@ -173,18 +185,20 @@ export const transferInputSlice = createSlice({ ) => { state.receiveAmount = payload; }, - setBalance: ( + setBalances: ( state: TransferInputState, - { - payload, - }: PayloadAction<{ type: 'source' | 'dest'; balances: Balances }>, + { payload }: PayloadAction<{ chain: ChainName; balances: Balances }>, ) => { - const { type, balances } = payload; - if (type === 'source') { - state.sourceBalances = { ...state.sourceBalances, ...balances }; - } else { - state.destBalances = { ...state.destBalances, ...balances }; - } + const { chain, balances } = payload; + state.balances = { + ...state.balances, + ...{ + [chain]: { + lastUpdated: Date.now(), + balances: { ...state.balances[chain], ...balances }, + }, + }, + }; }, setReceiverNativeBalance: ( state: TransferInputState, @@ -192,18 +206,6 @@ export const transferInputSlice = createSlice({ ) => { state.receiverNativeBalance = payload; }, - clearBalances: ( - state: TransferInputState, - { payload }: PayloadAction<'source' | 'dest' | 'all'>, - ) => { - if (payload === 'source' || payload === 'all') { - state.sourceBalances = {}; - } - - if (payload === 'dest' || payload === 'all') { - state.destBalances = {}; - } - }, setForeignAsset: ( state: TransferInputState, { payload }: PayloadAction, @@ -323,13 +325,12 @@ export const { setToChain, setAmount, setReceiveAmount, - setBalance, - clearBalances, setForeignAsset, setAssociatedTokenAddress, setTransferRoute, setSendingGasEst, setClaimGasEst, + setBalances, clearTransfer, setIsTransactionInProgress, setReceiverNativeBalance, diff --git a/wormhole-connect/src/utils/transferValidation.ts b/wormhole-connect/src/utils/transferValidation.ts index fca7085fc..fd7507267 100644 --- a/wormhole-connect/src/utils/transferValidation.ts +++ b/wormhole-connect/src/utils/transferValidation.ts @@ -12,6 +12,7 @@ import { touchValidations, ValidationErr, TransferValidations, + accessBalance, } from '../store/transferInput'; import { WalletData, WalletState } from '../store/wallet'; import { walletAcceptedChains } from './wallet'; @@ -191,7 +192,7 @@ export const validateAll = async ( token, destToken, amount, - sourceBalances: balances, + balances, foreignAsset, associatedTokenAddress, route, @@ -201,6 +202,7 @@ export const validateAll = async ( const { sending, receiving } = walletData; const isAutomatic = getIsAutomatic(route); const minAmt = getMinAmt(route, relayData); + const sendingTokenBalance = accessBalance(balances, fromChain, token); const baseValidations = { sendingWallet: await validateWallet(sending, fromChain), @@ -209,7 +211,7 @@ export const validateAll = async ( toChain: validateToChain(toChain, fromChain), token: validateToken(token, fromChain), destToken: validateDestToken(destToken, toChain), - amount: validateAmount(amount, balances[token], minAmt), + amount: validateAmount(amount, sendingTokenBalance, minAmt), route: validateRoute(route, availableRoutes), toNativeToken: '', foreignAsset: validateForeignAsset(foreignAsset), @@ -222,7 +224,7 @@ export const validateAll = async ( if (!isAutomatic) return baseValidations; return { ...baseValidations, - amount: validateAmount(amount, balances[token], minAmt), + amount: validateAmount(amount, sendingTokenBalance, minAmt), toNativeToken: validateToNativeAmt(toNativeToken, maxSwapAmt), }; }; diff --git a/wormhole-connect/src/views/Bridge/Inputs/From.tsx b/wormhole-connect/src/views/Bridge/Inputs/From.tsx index 72c0099fe..5cc669904 100644 --- a/wormhole-connect/src/views/Bridge/Inputs/From.tsx +++ b/wormhole-connect/src/views/Bridge/Inputs/From.tsx @@ -8,6 +8,7 @@ import { selectFromChain, setAmount, setReceiveAmount, + accessBalance, } from 'store/transferInput'; import { CHAINS, CHAINS_ARR, TOKENS } from 'config'; import { TransferWallet, walletAcceptedChains } from 'utils/wallet'; @@ -34,13 +35,13 @@ function FromInputs() { route, fromChain, toChain, - sourceBalances: balances, + balances, token, amount, isTransactionInProgress, } = useSelector((state: RootState) => state.transferInput); const tokenConfig = token && TOKENS[token]; - const balance = balances[token] || undefined; + const balance = accessBalance(balances, fromChain, token) || undefined; const isDisabled = (chain: ChainName) => { // Check if the wallet type (i.e. Metamask, Phantom...) is supported for the given chain diff --git a/wormhole-connect/src/views/Bridge/Inputs/To.tsx b/wormhole-connect/src/views/Bridge/Inputs/To.tsx index fa48f5141..6c2b33c0c 100644 --- a/wormhole-connect/src/views/Bridge/Inputs/To.tsx +++ b/wormhole-connect/src/views/Bridge/Inputs/To.tsx @@ -3,7 +3,11 @@ import { useDispatch, useSelector } from 'react-redux'; import { ChainName } from '@wormhole-foundation/wormhole-connect-sdk'; import { RootState } from 'store'; -import { selectToChain, setDestToken } from 'store/transferInput'; +import { + accessBalance, + selectToChain, + setDestToken, +} from 'store/transferInput'; import { TransferWallet, walletAcceptedChains } from 'utils/wallet'; import { getWrappedToken } from 'utils'; import { CHAINS, CHAINS_ARR, TOKENS } from 'config'; @@ -23,16 +27,15 @@ function ToInputs() { const { validate: showErrors, validations, - // route, fromChain, toChain, - destBalances, + balances, destToken, receiveAmount, isTransactionInProgress, } = useSelector((state: RootState) => state.transferInput); const { receiving } = useSelector((state: RootState) => state.wallet); - const balance = destBalances[destToken] || undefined; + const balance = accessBalance(balances, toChain, destToken) || undefined; const tokenConfig = TOKENS[destToken]; diff --git a/wormhole-connect/src/views/Bridge/SwapChains.tsx b/wormhole-connect/src/views/Bridge/SwapChains.tsx index a3eb89f77..920e4e85e 100644 --- a/wormhole-connect/src/views/Bridge/SwapChains.tsx +++ b/wormhole-connect/src/views/Bridge/SwapChains.tsx @@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux'; import { makeStyles } from 'tss-react/mui'; import { swapWallets } from 'store/wallet'; -import { swapChains, clearBalances } from 'store/transferInput'; +import { swapChains } from 'store/transferInput'; const useStyles = makeStyles()((theme: any) => ({ button: { @@ -28,7 +28,6 @@ function SwapChains() { const swap = () => { dispatch(swapChains()); dispatch(swapWallets()); - dispatch(clearBalances('all')); }; return (