@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
text/typescript
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 }
}