import { Currency, Token, CurrencyAmount, Ether } from "@uniswap/sdk-core"; import JSBI from "jsbi"; import { useMemo } from "react"; import { useAllTokens } from "./Tokens"; import { useMulticall2Contract } from "./useContract"; import { isAddress } from "../utils"; import { useMultipleContractSingleData, useSingleContractMultipleData, } from "../state/gmulticall/hooks"; import { Interface } from "@ethersproject/abi"; import ERC20ABI from "../abis/erc20.json"; import { Erc20Interface } from "../abis/types/Erc20"; import { useWeb3 } from "../web3"; /** * Returns a map of the given addresses to their eventually consistent ETH balances. */ export function useETHBalances( uncheckedAddresses?: (string | undefined)[] ): { [address: string]: CurrencyAmount | undefined } { const multicallContract = useMulticall2Contract(); const { chainId } = useWeb3(); const addresses: string[] = useMemo( () => uncheckedAddresses ? uncheckedAddresses .map(isAddress) .filter((a): a is string => a !== false) .sort() : [], [uncheckedAddresses] ); const results = useSingleContractMultipleData( multicallContract as any, "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; }, {} ), [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( token?: Token ): CurrencyAmount | undefined { const { account } = useWeb3(); const tokenBalances = useTokenBalances(account ?? undefined, [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( account?: string ): { [tokenAddress: string]: CurrencyAmount | undefined; } { const { account: defaultAccount } = useWeb3(); const allTokens = useAllTokens(); const allTokensArray = useMemo(() => Object.values(allTokens ?? {}), [ allTokens, ]); const balances = useTokenBalances( account ?? defaultAccount ?? undefined, allTokensArray ); return balances ?? {}; }