From 1e04c79f674318d9be0b9d16fd43d0147fa0f4b7 Mon Sep 17 00:00:00 2001 From: Shinji Date: Sun, 3 Dec 2023 07:50:48 +0000 Subject: [PATCH] fix: fix performance issues on token selector --- .../components/TokenList/TokenList.helpers.ts | 4 +- .../src/components/TokenList/TokenList.tsx | 22 +- .../components/TokenList/TokenList.types.ts | 13 +- .../src/components/TokenList/index.ts | 1 - .../src/containers/Wallets/Wallets.tsx | 5 +- widget/embedded/src/pages/Home.tsx | 42 +--- .../src/pages/SelectSwapItemsPage.tsx | 9 +- widget/embedded/src/store/quote.ts | 5 +- widget/embedded/src/store/wallets.ts | 86 ++++--- widget/embedded/src/types/wallets.ts | 19 +- widget/embedded/src/utils/wallets.ts | 214 +++++++----------- 11 files changed, 186 insertions(+), 234 deletions(-) diff --git a/widget/embedded/src/components/TokenList/TokenList.helpers.ts b/widget/embedded/src/components/TokenList/TokenList.helpers.ts index a8cdaa9753..039f4fcfd9 100644 --- a/widget/embedded/src/components/TokenList/TokenList.helpers.ts +++ b/widget/embedded/src/components/TokenList/TokenList.helpers.ts @@ -1,8 +1,8 @@ -import type { TokenWithBalance } from './TokenList.types'; +import type { Token } from 'rango-sdk'; import { containsText } from '../../utils/common'; -export const filterTokens = (list: TokenWithBalance[], searchedFor: string) => +export const filterTokens = (list: Token[], searchedFor: string) => list.filter( (token) => containsText(token.symbol, searchedFor) || diff --git a/widget/embedded/src/components/TokenList/TokenList.tsx b/widget/embedded/src/components/TokenList/TokenList.tsx index 3d8fdf1d1d..26c6bb545c 100644 --- a/widget/embedded/src/components/TokenList/TokenList.tsx +++ b/widget/embedded/src/components/TokenList/TokenList.tsx @@ -1,9 +1,6 @@ /* eslint-disable @typescript-eslint/no-magic-numbers */ -import type { - PropTypes, - RenderDescProps, - TokenWithBalance, -} from './TokenList.types'; +import type { PropTypes, RenderDescProps } from './TokenList.types'; +import type { Token } from 'rango-sdk'; import type { CommonProps } from 'react-window'; import { i18n } from '@lingui/core'; @@ -24,6 +21,7 @@ import React, { forwardRef, useEffect, useState } from 'react'; import { useAppStore } from '../../store/AppStore'; import { useWalletsStore } from '../../store/wallets'; import { generateRangeColors } from '../../utils/colors'; +import { formatBalance } from '../../utils/wallets'; import { LoadingTokenList } from './LoadingTokenList'; import { @@ -85,11 +83,11 @@ const renderDesc = (props: RenderDescProps) => { export function TokenList(props: PropTypes) { const { list, searchedFor = '', onChange, selectedBlockchain } = props; - const [tokens, setTokens] = useState(list); + const [tokens, setTokens] = useState(list); const fetchStatus = useAppStore().fetchStatus; const blockchains = useAppStore().blockchains(); const [hasNextPage, setHasNextPage] = useState(true); - const loadingWallet = useWalletsStore.use.loading(); + const { getBalanceFor, loading: loadingWallet } = useWalletsStore(); const { isTokenPinned } = useAppStore(); // eslint-disable-next-line react/display-name @@ -134,7 +132,7 @@ export function TokenList(props: PropTypes) { { const token = tokens[index]; - + const tokenBalance = formatBalance(getBalanceFor(token)); const address = token.address || ''; const blockchain = blockchains.find( (blockchain) => blockchain.name === token.blockchain @@ -229,18 +227,18 @@ export function TokenList(props: PropTypes) { ) : ( - tokens[index]?.balance && ( + tokenBalance && ( - {token.balance?.amount} + {tokenBalance.amount}
- {token.balance?.usdValue && ( + {tokenBalance.usdValue && ( - {`$${token.balance?.usdValue}`} + {`$${tokenBalance.usdValue}`} )} diff --git a/widget/embedded/src/components/TokenList/TokenList.types.ts b/widget/embedded/src/components/TokenList/TokenList.types.ts index a8371ac75d..d29a96f961 100644 --- a/widget/embedded/src/components/TokenList/TokenList.types.ts +++ b/widget/embedded/src/components/TokenList/TokenList.types.ts @@ -1,16 +1,9 @@ import type { Token } from 'rango-sdk'; -export interface TokenWithBalance extends Token { - balance?: { - amount: string; - usdValue: string; - }; -} - export interface PropTypes { - list: TokenWithBalance[]; + list: Token[]; searchedFor?: string; - onChange: (token: TokenWithBalance) => void; + onChange: (token: Token) => void; selectedBlockchain?: string; } @@ -22,7 +15,7 @@ export interface RenderDescProps { name?: string | null; address: string; url: string; - token: TokenWithBalance; + token: Token; customCssForTag: TagCSS; customCssForTagTitle: TitleCSS; } diff --git a/widget/embedded/src/components/TokenList/index.ts b/widget/embedded/src/components/TokenList/index.ts index 030fcf674c..a9fd049d07 100644 --- a/widget/embedded/src/components/TokenList/index.ts +++ b/widget/embedded/src/components/TokenList/index.ts @@ -1,2 +1 @@ export { TokenList } from './TokenList'; -export type { TokenWithBalance } from './TokenList.types'; diff --git a/widget/embedded/src/containers/Wallets/Wallets.tsx b/widget/embedded/src/containers/Wallets/Wallets.tsx index a055e89b1f..2f60c118e6 100644 --- a/widget/embedded/src/containers/Wallets/Wallets.tsx +++ b/widget/embedded/src/containers/Wallets/Wallets.tsx @@ -27,15 +27,14 @@ export const WidgetContext = createContext({ }); function Main(props: PropsWithChildren) { - const updateConfig = useAppStore().updateConfig; + const { updateConfig } = useAppStore(); const blockchains = useAppStore().blockchains(); const tokens = useAppStore().tokens(); const walletOptions: ProvidersOptions = { walletConnectProjectId: props.config?.walletConnectProjectId, }; const { providers } = useWalletProviders(props.config.wallets, walletOptions); - const disconnectWallet = useWalletsStore.use.disconnectWallet(); - const connectWallet = useWalletsStore.use.connectWallet(); + const { connectWallet, disconnectWallet } = useWalletsStore(); const onConnectWalletHandler = useRef(); useEffect(() => { diff --git a/widget/embedded/src/pages/Home.tsx b/widget/embedded/src/pages/Home.tsx index b430c3c1d7..80685f41ab 100644 --- a/widget/embedded/src/pages/Home.tsx +++ b/widget/embedded/src/pages/Home.tsx @@ -9,8 +9,6 @@ import { SwitchFromAndToButton } from '../components/SwitchFromAndTo'; import { errorMessages } from '../constants/errors'; import { navigationRoutes } from '../constants/navigationRoutes'; import { - BALANCE_MAX_DECIMALS, - BALANCE_MIN_DECIMALS, PERCENTAGE_CHANGE_MAX_DECIMALS, PERCENTAGE_CHANGE_MIN_DECIMALS, TOKEN_AMOUNT_MAX_DECIMALS, @@ -27,7 +25,7 @@ import { useWalletsStore } from '../store/wallets'; import { numberToString } from '../utils/numbers'; import { getPriceImpact, getPriceImpactLevel } from '../utils/quote'; import { canComputePriceImpact, getSwapButtonState } from '../utils/swap'; -import { getBalanceFromWallet } from '../utils/wallets'; +import { formatBalance } from '../utils/wallets'; const Container = styled('div', { display: 'flex', @@ -73,7 +71,7 @@ export function Home() { const fetchMetaStatus = useAppStore().fetchStatus; - const connectedWallets = useWalletsStore.use.connectedWallets(); + const { connectedWallets, getBalanceFor } = useWalletsStore(); const setCurrentPage = useUiStore.use.setCurrentPage(); const [showQuoteWarningModal, setShowQuoteWarningModal] = useState(false); const layoutRef = useRef(null); @@ -103,36 +101,14 @@ export function Home() { needsToWarnEthOnPath, }); - const tokenBalance = - !!fromBlockchain && !!fromToken - ? numberToString( - getBalanceFromWallet( - connectedWallets, - fromBlockchain?.name, - fromToken?.symbol, - fromToken?.address - )?.amount || '0', - BALANCE_MIN_DECIMALS, - BALANCE_MAX_DECIMALS - ) - : '0'; + const fromTokenBalance = fromToken ? getBalanceFor(fromToken) : null; + + const fromTokenFormattedBalance = + formatBalance(fromTokenBalance)?.amount ?? '0'; const tokenBalanceReal = !!fromBlockchain && !!fromToken - ? numberToString( - getBalanceFromWallet( - connectedWallets, - fromBlockchain?.name, - fromToken?.symbol, - fromToken?.address - )?.amount || '0', - getBalanceFromWallet( - connectedWallets, - fromBlockchain?.name, - fromToken?.symbol, - fromToken?.address - )?.decimal - ) + ? numberToString(fromTokenBalance?.amount, fromTokenBalance?.decimals) : '0'; useEffect(() => { @@ -194,7 +170,7 @@ export function Home() { label={i18n.t('From')} mode="From" onInputChange={setInputAmount} - balance={tokenBalance} + balance={fromTokenFormattedBalance} chain={{ displayName: fromBlockchain?.displayName || '', image: fromBlockchain?.logo || '', @@ -220,7 +196,7 @@ export function Home() { disabled={fetchMetaStatus === 'failed'} loading={fetchMetaStatus === 'loading'} onSelectMaxBalance={() => { - if (tokenBalance !== '0') { + if (fromTokenFormattedBalance !== '0') { setInputAmount(tokenBalanceReal.split(',').join('')); } }} diff --git a/widget/embedded/src/pages/SelectSwapItemsPage.tsx b/widget/embedded/src/pages/SelectSwapItemsPage.tsx index 35d65fc1e5..1429241aed 100644 --- a/widget/embedded/src/pages/SelectSwapItemsPage.tsx +++ b/widget/embedded/src/pages/SelectSwapItemsPage.tsx @@ -14,7 +14,7 @@ import { useNavigateBack } from '../hooks/useNavigateBack'; import { useAppStore } from '../store/AppStore'; import { useQuoteStore } from '../store/quote'; import { useWalletsStore } from '../store/wallets'; -import { getTokensBalanceFromWalletAndSort } from '../utils/wallets'; +import { sortTokensByBalance } from '../utils/wallets'; interface PropTypes { type: 'source' | 'destination'; @@ -32,7 +32,7 @@ export function SelectSwapItemsPage(props: PropTypes) { setFromBlockchain, setToBlockchain, } = useQuoteStore(); - const { connectedWallets } = useWalletsStore(); + const getBalanceFor = useWalletsStore.use.getBalanceFor(); const [searchedFor, setSearchedFor] = useState(''); const selectedBlockchain = type === 'source' ? fromBlockchain : toBlockchain; @@ -47,10 +47,7 @@ export function SelectSwapItemsPage(props: PropTypes) { blockchain: selectedBlockchainName, searchFor: searchedFor, }); - const tokensList = getTokensBalanceFromWalletAndSort( - tokens, - connectedWallets - ); + const tokensList = sortTokensByBalance(tokens, getBalanceFor); // Actions const updateBlockchain = (blockchain: BlockchainMeta) => { diff --git a/widget/embedded/src/store/quote.ts b/widget/embedded/src/store/quote.ts index 1acf3148a3..bf035503fa 100644 --- a/widget/embedded/src/store/quote.ts +++ b/widget/embedded/src/store/quote.ts @@ -1,4 +1,3 @@ -import type { TokenWithBalance } from '../components/TokenList'; import type { Wallet } from '../types'; import type { PendingSwap } from '@rango-dev/queue-manager-rango-preset/dist/shared'; import type { @@ -38,8 +37,8 @@ export interface QuoteState { inputUsdValue: BigNumber | null; outputAmount: BigNumber | null; outputUsdValue: BigNumber | null; - fromToken: TokenWithBalance | null; - toToken: TokenWithBalance | null; + fromToken: Token | null; + toToken: Token | null; quoteWalletsConfirmed: boolean; selectedWallets: Wallet[]; quoteWarningsConfirmed: boolean; diff --git a/widget/embedded/src/store/wallets.ts b/widget/embedded/src/store/wallets.ts index 196f5960e2..553344d796 100644 --- a/widget/embedded/src/store/wallets.ts +++ b/widget/embedded/src/store/wallets.ts @@ -1,4 +1,4 @@ -import type { Wallet } from '../types'; +import type { Balance, TokensBalance, Wallet } from '../types'; import type { WalletType } from '@rango-dev/wallets-shared'; import type { Token } from 'rango-sdk'; @@ -7,8 +7,10 @@ import { subscribeWithSelector } from 'zustand/middleware'; import { httpService } from '../services/httpService'; import { + createTokenHash, isAccountAndWalletMatched, makeBalanceFor, + makeTokensBalance, resetConnectedWalletState, } from '../utils/wallets'; @@ -33,9 +35,9 @@ export interface ConnectedWallet extends Wallet { loading: boolean; error: boolean; } - interface WalletsStore { connectedWallets: ConnectedWallet[]; + balances: TokensBalance; customDestination: string; loading: boolean; connectWallet: (accounts: Wallet[], tokens: Token[]) => void; @@ -47,12 +49,14 @@ interface WalletsStore { tokens: Token[], shouldRetry?: boolean ) => void; + getBalanceFor: (token: Token) => Balance | null; } export const useWalletsStore = createSelectors( create()( subscribeWithSelector((set, get) => ({ connectedWallets: [], + balances: {}, customDestination: '', loading: false, connectWallet: (accounts, tokens) => { @@ -92,21 +96,27 @@ export const useWalletsStore = createSelectors( connectedWallet.walletType !== walletType ) .map((selectedWallet) => selectedWallet.chain); + + const connectedWallets = state.connectedWallets + .filter( + (connectedWallet) => connectedWallet.walletType !== walletType + ) + .map((connectedWallet) => { + const anyWalletSelectedForBlockchain = selectedWallets.includes( + connectedWallet.chain + ); + if (anyWalletSelectedForBlockchain) { + return connectedWallet; + } + selectedWallets.push(connectedWallet.chain); + return { ...connectedWallet, selected: true }; + }); + + const balances = makeTokensBalance(connectedWallets); + return { - connectedWallets: state.connectedWallets - .filter( - (connectedWallet) => connectedWallet.walletType !== walletType - ) - .map((connectedWallet) => { - const anyWalletSelectedForBlockchain = selectedWallets.includes( - connectedWallet.chain - ); - if (anyWalletSelectedForBlockchain) { - return connectedWallet; - } - selectedWallets.push(connectedWallet.chain); - return { ...connectedWallet, selected: true }; - }), + balances, + connectedWallets, }; }); }, @@ -159,9 +169,8 @@ export const useWalletsStore = createSelectors( const response = await httpService().getWalletsDetails(data); const retrievedBalance = response.wallets; if (retrievedBalance) { - set((state) => ({ - loading: false, - connectedWallets: state.connectedWallets.map( + set((state) => { + const connectedWallets = state.connectedWallets.map( (connectedWallet) => { const matchedAccount = accounts.find((account) => isAccountAndWalletMatched(account, connectedWallet) @@ -186,30 +195,49 @@ export const useWalletsStore = createSelectors( retrievedBalanceAccount, tokens ), + loading: false, } : connectedWallet; } - ), - })); + ); + + const balances = makeTokensBalance(connectedWallets); + + return { + loading: false, + balances, + connectedWallets, + }; + }); } else { throw new Error('Wallet not found'); } } catch (error) { - set((state) => ({ - loading: false, - connectedWallets: state.connectedWallets.map((balance) => { + set((state) => { + const connectedWallets = state.connectedWallets.map((balance) => { return accounts.find((account) => isAccountAndWalletMatched(account, balance) ) ? resetConnectedWalletState(balance) : balance; - }), - })); + }); + + const balances = makeTokensBalance(connectedWallets); + + return { + loading: false, + balances, + connectedWallets, + }; + }); } }, + getBalanceFor: (token) => { + const { balances } = get(); + const tokenHash = createTokenHash(token); + const balance = balances[tokenHash]; + return balance ?? null; + }, })) ) ); - -export const fetchingBalanceSelector = (state: WalletsStore) => - !!state.connectedWallets.find((wallet) => wallet.loading); diff --git a/widget/embedded/src/types/wallets.ts b/widget/embedded/src/types/wallets.ts index cdad9e96b8..ffad83d4a4 100644 --- a/widget/embedded/src/types/wallets.ts +++ b/widget/embedded/src/types/wallets.ts @@ -1,7 +1,24 @@ -import { WalletType } from '@rango-dev/wallets-shared'; +import type { WalletType } from '@rango-dev/wallets-shared'; export interface Wallet { chain: string; address: string; walletType: WalletType; } + +export type Balance = { + amount: string; + decimals: number; + usdValue: string; +}; + +type Blockchain = string; +type TokenSymbol = string; +type Address = string; + +/** `blockchain-symbol-Address` */ +export type TokenHash = `${Blockchain}-${TokenSymbol}-${Address}`; + +export type TokensBalance = { + [key: TokenHash]: Balance; +}; diff --git a/widget/embedded/src/utils/wallets.ts b/widget/embedded/src/utils/wallets.ts index 8f238bc6e6..73c910eb38 100644 --- a/widget/embedded/src/utils/wallets.ts +++ b/widget/embedded/src/utils/wallets.ts @@ -1,8 +1,8 @@ -import type { TokenWithBalance } from '../components/TokenList'; import type { ConnectedWallet, TokenBalance } from '../store/wallets'; -import type { Wallet } from '../types'; +import type { Balance, TokenHash, TokensBalance, Wallet } from '../types'; import type { WalletInfo as ModalWalletInfo } from '@rango-dev/ui'; import type { + Asset, Network, WalletInfo, WalletState, @@ -29,6 +29,12 @@ import BigNumber from 'bignumber.js'; import { isCosmosBlockchain } from 'rango-types'; import { ZERO } from '../constants/numbers'; +import { + BALANCE_MAX_DECIMALS, + BALANCE_MIN_DECIMALS, + USD_VALUE_MAX_DECIMALS, + USD_VALUE_MIN_DECIMALS, +} from '../constants/routing'; import { EXCLUDED_WALLETS } from '../constants/wallets'; import { numberToString } from './numbers'; @@ -218,43 +224,6 @@ export function getRequiredChains(quote: BestRouteResponse | null) { type Blockchain = { name: string; accounts: ConnectedWallet[] }; -export function getBalanceFromWallet( - connectedWallets: ConnectedWallet[], - chain: string, - symbol: string, - address: string | null -): TokenBalance | null { - if (connectedWallets.length === 0) { - return null; - } - - const selectedChainWallets = connectedWallets.filter( - (wallet) => wallet.chain === chain - ); - if (selectedChainWallets.length === 0) { - return null; - } - - return ( - selectedChainWallets - .map( - (wallet) => - wallet.balances?.find( - (balance) => - (address !== null && balance.address === address) || - (address === null && - balance.address === address && - balance.symbol === symbol) - ) || null - ) - .filter((balance) => balance !== null) - .sort( - (a, b) => parseFloat(b?.amount || '0') - parseFloat(a?.amount || '1') - ) - .find(() => true) || null - ); -} - export function isAccountAndWalletMatched( account: Wallet, connectedWallet: ConnectedWallet @@ -348,30 +317,6 @@ function numberWithThousandSeparator(number: string | number): string { return parts.join('.'); } -export const sortTokens = (tokens: TokenWithBalance[]): TokenWithBalance[] => { - const walletConnected = !!tokens.some((token) => token.balance); - if (!walletConnected) { - return tokens - .filter((token) => !token.address) - .concat( - tokens.filter((token) => token.address && !token.isSecondaryCoin), - tokens.filter((token) => token.isSecondaryCoin) - ); - } - - return tokens - .filter((token) => !!token.balance) - .sort( - (tokenA, tokenB) => - parseFloat(tokenB.balance?.usdValue || '0') - - parseFloat(tokenA.balance?.usdValue || '0') - ) - .concat( - tokens.filter((token) => !token.balance && !token.isSecondaryCoin), - tokens.filter((token) => !token.balance && token.isSecondaryCoin) - ); -}; - export const getUsdPrice = ( blockchain: string, symbol: string, @@ -415,98 +360,58 @@ export const getKeplrCompatibleConnectedWallets = ( ); }; -export function getTokensWithBalance( - tokens: TokenWithBalance[], - connectedWallets: ConnectedWallet[] -): TokenWithBalance[] { - return tokens.map(({ balance, ...otherProps }) => { - const tokenAmount = numberToString( - new BigNumber( - getBalanceFromWallet( - connectedWallets, - otherProps.blockchain, - otherProps.symbol, - otherProps.address - )?.amount || ZERO - ) - ); - - let tokenUsdValue = ''; - if (otherProps.usdPrice) { - tokenUsdValue = numberToString( - new BigNumber( - getBalanceFromWallet( - connectedWallets, - otherProps.blockchain, - otherProps.symbol, - otherProps.address - )?.amount || ZERO - ).multipliedBy(otherProps.usdPrice) - ); - } +export function formatBalance(balance: Balance | null): Balance | null { + const formattedBalance: Balance | null = balance + ? { + ...balance, + amount: numberToString( + balance.amount, + BALANCE_MIN_DECIMALS, + BALANCE_MAX_DECIMALS + ), + usdValue: numberToString( + balance.usdValue, + USD_VALUE_MIN_DECIMALS, + USD_VALUE_MAX_DECIMALS + ), + } + : null; - return { - ...otherProps, - ...(tokenAmount !== '0' && { - balance: { amount: tokenAmount, usdValue: tokenUsdValue }, - }), - }; - }); + return formattedBalance; } -export function getTokensBalanceFromWalletAndSort( - tokens: TokenWithBalance[], - connectedWallets: ConnectedWallet[] +export function sortTokensByBalance( + tokens: Token[], + getBalanceFor: (token: Token) => Balance | null ) { - const list = - connectedWallets.length > 0 - ? getTokensWithBalance(tokens, connectedWallets) - : tokens; - list.sort((tokenA, tokenB) => { - if (tokenA.balance?.usdValue && tokenB.balance?.usdValue) { + tokens.sort((token1, token2) => { + const token1Balance = getBalanceFor(token1); + const token2Balance = getBalanceFor(token2); + if (token1Balance?.usdValue && token2Balance?.usdValue) { return ( - parseFloat(tokenB.balance.usdValue) - - parseFloat(tokenA.balance.usdValue) + parseFloat(token2Balance.usdValue) - parseFloat(token1Balance.usdValue) ); } - if (!tokenA.balance?.usdValue && tokenB.balance?.usdValue) { + if (!token1Balance?.usdValue && token2Balance?.usdValue) { return 1; } - if (tokenA.balance?.usdValue && !tokenB.balance?.usdValue) { + if (token1Balance?.usdValue && !token2Balance?.usdValue) { return -1; } - if (!tokenA.balance?.usdValue && !tokenB.balance?.usdValue) { + if (!token1Balance?.usdValue && !token2Balance?.usdValue) { return ( - parseFloat(tokenB.balance?.amount || '0') - - parseFloat(tokenA.balance?.amount || '0') + parseFloat(token2Balance?.amount || '0') - + parseFloat(token1Balance?.amount || '0') ); } return 0; }); - return list; -} - -export function getSortedTokens( - chain: BlockchainMeta | null, - tokens: Token[], - connectedWallets: ConnectedWallet[], - otherChainTokens: TokenWithBalance[] -): TokenWithBalance[] { - const fromChainEqualsToToBlockchain = - chain?.name === otherChainTokens[0]?.name; - if (fromChainEqualsToToBlockchain) { - return otherChainTokens; - } - - const filteredTokens = tokens.filter( - (token) => token.blockchain === chain?.name - ); - return sortTokens(getTokensWithBalance(filteredTokens, connectedWallets)); + return tokens; } export function tokensAreEqual( @@ -568,3 +473,44 @@ export function getAddress({ connectedWallet.chain === chain )?.address; } + +export function createTokenHash(token: Asset): TokenHash { + return `${token.blockchain}-${token.symbol}-${token.address ?? ''}`; +} + +export function makeTokensBalance(connectedWallets: ConnectedWallet[]) { + return connectedWallets + .flatMap((wallet) => wallet.balances) + .reduce((balances: TokensBalance, balance) => { + const currentBalance = { + amount: balance?.amount ?? '', + decimals: balance?.decimal ?? 0, + usdValue: balance?.usdPrice + ? new BigNumber(balance?.usdPrice ?? ZERO) + .multipliedBy(balance?.amount) + .toString() + : '', + }; + + const tokenHash = balance + ? createTokenHash({ + symbol: balance.symbol, + blockchain: balance.chain, + address: balance.address, + }) + : null; + + const prevBalance = tokenHash ? balances[tokenHash] : null; + + const shouldUpdateBalance = + tokenHash && + (!prevBalance || + (prevBalance && prevBalance.amount < currentBalance.amount)); + + if (shouldUpdateBalance) { + balances[tokenHash] = currentBalance; + } + + return balances; + }, {}); +}