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