Commit 104be830 authored by Moody Salem's avatar Moody Salem Committed by GitHub

perf(multicall): use single call to get token information (#855)

* use single call to get token information

* delete the bytes32 overload

* console log statement

* add a bunch of tests to actions.ts for multicall

* fix to work with bytes32 symbols/names

* only include name/symbol

* enforce lowercase calldata
parent 24c70791
...@@ -28,7 +28,8 @@ export default function TokenList({ ...@@ -28,7 +28,8 @@ export default function TokenList({
otherToken, otherToken,
showSendWithSwap, showSendWithSwap,
onRemoveAddedToken, onRemoveAddedToken,
otherSelectedText otherSelectedText,
hideRemove
}: { }: {
tokens: Token[] tokens: Token[]
selectedToken: string selectedToken: string
...@@ -38,6 +39,7 @@ export default function TokenList({ ...@@ -38,6 +39,7 @@ export default function TokenList({
otherToken: string otherToken: string
showSendWithSwap?: boolean showSendWithSwap?: boolean
otherSelectedText: string otherSelectedText: string
hideRemove?: boolean
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
...@@ -80,15 +82,16 @@ export default function TokenList({ ...@@ -80,15 +82,16 @@ export default function TokenList({
</Text> </Text>
<FadedSpan> <FadedSpan>
<TYPE.main fontWeight={500}>{customAdded && 'Added by user'}</TYPE.main> <TYPE.main fontWeight={500}>{customAdded && 'Added by user'}</TYPE.main>
{customAdded && ( {customAdded && !hideRemove && (
<div <LinkStyledButton
onClick={event => { onClick={event => {
event.stopPropagation() event.stopPropagation()
onRemoveAddedToken(chainId, address) onRemoveAddedToken(chainId, address)
}} }}
style={{ marginLeft: '4px', fontWeight: 400 }}
> >
<LinkStyledButton style={{ marginLeft: '4px', fontWeight: 400 }}>(Remove)</LinkStyledButton> (Remove)
</div> </LinkStyledButton>
)} )}
</FadedSpan> </FadedSpan>
</Column> </Column>
......
...@@ -189,6 +189,7 @@ function SearchModal({ ...@@ -189,6 +189,7 @@ function SearchModal({
otherToken={otherSelectedTokenAddress} otherToken={otherSelectedTokenAddress}
selectedToken={hiddenToken} selectedToken={hiddenToken}
showSendWithSwap={showSendWithSwap} showSendWithSwap={showSendWithSwap}
hideRemove={Boolean(isAddress(searchQuery))}
/> />
) : ( ) : (
<PairList <PairList
......
import { Interface } from '@ethersproject/abi' import { Interface } from '@ethersproject/abi'
import ERC20_ABI from './erc20.json' import ERC20_ABI from './erc20.json'
import ERC20_BYTES32_ABI from './erc20_bytes32.json'
const ERC20_INTERFACE = new Interface(ERC20_ABI) const ERC20_INTERFACE = new Interface(ERC20_ABI)
const ERC20_BYTES32_INTERFACE = new Interface(ERC20_BYTES32_ABI)
export default ERC20_INTERFACE export default ERC20_INTERFACE
export { ERC20_ABI, ERC20_BYTES32_INTERFACE, ERC20_BYTES32_ABI }
...@@ -3,56 +3,12 @@ ...@@ -3,56 +3,12 @@
"constant": true, "constant": true,
"inputs": [], "inputs": [],
"name": "name", "name": "name",
"outputs": [{ "name": "", "type": "bytes32" }], "outputs": [
"payable": false, {
"stateMutability": "view", "name": "",
"type": "function" "type": "bytes32"
}, }
{
"constant": false,
"inputs": [{ "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" }],
"name": "approve",
"outputs": [{ "name": "", "type": "bool" }],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_from", "type": "address" },
{ "name": "_to", "type": "address" },
{ "name": "_value", "type": "uint256" }
], ],
"name": "transferFrom",
"outputs": [{ "name": "", "type": "bool" }],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{ "name": "", "type": "uint8" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "_owner", "type": "address" }],
"name": "balanceOf",
"outputs": [{ "name": "balance", "type": "uint256" }],
"payable": false, "payable": false,
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
...@@ -61,48 +17,14 @@ ...@@ -61,48 +17,14 @@
"constant": true, "constant": true,
"inputs": [], "inputs": [],
"name": "symbol", "name": "symbol",
"outputs": [{ "name": "", "type": "bytes32" }], "outputs": [
"payable": false, {
"stateMutability": "view", "name": "",
"type": "function" "type": "bytes32"
}, }
{ ],
"constant": false,
"inputs": [{ "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" }],
"name": "transfer",
"outputs": [{ "name": "", "type": "bool" }],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" }],
"name": "allowance",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false, "payable": false,
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
},
{ "payable": true, "stateMutability": "payable", "type": "fallback" },
{
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "owner", "type": "address" },
{ "indexed": true, "name": "spender", "type": "address" },
{ "indexed": false, "name": "value", "type": "uint256" }
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "from", "type": "address" },
{ "indexed": true, "name": "to", "type": "address" },
{ "indexed": false, "name": "value", "type": "uint256" }
],
"name": "Transfer",
"type": "event"
} }
] ]
import { parseBytes32String } from '@ethersproject/strings'
import { ChainId, Token, WETH } from '@uniswap/sdk' import { ChainId, Token, WETH } from '@uniswap/sdk'
import { useEffect, useMemo } from 'react' import { useEffect, useMemo } from 'react'
import { ALL_TOKENS } from '../constants/tokens' import { ALL_TOKENS } from '../constants/tokens'
import { useAddUserToken, useFetchTokenByAddress, useUserAddedTokens } from '../state/user/hooks' import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
import { useAddUserToken, useUserAddedTokens } from '../state/user/hooks'
import { isAddress } from '../utils' import { isAddress } from '../utils'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
import { useBytes32TokenContract, useTokenContract } from './useContract'
export function useAllTokens(): { [address: string]: Token } { export function useAllTokens(): { [address: string]: Token } {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
...@@ -35,36 +38,83 @@ export function useAllTokens(): { [address: string]: Token } { ...@@ -35,36 +38,83 @@ export function useAllTokens(): { [address: string]: Token } {
}, [userAddedTokens, chainId]) }, [userAddedTokens, chainId])
} }
export function useToken(tokenAddress?: string): Token | undefined { // 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
: bytes32 && BYTES32_REGEX.test(bytes32)
? parseBytes32String(bytes32)
: defaultValue
}
// undefined if invalid or does not exist
// null if loading
// otherwise returns the token
export function useToken(tokenAddress?: string): Token | undefined | null {
const { chainId } = useActiveWeb3React()
const tokens = useAllTokens() 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(() => { return useMemo(() => {
const validatedAddress = isAddress(tokenAddress) if (token) return token
if (!validatedAddress) return if (!chainId || !address) return undefined
return tokens[validatedAddress] if (decimals.loading || symbol.loading || tokenName.loading) return null
}, [tokens, tokenAddress]) 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,
tokenName.loading,
tokenName.result,
tokenNameBytes32.result
])
} }
// gets token information by address (typically user input) and // gets token information by address (typically user input) and
// automatically adds it for the user if the token address is valid // automatically adds it for the user if it's a valid token address
export function useTokenByAddressAndAutomaticallyAdd(tokenAddress?: string): Token | undefined { export function useTokenByAddressAndAutomaticallyAdd(tokenAddress?: string): Token | undefined | null {
const fetchTokenByAddress = useFetchTokenByAddress()
const addToken = useAddUserToken() const addToken = useAddUserToken()
const token = useToken(tokenAddress) const token = useToken(tokenAddress)
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const allTokens = useAllTokens()
useEffect(() => { useEffect(() => {
if (!chainId || !isAddress(tokenAddress)) return if (!chainId || !token) return
const weth = WETH[chainId as ChainId] if (WETH[chainId as ChainId]?.address === token.address) return
if (weth && weth.address === isAddress(tokenAddress)) return if (allTokens[token.address]) return
addToken(token)
if (tokenAddress && !token) { }, [token, addToken, chainId, allTokens])
fetchTokenByAddress(tokenAddress).then(token => {
if (token !== null) {
addToken(token)
}
})
}
}, [tokenAddress, token, fetchTokenByAddress, addToken, chainId])
return token return token
} }
...@@ -2,6 +2,7 @@ import { Contract } from '@ethersproject/contracts' ...@@ -2,6 +2,7 @@ import { Contract } from '@ethersproject/contracts'
import { ChainId } from '@uniswap/sdk' import { ChainId } from '@uniswap/sdk'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json' import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { useMemo } from 'react' import { useMemo } from 'react'
import { ERC20_BYTES32_ABI } from '../constants/abis/erc20'
import ERC20_ABI from '../constants/abis/erc20.json' import ERC20_ABI from '../constants/abis/erc20.json'
import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator' import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator'
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from '../constants/v1' import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from '../constants/v1'
...@@ -41,6 +42,10 @@ export function useTokenContract(tokenAddress?: string, withSignerIfPossible = t ...@@ -41,6 +42,10 @@ export function useTokenContract(tokenAddress?: string, withSignerIfPossible = t
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible) return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible)
} }
export function useBytes32TokenContract(tokenAddress?: string, withSignerIfPossible = true): Contract | null {
return useContract(tokenAddress, ERC20_BYTES32_ABI, withSignerIfPossible)
}
export function usePairContract(pairAddress?: string, withSignerIfPossible = true): Contract | null { export function usePairContract(pairAddress?: string, withSignerIfPossible = true): Contract | null {
return useContract(pairAddress, IUniswapV2PairABI, withSignerIfPossible) return useContract(pairAddress, IUniswapV2PairABI, withSignerIfPossible)
} }
......
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import useDebounce from '../../hooks/useDebounce'
import useIsWindowVisible from '../../hooks/useIsWindowVisible' import useIsWindowVisible from '../../hooks/useIsWindowVisible'
import { updateBlockNumber } from './actions' import { updateBlockNumber } from './actions'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
...@@ -45,10 +46,12 @@ export default function Updater() { ...@@ -45,10 +46,12 @@ export default function Updater() {
} }
}, [dispatch, chainId, library, blockNumberCallback, windowVisible]) }, [dispatch, chainId, library, blockNumberCallback, windowVisible])
const debouncedState = useDebounce(state, 100)
useEffect(() => { useEffect(() => {
if (!state.chainId || !state.blockNumber || !windowVisible) return if (!debouncedState.chainId || !debouncedState.blockNumber || !windowVisible) return
dispatch(updateBlockNumber({ chainId: state.chainId, blockNumber: state.blockNumber })) dispatch(updateBlockNumber({ chainId: debouncedState.chainId, blockNumber: debouncedState.blockNumber }))
}, [windowVisible, dispatch, state.blockNumber, state.chainId]) }, [windowVisible, dispatch, debouncedState.blockNumber, debouncedState.chainId])
return null return null
} }
import { parseCallKey, toCallKey } from './actions'
describe('actions', () => {
describe('#parseCallKey', () => {
it('throws for invalid address', () => {
expect(() => parseCallKey('0x-0x')).toThrow('Invalid address: 0x')
})
it('throws for invalid calldata', () => {
expect(() => parseCallKey('0x6b175474e89094c44da98b954eedeac495271d0f-abc')).toThrow('Invalid hex: abc')
})
it('throws for invalid format', () => {
expect(() => parseCallKey('abc')).toThrow('Invalid call key: abc')
})
it('throws for uppercase hex', () => {
expect(() => parseCallKey('0x6b175474e89094c44da98b954eedeac495271d0f-0xabcD')).toThrow('Invalid hex: 0xabcD')
})
it('parses pieces into address', () => {
expect(parseCallKey('0x6b175474e89094c44da98b954eedeac495271d0f-0xabcd')).toEqual({
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
callData: '0xabcd'
})
})
})
describe('#toCallKey', () => {
it('throws for invalid address', () => {
expect(() => toCallKey({ callData: '0x', address: '0x' })).toThrow('Invalid address: 0x')
})
it('throws for invalid calldata', () => {
expect(() =>
toCallKey({
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
callData: 'abc'
})
).toThrow('Invalid hex: abc')
})
it('throws for uppercase hex', () => {
expect(() =>
toCallKey({
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
callData: '0xabcD'
})
).toThrow('Invalid hex: 0xabcD')
})
it('concatenates address to data', () => {
expect(toCallKey({ address: '0x6b175474e89094c44da98b954eedeac495271d0f', callData: '0xabcd' })).toEqual(
'0x6B175474E89094C44Da98b954EedeAC495271d0F-0xabcd'
)
})
})
})
...@@ -6,8 +6,16 @@ export interface Call { ...@@ -6,8 +6,16 @@ export interface Call {
callData: string callData: string
} }
const LOWER_HEX_REGEX = /^0x[a-f0-9]*$/
export function toCallKey(call: Call): string { export function toCallKey(call: Call): string {
return `${call.address}-${call.callData}` const addr = isAddress(call.address)
if (!addr) {
throw new Error(`Invalid address: ${call.address}`)
}
if (!LOWER_HEX_REGEX.test(call.callData)) {
throw new Error(`Invalid hex: ${call.callData}`)
}
return `${addr}-${call.callData}`
} }
export function parseCallKey(callKey: string): Call { export function parseCallKey(callKey: string): Call {
...@@ -20,12 +28,12 @@ export function parseCallKey(callKey: string): Call { ...@@ -20,12 +28,12 @@ export function parseCallKey(callKey: string): Call {
throw new Error(`Invalid address: ${pcs[0]}`) throw new Error(`Invalid address: ${pcs[0]}`)
} }
if (!pcs[1].match(/^0x[a-fA-F0-9]*$/)) { if (!LOWER_HEX_REGEX.test(pcs[1])) {
throw new Error(`Invalid hex: ${pcs[1]}`) throw new Error(`Invalid hex: ${pcs[1]}`)
} }
return { return {
address: pcs[0], address: addr,
callData: pcs[1] callData: pcs[1]
} }
} }
......
...@@ -121,22 +121,38 @@ const INVALID_CALL_STATE: CallState = { valid: false, result: undefined, loading ...@@ -121,22 +121,38 @@ const INVALID_CALL_STATE: CallState = { valid: false, result: undefined, loading
const LOADING_CALL_STATE: CallState = { valid: true, result: undefined, loading: true, syncing: true, error: false } const LOADING_CALL_STATE: CallState = { valid: true, result: undefined, loading: true, syncing: true, error: false }
function toCallState( function toCallState(
result: CallResult | undefined, callResult: CallResult | undefined,
contractInterface: Interface | undefined, contractInterface: Interface | undefined,
fragment: FunctionFragment | undefined, fragment: FunctionFragment | undefined,
latestBlockNumber: number | undefined latestBlockNumber: number | undefined
): CallState { ): CallState {
if (!result) return INVALID_CALL_STATE if (!callResult) return INVALID_CALL_STATE
const { valid, data, blockNumber } = result const { valid, data, blockNumber } = callResult
if (!valid) return INVALID_CALL_STATE if (!valid) return INVALID_CALL_STATE
if (valid && !blockNumber) return LOADING_CALL_STATE if (valid && !blockNumber) return LOADING_CALL_STATE
if (!contractInterface || !fragment || !latestBlockNumber) return LOADING_CALL_STATE if (!contractInterface || !fragment || !latestBlockNumber) return LOADING_CALL_STATE
const success = data && data.length > 2 const success = data && data.length > 2
const syncing = (blockNumber ?? 0) < latestBlockNumber
let result: Result | undefined = undefined
if (success && data) {
try {
result = contractInterface.decodeFunctionResult(fragment, data)
} catch (error) {
console.debug('Result data parsing failed', fragment, data)
return {
valid: true,
loading: false,
error: true,
syncing,
result
}
}
}
return { return {
valid: true, valid: true,
loading: false, loading: false,
syncing: (blockNumber ?? 0) < latestBlockNumber, syncing,
result: success && data ? contractInterface.decodeFunctionResult(fragment, data) : undefined, result: result,
error: !success error: !success
} }
} }
......
...@@ -2,6 +2,9 @@ import { addMulticallListeners, removeMulticallListeners, updateMulticallResults ...@@ -2,6 +2,9 @@ import { addMulticallListeners, removeMulticallListeners, updateMulticallResults
import reducer, { MulticallState } from './reducer' import reducer, { MulticallState } from './reducer'
import { Store, createStore } from '@reduxjs/toolkit' import { Store, createStore } from '@reduxjs/toolkit'
const DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f'
const CHECKSUMMED_DAI_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F'
describe('multicall reducer', () => { describe('multicall reducer', () => {
let store: Store<MulticallState> let store: Store<MulticallState>
beforeEach(() => { beforeEach(() => {
...@@ -20,7 +23,7 @@ describe('multicall reducer', () => { ...@@ -20,7 +23,7 @@ describe('multicall reducer', () => {
chainId: 1, chainId: 1,
calls: [ calls: [
{ {
address: '0x', address: DAI_ADDRESS,
callData: '0x' callData: '0x'
} }
] ]
...@@ -29,7 +32,7 @@ describe('multicall reducer', () => { ...@@ -29,7 +32,7 @@ describe('multicall reducer', () => {
expect(store.getState()).toEqual({ expect(store.getState()).toEqual({
callListeners: { callListeners: {
[1]: { [1]: {
'0x-0x': { [`${CHECKSUMMED_DAI_ADDRESS}-0x`]: {
[1]: 1 [1]: 1
} }
} }
...@@ -45,7 +48,7 @@ describe('multicall reducer', () => { ...@@ -45,7 +48,7 @@ describe('multicall reducer', () => {
removeMulticallListeners({ removeMulticallListeners({
calls: [ calls: [
{ {
address: '0x', address: DAI_ADDRESS,
callData: '0x' callData: '0x'
} }
], ],
...@@ -60,7 +63,7 @@ describe('multicall reducer', () => { ...@@ -60,7 +63,7 @@ describe('multicall reducer', () => {
chainId: 1, chainId: 1,
calls: [ calls: [
{ {
address: '0x', address: DAI_ADDRESS,
callData: '0x' callData: '0x'
} }
] ]
...@@ -70,14 +73,17 @@ describe('multicall reducer', () => { ...@@ -70,14 +73,17 @@ describe('multicall reducer', () => {
removeMulticallListeners({ removeMulticallListeners({
calls: [ calls: [
{ {
address: '0x', address: DAI_ADDRESS,
callData: '0x' callData: '0x'
} }
], ],
chainId: 1 chainId: 1
}) })
) )
expect(store.getState()).toEqual({ callResults: {}, callListeners: { [1]: { '0x-0x': {} } } }) expect(store.getState()).toEqual({
callResults: {},
callListeners: { [1]: { [`${CHECKSUMMED_DAI_ADDRESS}-0x`]: {} } }
})
}) })
}) })
......
...@@ -5,7 +5,6 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux' ...@@ -5,7 +5,6 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useAllTokens } from '../../hooks/Tokens' import { useAllTokens } from '../../hooks/Tokens'
import { getTokenInfoWithFallback, isAddress } from '../../utils'
import { AppDispatch, AppState } from '../index' import { AppDispatch, AppState } from '../index'
import { import {
addSerializedPair, addSerializedPair,
...@@ -64,26 +63,6 @@ export function useDarkModeManager(): [boolean, () => void] { ...@@ -64,26 +63,6 @@ export function useDarkModeManager(): [boolean, () => void] {
return [darkMode, toggleSetDarkMode] return [darkMode, toggleSetDarkMode]
} }
export function useFetchTokenByAddress(): (address: string) => Promise<Token | null> {
const { library, chainId } = useActiveWeb3React()
return useCallback(
async (address: string): Promise<Token | null> => {
if (!library || !chainId) return null
const validatedAddress = isAddress(address)
if (!validatedAddress) return null
const { name, symbol, decimals } = await getTokenInfoWithFallback(validatedAddress, library)
if (decimals === null) {
return null
} else {
return new Token(chainId, validatedAddress, decimals, symbol, name)
}
},
[library, chainId]
)
}
export function useAddUserToken(): (token: Token) => void { export function useAddUserToken(): (token: Token) => void {
const dispatch = useDispatch<AppDispatch>() const dispatch = useDispatch<AppDispatch>()
return useCallback( return useCallback(
......
...@@ -2,15 +2,10 @@ import { Contract } from '@ethersproject/contracts' ...@@ -2,15 +2,10 @@ import { Contract } from '@ethersproject/contracts'
import { getAddress } from '@ethersproject/address' import { getAddress } from '@ethersproject/address'
import { AddressZero } from '@ethersproject/constants' import { AddressZero } from '@ethersproject/constants'
import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers' import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { parseBytes32String } from '@ethersproject/strings'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json' import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { abi as IUniswapV2Router01ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router01.json' import { abi as IUniswapV2Router01ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router01.json'
import { ROUTER_ADDRESS } from '../constants' import { ROUTER_ADDRESS } from '../constants'
import ERC20_ABI from '../constants/abis/erc20.json'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32.json'
import { ChainId, JSBI, Percent, TokenAmount } from '@uniswap/sdk' import { ChainId, JSBI, Percent, TokenAmount } from '@uniswap/sdk'
// returns the checksummed address if the address is valid, otherwise returns false // returns the checksummed address if the address is valid, otherwise returns false
...@@ -102,51 +97,6 @@ export function getExchangeContract(pairAddress: string, library: Web3Provider, ...@@ -102,51 +97,6 @@ export function getExchangeContract(pairAddress: string, library: Web3Provider,
return getContract(pairAddress, IUniswapV2PairABI, library, account) return getContract(pairAddress, IUniswapV2PairABI, library, account)
} }
// get token info and fall back to unknown if not available, except for the
// decimals which falls back to null
export async function getTokenInfoWithFallback(
tokenAddress: string,
library: Web3Provider
): Promise<{ name: string; symbol: string; decimals: null | number }> {
if (!isAddress(tokenAddress)) {
throw Error(`Invalid 'tokenAddress' parameter '${tokenAddress}'.`)
}
const token = getContract(tokenAddress, ERC20_ABI, library)
const namePromise: Promise<string> = token.name().catch(() =>
getContract(tokenAddress, ERC20_BYTES32_ABI, library)
.name()
.then(parseBytes32String)
.catch((e: Error) => {
console.debug('Failed to get name for token address', e, tokenAddress)
return 'Unknown'
})
)
const symbolPromise: Promise<string> = token.symbol().catch(() => {
const contractBytes32 = getContract(tokenAddress, ERC20_BYTES32_ABI, library)
return contractBytes32
.symbol()
.then(parseBytes32String)
.catch((e: Error) => {
console.debug('Failed to get symbol for token address', e, tokenAddress)
return 'UNKNOWN'
})
})
const decimalsPromise: Promise<number | null> = token.decimals().catch((e: Error) => {
console.debug('Failed to get decimals for token address', e, tokenAddress)
return null
})
const [name, symbol, decimals]: [string, string, number | null] = (await Promise.all([
namePromise,
symbolPromise,
decimalsPromise
])) as [string, string, number | null]
return { name, symbol, decimals }
}
export function escapeRegExp(string: string): string { export function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment