import useSWR, { responseInterface } from 'swr' import { Token, TokenAmount, Pair, JSBI, ChainId } from '@uniswap/sdk' import { useWeb3React } from '@web3-react/core' import { Contract } from '@ethersproject/contracts' import { parseBytes32String } from '@ethersproject/strings' import { getAddress } from '@ethersproject/address' import { Web3Provider } from '@ethersproject/providers' import IERC20 from '@uniswap/v2-core/build/IERC20.json' import IUniswapV2Pair from '@uniswap/v2-core/build/IUniswapV2Pair.json' import { ZERO, ADDRESS_ZERO, ERC20_BYTES32 } from './constants' import { useContract, useKeepSWRDataLiveAsBlocksArrive } from './hooks' export enum DataType { BlockNumber, ETHBalance, TokenBalance, TokenAllowance, Reserves, Token, RemoteTokens, } function getBlockNumber(library: Web3Provider): () => Promise<number> { return async (): Promise<number> => { return library.getBlockNumber() } } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useBlockNumber(): responseInterface<number, any> { const { library } = useWeb3React() const shouldFetch = !!library return useSWR(shouldFetch ? [DataType.BlockNumber] : null, getBlockNumber(library), { refreshInterval: 10 * 1000, }) } function getETHBalance(library: Web3Provider): (chainId: number, address: string) => Promise<TokenAmount> { return async (chainId: number, address: string): Promise<TokenAmount> => { const ETH = new Token(chainId, ADDRESS_ZERO, 18) return library .getBalance(address) .then((balance: { toString: () => string }) => new TokenAmount(ETH, balance.toString())) } } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useETHBalance(address?: string | null, suspense = false): responseInterface<TokenAmount, any> { const { chainId, library } = useWeb3React() const shouldFetch = typeof chainId === 'number' && typeof address === 'string' && !!library const result = useSWR(shouldFetch ? [chainId, address, DataType.ETHBalance] : null, getETHBalance(library), { suspense, }) useKeepSWRDataLiveAsBlocksArrive(result.mutate) return result } function getTokenBalance(contract: Contract, token: Token): (address: string) => Promise<TokenAmount> { return async (address: string): Promise<TokenAmount> => contract .balanceOf(address) .then((balance: { toString: () => string }) => new TokenAmount(token, balance.toString())) } export function useTokenBalance( token?: Token, address?: string | null, suspense = false // eslint-disable-next-line @typescript-eslint/no-explicit-any ): responseInterface<TokenAmount, any> { const contract = useContract(token?.address, IERC20.abi) const result = useSWR( typeof address === 'string' && token && contract ? [address, token.chainId, token.address, DataType.TokenBalance] : null, getTokenBalance(contract as Contract, token as Token), { suspense } ) useKeepSWRDataLiveAsBlocksArrive(result.mutate) return result } function getTokenAllowance(contract: Contract, token: Token): (owner: string, spender: string) => Promise<TokenAmount> { return async (owner: string, spender: string): Promise<TokenAmount> => contract .allowance(owner, spender) .then((balance: { toString: () => string }) => new TokenAmount(token, balance.toString())) } export function useTokenAllowance( token?: Token, owner?: string | null, spender?: string // eslint-disable-next-line @typescript-eslint/no-explicit-any ): responseInterface<TokenAmount, any> { const contract = useContract(token?.address, IERC20.abi) const result = useSWR( typeof owner === 'string' && typeof spender === 'string' && token && contract ? [owner, spender, token.chainId, token.address, DataType.TokenAllowance] : null, getTokenAllowance(contract as Contract, token as Token) ) useKeepSWRDataLiveAsBlocksArrive(result.mutate) return result } function getReserves(contract: Contract, token0: Token, token1: Token): () => Promise<Pair | null> { return async (): Promise<Pair | null> => contract .getReserves() .then( ({ reserve0, reserve1 }: { reserve0: { toString: () => string }; reserve1: { toString: () => string } }) => { const pair = new Pair( new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()) ) return JSBI.equal(pair.reserve0.raw, ZERO) || JSBI.equal(pair.reserve1.raw, ZERO) ? null : pair } ) .catch(() => null) } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useReserves(tokenA?: Token, tokenB?: Token): responseInterface<Pair | null, any> { const invalid = !!tokenA && !!tokenB && tokenA.equals(tokenB) const [token0, token1] = !!tokenA && !!tokenB && !invalid ? (tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]) : [] const pairAddress = !!token0 && !!token1 ? Pair.getAddress(token0, token1) : undefined const contract = useContract(pairAddress, IUniswapV2Pair.abi) const result = useSWR( token0 && pairAddress && contract && token1 ? [token0.chainId, pairAddress, DataType.Reserves] : null, getReserves(contract as Contract, token0 as Token, token1 as Token) ) useKeepSWRDataLiveAsBlocksArrive(result.mutate) return result } function getOnchainToken( contract: Contract, contractBytes32: Contract ): (chainId: number, address: string) => Promise<Token | null> { return async (chainId: number, address: string): Promise<Token | null> => { const [decimals, symbol, name] = await Promise.all([ contract.decimals().catch(() => null), contract.symbol().catch(() => contractBytes32 .symbol() .then(parseBytes32String) .catch(() => 'UNKNOWN') ), contract.name().catch(() => contractBytes32 .name() .then(parseBytes32String) .catch(() => 'Unknown') ), ]) return decimals === null ? null : new Token(chainId, address, decimals, symbol, name) } } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useOnchainToken(address?: string, suspense = false): responseInterface<Token | null, any> { const { chainId } = useWeb3React() const contract = useContract(address, IERC20.abi) const contractBytes32 = useContract(address, ERC20_BYTES32) return useSWR( typeof chainId === 'number' && typeof address === 'string' && contract && contractBytes32 ? [chainId, address, DataType.Token] : null, getOnchainToken(contract as Contract, contractBytes32 as Contract), { dedupingInterval: 60 * 1000, refreshInterval: 60 * 1000, suspense, } ) } interface RemoteToken { address: string symbol: string name: string } // eslint-disable-next-line @typescript-eslint/no-explicit-any async function getRemoteTokens(searchQuery: string): Promise<RemoteToken[]> { const { request } = await import('graphql-request') return request( 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2', ` query getRemoteTokens($searchQuery: String!) { tokens(where: { symbol_contains: $searchQuery }) { id symbol name } }`, { searchQuery } ).then((result) => result.tokens.map( (token: { id: string; symbol: string; name: string }): RemoteToken => ({ address: getAddress(token.id), symbol: token.symbol ?? 'UNKNOWN', name: token.name ?? 'Unknown', }) ) ) } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useRemoteTokens(query = '', suspense = false): responseInterface<RemoteToken[], any> { const { chainId } = useWeb3React() const shouldFetch = chainId === ChainId.MAINNET && query.length > 0 return useSWR(shouldFetch ? [query, DataType.RemoteTokens] : null, getRemoteTokens, { dedupingInterval: 60 * 5 * 1000, refreshInterval: 60 * 5 * 1000, suspense, }) }