Commit d40d81ca authored by Noah Zinsmeister's avatar Noah Zinsmeister Committed by GitHub

noah changes (#739)

* remove duplicated prettier from dependencies

* use swr for allowances

* slight improvements to error cascading

* fetch totalsupply with SWR

fetch pairs with SWR (except persistent pairs, will be refactored later)

* fix lint error

* stop using pair context, instead store per-user pairs in local storage

* add more pairs to the default list

remove unnecessary isWETH

fix lint error

* add more bases to trade consideration

* fix lint warnings

* get SWR data on same schedule

* don't pass value if it's 0

fix WETH equality checks

* clean up fixed/significant formatting

* fix slippage calculation bug in swap

add slippage to remove

* fix token logos

* show fewer sig figs

* lint error

* remove unused signer

* add calculateSlippageAmount safety check and tests

* fix useAllBalances type

stop spuriously sorting token amounts of different tokens

use slightly better comparisons for slippage calculations

* improve priceSlippage comparison logic

* useReserves -> usePair

* use checksum for DAI logo address
parent dba6abad
......@@ -34,7 +34,6 @@
"i18next-xhr-backend": "^2.0.1",
"jazzicon": "^1.5.0",
"polished": "^3.3.2",
"prettier": "^1.17.0",
"qrcode.react": "^0.9.3",
"react": "^16.13.1",
"react-device-detect": "^1.6.2",
......@@ -48,6 +47,7 @@
"react-use-gesture": "^6.0.14",
"rebass": "^4.0.7",
"styled-components": "^4.2.0",
"swr": "0.1.18",
"use-media": "^1.4.0"
},
"devDependencies": {
......
......@@ -71,7 +71,7 @@ export default function BalanceCard({ token0, balance0, import0, token1, balance
<TYPE.body fontWeight={500}>
{token0?.name} ({token0?.symbol})
</TYPE.body>
<TokenLogo size={'20px'} address={token0?.address || ''} />
<TokenLogo size={'20px'} address={token0?.address} />
</RowBetween>
{import0 && <TYPE.yellow style={{ paddingLeft: '0' }}>Token imported by user</TYPE.yellow>}
......@@ -113,7 +113,7 @@ export default function BalanceCard({ token0, balance0, import0, token1, balance
<TYPE.body fontWeight={500}>
{token1?.name} ({token1?.symbol})
</TYPE.body>
<TokenLogo size={'20px'} address={token1?.address || ''} />
<TokenLogo size={'20px'} address={token1?.address} />
</RowBetween>
{import1 && <TYPE.yellow style={{ paddingLeft: '0' }}>Token imported by user</TYPE.yellow>}
......
......@@ -12,9 +12,9 @@ import { TYPE, Link } from '../../theme'
import { AutoColumn, ColumnCenter } from '../Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
import { usePair } from '../../contexts/Pairs'
import { useToken } from '../../contexts/Tokens'
import { useWeb3React } from '../../hooks'
import { usePair } from '../../data/Reserves'
const Fields = {
TOKEN0: 0,
......
This diff is collapsed.
......@@ -205,7 +205,7 @@ export default function Header() {
<AccountElement active={!!account}>
{account ? (
<Row style={{ marginRight: '-1.25rem' }}>
<Text fontWeight={500}> {userEthBalance && userEthBalance?.toFixed(4) + ' ETH'}</Text>
<Text fontWeight={500}> {userEthBalance && `${userEthBalance?.toSignificant(4)} ETH`}</Text>
</Row>
) : (
''
......
......@@ -6,7 +6,6 @@ import Row from '../Row'
import TokenLogo from '../TokenLogo'
import SearchModal from '../SearchModal'
import PositionCard from '../PositionCard'
import DoubleTokenLogo from '../DoubleLogo'
import { Link } from '../../theme'
import { Text } from 'rebass'
import { Plus } from 'react-feather'
......@@ -15,71 +14,40 @@ import { AutoColumn, ColumnCenter } from '../Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
import { useToken } from '../../contexts/Tokens'
import { usePopups } from '../../contexts/Application'
import { usePrevious } from '../../hooks'
import { useWeb3React } from '@web3-react/core'
import { useAddressBalance } from '../../contexts/Balances'
import { usePair, useAllPairs } from '../../contexts/Pairs'
import { useLocalStoragePairAdder } from '../../contexts/LocalStorage'
import { usePair } from '../../data/Reserves'
function PoolFinder({ history }: RouteComponentProps) {
const Fields = {
TOKEN0: 0,
TOKEN1: 1
}
const Fields = {
TOKEN0: 0,
TOKEN1: 1
}
function PoolFinder({ history }: RouteComponentProps) {
const { account } = useWeb3React()
const [showSearch, setShowSearch] = useState<boolean>(false)
const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
const [, addPopup] = usePopups()
const [token0Address, setToken0Address] = useState<string>()
const [token1Address, setToken1Address] = useState<string>()
const token0: Token = useToken(token0Address)
const token1: Token = useToken(token1Address)
const pair: Pair = usePair(token0, token1)
const position: TokenAmount = useAddressBalance(account, pair?.liquidityToken)
const newPair: boolean = pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0))
const allowImport: boolean = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
const allPairs = useAllPairs()
const pairCount = Object.keys(allPairs)?.length
const pairCountPrevious = usePrevious(pairCount)
const [newLiquidity, setNewLiquidity] = useState<boolean>(false) // check for unimported pair
const addPair = useLocalStoragePairAdder()
// use previous ref to detect new pair added
useEffect(() => {
if (pairCount !== pairCountPrevious && pairCountPrevious) {
setNewLiquidity(true)
if (pair) {
addPair(pair)
}
}, [pairCount, pairCountPrevious])
}, [pair, addPair])
// reset the watcher if tokens change
useEffect(() => {
setNewLiquidity(false)
}, [token0, token1])
const position: TokenAmount = useAddressBalance(account, pair?.liquidityToken)
function endSearch() {
history.goBack() // return to previous page
newLiquidity &&
addPopup(
<AutoColumn gap={'10px'}>
<Text fontSize={20} fontWeight={500}>
Pool Imported
</Text>
<Row>
<DoubleTokenLogo a0={token0Address || ''} a1={token1Address || ''} margin={true} />
<Text fontSize={16} fontWeight={500}>
UNI {token0?.symbol} / {token1?.symbol}
</Text>
</Row>
<Link>View on Uniswap Info.</Link>
</AutoColumn>
)
}
const newPair: boolean =
!!pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0))
const allowImport: boolean = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
return (
<>
......@@ -140,19 +108,13 @@ function PoolFinder({ history }: RouteComponentProps) {
style={{ justifyItems: 'center', backgroundColor: '', padding: '12px 0px', borderRadius: '12px' }}
>
<Text textAlign="center" fontWeight={500} color="">
{newLiquidity ? 'Pool Found!' : 'Pool already imported.'}
Pool Imported!
</Text>
</ColumnCenter>
)}
{position ? (
!JSBI.equal(position.raw, JSBI.BigInt(0)) ? (
<PositionCard
pairAddress={pair?.liquidityToken.address}
token0={token0}
token1={token1}
minimal={true}
border="1px solid #CED0D9"
/>
<PositionCard pair={pair} minimal={true} border="1px solid #CED0D9" />
) : (
<LightCard padding="45px 10px">
<AutoColumn gap="sm" justify="center">
......@@ -176,7 +138,7 @@ function PoolFinder({ history }: RouteComponentProps) {
history.push('/add/' + token0Address + '-' + token1Address)
}}
>
Create pool instead.
Create pool?
</Link>
</AutoColumn>
</LightCard>
......@@ -188,9 +150,9 @@ function PoolFinder({ history }: RouteComponentProps) {
</LightCard>
)}
<ButtonPrimary disabled={!allowImport} onClick={endSearch}>
<ButtonPrimary disabled={!allowImport} onClick={() => history.goBack()}>
<Text fontWeight={500} fontSize={20}>
{newLiquidity ? 'Import' : 'Close'}
Close
</Text>
</ButtonPrimary>
</AutoColumn>
......
......@@ -2,11 +2,11 @@ import React, { useState } from 'react'
import styled from 'styled-components'
import { darken } from 'polished'
import { RouteComponentProps, withRouter } from 'react-router-dom'
import { Percent, Pair, Token } from '@uniswap/sdk'
import { Percent, Pair } from '@uniswap/sdk'
import { useWeb3React } from '@web3-react/core'
import { useAllBalances } from '../../contexts/Balances'
import { useTotalSupply } from '../../contexts/Pairs'
import { useTotalSupply } from '../../data/TotalSupply'
import Card, { GreyCard } from '../Card'
import TokenLogo from '../TokenLogo'
......@@ -30,26 +30,22 @@ const HoverCard = styled(Card)`
`
interface PositionCardProps extends RouteComponentProps<{}> {
pairAddress: string
token0: Token
token1: Token
pair: Pair
minimal?: boolean
border?: string
}
function PositionCard({ pairAddress, token0, token1, history, border, minimal = false }: PositionCardProps) {
function PositionCard({ pair, history, border, minimal = false }: PositionCardProps) {
const { account } = useWeb3React()
const allBalances = useAllBalances()
const [showMore, setShowMore] = useState(false)
const tokenAmount0 = allBalances?.[pairAddress]?.[token0?.address]
const tokenAmount1 = allBalances?.[pairAddress]?.[token1?.address]
const token0 = pair?.token0
const token1 = pair?.token1
const pair = tokenAmount0 && tokenAmount1 && new Pair(tokenAmount0, tokenAmount1)
const [showMore, setShowMore] = useState(false)
const userPoolBalance = allBalances?.[account]?.[pairAddress]
const totalPoolTokens = useTotalSupply(token0, token1)
const userPoolBalance = allBalances?.[account]?.[pair?.liquidityToken?.address]
const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens ? new Percent(userPoolBalance.raw, totalPoolTokens.raw) : undefined
......@@ -73,7 +69,7 @@ function PositionCard({ pairAddress, token0, token1, history, border, minimal =
if (minimal) {
return (
<>
{userPoolBalance && userPoolBalance.toFixed(6) > 0 && (
{userPoolBalance && (
<GreyCard border={border}>
<AutoColumn gap="12px">
<FixedHeightRow>
......@@ -92,7 +88,7 @@ function PositionCard({ pairAddress, token0, token1, history, border, minimal =
</RowFixed>
<RowFixed>
<Text fontWeight={500} fontSize={20}>
{userPoolBalance ? userPoolBalance.toFixed(6) : '-'}
{userPoolBalance ? userPoolBalance.toSignificant(5) : '-'}
</Text>
</RowFixed>
</FixedHeightRow>
......@@ -103,9 +99,9 @@ function PositionCard({ pairAddress, token0, token1, history, border, minimal =
</Text>
{token0Deposited ? (
<RowFixed>
{!minimal && <TokenLogo address={token0?.address || ''} />}
{!minimal && <TokenLogo address={token0?.address} />}
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token0Deposited?.toFixed(8)}
{token0Deposited?.toSignificant(6)}
</Text>
</RowFixed>
) : (
......@@ -118,9 +114,9 @@ function PositionCard({ pairAddress, token0, token1, history, border, minimal =
</Text>
{token1Deposited ? (
<RowFixed>
{!minimal && <TokenLogo address={token1?.address || ''} />}
{!minimal && <TokenLogo address={token1?.address} />}
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token1Deposited?.toFixed(8)}
{token1Deposited?.toSignificant(6)}
</Text>
</RowFixed>
) : (
......@@ -163,11 +159,9 @@ function PositionCard({ pairAddress, token0, token1, history, border, minimal =
{token0Deposited ? (
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token0Deposited?.toFixed(8)}
{token0Deposited?.toSignificant(6)}
</Text>
{!minimal && (
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token0?.address || ''} />
)}
{!minimal && <TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token0?.address} />}
</RowFixed>
) : (
'-'
......@@ -183,11 +177,9 @@ function PositionCard({ pairAddress, token0, token1, history, border, minimal =
{token1Deposited ? (
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token1Deposited?.toFixed(8)}
{token1Deposited?.toSignificant(6)}
</Text>
{!minimal && (
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token1?.address || ''} />
)}
{!minimal && <TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token1?.address} />}
</RowFixed>
) : (
'-'
......@@ -199,7 +191,7 @@ function PositionCard({ pairAddress, token0, token1, history, border, minimal =
Your pool tokens:
</Text>
<Text fontSize={16} fontWeight={500}>
{userPoolBalance ? userPoolBalance.toFixed(6) : '-'}
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
</Text>
</FixedHeightRow>
)}
......
......@@ -23,9 +23,8 @@ import { Spinner, TYPE } from '../../theme'
import { RowBetween, RowFixed, AutoRow } from '../Row'
import { isAddress } from '../../utils'
import { useAllPairs } from '../../contexts/Pairs'
import { useWeb3React } from '../../hooks'
import { useLocalStorageTokens } from '../../contexts/LocalStorage'
import { useLocalStorageTokens, useAllDummyPairs } from '../../contexts/LocalStorage'
import { useAllBalances } from '../../contexts/Balances'
import { useTranslation } from 'react-i18next'
import { useToken, useAllTokens, ALL_TOKENS } from '../../contexts/Tokens'
......@@ -174,7 +173,7 @@ function SearchModal({
const theme = useContext(ThemeContext)
const allTokens = useAllTokens()
const allPairs = useAllPairs()
const allPairs = useAllDummyPairs()
const allBalances = useAllBalances()
const [searchQuery, setSearchQuery] = useState('')
......@@ -226,15 +225,12 @@ function SearchModal({
const balanceA = allBalances?.[account]?.[a]
const balanceB = allBalances?.[account]?.[b]
if (balanceA && !balanceB) {
if (balanceA?.greaterThan('0') && !balanceB?.greaterThan('0')) {
return sortDirection ? -1 : 1
}
if (!balanceA && balanceB) {
if (!balanceA?.greaterThan('0') && balanceB?.greaterThan('0')) {
return sortDirection ? 1 : -1
}
if (balanceA && balanceB) {
return sortDirection && parseFloat(balanceA.toExact()) > parseFloat(balanceB.toExact()) ? -1 : 1
}
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0
} else {
return 0
......@@ -306,20 +302,16 @@ function SearchModal({
const escapeStringRegexp = string => string
const sortedPairList = useMemo(() => {
return Object.keys(allPairs).sort((a, b): number => {
return allPairs.sort((a, b): number => {
// sort by balance
const balanceA = allBalances?.[account]?.[a]
const balanceB = allBalances?.[account]?.[b]
const balanceA = allBalances?.[account]?.[a.liquidityToken.address]
const balanceB = allBalances?.[account]?.[b.liquidityToken.address]
if (balanceA && !balanceB) {
if (balanceA?.greaterThan('0') && !balanceB?.greaterThan('0')) {
return sortDirection ? -1 : 1
}
if (!balanceA && balanceB) {
if (!balanceA?.greaterThan('0') && balanceB?.greaterThan('0')) {
return sortDirection ? 1 : -1
}
if (balanceA && balanceB) {
const order = sortDirection && (parseFloat(balanceA.toExact()) > parseFloat(balanceB.toExact()) ? -1 : 1)
return order ? 1 : -1
} else {
return 0
}
......@@ -328,13 +320,12 @@ function SearchModal({
const filteredPairList = useMemo(() => {
const isAddress = searchQuery.slice(0, 2) === '0x'
return sortedPairList.filter(pairAddress => {
const pair = allPairs[pairAddress]
return sortedPairList.filter(pair => {
if (searchQuery === '') {
return true
}
const token0 = allTokens[pair.token0]
const token1 = allTokens[pair.token1]
const token0 = pair.token0
const token1 = pair.token1
if (!token0 || !token1) {
return false // no token fetched yet
} else {
......@@ -354,7 +345,7 @@ function SearchModal({
return regexMatches.some(m => m)
}
})
}, [allPairs, allTokens, searchQuery, sortedPairList])
}, [searchQuery, sortedPairList])
function renderPairsList() {
if (filteredPairList?.length === 0) {
......@@ -367,9 +358,10 @@ function SearchModal({
return (
filteredPairList &&
filteredPairList.map((pairAddress, i) => {
const token0 = allTokens[allPairs[pairAddress].token0]
const token1 = allTokens[allPairs[pairAddress].token1]
filteredPairList.map((pair, i) => {
const token0 = pair.token0
const token1 = pair.token1
const pairAddress = pair.liquidityToken.address
const balance = allBalances?.[account]?.[pairAddress]?.toSignificant(6)
const zeroBalance =
allBalances?.[account]?.[pairAddress]?.raw &&
......@@ -451,16 +443,17 @@ function SearchModal({
*/
return filteredTokenList
.sort((a, b) => {
if (b?.address === WETH[chainId]?.address) {
if (a.address === WETH[chainId].address) {
return -1
} else if (b.address === WETH[chainId].address) {
return 1
} else
return parseFloat(a?.balance?.toExact()) > parseFloat(b?.balance?.toExact())
? sortDirection
? -1
: 1
: sortDirection
? 1
: -1
} else if (a.balance?.greaterThan('0') && !b.balance?.greaterThan('0')) {
return sortDirection ? -1 : 1
} else if (!a.balance?.greaterThan('0') && b.balance?.greaterThan('0')) {
return sortDirection ? 1 : -1
} else {
return sortDirection ? -1 : 1
}
})
.map(({ address, symbol, balance }) => {
const urlAdded = urlAddedTokens && urlAddedTokens.hasOwnProperty(address)
......@@ -580,13 +573,7 @@ function SearchModal({
<TYPE.body style={{ marginTop: '10px' }}>
To import a custom token, paste token address in the search bar.
</TYPE.body>
<Input
type={'text'}
placeholder={'0x0000000000...'}
value={searchQuery}
ref={inputRef}
onChange={onInput}
/>
<Input type={'text'} placeholder={'0x000000...'} value={searchQuery} ref={inputRef} onChange={onInput} />
{renderTokenList()}
</PaddedColumn>
) : (
......
......@@ -7,9 +7,7 @@ import { WETH } from '@uniswap/sdk'
import { ReactComponent as EthereumLogo } from '../../assets/images/ethereum-logo.svg'
const TOKEN_ICON_API = address =>
`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${isAddress(
address
)}/logo.png`
`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${address}/logo.png`
const BAD_IMAGES = {}
const Image = styled.img<{ size: string }>`
......@@ -40,25 +38,21 @@ export default function TokenLogo({
size = '24px',
...rest
}: {
address: string
address?: string
size?: string
style?: React.CSSProperties
}) {
const [error, setError] = useState(false)
const { chainId } = useWeb3React()
// hard code change to show ETH instead of WETH in UI
if (address === WETH[chainId].address) {
address = 'ETH'
}
// remove this just for testing
if (address === isAddress('0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735')) {
address = '0x6b175474e89094c44da98b954eedeac495271d0f'
// mock rinkeby DAI
if (chainId === 4 && address === '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735') {
address = '0x6B175474E89094C44Da98b954EedeAC495271d0F'
}
let path = ''
if (address === 'ETH') {
// hard code to show ETH instead of WETH in UI
if (address === WETH[chainId].address) {
return (
<StyledEthereumLogo
style={{ boxShadow: '0px 6px 10px rgba(0, 0, 0, 0.075)', borderRadius: '24px' }}
......@@ -66,8 +60,8 @@ export default function TokenLogo({
{...rest}
/>
)
} else if (!error && !BAD_IMAGES[address]) {
path = TOKEN_ICON_API(address?.toLowerCase())
} else if (!error && !BAD_IMAGES[address] && isAddress(address)) {
path = TOKEN_ICON_API(address)
} else {
return (
<Emoji {...rest} size={size}>
......
import { BigintIsh, Token, TokenAmount, WETH } from '@uniswap/sdk'
import { BigNumber } from 'ethers/utils'
import React, { createContext, useCallback, useContext, useEffect, useMemo, useReducer } from 'react'
import { useWeb3React } from '../hooks'
import { getTokenAllowance, isAddress } from '../utils'
import { useBlockNumber } from './Application'
const UPDATE = 'UPDATE'
interface AllowancesState {
[chainId: number]: {
[address: string]: {
[tokenAddress: string]: {
[spenderAddress: string]: {
value: BigintIsh
blockNumber: BigNumber
}
}
}
}
}
const AllowancesContext = createContext<[AllowancesState, any]>([{}, {}])
function useAllowancesContext() {
return useContext(AllowancesContext)
}
function reducer(state, { type, payload }) {
switch (type) {
case UPDATE: {
const { networkId, address, tokenAddress, spenderAddress, value, blockNumber } = payload
return {
...state,
[networkId]: {
...state?.[networkId],
[address]: {
...state?.[networkId]?.[address],
[tokenAddress]: {
...state?.[networkId]?.[address]?.[tokenAddress],
[spenderAddress]: {
value,
blockNumber
}
}
}
}
}
}
default:
throw Error(`Unexpected action type in AllowancesContext reducer: '${type}'.`)
}
}
export default function Provider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, {})
const update = useCallback((networkId, address, tokenAddress, spenderAddress, value, blockNumber) => {
dispatch({ type: UPDATE, payload: { networkId, address, tokenAddress, spenderAddress, value, blockNumber } })
}, [])
return (
<AllowancesContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
{children}
</AllowancesContext.Provider>
)
}
export function useAddressAllowance(address: string, token: Token, spenderAddress: string): TokenAmount {
const { library, chainId } = useWeb3React()
const globalBlockNumber = useBlockNumber()
const [state, { update }] = useAllowancesContext()
const { value, blockNumber } = state?.[chainId]?.[address]?.[token?.address]?.[spenderAddress] ?? {}
useEffect(() => {
if (
isAddress(address) &&
isAddress(token?.address) &&
isAddress(token?.address) !== WETH[chainId].address &&
isAddress(spenderAddress) &&
(value === undefined || blockNumber !== globalBlockNumber) &&
(chainId || chainId === 0) &&
library
) {
let stale = false
getTokenAllowance(address, token?.address, spenderAddress, library)
.then(value => {
if (!stale) {
update(chainId, address, token?.address, spenderAddress, value, globalBlockNumber)
}
})
.catch(() => {
if (!stale) {
update(chainId, address, token?.address, spenderAddress, null, globalBlockNumber)
}
})
return () => {
stale = true
}
}
}, [address, token, spenderAddress, value, blockNumber, globalBlockNumber, chainId, library, update])
return value ? new TokenAmount(token, value) : null
}
import React, { createContext, useContext, useReducer, useRef, useMemo, useCallback, useEffect, ReactNode } from 'react'
import { TokenAmount, Token, JSBI, WETH } from '@uniswap/sdk'
import { useAllPairs } from './Pairs'
import { useAllTokens } from './Tokens'
import { useBlockNumber } from './Application'
import { useWeb3React, useDebounce } from '../hooks'
import { getEtherBalance, getTokenBalance, isAddress } from '../utils'
import { useAllDummyPairs } from './LocalStorage'
const LOCAL_STORAGE_KEY = 'BALANCES'
const SHORT_BLOCK_TIMEOUT = (60 * 2) / 15 // in seconds, represented as a block number delta
......@@ -324,26 +324,27 @@ export function Updater() {
}
}, [chainId, account, blockNumber, allTokens, fetchBalance, batchUpdateAccount])
// ensure token balances for all exchanges
const allPairs = useAllPairs()
// ensure token balances for all exchanges
const allPairs = useAllDummyPairs()
useEffect(() => {
if (typeof chainId === 'number' && typeof blockNumber === 'number') {
Promise.all(
Object.keys(allPairs)
.filter(pairAddress => {
const token0 = allPairs[pairAddress].token0
const token1 = allPairs[pairAddress].token1
allPairs
.filter(pair => {
const token0Address = pair.token0.address
const token1Address = pair.token1.address
const pairAddress = pair.liquidityToken.address
const hasValueToken0 = !!stateRef.current?.[chainId]?.[pairAddress]?.[token0]?.value
const hasValueToken1 = !!stateRef.current?.[chainId]?.[pairAddress]?.[token1]?.value
const hasValueToken0 = !!stateRef.current?.[chainId]?.[pairAddress]?.[token0Address]?.value
const hasValueToken1 = !!stateRef.current?.[chainId]?.[pairAddress]?.[token1Address]?.value
const cachedFetchedAsOfToken0 = fetchedAsOfCache.current?.[chainId]?.[pairAddress]?.token0
const cachedFetchedAsOfToken1 = fetchedAsOfCache.current?.[chainId]?.[pairAddress]?.token1
const fetchedAsOfToken0 =
stateRef.current?.[chainId]?.[pairAddress]?.[token0]?.blockNumber ?? cachedFetchedAsOfToken0
stateRef.current?.[chainId]?.[pairAddress]?.[token0Address]?.blockNumber ?? cachedFetchedAsOfToken0
const fetchedAsOfToken1 =
stateRef.current?.[chainId]?.[pairAddress]?.[token1]?.blockNumber ?? cachedFetchedAsOfToken1
stateRef.current?.[chainId]?.[pairAddress]?.[token1Address]?.blockNumber ?? cachedFetchedAsOfToken1
// if there's no values, and they're not being fetched, we need to fetch!
if (
......@@ -366,9 +367,10 @@ export function Updater() {
return false
}
})
.map(async pairAddress => {
const token0 = allPairs[pairAddress].token0
const token1 = allPairs[pairAddress].token1
.map(async pair => {
const token0Address = pair.token0.address
const token1Address = pair.token1.address
const pairAddress = pair.liquidityToken.address
fetchedAsOfCache.current = {
...fetchedAsOfCache.current,
......@@ -376,21 +378,27 @@ export function Updater() {
...fetchedAsOfCache.current?.[chainId],
[pairAddress]: {
...fetchedAsOfCache.current?.[chainId]?.[pairAddress],
[token0]: blockNumber,
[token1]: blockNumber
[token0Address]: blockNumber,
[token1Address]: blockNumber
}
}
}
return Promise.all([
fetchBalance(pairAddress, token0),
fetchBalance(pairAddress, token1)
]).then(([valueToken0, valueToken1]) => ({ pairAddress, token0, token1, valueToken0, valueToken1 }))
fetchBalance(pairAddress, token0Address),
fetchBalance(pairAddress, token1Address)
]).then(([valueToken0, valueToken1]) => ({
pairAddress,
token0Address,
token1Address,
valueToken0,
valueToken1
}))
})
).then(results => {
batchUpdateExchanges(
chainId,
results.flatMap(result => [result.pairAddress, result.pairAddress]),
results.flatMap(result => [result.token0, result.token1]),
results.flatMap(result => [result.token0Address, result.token1Address]),
results.flatMap(result => [result.valueToken0, result.valueToken1]),
blockNumber
)
......@@ -401,7 +409,7 @@ export function Updater() {
return null
}
export function useAllBalances(): Array<TokenAmount> {
export function useAllBalances(): { [ownerAddress: string]: { [tokenAddress: string]: TokenAmount } } {
const { chainId } = useWeb3React()
const [state] = useBalancesContext()
......@@ -453,7 +461,7 @@ export function useAddressBalance(address: string, token: Token): TokenAmount |
const formattedValue = value && token && new TokenAmount(token, value)
useEffect(() => {
if (typeof chainId === 'number' && isAddress(address) && token && token.address && isAddress(token.address)) {
if (typeof chainId === 'number' && isAddress(address) && isAddress(token?.address)) {
startListening(chainId, address, token.address)
return () => {
stopListening(chainId, address, token.address)
......@@ -467,17 +475,19 @@ export function useAddressBalance(address: string, token: Token): TokenAmount |
export function useAccountLPBalances(account: string) {
const { chainId } = useWeb3React()
const [, { startListening, stopListening }] = useBalancesContext()
const allPairs = useAllPairs()
const pairs = useAllDummyPairs()
useEffect(() => {
Object.keys(allPairs).map(pairAddress => {
if (typeof chainId === 'number' && isAddress(account)) {
startListening(chainId, account, pairAddress)
return () => {
stopListening(chainId, account, pairAddress)
}
if (typeof chainId === 'number' && isAddress(account)) {
const cleanupFunctions = []
pairs.forEach(pair => {
startListening(chainId, account, pair.liquidityToken.address)
cleanupFunctions.push(() => stopListening(chainId, account, pair.liquidityToken.address))
})
return () => {
cleanupFunctions.forEach(cleanupFunction => cleanupFunction())
}
return true
})
}, [account, allPairs, chainId, startListening, stopListening])
}
}, [chainId, account, pairs, startListening, stopListening])
}
import React, { createContext, useContext, useMemo, useCallback, useEffect, useState } from 'react'
import { Token } from '@uniswap/sdk'
import { Token, Pair, TokenAmount, JSBI, WETH, ChainId } from '@uniswap/sdk'
import { getTokenDecimals, getTokenSymbol, getTokenName, isAddress } from '../utils'
import { useWeb3React } from '@web3-react/core'
import { useAllTokens } from './Tokens'
enum LocalStorageKeys {
VERSION = 'version',
......@@ -9,7 +10,8 @@ enum LocalStorageKeys {
BETA_MESSAGE_DISMISSED = 'betaMessageDismissed',
MIGRATION_MESSAGE_DISMISSED = 'migrationMessageDismissed',
DARK_MODE = 'darkMode',
TOKENS = 'tokens'
TOKENS = 'tokens',
PAIRS = 'pairs'
}
function useLocalStorage<T, S = T>(
......@@ -39,30 +41,31 @@ function useLocalStorage<T, S = T>(
return [value, setValue]
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function serializeTokens(
tokens: Token[]
): { chainId: number; address: string; decimals: number; symbol: string; name: string }[] {
return tokens.map(token => ({
interface SerializedToken {
chainId: number
address: string
decimals: number
symbol: string
name: string
}
function serializeToken(token: Token): SerializedToken {
return {
chainId: token.chainId,
address: token.address,
decimals: token.decimals,
symbol: token.symbol,
name: token.name
}))
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function deserializeTokens(serializedTokens: ReturnType<typeof serializeTokens>): Token[] {
return serializedTokens.map(
serializedToken =>
new Token(
serializedToken.chainId,
serializedToken.address,
serializedToken.decimals,
serializedToken.symbol,
serializedToken.name
)
function deserializeToken(serializedToken: SerializedToken): Token {
return new Token(
serializedToken.chainId,
serializedToken.address,
serializedToken.decimals,
serializedToken.symbol,
serializedToken.name
)
}
......@@ -88,28 +91,29 @@ export default function Provider({ children }: { children: React.ReactNode }) {
LocalStorageKeys.DARK_MODE,
window?.matchMedia('(prefers-color-scheme: dark)')?.matches ? true : false
)
const [tokens, setTokens] = useLocalStorage<Token[], ReturnType<typeof serializeTokens>>(
LocalStorageKeys.TOKENS,
[],
{
serialize: serializeTokens,
deserialize: deserializeTokens
}
)
const [tokens, setTokens] = useLocalStorage<Token[], SerializedToken[]>(LocalStorageKeys.TOKENS, [], {
serialize: (tokens: Token[]) => tokens.map(serializeToken),
deserialize: (serializedTokens: SerializedToken[]) => serializedTokens.map(deserializeToken)
})
const [pairs, setPairs] = useLocalStorage<Token[][], SerializedToken[][]>(LocalStorageKeys.PAIRS, [], {
serialize: (nestedTokens: Token[][]) => nestedTokens.map(tokens => tokens.map(serializeToken)),
deserialize: (serializedNestedTokens: SerializedToken[][]) =>
serializedNestedTokens.map(serializedTokens => serializedTokens.map(deserializeToken))
})
return (
<LocalStorageContext.Provider
value={useMemo(
() => [
{ version, lastSaved, betaMessageDismissed, migrationMessageDismissed, darkMode, tokens },
{ version, lastSaved, betaMessageDismissed, migrationMessageDismissed, darkMode, tokens, pairs },
{
setVersion,
setLastSaved,
setBetaMessageDismissed,
setMigrationMessageDismissed,
setDarkMode,
setTokens
setTokens,
setPairs
}
],
[
......@@ -120,12 +124,14 @@ export default function Provider({ children }: { children: React.ReactNode }) {
darkMode,
tokens,
pairs,
setVersion,
setLastSaved,
setBetaMessageDismissed,
setMigrationMessageDismissed,
setDarkMode,
setTokens
setTokens,
setPairs
]
)}
>
......@@ -242,3 +248,78 @@ export function useLocalStorageTokens(): [
return [tokens, { fetchTokenByAddress, addToken, removeTokenByAddress }]
}
const ZERO = JSBI.BigInt(0)
export function useLocalStoragePairAdder(): (pair: Pair) => void {
const [, { setPairs }] = useLocalStorageContext()
return useCallback(
(pair: Pair) => {
setPairs(pairs =>
pairs
.filter(tokens => !(tokens[0].equals(pair.token0) && tokens[1].equals(pair.token1)))
.concat([[pair.token0, pair.token1]])
)
},
[setPairs]
)
}
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[] {
const { chainId } = useWeb3React()
const tokens = useAllTokens()
const generatedPairs: Pair[] = useMemo(
() =>
Object.values(tokens)
// select only tokens on the current chain
.filter(token => token.chainId === chainId)
.flatMap(token => {
// for each token on the current chain,
return (
bases
// loop through all the bases valid for the current chain,
.filter(base => base.chainId === chainId)
// to construct pairs of the given token with each base
.map(base => {
if (base.equals(token)) {
return null
} else {
return new Pair(new TokenAmount(base, ZERO), new TokenAmount(token, ZERO))
}
})
.filter(pair => !!pair)
)
}),
[tokens, chainId]
)
const [{ pairs }] = useLocalStorageContext()
const userPairs = useMemo(
() =>
pairs
.filter(tokens => tokens[0].chainId === chainId)
.map(tokens => new Pair(new TokenAmount(tokens[0], ZERO), new TokenAmount(tokens[1], ZERO))),
[pairs, chainId]
)
return useMemo(() => {
return (
generatedPairs
.concat(userPairs)
// filter out duplicate pairs
.filter((pair, i, concatenatedPairs) => {
const firstAppearance = concatenatedPairs.findIndex(
concatenatedPair =>
concatenatedPair.token0.equals(pair.token0) && concatenatedPair.token1.equals(pair.token1)
)
return i === firstAppearance
})
)
}, [generatedPairs, userPairs])
}
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect, useState } from 'react'
import { useAddressBalance } from './Balances'
import { useWeb3React, usePairContract } from '../hooks'
import { ALL_TOKENS } from './Tokens'
import { ChainId, WETH, Token, TokenAmount, Pair, JSBI } from '@uniswap/sdk'
const ADDRESSES_KEY = 'ADDRESSES_KEY'
const ENTITIES_KEY = 'ENTITIES_KEY'
const UPDATE = 'UPDATE'
const UPDATE_PAIR_ENTITY = 'UPDATE_PAIR_ENTITY'
const ALL_PAIRS: [Token, Token][] = [
[
ALL_TOKENS[ChainId.RINKEBY][WETH[ChainId.RINKEBY].address],
ALL_TOKENS[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'] //dai
],
[
ALL_TOKENS[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'], // dai
ALL_TOKENS[ChainId.RINKEBY]['0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44'] // mkr
]
]
const PAIR_MAP: {
[chainId: number]: { [token0Address: string]: { [token1Address: string]: string } }
} = ALL_PAIRS.reduce((pairMap, [tokenA, tokenB]) => {
const tokens: [Token, Token] = tokenA?.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
// ensure exchanges are unique
if (pairMap?.[tokens[0].chainId]?.[tokens[0].address]?.[tokens[1].address]?.address !== undefined)
throw Error(`Duplicate exchange: ${tokenA} ${tokenB}`)
return {
...pairMap,
[tokens[0].chainId]: {
...pairMap?.[tokens[0].chainId],
[ADDRESSES_KEY]: {
...pairMap?.[tokens[0].chainId]?.[ADDRESSES_KEY],
[tokens[0].address]: {
...pairMap?.[tokens[0].chainId]?.[ADDRESSES_KEY]?.[tokens[0].address],
[tokens[1].address]: Pair.getAddress(...tokens)
}
},
[ENTITIES_KEY]: {}
}
}
}, {})
const PairContext = createContext([])
function usePairContext() {
return useContext(PairContext)
}
function reducer(state, { type, payload }) {
switch (type) {
case UPDATE: {
const { tokens } = payload
const tokensSorted: [Token, Token] = tokens[0].sortsBefore(tokens[1])
? [tokens[0], tokens[1]]
: [tokens[1], tokens[0]]
return {
...state,
[tokensSorted[0].chainId]: {
...state?.[tokensSorted[0].chainId],
[ADDRESSES_KEY]: {
...state?.[tokensSorted[0].chainId]?.[ADDRESSES_KEY],
[tokensSorted[0].address]: {
...state?.[tokensSorted[0].chainId]?.[ADDRESSES_KEY]?.[tokensSorted[0].address],
[tokensSorted[1].address]: Pair.getAddress(tokensSorted[0], tokensSorted[1])
}
}
}
}
}
case UPDATE_PAIR_ENTITY: {
const { pairAddress, pair, chainId } = payload
return {
...state,
[chainId]: {
...state?.[chainId],
[ENTITIES_KEY]: {
...state?.[chainId]?.[ENTITIES_KEY],
[pairAddress]: pair
}
}
}
}
default: {
throw Error(`Unexpected action type in ExchangesContext reducer: '${type}'.`)
}
}
}
export default function Provider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, PAIR_MAP)
const update = useCallback((chainId, tokens) => {
dispatch({ type: UPDATE, payload: { chainId, tokens } })
}, [])
const updatePairEntity = useCallback((pairAddress, pair, chainId) => {
dispatch({ type: UPDATE_PAIR_ENTITY, payload: { pairAddress, pair, chainId } })
}, [])
return (
<PairContext.Provider
value={useMemo(() => [state, { update, updatePairEntity }], [state, update, updatePairEntity])}
>
{children}
</PairContext.Provider>
)
}
export function usePairAddress(tokenA?: Token, tokenB?: Token): string | undefined {
const { chainId } = useWeb3React()
const [state, { update }] = usePairContext()
const tokens: [Token, Token] = tokenA && tokenB && tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
const address = state?.[chainId]?.[ADDRESSES_KEY]?.[tokens[0]?.address]?.[tokens[1]?.address]
useEffect(() => {
if (address === undefined && tokenA && tokenB) {
const pairAddress = Pair.getAddress(...tokens)
pairAddress && update(chainId, tokens)
}
}, [chainId, address, tokenA, tokenB, tokens, update])
return address
}
export function usePair(tokenA?: Token, tokenB?: Token): Pair | undefined {
const { chainId } = useWeb3React()
const [state, { updatePairEntity }] = usePairContext()
const address = usePairAddress(tokenA, tokenB)
const pair = state?.[chainId]?.[ENTITIES_KEY]?.[address]
const tokenAmountA = useAddressBalance(address, tokenA)
const tokenAmountB = useAddressBalance(address, tokenB)
useEffect(() => {
if (!pair && tokenAmountA && tokenAmountB) {
updatePairEntity(address, new Pair(tokenAmountA, tokenAmountB), chainId)
}
}, [pair, tokenAmountA, tokenAmountB, address, updatePairEntity, chainId])
return pair
}
export function useAllPairs() {
const { chainId } = useWeb3React()
const [state] = usePairContext()
const allPairDetails = state?.[chainId]?.[ADDRESSES_KEY]
const allPairs = useMemo(() => {
if (!allPairDetails) {
return {}
}
const formattedExchanges = {}
Object.keys(allPairDetails).map(token0Address => {
return Object.keys(allPairDetails[token0Address]).map(token1Address => {
const pairAddress = allPairDetails[token0Address][token1Address]
if (pairAddress) {
return (formattedExchanges[pairAddress] = {
token0: token0Address,
token1: token1Address
})
} else {
return null
}
})
})
return formattedExchanges
}, [allPairDetails])
return useMemo(() => {
return allPairs || {}
}, [allPairs])
}
export function useTotalSupply(tokenA?: Token, tokenB?: Token) {
const { library } = useWeb3React()
const pair = usePair(tokenA, tokenB)
const [totalPoolTokens, setTotalPoolTokens] = useState<TokenAmount>()
const pairContract = usePairContract(pair?.liquidityToken.address)
const fetchPoolTokens = useCallback(async () => {
!!pairContract &&
pairContract
.deployed()
.then(() => {
if (pairContract) {
pairContract.totalSupply().then(totalSupply => {
if (totalSupply !== undefined && pair?.liquidityToken?.decimals) {
const supplyFormatted = JSBI.BigInt(totalSupply)
const tokenSupplyFormatted = new TokenAmount(pair?.liquidityToken, supplyFormatted)
setTotalPoolTokens(tokenSupplyFormatted)
}
})
}
})
.catch(error => {
console.log(error)
})
}, [pairContract, pair])
// on the block make sure we're updated
useEffect(() => {
fetchPoolTokens()
library.on('block', fetchPoolTokens)
return () => {
library.removeListener('block', fetchPoolTokens)
}
}, [fetchPoolTokens, library])
return totalPoolTokens
}
......@@ -8,20 +8,31 @@ export const ALL_TOKENS = [
...Object.values(WETH),
// Mainnet Tokens
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C'),
new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker'),
// Rinkeby Tokens
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.RINKEBY, '0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44', 18, 'IANV2', 'IAn V2 /Coin'),
new Token(ChainId.RINKEBY, '0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85', 18, 'MKR', 'Maker'),
// Kovan Tokens
new Token(ChainId.KOVAN, '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.KOVAN, '0xAaF64BFCC32d0F15873a02163e7E500671a4ffcD', 18, 'MKR', 'Maker'),
// Ropsten Tokens
new Token(ChainId.ROPSTEN, '0xaD6D458402F60fD3Bd25163575031ACDce07538D', 18, 'DAI', 'Dai Stablecoin')
// Goerli Tokens
]
// remap WETH to ETH
.map(token => {
if (token.equals(WETH[token.chainId])) {
;(token as any).symbol = 'ETH'
;(token as any).name = 'Ether'
}
return token
})
// put into an object
.reduce((tokenMap, token) => {
if (tokenMap?.[token.chainId]?.[token.address] !== undefined) throw Error('Duplicate tokens.')
......@@ -39,11 +50,6 @@ export function useAllTokens(): { [address: string]: Token } {
const [localStorageTokens] = useLocalStorageTokens()
return useMemo(() => {
// rename WETH to ETH (in case not used in useToken yet)
if (ALL_TOKENS[chainId]?.[WETH[chainId]?.address]) {
ALL_TOKENS[chainId][WETH[chainId].address].name = 'ETH'
ALL_TOKENS[chainId][WETH[chainId].address].symbol = 'ETH'
}
return (
localStorageTokens
// filter to the current chain
......@@ -64,11 +70,5 @@ export function useToken(tokenAddress: string): Token {
const token = tokens?.[tokenAddress]
// rename WETH to ETH
if (token?.equals(WETH[token?.chainId])) {
;(token as any).symbol = 'ETH'
;(token as any).name = 'Ether'
}
return token
}
import { Contract } from '@ethersproject/contracts'
import { Token, TokenAmount } from '@uniswap/sdk'
import useSWR from 'swr'
import { SWRKeys } from '.'
import { useTokenContract } from '../hooks'
function getTokenAllowance(
contract: Contract,
token: Token
): (_: SWRKeys, __: number, ___: string, 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,
spender?: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): TokenAmount {
const contract = useTokenContract(token?.address, false)
const shouldFetch = !!contract && typeof owner === 'string' && typeof spender === 'string'
const { data } = useSWR(
shouldFetch ? [SWRKeys.Allowances, token.chainId, token.address, owner, spender] : null,
getTokenAllowance(contract, token),
{
dedupingInterval: 10 * 1000,
refreshInterval: 20 * 1000
}
)
return data
}
import { Contract } from '@ethersproject/contracts'
import { Token, TokenAmount, Pair } from '@uniswap/sdk'
import useSWR from 'swr'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { useContract } from '../hooks'
import { SWRKeys } from '.'
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 } }) => {
return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
}
)
}
// undefined while loading, null if no liquidity, pair otherwise
export function usePair(tokenA?: Token, tokenB?: Token): undefined | Pair | null {
const bothDefined = !!tokenA && !!tokenB
const invalid = bothDefined && tokenA.equals(tokenB)
const [token0, token1] =
bothDefined && !invalid ? (tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]) : []
const pairAddress = !!token0 && !!token1 ? Pair.getAddress(token0, token1) : undefined
const contract = useContract(pairAddress, IUniswapV2PairABI, false)
const shouldFetch = !!contract
const { data } = useSWR(
shouldFetch ? [SWRKeys.Reserves, token0.chainId, pairAddress] : null,
getReserves(contract, token0, token1),
{
dedupingInterval: 10 * 1000,
refreshInterval: 20 * 1000
}
)
return data
}
import { Contract } from '@ethersproject/contracts'
import { Token, TokenAmount } from '@uniswap/sdk'
import useSWR from 'swr'
import { abi as IERC20ABI } from '@uniswap/v2-core/build/IERC20.json'
import { useContract } from '../hooks'
import { SWRKeys } from '.'
function getTotalSupply(contract: Contract, token: Token): () => Promise<TokenAmount> {
return async (): Promise<TokenAmount> =>
contract
.totalSupply()
.then((totalSupply: { toString: () => string }) => new TokenAmount(token, totalSupply.toString()))
}
export function useTotalSupply(token?: Token): TokenAmount {
const contract = useContract(token?.address, IERC20ABI, false)
const shouldFetch = !!contract
const { data } = useSWR(
shouldFetch ? [SWRKeys.TotalSupply, token.chainId, token.address] : null,
getTotalSupply(contract, token),
{
dedupingInterval: 10 * 1000,
refreshInterval: 20 * 1000
}
)
return data
}
export enum SWRKeys {
Allowances,
Reserves,
TotalSupply
}
import { useMemo } from 'react'
import { WETH, Token, TokenAmount, Trade } from '@uniswap/sdk'
import { WETH, Token, TokenAmount, Trade, ChainId } from '@uniswap/sdk'
import { useWeb3React } from './index'
import { usePair } from '../contexts/Pairs'
import { isWETH } from '../utils'
import { usePair } from '../data/Reserves'
/**
* Returns the best trade for the exact amount of tokens in to the given token out
*/
const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin')
const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
export function useTradeExactIn(amountIn?: TokenAmount, tokenOut?: Token): Trade | null {
const inputToken = amountIn?.token
const outputToken = tokenOut
const { chainId } = useWeb3React()
// check for direct pair between tokens
const pairBetween = usePair(amountIn?.token, tokenOut)
const pairBetween = usePair(inputToken, outputToken)
// get token<->WETH pairs
const aToETH = usePair(amountIn && !isWETH(amountIn.token) ? amountIn.token : null, WETH[chainId])
const bToETH = usePair(tokenOut && !isWETH(tokenOut) ? tokenOut : null, WETH[chainId])
const aToETH = usePair(inputToken, WETH[chainId])
const bToETH = usePair(outputToken, WETH[chainId])
// get token<->DAI pairs
const aToDAI = usePair(inputToken, chainId === ChainId.MAINNET ? DAI : null)
const bToDAI = usePair(outputToken, chainId === ChainId.MAINNET ? DAI : null)
// get token<->USDC pairs
const aToUSDC = usePair(inputToken, chainId === ChainId.MAINNET ? USDC : null)
const bToUSDC = usePair(outputToken, chainId === ChainId.MAINNET ? USDC : null)
return useMemo(() => {
const allPairs = [pairBetween, aToETH, bToETH].filter(p => !!p)
const allPairs = [pairBetween, aToETH, bToETH, aToDAI, bToDAI, aToUSDC, bToUSDC].filter(p => !!p)
if (amountIn && allPairs.length > 0 && tokenOut) {
if (amountIn && tokenOut && allPairs.length > 0) {
try {
// TODO(moodysalem): remove when the insufficient reserves/input errors do not throw exceptions
return Trade.bestTradeExactIn(allPairs, amountIn, tokenOut)[0] ?? null
......@@ -29,26 +41,37 @@ export function useTradeExactIn(amountIn?: TokenAmount, tokenOut?: Token): Trade
}
}
return null
}, [aToETH, bToETH, pairBetween, amountIn, tokenOut])
}, [pairBetween, aToETH, bToETH, aToDAI, bToDAI, aToUSDC, bToUSDC, amountIn, tokenOut])
}
/**
* Returns the best trade for the token in to the exact amount of token out
*/
export function useTradeExactOut(tokenIn?: Token, amountOut?: TokenAmount): Trade | null {
const inputToken = tokenIn
const outputToken = amountOut?.token
const { chainId } = useWeb3React()
// check for direct pair between tokens
const pairBetween = usePair(amountOut?.token, tokenIn)
// get token<->WETH pairs
const aToETH = usePair(amountOut && !isWETH(amountOut.token) ? amountOut.token : null, WETH[chainId])
const bToETH = usePair(tokenIn && !isWETH(tokenIn) ? tokenIn : null, WETH[chainId])
const aToETH = usePair(inputToken, WETH[chainId])
const bToETH = usePair(outputToken, WETH[chainId])
// get token<->DAI pairs
const aToDAI = usePair(inputToken, chainId === ChainId.MAINNET ? DAI : null)
const bToDAI = usePair(outputToken, chainId === ChainId.MAINNET ? DAI : null)
// get token<->USDC pairs
const aToUSDC = usePair(inputToken, chainId === ChainId.MAINNET ? USDC : null)
const bToUSDC = usePair(outputToken, chainId === ChainId.MAINNET ? USDC : null)
return useMemo(() => {
const allPairs = [pairBetween, aToETH, bToETH].filter(p => !!p)
const allPairs = [pairBetween, aToETH, bToETH, aToDAI, bToDAI, aToUSDC, bToUSDC].filter(p => !!p)
if (amountOut && allPairs.length > 0 && tokenIn) {
if (tokenIn && amountOut && allPairs.length > 0) {
try {
// TODO(moodysalem): remove when the insufficient reserves/input errors do not throw exceptions
return Trade.bestTradeExactOut(allPairs, tokenIn, amountOut)[0] ?? null
......@@ -57,5 +80,5 @@ export function useTradeExactOut(tokenIn?: Token, amountOut?: TokenAmount): Trad
}
}
return null
}, [pairBetween, aToETH, bToETH, amountOut, tokenIn])
}, [pairBetween, aToETH, bToETH, aToDAI, bToDAI, aToUSDC, bToUSDC, tokenIn, amountOut])
}
......@@ -10,8 +10,6 @@ import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } fr
import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application'
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances'
import ExchangesContextProvider from './contexts/Pairs'
import AllowancesContextProvider from './contexts/Allowances'
import App from './pages/App'
import ThemeProvider, { GlobalStyle } from './theme'
import './i18n'
......@@ -40,11 +38,7 @@ function ContextProviders({ children }: { children: React.ReactNode }) {
<LocalStorageContextProvider>
<ApplicationContextProvider>
<TransactionContextProvider>
<ExchangesContextProvider>
<BalancesContextProvider>
<AllowancesContextProvider>{children}</AllowancesContextProvider>
</BalancesContextProvider>
</ExchangesContextProvider>
<BalancesContextProvider>{children}</BalancesContextProvider>
</TransactionContextProvider>
</ApplicationContextProvider>
</LocalStorageContextProvider>
......
This diff is collapsed.
This diff is collapsed.
import React, { useState, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components'
import { JSBI } from '@uniswap/sdk'
import { JSBI, Pair } from '@uniswap/sdk'
import { RouteComponentProps, withRouter } from 'react-router-dom'
import Question from '../../components/Question'
......@@ -13,10 +13,10 @@ import { RowBetween } from '../../components/Row'
import { ButtonPrimary, ButtonSecondary } from '../../components/Button'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { useAllPairs } from '../../contexts/Pairs'
import { useWeb3React } from '@web3-react/core'
import { useAllTokens } from '../../contexts/Tokens'
import { useAllBalances, useAccountLPBalances } from '../../contexts/Balances'
import { usePair } from '../../data/Reserves'
import { useAllDummyPairs } from '../../contexts/LocalStorage'
const Positions = styled.div`
position: relative;
......@@ -29,36 +29,33 @@ const FixedBottom = styled.div`
width: 100%;
`
function PositionCardWrapper({ dummyPair }: { dummyPair: Pair }) {
const pair = usePair(dummyPair.token0, dummyPair.token1)
return <PositionCard pair={pair} />
}
function Supply({ history }: RouteComponentProps) {
const theme = useContext(ThemeContext)
const { account } = useWeb3React()
const [showPoolSearch, setShowPoolSearch] = useState(false)
const allTokens = useAllTokens()
const allBalances = useAllBalances()
const allPairs = useAllPairs()
const theme = useContext(ThemeContext)
// initiate listener for LP balances
const allBalances = useAllBalances()
useAccountLPBalances(account)
const filteredExchangeList = Object.keys(allPairs)
.filter(pairAddress => {
const pairs = useAllDummyPairs()
const filteredExchangeList = pairs
.filter(pair => {
return (
allBalances &&
allBalances[account] &&
allBalances[account][pairAddress] &&
JSBI.greaterThan(allBalances[account][pairAddress].raw, JSBI.BigInt(0))
allBalances[account][pair.liquidityToken.address] &&
JSBI.greaterThan(allBalances[account][pair.liquidityToken.address].raw, JSBI.BigInt(0))
)
})
.map((pairAddress, i) => {
return (
<PositionCard
key={i}
pairAddress={pairAddress}
token0={allTokens[allPairs[pairAddress].token0]}
token1={allTokens[allPairs[pairAddress].token1]}
/>
)
.map((pair, i) => {
return <PositionCardWrapper key={i} dummyPair={pair} />
})
return (
......
import { getEtherscanLink } from './index'
import { AddressZero } from '@ethersproject/constants'
import { TokenAmount, Token, ChainId } from '@uniswap/sdk'
import { getEtherscanLink, calculateSlippageAmount } from '.'
describe('utils', () => {
describe('#getEtherscanLink', () => {
......@@ -9,4 +12,16 @@ describe('utils', () => {
expect(getEtherscanLink(1, 'abc', 'address')).toEqual('https://etherscan.io/address/abc')
})
})
describe('#calculateSlippageAmount', () => {
it('bounds are correct', () => {
const tokenAmount = new TokenAmount(new Token(ChainId.MAINNET, AddressZero, 0), '100')
expect(() => calculateSlippageAmount(tokenAmount, -1)).toThrow()
expect(calculateSlippageAmount(tokenAmount, 0).map(bound => bound.toString())).toEqual(['100', '100'])
expect(calculateSlippageAmount(tokenAmount, 100).map(bound => bound.toString())).toEqual(['99', '101'])
expect(calculateSlippageAmount(tokenAmount, 200).map(bound => bound.toString())).toEqual(['98', '102'])
expect(calculateSlippageAmount(tokenAmount, 10000).map(bound => bound.toString())).toEqual(['0', '200'])
expect(() => calculateSlippageAmount(tokenAmount, 10001)).toThrow()
})
})
})
......@@ -3,7 +3,6 @@ import { getAddress } from '@ethersproject/address'
import { AddressZero } from '@ethersproject/constants'
import { parseBytes32String } from '@ethersproject/strings'
import { BigNumber } from '@ethersproject/bignumber'
import { WETH } from '@uniswap/sdk'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { abi as IUniswapV2Router01ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router01.json'
......@@ -11,6 +10,7 @@ import { ROUTER_ADDRESS, SUPPORTED_THEMES } from '../constants'
import ERC20_ABI from '../constants/abis/erc20.json'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32.json'
import { JSBI, TokenAmount } from '@uniswap/sdk'
export function isAddress(value: any): string | false {
try {
......@@ -106,6 +106,16 @@ export function calculateGasMargin(value: BigNumber): BigNumber {
return value.mul(BigNumber.from(10000).add(BigNumber.from(1000))).div(BigNumber.from(10000)) // add 10%
}
export function calculateSlippageAmount(value: TokenAmount, slippage: number): [JSBI, JSBI] {
if (slippage < 0 || slippage > 10000) {
throw Error(`Unexpected slippage value: ${slippage}`)
}
return [
JSBI.divide(JSBI.multiply(value.raw, JSBI.BigInt(10000 - slippage)), JSBI.BigInt(10000)),
JSBI.divide(JSBI.multiply(value.raw, JSBI.BigInt(10000 + slippage)), JSBI.BigInt(10000))
]
}
// account is optional
export function getProviderOrSigner(library: any, account?: string): any {
return account ? library.getSigner(account).connectUnchecked() : library
......@@ -197,23 +207,3 @@ export async function getTokenBalance(tokenAddress, address, library) {
return getContract(tokenAddress, ERC20_ABI, library).balanceOf(address)
}
// get the token allowance
export async function getTokenAllowance(address, tokenAddress, spenderAddress, library) {
if (!isAddress(address) || !isAddress(tokenAddress) || !isAddress(spenderAddress)) {
throw Error(
"Invalid 'address' or 'tokenAddress' or 'spenderAddress' parameter" +
`'${address}' or '${tokenAddress}' or '${spenderAddress}'.`
)
}
return getContract(tokenAddress, ERC20_ABI, library).allowance(address, spenderAddress)
}
export function isWETH(token) {
if (token && token.address === WETH[token.chainId].address) {
return true
} else {
return false
}
}
import { Signer } from 'ethers'
import { JsonRpcSigner, Provider } from 'ethers/providers'
import { TransactionResponse } from 'ethers/providers/abstract-provider'
/**
* Wraps a JsonRpcSigner and replaces `sendTransaction` with `sendUncheckedTransaction`
*/
export default class UncheckedJsonRpcSigner extends Signer {
private readonly signer: JsonRpcSigner
public readonly provider: Provider
constructor(signer: JsonRpcSigner) {
super()
this.signer = signer
this.provider = signer.provider
}
getAddress(): Promise<string> {
return this.signer.getAddress()
}
sendTransaction(transaction): Promise<TransactionResponse> {
return this.signer.sendUncheckedTransaction(transaction).then(hash => {
return {
hash,
nonce: null,
gasLimit: null,
gasPrice: null,
data: null,
value: null,
chainId: null,
confirmations: 0,
from: null,
wait: confirmations => {
return this.signer.provider.waitForTransaction(hash, confirmations)
}
}
})
}
signMessage(message) {
return this.signer.signMessage(message)
}
}
......@@ -4982,9 +4982,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001039, caniuse-lite@^1.0.30001043:
version "1.0.30001053"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001053.tgz#b7ae027567ce2665b965b0437e4512b296ccd20d"
integrity sha512-HtV4wwIZl6GA4Oznse8aR274XUOYGZnQLcf/P8vHgmlfqSNelwD+id8CyHOceqLqt9yfKmo7DUZTh1EuS9pukg==
version "1.0.30001054"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001054.tgz#7e82fc42d927980b0ce1426c4813df12381e1a75"
integrity sha512-jiKlTI6Ur8Kjfj8z0muGrV6FscpRvefcQVPSuMuXnvRCfExU7zlVLNjmOz1TnurWgUrAY7MMmjyy+uTgIl1XHw==
capture-exit@^2.0.0:
version "2.0.0"
......@@ -6602,9 +6602,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.413, electron-to-chromium@^1.3.47:
version "1.3.430"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.430.tgz#33914f7c2db771bdcf30977bd4fd6258ee8a2f37"
integrity sha512-HMDYkANGhx6vfbqpOf/hc6hWEmiOipOHGDeRDeUb3HLD3XIWpvKQxFgWf0tgHcr3aNv6I/8VPecplqmQsXoZSw==
version "1.3.431"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.431.tgz#705dd8ef46200415ba837b31d927cdc1e43db303"
integrity sha512-2okqkXCIda7qDwjYhUFxPcQdZDIZZ/zBLDzVOif7WW/TSNfEhdT6SO07O1x/sFteEHX189Z//UwjbZKKCOn2Fg==
elegant-spinner@^1.0.1:
version "1.0.1"
......@@ -7843,7 +7843,7 @@ fancy-log@^1.3.2:
parse-node-version "^1.0.0"
time-stamp "^1.0.0"
fast-deep-equal@^2.0.1:
fast-deep-equal@2.0.1, fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
......@@ -15931,6 +15931,13 @@ swarm-js@0.1.39:
tar "^4.0.2"
xhr-request-promise "^0.1.2"
swr@0.1.18:
version "0.1.18"
resolved "https://registry.yarnpkg.com/swr/-/swr-0.1.18.tgz#be62df4cb8d188dc092305b35ecda1f3be8e61c1"
integrity sha512-lD31JxsD0bXdT7dyGVIB7MHcwgFp+HbBBOLt075hJT0sEgW01E3+EuCeB6fsavxZ2UjUZ3f+SbNMo9c8pv9uiA==
dependencies:
fast-deep-equal "2.0.1"
symbol-observable@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
......
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