Skip to content

Commit

Permalink
refactor: mv token hooks to lib (#3122)
Browse files Browse the repository at this point in the history
* refactor: mv useNativeCurrency to lib/hooks

* refactor: mv useCurrency logic to lib/hooks
  • Loading branch information
zzmp authored Jan 14, 2022
1 parent 1127e74 commit 99f6818
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 101 deletions.
9 changes: 2 additions & 7 deletions src/components/SearchModal/CurrencySearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import useDebounce from 'hooks/useDebounce'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useTheme from 'hooks/useTheme'
import useToggle from 'hooks/useToggle'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
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'
Expand All @@ -17,13 +18,7 @@ import { Text } from 'rebass'
import { useAllTokenBalances } from 'state/wallet/hooks'
import styled from 'styled-components/macro'

import {
useAllTokens,
useIsUserAddedToken,
useNativeCurrency,
useSearchInactiveTokenLists,
useToken,
} from '../../hooks/Tokens'
import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
import { ButtonText, CloseIcon, IconWrapper, ThemedText } from '../../theme'
import { isAddress } from '../../utils'
import Column from '../Column'
Expand Down
97 changes: 6 additions & 91 deletions src/hooks/Tokens.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { arrayify } from '@ethersproject/bytes'
import { parseBytes32String } from '@ethersproject/strings'
import { Currency, Token } from '@uniswap/sdk-core'
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 { useCurrencyFromMap, useTokenFromMap } from 'lib/hooks/useCurrency'
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
import { useMemo } from 'react'

import { nativeOnChain } from '../constants/tokens'
import { useAllLists, useCombinedActiveList, useInactiveListUrls } from '../state/lists/hooks'
import { WrappedTokenInfo } from '../state/lists/wrappedTokenInfo'
import { useUserAddedTokens } from '../state/user/hooks'
import { isAddress } from '../utils'
import { TokenAddressMap, useUnsupportedTokenList } from './../state/lists/hooks'
import { useBytes32TokenContract, useTokenContract } from './useContract'

// reduce token map into standard address <-> Token mapping, optionally include user added tokens
function useTokensFromMap(tokenMap: TokenAddressMap, includeUserAdded: boolean): { [address: string]: Token } {
Expand Down Expand Up @@ -159,95 +154,15 @@ export function useIsUserAddedToken(currency: Currency | undefined | null): bool
return !!userAddedTokens.find((token) => currency.equals(token))
}

// parse a name or symbol from a token response
const BYTES32_REGEX = /^0x[a-fA-F0-9]{64}$/

function parseStringOrBytes32(str: string | undefined, bytes32: string | undefined, defaultValue: string): string {
return str && str.length > 0
? str
: // need to check for proper bytes string and valid terminator
bytes32 && BYTES32_REGEX.test(bytes32) && arrayify(bytes32)[31] === 0
? parseBytes32String(bytes32)
: defaultValue
}

// undefined if invalid or does not exist
// null if loading or null was passed
// otherwise returns the token
export function useToken(tokenAddress?: string | null): Token | undefined | null {
const { chainId } = useActiveWeb3React()
export function useToken(tokenAddress?: string | null): Token | null | undefined {
const tokens = useAllTokens()

const address = isAddress(tokenAddress)

const tokenContract = useTokenContract(address ? address : undefined, false)
const tokenContractBytes32 = useBytes32TokenContract(address ? address : undefined, false)
const token: Token | undefined = address ? tokens[address] : undefined

const tokenName = useSingleCallResult(token ? undefined : tokenContract, 'name', undefined, NEVER_RELOAD)
const tokenNameBytes32 = useSingleCallResult(
token ? undefined : tokenContractBytes32,
'name',
undefined,
NEVER_RELOAD
)
const symbol = useSingleCallResult(token ? undefined : tokenContract, 'symbol', undefined, NEVER_RELOAD)
const symbolBytes32 = useSingleCallResult(token ? undefined : tokenContractBytes32, 'symbol', undefined, NEVER_RELOAD)
const decimals = useSingleCallResult(token ? undefined : tokenContract, 'decimals', undefined, NEVER_RELOAD)

return useMemo(() => {
if (token) return token
if (tokenAddress === null) return null
if (!chainId || !address) return undefined
if (decimals.loading || symbol.loading || tokenName.loading) return null
if (decimals.result) {
return new Token(
chainId,
address,
decimals.result[0],
parseStringOrBytes32(symbol.result?.[0], symbolBytes32.result?.[0], 'UNKNOWN'),
parseStringOrBytes32(tokenName.result?.[0], tokenNameBytes32.result?.[0], 'Unknown Token')
)
}
return undefined
}, [
address,
chainId,
decimals.loading,
decimals.result,
symbol.loading,
symbol.result,
symbolBytes32.result,
token,
tokenAddress,
tokenName.loading,
tokenName.result,
tokenNameBytes32.result,
])
}

export function useNativeCurrency(): Currency {
const { chainId } = useActiveWeb3React()
return useMemo(
() =>
chainId
? nativeOnChain(chainId)
: // display mainnet when not connected
nativeOnChain(SupportedChainId.MAINNET),
[chainId]
)
return useTokenFromMap(tokens, tokenAddress)
}

export function useCurrency(currencyId: string | null | undefined): Currency | null | undefined {
const nativeCurrency = useNativeCurrency()
const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH')
const token = useToken(isNative ? undefined : currencyId)

if (currencyId === null || currencyId === undefined) return currencyId

// this case so we use our builtin wrapped token instead of wrapped tokens on token lists
const wrappedNative = nativeCurrency?.wrapped
if (wrappedNative?.address?.toUpperCase() === currencyId?.toUpperCase()) return wrappedNative

return isNative ? nativeCurrency : token
export function useCurrency(currencyId?: string | null): Currency | null | undefined {
const tokens = useAllTokens()
return useCurrencyFromMap(tokens, currencyId)
}
2 changes: 1 addition & 1 deletion src/hooks/useSwapSlippageTolerance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import { L2_CHAIN_IDS } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import JSBI from 'jsbi'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types'

import { useUserSlippageToleranceWithDefault } from '../state/user/hooks'
import { useNativeCurrency } from './Tokens'
import useGasPrice from './useGasPrice'
import useUSDCPrice, { useUSDCValue } from './useUSDCPrice'

Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useWrapCallback.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react'

import { WRAPPED_NATIVE_CURRENCY } from '../constants/tokens'
import { tryParseAmount } from '../state/swap/hooks'
import { TransactionType } from '../state/transactions/actions'
import { useTransactionAdder } from '../state/transactions/hooks'
import { useCurrencyBalance } from '../state/wallet/hooks'
import { useNativeCurrency } from './Tokens'
import { useWETHContract } from './useContract'

export enum WrapType {
Expand Down
119 changes: 119 additions & 0 deletions src/lib/hooks/useCurrency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { arrayify } from '@ethersproject/bytes'
import { parseBytes32String } from '@ethersproject/strings'
import { Currency, Token } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useBytes32TokenContract, useTokenContract } from 'hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react'

import { isAddress } from '../../utils'
import { useTokenMap } from './useTokenList'
import { TokenMap } from './useTokenList'

// parse a name or symbol from a token response
const BYTES32_REGEX = /^0x[a-fA-F0-9]{64}$/

function parseStringOrBytes32(str: string | undefined, bytes32: string | undefined, defaultValue: string): string {
return str && str.length > 0
? str
: // need to check for proper bytes string and valid terminator
bytes32 && BYTES32_REGEX.test(bytes32) && arrayify(bytes32)[31] === 0
? parseBytes32String(bytes32)
: defaultValue
}

/**
* Returns a Token from the tokenAddress.
* Returns null if token is loading or null was passed.
* Returns undefined if tokenAddress is invalid or token does not exist.
*/
export function useTokenFromMap(tokens: TokenMap, tokenAddress?: string | null): Token | null | undefined {
const { chainId } = useActiveWeb3React()

const address = isAddress(tokenAddress)

const tokenContract = useTokenContract(address ? address : undefined, false)
const tokenContractBytes32 = useBytes32TokenContract(address ? address : undefined, false)
const token: Token | undefined = address ? tokens[address] : undefined

const tokenName = useSingleCallResult(token ? undefined : tokenContract, 'name', undefined, NEVER_RELOAD)
const tokenNameBytes32 = useSingleCallResult(
token ? undefined : tokenContractBytes32,
'name',
undefined,
NEVER_RELOAD
)
const symbol = useSingleCallResult(token ? undefined : tokenContract, 'symbol', undefined, NEVER_RELOAD)
const symbolBytes32 = useSingleCallResult(token ? undefined : tokenContractBytes32, 'symbol', undefined, NEVER_RELOAD)
const decimals = useSingleCallResult(token ? undefined : tokenContract, 'decimals', undefined, NEVER_RELOAD)

return useMemo(() => {
if (token) return token
if (tokenAddress === null) return null
if (!chainId || !address) return undefined
if (decimals.loading || symbol.loading || tokenName.loading) return null
if (decimals.result) {
return new Token(
chainId,
address,
decimals.result[0],
parseStringOrBytes32(symbol.result?.[0], symbolBytes32.result?.[0], 'UNKNOWN'),
parseStringOrBytes32(tokenName.result?.[0], tokenNameBytes32.result?.[0], 'Unknown Token')
)
}
return undefined
}, [
address,
chainId,
decimals.loading,
decimals.result,
symbol.loading,
symbol.result,
symbolBytes32.result,
token,
tokenAddress,
tokenName.loading,
tokenName.result,
tokenNameBytes32.result,
])
}

/**
* Returns a Token from the tokenAddress.
* Returns null if token is loading or null was passed.
* Returns undefined if tokenAddress is invalid or token does not exist.
*/
export function useToken(tokenAddress?: string | null): Token | null | undefined {
const tokens = useTokenMap()
return useTokenFromMap(tokens, tokenAddress)
}

/**
* Returns a Currency from the currencyId.
* Returns null if currency is loading or null was passed.
* Returns undefined if currencyId is invalid or token does not exist.
*/
export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null): Currency | null | undefined {
const nativeCurrency = useNativeCurrency()
const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH')
const token = useTokenFromMap(tokens, isNative ? undefined : currencyId)

if (currencyId === null || currencyId === undefined) return currencyId

// this case so we use our builtin wrapped token instead of wrapped tokens on token lists
const wrappedNative = nativeCurrency?.wrapped
if (wrappedNative?.address?.toUpperCase() === currencyId?.toUpperCase()) return wrappedNative

return isNative ? nativeCurrency : token
}

/**
* Returns a Currency from the currencyId.
* Returns null if currency is loading or null was passed.
* Returns undefined if currencyId is invalid or token does not exist.
*/
export function useCurrency(currencyId?: string | null): Currency | null | undefined {
const tokens = useTokenMap()
return useCurrencyFromMap(tokens, currencyId)
}
17 changes: 17 additions & 0 deletions src/lib/hooks/useNativeCurrency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Currency } from '@uniswap/sdk-core'
import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useMemo } from 'react'

export default function useNativeCurrency(): Currency {
const { chainId } = useActiveWeb3React()
return useMemo(
() =>
chainId
? nativeOnChain(chainId)
: // display mainnet when not connected
nativeOnChain(SupportedChainId.MAINNET),
[chainId]
)
}
15 changes: 15 additions & 0 deletions src/lib/hooks/useTokenList/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Token } from '@uniswap/sdk-core'
import { TokenInfo, TokenList } from '@uniswap/token-lists'
import { atom, useAtom } from 'jotai'
import { useAtomValue } from 'jotai/utils'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import resolveENSContentHash from 'lib/utils/resolveENSContentHash'
import { useEffect, useMemo, useState } from 'react'
Expand Down Expand Up @@ -44,6 +46,19 @@ export default function useTokenList(list?: string | TokenInfo[]): WrappedTokenI
}, [chainId, chainTokenMap])
}

export type TokenMap = { [address: string]: Token }

export function useTokenMap(): TokenMap {
const { chainId } = useActiveWeb3React()
const chainTokenMap = useAtomValue(chainTokenMapAtom)
return useMemo(() => {
return Object.entries((chainId && chainTokenMap[chainId]) || {}).reduce((map, [address, { token }]) => {
map[address] = token
return map
}, {} as TokenMap)
}, [chainId, chainTokenMap])
}

export function useQueryTokenList(query: string) {
return useQueryTokens(query, useTokenList())
}
2 changes: 1 addition & 1 deletion src/lib/hooks/useTokenList/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TokenInfo, TokenList } from '@uniswap/token-lists'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'

export type TokenMap = Readonly<{ [tokenAddress: string]: { token: WrappedTokenInfo; list?: TokenList } }>
type TokenMap = Readonly<{ [tokenAddress: string]: { token: WrappedTokenInfo; list?: TokenList } }>
export type ChainTokenMap = Readonly<{ [chainId: number]: TokenMap }>

type Mutable<T> = {
Expand Down

0 comments on commit 99f6818

Please sign in to comment.