import { Currency, Token, CurrencyAmount, Ether } from '@uniswap/sdk-core' import JSBI from 'jsbi' import { useMemo } from 'react' import { useActiveWeb3React } from '../../hooks/web3' import { useAllTokens } from '../../hooks/Tokens' import { useMulticall2Contract } from '../../hooks/useContract' import { isAddress } from '../../utils' import { useMultipleContractSingleData, useSingleContractMultipleData } from '../multicall/hooks' import { Interface } from '@ethersproject/abi' import ERC20ABI from '../../abis/erc20.json' import { Erc20Interface } from '../../abis/types/Erc20' /** * Returns a map of the given addresses to their eventually consistent ETH balances. */ export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): { [address: string]: CurrencyAmount | undefined } { const { chainId } = useActiveWeb3React() const multicallContract = useMulticall2Contract() const addresses: string[] = useMemo( () => uncheckedAddresses ? uncheckedAddresses .map(isAddress) .filter((a): a is string => a !== false) .sort() : [], [uncheckedAddresses] ) const results = useSingleContractMultipleData( multicallContract, 'getEthBalance', addresses.map((address) => [address]) ) return useMemo( () => addresses.reduce<{ [address: string]: CurrencyAmount }>((memo, address, i) => { const value = results?.[i]?.result?.[0] if (value && chainId) memo[address] = CurrencyAmount.fromRawAmount(Ether.onChain(chainId), JSBI.BigInt(value.toString())) return memo return memo }, {}), [addresses, chainId, results] ) } /** * Returns a map of token addresses to their eventually consistent token balances for a single account. */ export function useTokenBalancesWithLoadingIndicator( address?: string, tokens?: (Token | undefined)[] ): [{ [tokenAddress: string]: CurrencyAmount | undefined }, boolean] { const validatedTokens: Token[] = useMemo( () => tokens?.filter((t?: Token): t is Token => isAddress(t?.address) !== false) ?? [], [tokens] ) const validatedTokenAddresses = useMemo(() => validatedTokens.map((vt) => vt.address), [validatedTokens]) const ERC20Interface = new Interface(ERC20ABI) as Erc20Interface const balances = useMultipleContractSingleData( validatedTokenAddresses, ERC20Interface, 'balanceOf', [address], undefined, 100_000 ) const anyLoading: boolean = useMemo(() => balances.some((callState) => callState.loading), [balances]) return [ useMemo( () => address && validatedTokens.length > 0 ? validatedTokens.reduce<{ [tokenAddress: string]: CurrencyAmount | undefined }>((memo, token, i) => { const value = balances?.[i]?.result?.[0] const amount = value ? JSBI.BigInt(value.toString()) : undefined if (amount) { memo[token.address] = CurrencyAmount.fromRawAmount(token, amount) } return memo }, {}) : {}, [address, validatedTokens, balances] ), anyLoading, ] } export function useTokenBalances( address?: string, tokens?: (Token | undefined)[] ): { [tokenAddress: string]: CurrencyAmount | undefined } { return useTokenBalancesWithLoadingIndicator(address, tokens)[0] } // get the balance for a single token/account combo export function useTokenBalance(account?: string, token?: Token): CurrencyAmount | undefined { const tokenBalances = useTokenBalances(account, [token]) if (!token) return undefined return tokenBalances[token.address] } export function useCurrencyBalances( account?: string, currencies?: (Currency | undefined)[] ): (CurrencyAmount | undefined)[] { const tokens = useMemo( () => currencies?.filter((currency): currency is Token => currency?.isToken ?? false) ?? [], [currencies] ) const tokenBalances = useTokenBalances(account, tokens) const containsETH: boolean = useMemo(() => currencies?.some((currency) => currency?.isNative) ?? false, [currencies]) const ethBalance = useETHBalances(containsETH ? [account] : []) return useMemo( () => currencies?.map((currency) => { if (!account || !currency) return undefined if (currency.isToken) return tokenBalances[currency.address] if (currency.isNative) return ethBalance[account] return undefined }) ?? [], [account, currencies, ethBalance, tokenBalances] ) } export function useCurrencyBalance(account?: string, currency?: Currency): CurrencyAmount | undefined { return useCurrencyBalances(account, [currency])[0] } // mimics useAllBalances export function useAllTokenBalances(): { [tokenAddress: string]: CurrencyAmount | undefined } { const { account } = useActiveWeb3React() const allTokens = useAllTokens() const allTokensArray = useMemo(() => Object.values(allTokens ?? {}), [allTokens]) const balances = useTokenBalances(account ?? undefined, allTokensArray) return balances ?? {} }