UNPKG

@lifi/widget

Version:

LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.

136 lines (119 loc) 4.35 kB
import { type BaseToken, ChainType, getWalletBalances, type TokenExtended, type WalletTokenExtended, } from '@lifi/sdk' import { useQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js' import type { FormType } from '../stores/form/types.js' import { getConfigItemSets, isFormItemAllowed } from '../utils/item.js' import { isSupportedToken } from '../utils/tokenList.js' export const useFilteredTokensByBalance = ( accountsWithTokens?: Record< string, { chainType: ChainType; tokens: Record<number, TokenExtended[]> } >, formType?: FormType ) => { const { tokens: configTokens } = useWidgetConfig() const evmAddress = useMemo(() => { const evmAccount = Object.entries(accountsWithTokens ?? {}).find( ([_, { chainType }]) => chainType === ChainType.EVM ) return evmAccount?.[0] }, [accountsWithTokens]) const { data: existingBalances, isLoading } = useQuery({ queryKey: ['existing-evm-balances', evmAddress], queryFn: () => getWalletBalances(evmAddress ?? ''), enabled: !!evmAddress, refetchInterval: 30_000, // 30 seconds staleTime: 30_000, // 30 seconds retry: false, }) const accountsWithFilteredTokens = useMemo(() => { if (!accountsWithTokens) { return undefined } // Early return if no existing balances - return all tokens const result: Record<string, Record<number, TokenExtended[]>> = {} if (!existingBalances) { for (const [address, { tokens }] of Object.entries(accountsWithTokens)) { result[address] = tokens } return result } for (const [address, { tokens }] of Object.entries(accountsWithTokens)) { result[address] = {} for (const [chainIdStr, chainTokens] of Object.entries(tokens)) { const chainId = Number(chainIdStr) // Get balances for this specific chain const balances = existingBalances?.[chainId] // If no balances, RPC all tokens of the chain if (!balances?.length) { if (chainTokens.length) { result[address][chainId] = chainTokens } continue } // Optimize token matching with Set for O(1) lookup const balanceSet = new Set( balances.map((balance: WalletTokenExtended) => balance.address.toLowerCase() ) ) // Get tokens that are in chainTokens and have balances const filteredTokens = chainTokens.filter((token) => { const tokenKey = token.address.toLowerCase() return balanceSet.has(tokenKey) }) // Get tokens that are in balances but not in chainTokens const chainTokenSet = new Set( chainTokens.map((token) => token.address.toLowerCase()) ) // Get allowed addresses from config tokens const allowedAddressesConfig = getConfigItemSets( configTokens, (tokens: BaseToken[]) => new Set( tokens .filter((t) => Number(t.chainId) === chainId) .map((t) => t.address.toLowerCase()) ), formType ) const additionalTokens = balances .filter((balance: WalletTokenExtended) => { const balanceKey = balance.address.toLowerCase() return ( !chainTokenSet.has(balanceKey) && isSupportedToken(balance) && isFormItemAllowed( balance, allowedAddressesConfig, formType, (t) => t.address.toLowerCase() ) ) }) // Mark tokens from wallet balances as unverified .map((token: WalletTokenExtended) => ({ ...token, verified: false, })) as TokenExtended[] // Combine both sets of tokens - convert WalletTokenExtended to TokenAmount const allTokens = [ ...filteredTokens, ...additionalTokens, ] as TokenExtended[] if (allTokens.length) { result[address][chainId] = allTokens } } } return result }, [accountsWithTokens, existingBalances, configTokens, formType]) return { data: accountsWithFilteredTokens, isLoading } }