feat: sort the widget token select (#3114)

* refactor: mv token list utils to lib

* refactor: mv balance hooks to lib

* feat: interactive token select
This commit is contained in:
Zach Pomerantz 2022-01-13 14:37:47 -08:00 committed by GitHub
parent 8784a761d6
commit e68e1afd9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 306 additions and 287 deletions

@ -6,12 +6,15 @@ import useDebounce from 'hooks/useDebounce'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useTheme from 'hooks/useTheme'
import useToggle from 'hooks/useToggle'
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
import { tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting'
import { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Edit } from 'react-feather'
import ReactGA from 'react-ga'
import AutoSizer from 'react-virtualized-auto-sizer'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'
import { useAllTokenBalances } from 'state/wallet/hooks'
import styled from 'styled-components/macro'
import {
@ -27,9 +30,7 @@ import Column from '../Column'
import Row, { RowBetween, RowFixed } from '../Row'
import CommonBases from './CommonBases'
import CurrencyList from './CurrencyList'
import { filterTokens, useSortedTokensByQuery } from './filtering'
import ImportRow from './ImportRow'
import { useTokenComparator } from './sorting'
import { PaddedColumn, SearchInput, Separator } from './styleds'
const ContentWrapper = styled(Column)`
@ -84,8 +85,6 @@ export function CurrencySearch({
const [searchQuery, setSearchQuery] = useState<string>('')
const debouncedQuery = useDebounce(searchQuery, 200)
const [invertSearchOrder] = useState<boolean>(false)
const allTokens = useAllTokens()
// if they input an address, use it
@ -105,17 +104,16 @@ export function CurrencySearch({
}
}, [isAddressSearch])
const tokenComparator = useTokenComparator(invertSearchOrder)
const filteredTokens: Token[] = useMemo(() => {
return filterTokens(Object.values(allTokens), debouncedQuery)
return Object.values(allTokens).filter(getTokenFilter(debouncedQuery))
}, [allTokens, debouncedQuery])
const balances = useAllTokenBalances()
const sortedTokens: Token[] = useMemo(() => {
return filteredTokens.sort(tokenComparator)
}, [filteredTokens, tokenComparator])
return filteredTokens.sort(tokenComparator.bind(null, balances))
}, [balances, filteredTokens])
const filteredSortedTokens = useSortedTokensByQuery(sortedTokens, debouncedQuery)
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
const native = useNativeCurrency()

@ -1,76 +0,0 @@
import { Token } from '@uniswap/sdk-core'
import { TokenInfo } from '@uniswap/token-lists'
import { useMemo } from 'react'
import { isAddress } from '../../utils'
const alwaysTrue = () => true
/**
* Create a filter function to apply to a token for whether it matches a particular search query
* @param search the search query to apply to the token
*/
export function createTokenFilterFunction<T extends Token | TokenInfo>(search: string): (tokens: T) => boolean {
const searchingAddress = isAddress(search)
if (searchingAddress) {
const lower = searchingAddress.toLowerCase()
return (t: T) => ('isToken' in t ? searchingAddress === t.address : lower === t.address.toLowerCase())
}
const lowerSearchParts = search
.toLowerCase()
.split(/\s+/)
.filter((s) => s.length > 0)
if (lowerSearchParts.length === 0) return alwaysTrue
const matchesSearch = (s: string): boolean => {
const sParts = s
.toLowerCase()
.split(/\s+/)
.filter((s) => s.length > 0)
return lowerSearchParts.every((p) => p.length === 0 || sParts.some((sp) => sp.startsWith(p) || sp.endsWith(p)))
}
return ({ name, symbol }: T): boolean => Boolean((symbol && matchesSearch(symbol)) || (name && matchesSearch(name)))
}
export function filterTokens<T extends Token | TokenInfo>(tokens: T[], search: string): T[] {
return tokens.filter(createTokenFilterFunction(search))
}
export function useSortedTokensByQuery(tokens: Token[] | undefined, searchQuery: string): Token[] {
return useMemo(() => {
if (!tokens) {
return []
}
const symbolMatch = searchQuery
.toLowerCase()
.split(/\s+/)
.filter((s) => s.length > 0)
if (symbolMatch.length > 1) {
return tokens
}
const exactMatches: Token[] = []
const symbolSubtrings: Token[] = []
const rest: Token[] = []
// sort tokens by exact match -> subtring on symbol match -> rest
tokens.map((token) => {
if (token.symbol?.toLowerCase() === symbolMatch[0]) {
return exactMatches.push(token)
} else if (token.symbol?.toLowerCase().startsWith(searchQuery.toLowerCase().trim())) {
return symbolSubtrings.push(token)
} else {
return rest.push(token)
}
})
return [...exactMatches, ...symbolSubtrings, ...rest]
}, [tokens, searchQuery])
}

@ -1,51 +0,0 @@
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { useAllTokenBalances } from '../../state/wallet/hooks'
// compare two token amounts with highest one coming first
function balanceComparator(balanceA?: CurrencyAmount<Currency>, balanceB?: CurrencyAmount<Currency>) {
if (balanceA && balanceB) {
return balanceA.greaterThan(balanceB) ? -1 : balanceA.equalTo(balanceB) ? 0 : 1
} else if (balanceA && balanceA.greaterThan('0')) {
return -1
} else if (balanceB && balanceB.greaterThan('0')) {
return 1
}
return 0
}
function getTokenComparator(balances: {
[tokenAddress: string]: CurrencyAmount<Currency> | undefined
}): (tokenA: Token, tokenB: Token) => number {
return function sortTokens(tokenA: Token, tokenB: Token): number {
// -1 = a is first
// 1 = b is first
// sort by balances
const balanceA = balances[tokenA.address]
const balanceB = balances[tokenB.address]
const balanceComp = balanceComparator(balanceA, balanceB)
if (balanceComp !== 0) return balanceComp
if (tokenA.symbol && tokenB.symbol) {
// sort by symbol
return tokenA.symbol.toLowerCase() < tokenB.symbol.toLowerCase() ? -1 : 1
} else {
return tokenA.symbol ? -1 : tokenB.symbol ? -1 : 0
}
}
}
export function useTokenComparator(inverted: boolean): (tokenA: Token, tokenB: Token) => number {
const balances = useAllTokenBalances()
const comparator = useMemo(() => getTokenComparator(balances ?? {}), [balances])
return useMemo(() => {
if (inverted) {
return (tokenA: Token, tokenB: Token) => comparator(tokenA, tokenB) * -1
} else {
return comparator
}
}, [inverted, comparator])
}

@ -5,9 +5,9 @@ import { CHAIN_INFO } from 'constants/chainInfo'
import { L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
import { useMemo } from 'react'
import { createTokenFilterFunction } from '../components/SearchModal/filtering'
import { nativeOnChain } from '../constants/tokens'
import { useAllLists, useCombinedActiveList, useInactiveListUrls } from '../state/lists/hooks'
import { WrappedTokenInfo } from '../state/lists/wrappedTokenInfo'
@ -117,7 +117,7 @@ export function useSearchInactiveTokenLists(search: string | undefined, minResul
const activeTokens = useAllTokens()
return useMemo(() => {
if (!search || search.trim().length === 0) return []
const tokenFilter = createTokenFilterFunction(search)
const tokenFilter = getTokenFilter(search)
const result: WrappedTokenInfo[] = []
const addressSet: { [address: string]: true } = {}
for (const url of inactiveUrls) {

@ -1,3 +1,5 @@
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
import useNativeEvent from 'lib/hooks/useNativeEvent'
import useScrollbar from 'lib/hooks/useScrollbar'
import styled, { ThemedText } from 'lib/theme'
@ -74,6 +76,10 @@ function TokenOption({ index, value, style }: TokenOptionProps) {
e.token = value
e.ref = ref.current ?? undefined
}
const { account } = useActiveWeb3React()
const balance = useCurrencyBalance(account, value)
return (
<TokenButton
data-index={index}
@ -94,7 +100,7 @@ function TokenOption({ index, value, style }: TokenOptionProps) {
<ThemedText.Caption color="secondary">{value.name}</ThemedText.Caption>
</Column>
</Row>
1.234
{balance?.greaterThan(0) && balance?.toFixed(2)}
</Row>
</ThemedText.Body1>
</TokenButton>

@ -1,8 +1,8 @@
import { t, Trans } from '@lingui/macro'
import useTokenList from 'lib/hooks/useTokenList'
import { useQueryTokenList } from 'lib/hooks/useTokenList'
import styled, { ThemedText } from 'lib/theme'
import { Token } from 'lib/types'
import { ElementRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { ElementRef, useCallback, useEffect, useRef, useState } from 'react'
import Column from '../Column'
import Dialog, { Header } from '../Dialog'
@ -18,17 +18,12 @@ const SearchInput = styled(StringInput)`
`
export function TokenSelectDialog({ onSelect }: { onSelect: (token: Token) => void }) {
const tokenMap = useTokenList()
const tokens = useMemo(() => Object.values(tokenMap).map(({ token }) => token), [tokenMap])
const [query, setQuery] = useState('')
const tokens = useQueryTokenList(query)
const baseTokens: Token[] = [] // TODO(zzmp): Add base tokens to token list functionality
// TODO(zzmp): Load token balances
// TODO(zzmp): Sort tokens
// TODO(zzmp): Disable already selected tokens
// TODO(zzmp): Include native Currency
// TODO(zzmp): Filter tokens by search
const [search, setSearch] = useState('')
// TODO(zzmp): Disable already selected tokens (passed as props?)
const input = useRef<HTMLInputElement>(null)
useEffect(() => input.current?.focus(), [input])
@ -42,8 +37,8 @@ export function TokenSelectDialog({ onSelect }: { onSelect: (token: Token) => vo
<Row pad={0.75} grow>
<ThemedText.Body1>
<SearchInput
value={search}
onChange={setSearch}
value={query}
onChange={setQuery}
placeholder={t`Search by token name or address`}
onKeyDown={options?.onKeyDown}
onBlur={options?.blur}

@ -0,0 +1,140 @@
import { Interface } from '@ethersproject/abi'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import ERC20ABI from 'abis/erc20.json'
import { Erc20Interface } from 'abis/types/Erc20'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import JSBI from 'jsbi'
import { useMultipleContractSingleData, useSingleContractMultipleData } from 'lib/hooks/multicall'
import { useMemo } from 'react'
import { nativeOnChain } from '../../constants/tokens'
import { useInterfaceMulticall } from '../../hooks/useContract'
import { isAddress } from '../../utils'
/**
* Returns a map of the given addresses to their eventually consistent ETH balances.
*/
export function useNativeCurrencyBalances(uncheckedAddresses?: (string | undefined)[]): {
[address: string]: CurrencyAmount<Currency> | undefined
} {
const { chainId } = useActiveWeb3React()
const multicallContract = useInterfaceMulticall()
const validAddressInputs: [string][] = useMemo(
() =>
uncheckedAddresses
? uncheckedAddresses
.map(isAddress)
.filter((a): a is string => a !== false)
.sort()
.map((addr) => [addr])
: [],
[uncheckedAddresses]
)
const results = useSingleContractMultipleData(multicallContract, 'getEthBalance', validAddressInputs)
return useMemo(
() =>
validAddressInputs.reduce<{ [address: string]: CurrencyAmount<Currency> }>((memo, [address], i) => {
const value = results?.[i]?.result?.[0]
if (value && chainId)
memo[address] = CurrencyAmount.fromRawAmount(nativeOnChain(chainId), JSBI.BigInt(value.toString()))
return memo
}, {}),
[validAddressInputs, chainId, results]
)
}
const ERC20Interface = new Interface(ERC20ABI) as Erc20Interface
const tokenBalancesGasRequirement = { gasRequired: 125_000 }
/**
* 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<Token> | 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 balances = useMultipleContractSingleData(
validatedTokenAddresses,
ERC20Interface,
'balanceOf',
useMemo(() => [address], [address]),
tokenBalancesGasRequirement
)
const anyLoading: boolean = useMemo(() => balances.some((callState) => callState.loading), [balances])
return useMemo(
() => [
address && validatedTokens.length > 0
? validatedTokens.reduce<{ [tokenAddress: string]: CurrencyAmount<Token> | 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
}, {})
: {},
anyLoading,
],
[address, validatedTokens, anyLoading, balances]
)
}
export function useTokenBalances(
address?: string,
tokens?: (Token | undefined)[]
): { [tokenAddress: string]: CurrencyAmount<Token> | undefined } {
return useTokenBalancesWithLoadingIndicator(address, tokens)[0]
}
// get the balance for a single token/account combo
export function useTokenBalance(account?: string, token?: Token): CurrencyAmount<Token> | undefined {
const tokenBalances = useTokenBalances(account, [token])
if (!token) return undefined
return tokenBalances[token.address]
}
export function useCurrencyBalances(
account?: string,
currencies?: (Currency | undefined)[]
): (CurrencyAmount<Currency> | 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 = useNativeCurrencyBalances(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 default function useCurrencyBalance(
account?: string,
currency?: Currency
): CurrencyAmount<Currency> | undefined {
return useCurrencyBalances(
account,
useMemo(() => [currency], [currency])
)[0]
}

@ -0,0 +1,34 @@
import { Token } from '@uniswap/sdk-core'
import { TokenInfo } from '@uniswap/token-lists'
import { isAddress } from '../../../utils'
const alwaysTrue = () => true
/** Creates a filter function that filters tokens that do not match the query. */
export function getTokenFilter<T extends Token | TokenInfo>(query: string): (token: T) => boolean {
const searchingAddress = isAddress(query)
if (searchingAddress) {
const lower = searchingAddress.toLowerCase()
return (t: T) => ('isToken' in t ? searchingAddress === t.address : lower === t.address.toLowerCase())
}
const queryParts = query
.toLowerCase()
.split(/\s+/)
.filter((s) => s.length > 0)
if (queryParts.length === 0) return alwaysTrue
const match = (s: string): boolean => {
const parts = s
.toLowerCase()
.split(/\s+/)
.filter((s) => s.length > 0)
return queryParts.every((p) => p.length === 0 || parts.some((sp) => sp.startsWith(p) || sp.endsWith(p)))
}
return ({ name, symbol }: T): boolean => Boolean((symbol && match(symbol)) || (name && match(name)))
}

@ -3,16 +3,18 @@ import { atom, useAtom } from 'jotai'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import resolveENSContentHash from 'lib/utils/resolveENSContentHash'
import { useEffect, useMemo, useState } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import fetchTokenList from './fetchTokenList'
import { ChainTokenMap, TokenMap, tokensToChainTokenMap } from './utils'
import { useQueryTokens } from './querying'
import { ChainTokenMap, tokensToChainTokenMap } from './utils'
import { validateTokens } from './validateTokenList'
export { DEFAULT_TOKEN_LIST } from './fetchTokenList'
const chainTokenMapAtom = atom<ChainTokenMap>({})
export default function useTokenList(list?: string | TokenInfo[]): TokenMap {
export default function useTokenList(list?: string | TokenInfo[]): WrappedTokenInfo[] {
const { chainId, library } = useActiveWeb3React()
const [chainTokenMap, setChainTokenMap] = useAtom(chainTokenMapAtom)
@ -38,6 +40,10 @@ export default function useTokenList(list?: string | TokenInfo[]): TokenMap {
}, [chainId, library, list, setChainTokenMap])
return useMemo(() => {
return (chainId && chainTokenMap[chainId]) || {}
return Object.values((chainId && chainTokenMap[chainId]) || {}).map(({ token }) => token)
}, [chainId, chainTokenMap])
}
export function useQueryTokenList(query: string) {
return useQueryTokens(query, useTokenList())
}

@ -0,0 +1,22 @@
import useDebounce from 'hooks/useDebounce'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import { useTokenBalances } from 'lib/hooks/useCurrencyBalance'
import { useMemo } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import { getTokenFilter } from './filtering'
import { tokenComparator, useSortTokensByQuery } from './sorting'
export function useQueryTokens(query: string, tokens: WrappedTokenInfo[]) {
const { account } = useActiveWeb3React()
const balances = useTokenBalances(account, tokens)
const sortedTokens = useMemo(() => [...tokens.sort(tokenComparator.bind(null, balances))], [balances, tokens])
const debouncedQuery = useDebounce(query, 200)
const filteredTokens = useMemo(
() => sortedTokens.filter(getTokenFilter(debouncedQuery)),
[debouncedQuery, sortedTokens]
)
return useSortTokensByQuery(debouncedQuery, filteredTokens)
}

@ -0,0 +1,65 @@
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { TokenInfo } from '@uniswap/token-lists'
import { useMemo } from 'react'
import { useTokenBalances } from 'state/wallet/hooks'
/** Sorts currency amounts (descending). */
function balanceComparator(a?: CurrencyAmount<Currency>, b?: CurrencyAmount<Currency>) {
if (a && b) {
return a.greaterThan(b) ? -1 : a.equalTo(b) ? 0 : 1
} else if (a?.greaterThan('0')) {
return -1
} else if (b?.greaterThan('0')) {
return 1
}
return 0
}
/** Sorts tokens by currency amount (descending), then symbol (ascending). */
export function tokenComparator(balances: ReturnType<typeof useTokenBalances>, a: Token, b: Token) {
// Sorts by balances
const balanceComparison = balanceComparator(balances[a.address], balances[b.address])
if (balanceComparison !== 0) return balanceComparison
// Sorts by symbol
if (a.symbol && b.symbol) {
return a.symbol.toLowerCase() < b.symbol.toLowerCase() ? -1 : 1
}
return -1
}
/** Sorts tokens by query, giving precedence to exact matches and partial matches. */
export function useSortTokensByQuery<T extends Token | TokenInfo>(query: string, tokens?: T[]): T[] {
return useMemo(() => {
if (!tokens) {
return []
}
const matches = query
.toLowerCase()
.split(/\s+/)
.filter((s) => s.length > 0)
if (matches.length > 1) {
return tokens
}
const exactMatches: T[] = []
const symbolSubtrings: T[] = []
const rest: T[] = []
// sort tokens by exact match -> subtring on symbol match -> rest
tokens.map((token) => {
if (token.symbol?.toLowerCase() === matches[0]) {
return exactMatches.push(token)
} else if (token.symbol?.toLowerCase().startsWith(query.toLowerCase().trim())) {
return symbolSubtrings.push(token)
} else {
return rest.push(token)
}
})
return [...exactMatches, ...symbolSubtrings, ...rest]
}, [tokens, query])
}

@ -1,142 +1,22 @@
import { Interface } from '@ethersproject/abi'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import ERC20ABI from 'abis/erc20.json'
import { Erc20Interface } from 'abis/types/Erc20'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import JSBI from 'jsbi'
import { useMultipleContractSingleData, useSingleContractMultipleData } from 'lib/hooks/multicall'
import { useTokenBalance, useTokenBalances } from 'lib/hooks/useCurrencyBalance'
import { useMemo } from 'react'
import { nativeOnChain, UNI } from '../../constants/tokens'
import { UNI } from '../../constants/tokens'
import { useAllTokens } from '../../hooks/Tokens'
import { useInterfaceMulticall } from '../../hooks/useContract'
import { isAddress } from '../../utils'
import { useUserUnclaimedAmount } from '../claim/hooks'
import { useTotalUniEarned } from '../stake/hooks'
/**
* Returns a map of the given addresses to their eventually consistent ETH balances.
*/
export function useNativeCurrencyBalances(uncheckedAddresses?: (string | undefined)[]): {
[address: string]: CurrencyAmount<Currency> | undefined
} {
const { chainId } = useActiveWeb3React()
const multicallContract = useInterfaceMulticall()
const validAddressInputs: [string][] = useMemo(
() =>
uncheckedAddresses
? uncheckedAddresses
.map(isAddress)
.filter((a): a is string => a !== false)
.sort()
.map((addr) => [addr])
: [],
[uncheckedAddresses]
)
const results = useSingleContractMultipleData(multicallContract, 'getEthBalance', validAddressInputs)
return useMemo(
() =>
validAddressInputs.reduce<{ [address: string]: CurrencyAmount<Currency> }>((memo, [address], i) => {
const value = results?.[i]?.result?.[0]
if (value && chainId)
memo[address] = CurrencyAmount.fromRawAmount(nativeOnChain(chainId), JSBI.BigInt(value.toString()))
return memo
}, {}),
[validAddressInputs, chainId, results]
)
}
const ERC20Interface = new Interface(ERC20ABI) as Erc20Interface
const tokenBalancesGasRequirement = { gasRequired: 125_000 }
/**
* 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<Token> | 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 balances = useMultipleContractSingleData(
validatedTokenAddresses,
ERC20Interface,
'balanceOf',
useMemo(() => [address], [address]),
tokenBalancesGasRequirement
)
const anyLoading: boolean = useMemo(() => balances.some((callState) => callState.loading), [balances])
return useMemo(
() => [
address && validatedTokens.length > 0
? validatedTokens.reduce<{ [tokenAddress: string]: CurrencyAmount<Token> | 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
}, {})
: {},
anyLoading,
],
[address, validatedTokens, anyLoading, balances]
)
}
export function useTokenBalances(
address?: string,
tokens?: (Token | undefined)[]
): { [tokenAddress: string]: CurrencyAmount<Token> | undefined } {
return useTokenBalancesWithLoadingIndicator(address, tokens)[0]
}
// get the balance for a single token/account combo
export function useTokenBalance(account?: string, token?: Token): CurrencyAmount<Token> | undefined {
const tokenBalances = useTokenBalances(account, [token])
if (!token) return undefined
return tokenBalances[token.address]
}
export function useCurrencyBalances(
account?: string,
currencies?: (Currency | undefined)[]
): (CurrencyAmount<Currency> | 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 = useNativeCurrencyBalances(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<Currency> | undefined {
return useCurrencyBalances(
account,
useMemo(() => [currency], [currency])
)[0]
}
export {
default as useCurrencyBalance,
useCurrencyBalances,
useNativeCurrencyBalances,
useTokenBalance,
useTokenBalances,
useTokenBalancesWithLoadingIndicator,
} from 'lib/hooks/useCurrencyBalance'
// mimics useAllBalances
export function useAllTokenBalances(): { [tokenAddress: string]: CurrencyAmount<Token> | undefined } {