Commit 2d6eddf9 authored by Noah Zinsmeister's avatar Noah Zinsmeister Committed by GitHub

introduce batched usePairs (#851)

centralize all lists of bases

pin some pairs
parent aadf43ef
import React from 'react' import React from 'react'
import { Text } from 'rebass' import { Text } from 'rebass'
import { COMMON_BASES } from '../../constants' import { Token } from '@uniswap/sdk'
import { SUGGESTED_BASES } from '../../constants'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import QuestionHelper from '../QuestionHelper' import QuestionHelper from '../QuestionHelper'
import { AutoRow } from '../Row' import { AutoRow } from '../Row'
...@@ -25,7 +27,7 @@ export default function CommonBases({ ...@@ -25,7 +27,7 @@ export default function CommonBases({
<QuestionHelper text="These tokens are commonly used in pairs." /> <QuestionHelper text="These tokens are commonly used in pairs." />
</AutoRow> </AutoRow>
<AutoRow gap="10px"> <AutoRow gap="10px">
{COMMON_BASES[chainId]?.map(token => { {(SUGGESTED_BASES[chainId] ?? []).map((token: Token) => {
return ( return (
<BaseWrapper <BaseWrapper
gap="6px" gap="6px"
......
...@@ -20,7 +20,7 @@ import Tooltip from '../Tooltip' ...@@ -20,7 +20,7 @@ import Tooltip from '../Tooltip'
import CommonBases from './CommonBases' import CommonBases from './CommonBases'
import { filterPairs, filterTokens } from './filtering' import { filterPairs, filterTokens } from './filtering'
import PairList from './PairList' import PairList from './PairList'
import { balanceComparator, useTokenComparator } from './sorting' import { useTokenComparator, pairComparator } from './sorting'
import { PaddedColumn, SearchInput } from './styleds' import { PaddedColumn, SearchInput } from './styleds'
import TokenList from './TokenList' import TokenList from './TokenList'
import SortButton from './SortButton' import SortButton from './SortButton'
...@@ -105,11 +105,9 @@ function SearchModal({ ...@@ -105,11 +105,9 @@ function SearchModal({
const sortedPairList = useMemo(() => { const sortedPairList = useMemo(() => {
if (isTokenView) return [] if (isTokenView) return []
return allPairs.sort((a, b): number => { return allPairs.sort((a, b): number => {
// sort by balance
const balanceA = allPairBalances[a.liquidityToken.address] const balanceA = allPairBalances[a.liquidityToken.address]
const balanceB = allPairBalances[b.liquidityToken.address] const balanceB = allPairBalances[b.liquidityToken.address]
return pairComparator(a, b, balanceA, balanceB)
return balanceComparator(balanceA, balanceB)
}) })
}, [isTokenView, allPairs, allPairBalances]) }, [isTokenView, allPairs, allPairBalances])
......
import { Token, TokenAmount, WETH } from '@uniswap/sdk' import { Token, TokenAmount, WETH, Pair } from '@uniswap/sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks' import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks'
import { DUMMY_PAIRS_TO_PIN } from '../../constants'
// compare two token amounts with highest one coming first // compare two token amounts with highest one coming first
export function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) { function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
if (balanceA && balanceB) { if (balanceA && balanceB) {
return balanceA.greaterThan(balanceB) ? -1 : balanceA.equalTo(balanceB) ? 0 : 1 return balanceA.greaterThan(balanceB) ? -1 : balanceA.equalTo(balanceB) ? 0 : 1
} else if (balanceA && balanceA.greaterThan('0')) { } else if (balanceA && balanceA.greaterThan('0')) {
...@@ -15,6 +16,26 @@ export function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount ...@@ -15,6 +16,26 @@ export function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount
return 0 return 0
} }
// compare two pairs, favoring "pinned" pairs, and falling back to balances
export function pairComparator(pairA: Pair, pairB: Pair, balanceA?: TokenAmount, balanceB?: TokenAmount) {
const aShouldBePinned =
DUMMY_PAIRS_TO_PIN[pairA?.token0?.chainId]?.some(
dummyPairToPin => dummyPairToPin.liquidityToken.address === pairA?.liquidityToken?.address
) ?? false
const bShouldBePinned =
DUMMY_PAIRS_TO_PIN[pairB?.token0?.chainId]?.some(
dummyPairToPin => dummyPairToPin.liquidityToken.address === pairB?.liquidityToken?.address
) ?? false
if (aShouldBePinned && !bShouldBePinned) {
return -1
} else if (!aShouldBePinned && bShouldBePinned) {
return 1
} else {
return balanceComparator(balanceA, balanceB)
}
}
function getTokenComparator( function getTokenComparator(
weth: Token | undefined, weth: Token | undefined,
balances: { [tokenAddress: string]: TokenAmount } balances: { [tokenAddress: string]: TokenAmount }
......
import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk' import { ChainId, JSBI, Percent, Token, WETH, Pair, TokenAmount } from '@uniswap/sdk'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors' import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
export const ROUTER_ADDRESS = '0xf164fC0Ec4E93095b804a4795bBe1e041497b92a' export const ROUTER_ADDRESS = '0xf164fC0Ec4E93095b804a4795bBe1e041497b92a'
// used for display in the default list when adding liquidity // used to construct intermediary pairs for trading
export const COMMON_BASES = {
export const BASES_TO_CHECK_TRADES_AGAINST: { readonly [chainId in ChainId]: Token[] } = {
[ChainId.MAINNET]: [ [ChainId.MAINNET]: [
WETH[ChainId.MAINNET], WETH[ChainId.MAINNET],
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'), new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C') new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
], ],
[ChainId.ROPSTEN]: [WETH[ChainId.ROPSTEN]], [ChainId.ROPSTEN]: [WETH[ChainId.ROPSTEN]],
[ChainId.RINKEBY]: [ [ChainId.RINKEBY]: [WETH[ChainId.RINKEBY]],
WETH[ChainId.RINKEBY],
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin')
],
[ChainId.GÖRLI]: [WETH[ChainId.GÖRLI]], [ChainId.GÖRLI]: [WETH[ChainId.GÖRLI]],
[ChainId.KOVAN]: [WETH[ChainId.KOVAN]] [ChainId.KOVAN]: [WETH[ChainId.KOVAN]]
} }
// used for display in the default list when adding liquidity
export const SUGGESTED_BASES = BASES_TO_CHECK_TRADES_AGAINST
// used to construct the list of all pairs we consider by default in the frontend
export const BASES_TO_TRACK_LIQUIDITY_FOR = BASES_TO_CHECK_TRADES_AGAINST
export const DUMMY_PAIRS_TO_PIN: { readonly [chainId in ChainId]?: Pair[] } = {
[ChainId.MAINNET]: [
new Pair(
new TokenAmount(
new Token(ChainId.MAINNET, '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 8, 'cDAI', 'Compound Dai'),
'0'
),
new TokenAmount(
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'),
'0'
)
),
new Pair(
new TokenAmount(
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C'),
'0'
),
new TokenAmount(
new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD'),
'0'
)
),
new Pair(
new TokenAmount(
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
'0'
),
new TokenAmount(
new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD'),
'0'
)
)
]
}
const MAINNET_WALLETS = { const MAINNET_WALLETS = {
INJECTED: { INJECTED: {
connector: injected, connector: injected,
......
import { Token, TokenAmount, Pair } from '@uniswap/sdk' import { Token, TokenAmount, Pair } from '@uniswap/sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { Interface } from '@ethersproject/abi'
import { usePairContract } from '../hooks/useContract' import { usePairContract } from '../hooks/useContract'
import { useSingleCallResult } from '../state/multicall/hooks' import { useSingleCallResult, useMultipleContractSingleData } from '../state/multicall/hooks'
/* /*
* if loading, return undefined * if loading, return undefined
...@@ -22,3 +24,30 @@ export function usePair(tokenA?: Token, tokenB?: Token): undefined | Pair | null ...@@ -22,3 +24,30 @@ export function usePair(tokenA?: Token, tokenB?: Token): undefined | Pair | null
return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString())) return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
}, [loading, reserves, tokenA, tokenB]) }, [loading, reserves, tokenA, tokenB])
} }
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)
export function usePairs(tokens: [Token | undefined, Token | undefined][]): (undefined | Pair | null)[] {
const pairAddresses = useMemo(
() =>
tokens.map(([tokenA, tokenB]) => {
return tokenA && tokenB && !tokenA.equals(tokenB) ? Pair.getAddress(tokenA, tokenB) : undefined
}),
[tokens]
)
const results = useMultipleContractSingleData(pairAddresses, PAIR_INTERFACE, 'getReserves')
return useMemo(() => {
return results.map((result, i) => {
const { result: reserves, loading } = result
const tokenA = tokens[i][0]
const tokenB = tokens[i][1]
if (loading || !tokenA || !tokenB) return undefined
if (!reserves) return null
const { reserve0, reserve1 } = reserves
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
})
}, [results, tokens])
}
import { useMemo } from 'react' import { useMemo } from 'react'
import { WETH, Token, TokenAmount, Trade, ChainId, Pair } from '@uniswap/sdk' import { Token, TokenAmount, Trade, ChainId, Pair } from '@uniswap/sdk'
import flatMap from 'lodash.flatmap'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
import { usePair } from '../data/Reserves' import { usePairs } from '../data/Reserves'
import { BASES_TO_CHECK_TRADES_AGAINST } from '../constants'
const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin')
const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] { function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
// check for direct pair between tokens const bases = useMemo(() => BASES_TO_CHECK_TRADES_AGAINST[chainId as ChainId] ?? [], [chainId])
const pairBetween = usePair(tokenA, tokenB)
// get token<->WETH pairs
const aToETH = usePair(tokenA, WETH[chainId as ChainId])
const bToETH = usePair(tokenB, WETH[chainId as ChainId])
// get token<->DAI pairs
const aToDAI = usePair(tokenA, chainId === ChainId.MAINNET ? DAI : undefined)
const bToDAI = usePair(tokenB, chainId === ChainId.MAINNET ? DAI : undefined)
// get token<->USDC pairs const allPairs = usePairs([
const aToUSDC = usePair(tokenA, chainId === ChainId.MAINNET ? USDC : undefined) // the direct pair
const bToUSDC = usePair(tokenB, chainId === ChainId.MAINNET ? USDC : undefined) [tokenA, tokenB],
// token A against all bases
// get connecting pairs ...bases.map((base): [Token | undefined, Token | undefined] => [tokenA, base]),
const DAIToETH = usePair(chainId === ChainId.MAINNET ? DAI : undefined, WETH[chainId as ChainId]) // token B against all bases
const USDCToETH = usePair(chainId === ChainId.MAINNET ? USDC : undefined, WETH[chainId as ChainId]) ...bases.map((base): [Token | undefined, Token | undefined] => [tokenB, base]),
const DAIToUSDC = usePair( // each base against all bases
chainId === ChainId.MAINNET ? DAI : undefined, ...flatMap(bases, (base): [Token, Token][] => bases.map(otherBase => [base, otherBase]))
chainId === ChainId.MAINNET ? USDC : undefined ])
)
// only pass along valid pairs, non-duplicated pairs // only pass along valid pairs, non-duplicated pairs
return useMemo( return useMemo(
() => () =>
[pairBetween, aToETH, bToETH, aToDAI, bToDAI, aToUSDC, bToUSDC, DAIToETH, USDCToETH, DAIToUSDC] allPairs
// filter out invalid pairs // filter out invalid pairs
.filter((p): p is Pair => !!p) .filter((p): p is Pair => !!p)
// filter out duplicated pairs // filter out duplicated pairs
.filter( .filter(
(p, i, pairs) => i === pairs.findIndex(pair => pair?.liquidityToken.address === p.liquidityToken.address) (p, i, pairs) => i === pairs.findIndex(pair => pair?.liquidityToken.address === p.liquidityToken.address)
), ),
[pairBetween, aToETH, bToETH, aToDAI, bToDAI, aToUSDC, bToUSDC, DAIToETH, USDCToETH, DAIToUSDC] [allPairs]
) )
} }
......
import { ChainId, JSBI, Pair, Token, TokenAmount, WETH } from '@uniswap/sdk' import { ChainId, JSBI, Pair, Token, TokenAmount } from '@uniswap/sdk'
import { useActiveWeb3React } from '../../hooks' import flatMap from 'lodash.flatmap'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { shallowEqual, useDispatch, useSelector } from 'react-redux' import { shallowEqual, useDispatch, useSelector } from 'react-redux'
import { useActiveWeb3React } from '../../hooks'
import { useAllTokens } from '../../hooks/Tokens' import { useAllTokens } from '../../hooks/Tokens'
import { getTokenInfoWithFallback, isAddress } from '../../utils' import { getTokenInfoWithFallback, isAddress } from '../../utils'
import { AppDispatch, AppState } from '../index' import { AppDispatch, AppState } from '../index'
...@@ -14,7 +16,7 @@ import { ...@@ -14,7 +16,7 @@ import {
SerializedToken, SerializedToken,
updateUserDarkMode updateUserDarkMode
} from './actions' } from './actions'
import flatMap from 'lodash.flatmap' import { BASES_TO_TRACK_LIQUIDITY_FOR, DUMMY_PAIRS_TO_PIN } from '../../constants'
function serializeToken(token: Token): SerializedToken { function serializeToken(token: Token): SerializedToken {
return { return {
...@@ -154,16 +156,14 @@ export function useTokenWarningDismissal(chainId?: number, token?: Token): [bool ...@@ -154,16 +156,14 @@ export function useTokenWarningDismissal(chainId?: number, token?: Token): [bool
}, [chainId, token, dismissalState, dispatch]) }, [chainId, token, dismissalState, dispatch])
} }
const bases = [
...Object.values(WETH),
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
]
export function useAllDummyPairs(): Pair[] { export function useAllDummyPairs(): Pair[] {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const tokens = useAllTokens() const tokens = useAllTokens()
// pinned pairs
const pinnedPairs = useMemo(() => DUMMY_PAIRS_TO_PIN[chainId as ChainId] ?? [], [chainId])
// pairs for every token against every base
const generatedPairs: Pair[] = useMemo( const generatedPairs: Pair[] = useMemo(
() => () =>
flatMap( flatMap(
...@@ -173,9 +173,8 @@ export function useAllDummyPairs(): Pair[] { ...@@ -173,9 +173,8 @@ export function useAllDummyPairs(): Pair[] {
token => { token => {
// for each token on the current chain, // for each token on the current chain,
return ( return (
bases // loop though all bases on the current chain
// loop through all the bases valid for the current chain, (BASES_TO_TRACK_LIQUIDITY_FOR[chainId as ChainId] ?? [])
.filter(base => base.chainId === chainId)
// to construct pairs of the given token with each base // to construct pairs of the given token with each base
.map(base => { .map(base => {
if (base.equals(token)) { if (base.equals(token)) {
...@@ -191,8 +190,8 @@ export function useAllDummyPairs(): Pair[] { ...@@ -191,8 +190,8 @@ export function useAllDummyPairs(): Pair[] {
[tokens, chainId] [tokens, chainId]
) )
// pairs saved by users
const savedSerializedPairs = useSelector<AppState, AppState['user']['pairs']>(({ user: { pairs } }) => pairs) const savedSerializedPairs = useSelector<AppState, AppState['user']['pairs']>(({ user: { pairs } }) => pairs)
const userPairs = useMemo( const userPairs = useMemo(
() => () =>
Object.values<SerializedPair>(savedSerializedPairs[chainId ?? -1] ?? {}).map( Object.values<SerializedPair>(savedSerializedPairs[chainId ?? -1] ?? {}).map(
...@@ -208,7 +207,8 @@ export function useAllDummyPairs(): Pair[] { ...@@ -208,7 +207,8 @@ export function useAllDummyPairs(): Pair[] {
return useMemo(() => { return useMemo(() => {
const cache: { [pairKey: string]: boolean } = {} const cache: { [pairKey: string]: boolean } = {}
return ( return (
generatedPairs pinnedPairs
.concat(generatedPairs)
.concat(userPairs) .concat(userPairs)
// filter out duplicate pairs // filter out duplicate pairs
.filter(pair => { .filter(pair => {
...@@ -219,5 +219,5 @@ export function useAllDummyPairs(): Pair[] { ...@@ -219,5 +219,5 @@ export function useAllDummyPairs(): Pair[] {
return (cache[pairKey] = true) return (cache[pairKey] = true)
}) })
) )
}, [generatedPairs, userPairs]) }, [pinnedPairs, generatedPairs, userPairs])
} }
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