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,
......
import React, { useState, useCallback, useEffect, useContext } from 'react' import React, { useState, useCallback, useEffect, useContext } from 'react'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { parseEther, parseUnits } from '@ethersproject/units' import { parseEther, parseUnits } from '@ethersproject/units'
import { Fraction, JSBI, Percent, TokenAmount, TradeType, WETH } from '@uniswap/sdk' import { JSBI, Percent, TokenAmount, TradeType, WETH, Fraction } from '@uniswap/sdk'
import { ArrowDown, ChevronDown, ChevronUp, Repeat } from 'react-feather' import { ArrowDown, ChevronDown, ChevronUp, Repeat } from 'react-feather'
import { withRouter, RouteComponentProps } from 'react-router-dom' import { withRouter, RouteComponentProps } from 'react-router-dom'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { Zero, MaxUint256 } from '@ethersproject/constants' import { MaxUint256 } from '@ethersproject/constants'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { Field, SwapAction, useSwapStateReducer } from './swap-store' import { Field, SwapAction, useSwapStateReducer } from './swap-store'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -13,11 +13,11 @@ import Card, { BlueCard, GreyCard, YellowCard } from '../../components/Card' ...@@ -13,11 +13,11 @@ import Card, { BlueCard, GreyCard, YellowCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import { AutoRow, RowBetween, RowFixed } from '../Row' import { AutoRow, RowBetween, RowFixed } from '../Row'
import { ROUTER_ADDRESS } from '../../constants' import { ROUTER_ADDRESS } from '../../constants'
import { useAddressAllowance } from '../../contexts/Allowances' import { useTokenAllowance } from '../../data/Allowances'
import { useUserAdvanced } from '../../contexts/Application' import { useUserAdvanced } from '../../contexts/Application'
import { useAddressBalance, useAllBalances } from '../../contexts/Balances' import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
import { useLocalStorageTokens } from '../../contexts/LocalStorage' import { useLocalStorageTokens } from '../../contexts/LocalStorage'
import { usePair } from '../../contexts/Pairs' import { usePair } from '../../data/Reserves'
import { useAllTokens, useToken } from '../../contexts/Tokens' import { useAllTokens, useToken } from '../../contexts/Tokens'
import { usePendingApproval, useTransactionAdder } from '../../contexts/Transactions' import { usePendingApproval, useTransactionAdder } from '../../contexts/Transactions'
import { useTokenContract, useWeb3React } from '../../hooks' import { useTokenContract, useWeb3React } from '../../hooks'
...@@ -29,8 +29,8 @@ import { ...@@ -29,8 +29,8 @@ import {
getEtherscanLink, getEtherscanLink,
getProviderOrSigner, getProviderOrSigner,
getRouterContract, getRouterContract,
isWETH, QueryParams,
QueryParams calculateSlippageAmount
} from '../../utils' } from '../../utils'
import Copy from '../AccountDetails/Copy' import Copy from '../AccountDetails/Copy'
import AddressInputPanel from '../AddressInputPanel' import AddressInputPanel from '../AddressInputPanel'
...@@ -55,12 +55,6 @@ import { ...@@ -55,12 +55,6 @@ import {
Wrapper Wrapper
} from './styleds' } from './styleds'
// import BalanceCard from '../BalanceCard'
function hex(value: JSBI) {
return BigNumber.from(value.toString())
}
enum SwapType { enum SwapType {
EXACT_TOKENS_FOR_TOKENS, EXACT_TOKENS_FOR_TOKENS,
EXACT_TOKENS_FOR_ETH, EXACT_TOKENS_FOR_ETH,
...@@ -141,9 +135,6 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -141,9 +135,6 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
const tokenContractInput: Contract = useTokenContract(tokens[Field.INPUT]?.address) const tokenContractInput: Contract = useTokenContract(tokens[Field.INPUT]?.address)
const tokenContractOutput: Contract = useTokenContract(tokens[Field.OUTPUT]?.address) const tokenContractOutput: Contract = useTokenContract(tokens[Field.OUTPUT]?.address)
// check on pending approvals for token amounts
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address)
// modal and loading // modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false) const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) const [showAdvanced, setShowAdvanced] = useState<boolean>(false)
...@@ -155,11 +146,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -155,11 +146,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW) const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE) const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
// input approval
const inputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.INPUT], ROUTER_ADDRESS)
// all balances for detecting a swap with send // all balances for detecting a swap with send
const allBalances: TokenAmount[] = useAllBalances() const allBalances = useAllBalances()
// get user- and token-specific lookup data // get user- and token-specific lookup data
const userBalances = { const userBalances = {
...@@ -167,6 +155,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -167,6 +155,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT]) [Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
} }
// parse the amount that the user typed
const parsedAmounts: { [field: number]: TokenAmount } = {} const parsedAmounts: { [field: number]: TokenAmount } = {}
if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) { if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) {
try { try {
...@@ -174,11 +163,13 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -174,11 +163,13 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
if (typedValueParsed !== '0') if (typedValueParsed !== '0')
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed) parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
} catch (error) { } catch (error) {
// should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
console.error(error) console.error(error)
} }
} }
const pair = usePair(tokens[Field.INPUT], tokens[Field.OUTPUT]) const pair = usePair(tokens[Field.INPUT], tokens[Field.OUTPUT])
const bestTradeExactIn = useTradeExactIn( const bestTradeExactIn = useTradeExactIn(
tradeType === TradeType.EXACT_INPUT ? parsedAmounts[independentField] : null, tradeType === TradeType.EXACT_INPUT ? parsedAmounts[independentField] : null,
tokens[Field.OUTPUT] tokens[Field.OUTPUT]
...@@ -191,10 +182,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -191,10 +182,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
const trade = tradeType === TradeType.EXACT_INPUT ? bestTradeExactIn : bestTradeExactOut const trade = tradeType === TradeType.EXACT_INPUT ? bestTradeExactIn : bestTradeExactOut
const route = trade?.route const route = trade?.route
const userHasSpecifiedInputOutput = const userHasSpecifiedInputOutput =
!!parsedAmounts[independentField] &&
parsedAmounts[independentField].greaterThan(JSBI.BigInt(0)) &&
!!tokens[Field.INPUT] && !!tokens[Field.INPUT] &&
!!tokens[Field.OUTPUT] !!tokens[Field.OUTPUT] &&
!!parsedAmounts[independentField] &&
parsedAmounts[independentField].greaterThan(JSBI.BigInt(0))
const noRoute = !route const noRoute = !route
const slippageFromTrade: Percent = trade && trade.slippage const slippageFromTrade: Percent = trade && trade.slippage
...@@ -202,6 +193,15 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -202,6 +193,15 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
if (trade) if (trade)
parsedAmounts[dependentField] = tradeType === TradeType.EXACT_INPUT ? trade.outputAmount : trade.inputAmount parsedAmounts[dependentField] = tradeType === TradeType.EXACT_INPUT ? trade.outputAmount : trade.inputAmount
// check whether the user has approved the router on the input token
const inputApproval: TokenAmount = useTokenAllowance(tokens[Field.INPUT], account, ROUTER_ADDRESS)
const userHasApprovedRouter =
tokens[Field.INPUT]?.equals(WETH[chainId]) ||
(!!inputApproval &&
!!parsedAmounts[Field.INPUT] &&
JSBI.greaterThanOrEqual(inputApproval.raw, parsedAmounts[Field.INPUT].raw))
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address)
const feeAsPercent = new Percent(JSBI.BigInt(3), JSBI.BigInt(1000)) const feeAsPercent = new Percent(JSBI.BigInt(3), JSBI.BigInt(1000))
const feeTimesInputRaw = const feeTimesInputRaw =
parsedAmounts[Field.INPUT] && feeAsPercent.multiply(JSBI.BigInt(parsedAmounts[Field.INPUT].raw)) parsedAmounts[Field.INPUT] && feeAsPercent.multiply(JSBI.BigInt(parsedAmounts[Field.INPUT].raw))
...@@ -209,17 +209,14 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -209,17 +209,14 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
const formattedAmounts = { const formattedAmounts = {
[independentField]: typedValue, [independentField]: typedValue,
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(8) : '' [dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(6) : ''
} }
const priceSlippage = const priceSlippage =
slippageFromTrade && slippageFromTrade &&
new Percent( new Percent(
JSBI.subtract( slippageFromTrade.subtract(new Fraction('30', '10000')).numerator,
JSBI.multiply(slippageFromTrade.numerator, JSBI.BigInt('1000')), slippageFromTrade.subtract(new Fraction('30', '10000')).denominator
JSBI.multiply(JSBI.BigInt('3'), slippageFromTrade.denominator)
),
JSBI.multiply(slippageFromTrade.denominator, JSBI.BigInt('1000'))
) )
const onTokenSelection = useCallback( const onTokenSelection = useCallback(
...@@ -288,8 +285,11 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -288,8 +285,11 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
!!userBalances[Field.INPUT] && !!userBalances[Field.INPUT] &&
!!tokens[Field.INPUT] && !!tokens[Field.INPUT] &&
WETH[chainId] && WETH[chainId] &&
JSBI.greaterThan(userBalances[Field.INPUT].raw, isWETH(tokens[Field.INPUT]) ? MIN_ETHER.raw : JSBI.BigInt(0)) JSBI.greaterThan(
? isWETH(tokens[Field.INPUT]) userBalances[Field.INPUT].raw,
tokens[Field.INPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
)
? tokens[Field.INPUT].equals(WETH[chainId])
? userBalances[Field.INPUT].subtract(MIN_ETHER) ? userBalances[Field.INPUT].subtract(MIN_ETHER)
: userBalances[Field.INPUT] : userBalances[Field.INPUT]
: undefined : undefined
...@@ -312,17 +312,17 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -312,17 +312,17 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
function getSwapType(): SwapType { function getSwapType(): SwapType {
if (tradeType === TradeType.EXACT_INPUT) { if (tradeType === TradeType.EXACT_INPUT) {
if (tokens[Field.INPUT] === WETH[chainId]) { if (tokens[Field.INPUT].equals(WETH[chainId])) {
return SwapType.EXACT_ETH_FOR_TOKENS return SwapType.EXACT_ETH_FOR_TOKENS
} else if (tokens[Field.OUTPUT] === WETH[chainId]) { } else if (tokens[Field.OUTPUT].equals(WETH[chainId])) {
return SwapType.EXACT_TOKENS_FOR_ETH return SwapType.EXACT_TOKENS_FOR_ETH
} else { } else {
return SwapType.EXACT_TOKENS_FOR_TOKENS return SwapType.EXACT_TOKENS_FOR_TOKENS
} }
} else if (tradeType === TradeType.EXACT_OUTPUT) { } else if (tradeType === TradeType.EXACT_OUTPUT) {
if (tokens[Field.INPUT] === WETH[chainId]) { if (tokens[Field.INPUT].equals(WETH[chainId])) {
return SwapType.ETH_FOR_EXACT_TOKENS return SwapType.ETH_FOR_EXACT_TOKENS
} else if (tokens[Field.OUTPUT] === WETH[chainId]) { } else if (tokens[Field.OUTPUT].equals(WETH[chainId])) {
return SwapType.TOKENS_FOR_EXACT_ETH return SwapType.TOKENS_FOR_EXACT_ETH
} else { } else {
return SwapType.TOKENS_FOR_EXACT_TOKENS return SwapType.TOKENS_FOR_EXACT_TOKENS
...@@ -330,30 +330,21 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -330,30 +330,21 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
} }
} }
function calculateSlippageAmount(value: TokenAmount): JSBI[] { const slippageAdjustedAmounts: { [field: number]: TokenAmount } = {
if (value && value.raw) { [independentField]: parsedAmounts[independentField],
const offset = JSBI.divide(JSBI.multiply(JSBI.BigInt(allowedSlippage), value.raw), JSBI.BigInt(10000)) [dependentField]: parsedAmounts[dependentField]
return [JSBI.subtract(value.raw, offset), JSBI.add(value.raw, offset)] ? tradeType === TradeType.EXACT_INPUT
} ? new TokenAmount(
return null tokens[dependentField],
} calculateSlippageAmount(parsedAmounts[dependentField], allowedSlippage)[0]
)
const slippageAdjustedAmounts: { [field in Field]: TokenAmount } = { : new TokenAmount(
[Field.INPUT]: tokens[dependentField],
Field.INPUT === independentField calculateSlippageAmount(parsedAmounts[dependentField], allowedSlippage)[1]
? parsedAmounts[Field.INPUT] )
: calculateSlippageAmount(parsedAmounts[Field.INPUT])?.[0] && : undefined
new TokenAmount(tokens[Field.INPUT], calculateSlippageAmount(parsedAmounts[Field.INPUT])?.[1]),
[Field.OUTPUT]:
Field.OUTPUT === independentField
? parsedAmounts[Field.OUTPUT]
: calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0] &&
new TokenAmount(tokens[Field.INPUT], calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0])
} }
const showInputApprove: boolean =
parsedAmounts[Field.INPUT] && inputApproval && JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
// reset modal state when closed // reset modal state when closed
function resetModal() { function resetModal() {
// clear input if txn submitted // clear input if txn submitted
...@@ -371,10 +362,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -371,10 +362,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
const signer = await getProviderOrSigner(library, account) const signer = await getProviderOrSigner(library, account)
// get token contract if needed // get token contract if needed
let estimate: Function, method: Function, args, value let estimate: Function, method: Function, args
if (tokens[Field.INPUT] === WETH[chainId]) { if (tokens[Field.INPUT].equals(WETH[chainId])) {
;(signer as any) ;(signer as any)
.sendTransaction({ to: recipient.toString(), value: hex(parsedAmounts[Field.INPUT].raw) }) .sendTransaction({ to: recipient.toString(), value: BigNumber.from(parsedAmounts[Field.INPUT].raw.toString()) })
.then(response => { .then(response => {
setTxHash(response.hash) setTxHash(response.hash)
addTransaction( addTransaction(
...@@ -396,11 +387,9 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -396,11 +387,9 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
estimate = tokenContractInput.estimateGas.transfer estimate = tokenContractInput.estimateGas.transfer
method = tokenContractInput.transfer method = tokenContractInput.transfer
args = [recipient, parsedAmounts[Field.INPUT].raw.toString()] args = [recipient, parsedAmounts[Field.INPUT].raw.toString()]
value = Zero await estimate(...args)
await estimate(...args, { value })
.then(estimatedGasLimit => .then(estimatedGasLimit =>
method(...args, { method(...args, {
value,
gasLimit: calculateGasMargin(estimatedGasLimit) gasLimit: calculateGasMargin(estimatedGasLimit)
}).then(response => { }).then(response => {
setTxHash(response.hash) setTxHash(response.hash)
...@@ -446,7 +435,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -446,7 +435,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
sending ? recipient : account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = Zero value = null
break break
case SwapType.TOKENS_FOR_EXACT_TOKENS: case SwapType.TOKENS_FOR_EXACT_TOKENS:
estimate = routerContract.estimateGas.swapTokensForExactTokens estimate = routerContract.estimateGas.swapTokensForExactTokens
...@@ -458,7 +447,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -458,7 +447,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
sending ? recipient : account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = Zero value = null
break break
case SwapType.EXACT_ETH_FOR_TOKENS: case SwapType.EXACT_ETH_FOR_TOKENS:
estimate = routerContract.estimateGas.swapExactETHForTokens estimate = routerContract.estimateGas.swapExactETHForTokens
...@@ -469,7 +458,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -469,7 +458,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
sending ? recipient : account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = hex(slippageAdjustedAmounts[Field.INPUT].raw) value = BigNumber.from(slippageAdjustedAmounts[Field.INPUT].raw.toString())
break break
case SwapType.TOKENS_FOR_EXACT_ETH: case SwapType.TOKENS_FOR_EXACT_ETH:
estimate = routerContract.estimateGas.swapTokensForExactETH estimate = routerContract.estimateGas.swapTokensForExactETH
...@@ -481,7 +470,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -481,7 +470,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
sending ? recipient : account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = Zero value = null
break break
case SwapType.EXACT_TOKENS_FOR_ETH: case SwapType.EXACT_TOKENS_FOR_ETH:
estimate = routerContract.estimateGas.swapExactTokensForETH estimate = routerContract.estimateGas.swapExactTokensForETH
...@@ -493,7 +482,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -493,7 +482,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
sending ? recipient : account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = Zero value = null
break break
case SwapType.ETH_FOR_EXACT_TOKENS: case SwapType.ETH_FOR_EXACT_TOKENS:
estimate = routerContract.estimateGas.swapETHForExactTokens estimate = routerContract.estimateGas.swapETHForExactTokens
...@@ -504,14 +493,14 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -504,14 +493,14 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
sending ? recipient : account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = hex(slippageAdjustedAmounts[Field.INPUT].raw) value = BigNumber.from(slippageAdjustedAmounts[Field.INPUT].raw.toString())
break break
} }
await estimate(...args, { value }) await estimate(...args, value ? { value } : {})
.then(estimatedGasLimit => .then(estimatedGasLimit =>
method(...args, { method(...args, {
value, ...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit) gasLimit: calculateGasMargin(estimatedGasLimit)
}).then(response => { }).then(response => {
setTxHash(response.hash) setTxHash(response.hash)
...@@ -579,6 +568,11 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -579,6 +568,11 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
setIsValid(false) setIsValid(false)
} }
if (!account) {
setGeneralError('Connect Wallet')
setIsValid(false)
}
if (!parsedAmounts[Field.INPUT]) { if (!parsedAmounts[Field.INPUT]) {
setInputError('Enter an amount') setInputError('Enter an amount')
setIsValid(false) setIsValid(false)
...@@ -643,18 +637,20 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -643,18 +637,20 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
tokens, tokens,
route, route,
trade, trade,
userBalances userBalances,
account
]) ])
// warnings on slippage // warnings on slippage
const warningLow: boolean = const warningLow: boolean = slippageFromTrade?.lessThan(new Percent(ALLOWED_SLIPPAGE_MEDIUM.toString(), '10000'))
slippageFromTrade && // TODO greaterThanOrEqualTo in SDK
parseFloat(slippageFromTrade.toFixed(4)) < ALLOWED_SLIPPAGE_MEDIUM / 100 &&
parseFloat(slippageFromTrade.toFixed(4)) > 0
const warningMedium: boolean = const warningMedium: boolean =
slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_MEDIUM / 100 slippageFromTrade?.equalTo(new Percent(ALLOWED_SLIPPAGE_MEDIUM.toString(), '10000')) ||
slippageFromTrade?.greaterThan(new Percent(ALLOWED_SLIPPAGE_MEDIUM.toString(), '10000'))
// TODO greaterThanOrEqualTo in SDK
const warningHigh: boolean = const warningHigh: boolean =
slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_HIGH / 100 slippageFromTrade?.equalTo(new Percent(ALLOWED_SLIPPAGE_HIGH.toString(), '10000')) ||
slippageFromTrade?.greaterThan(new Percent(ALLOWED_SLIPPAGE_HIGH.toString(), '10000'))
function modalHeader() { function modalHeader() {
if (sending && !sendingWithSwap) { if (sending && !sendingWithSwap) {
...@@ -751,8 +747,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -751,8 +747,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
{`Output is estimated. You will receive at least `} {`Output is estimated. You will receive at least `}
<b> <b>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {tokens[Field.OUTPUT]?.symbol}{' '} {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {tokens[Field.OUTPUT]?.symbol}{' '}
</b>{' '} </b>
{` or the transaction will revert.`} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
) : ( ) : (
<TYPE.italic textAlign="left" style={{ width: '100%' }}> <TYPE.italic textAlign="left" style={{ width: '100%' }}>
...@@ -760,7 +756,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -760,7 +756,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
<b> <b>
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {tokens[Field.INPUT]?.symbol} {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {tokens[Field.INPUT]?.symbol}
</b> </b>
{` or the transaction will revert.`} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
)} )}
</AutoColumn> </AutoColumn>
...@@ -824,16 +820,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -824,16 +820,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
<RowFixed> <RowFixed>
<TYPE.black fontSize={14}> <TYPE.black fontSize={14}>
{independentField === Field.INPUT {independentField === Field.INPUT
? slippageAdjustedAmounts[Field.OUTPUT] ? slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4) ?? '-'
? slippageAdjustedAmounts[Field.OUTPUT]?.toFixed(5) === '0.00000' : slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4) ?? '-'}
? '<0.00001'
: slippageAdjustedAmounts[Field.OUTPUT]?.toFixed(5)
: '-'
: slippageAdjustedAmounts[Field.INPUT]
? slippageAdjustedAmounts[Field.INPUT]?.toFixed(5) === '0.00000'
? '<0.00001'
: slippageAdjustedAmounts[Field.INPUT]?.toFixed(5)
: '-'}
</TYPE.black> </TYPE.black>
{parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && ( {parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && (
<TYPE.black fontSize={14} marginLeft={'4px'}> <TYPE.black fontSize={14} marginLeft={'4px'}>
...@@ -858,11 +846,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -858,11 +846,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
warningMedium={warningMedium} warningMedium={warningMedium}
warningHigh={warningHigh} warningHigh={warningHigh}
> >
{priceSlippage {priceSlippage?.lessThan(new Percent('1', '10000')) ? '<0.01%' : `${priceSlippage?.toFixed(2)}%` ?? '-'}
? priceSlippage.toFixed(4) === '0.0000'
? '<0.0001%'
: priceSlippage.toFixed(4) + '%'
: '-'}
</ErrorText> </ErrorText>
</RowBetween> </RowBetween>
<RowBetween> <RowBetween>
...@@ -920,11 +904,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -920,11 +904,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
warningMedium={warningMedium} warningMedium={warningMedium}
warningHigh={warningHigh} warningHigh={warningHigh}
> >
{priceSlippage {priceSlippage?.lessThan(new Percent('1', '10000')) ? '<0.01%' : `${priceSlippage?.toFixed(2)}%` ?? '-'}
? priceSlippage.toFixed(4) === '0.0000'
? '<0.0001%'
: priceSlippage.toFixed(4) + '%'
: '-'}
</ErrorText> </ErrorText>
<Text fontWeight={500} fontSize={16} color={theme.text3} pt={1}> <Text fontWeight={500} fontSize={16} color={theme.text3} pt={1}>
Price Impact Price Impact
...@@ -1130,7 +1110,6 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -1130,7 +1110,6 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
<PriceBar /> <PriceBar />
) : ( ) : (
<AutoColumn gap="4px"> <AutoColumn gap="4px">
{' '}
<RowBetween align="center"> <RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}> <Text fontWeight={500} fontSize={14} color={theme.text2}>
Price Price
...@@ -1167,11 +1146,9 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -1167,11 +1146,9 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
</TYPE.main> </TYPE.main>
<RowFixed> <RowFixed>
<ErrorText fontWeight={500} fontSize={14} warningMedium={warningMedium} warningHigh={warningHigh}> <ErrorText fontWeight={500} fontSize={14} warningMedium={warningMedium} warningHigh={warningHigh}>
{priceSlippage {priceSlippage?.lessThan(new Percent('1', '10000'))
? priceSlippage.toFixed(4) === '0.0000' ? '<0.01%'
? '<0.0001%' : `${priceSlippage?.toFixed(2)}%` ?? '-'}
: priceSlippage.toFixed(4) + '%'
: '-'}{' '}
</ErrorText> </ErrorText>
<QuestionHelper text="The difference between the market price and your quoted price due to trade size." /> <QuestionHelper text="The difference between the market price and your quoted price due to trade size." />
</RowFixed> </RowFixed>
...@@ -1192,11 +1169,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -1192,11 +1169,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
history.push('/add/' + tokens[Field.INPUT]?.address + '-' + tokens[Field.OUTPUT]?.address) history.push('/add/' + tokens[Field.INPUT]?.address + '-' + tokens[Field.OUTPUT]?.address)
}} }}
> >
{' '}
Create one now Create one now
</Link> </Link>
</GreyCard> </GreyCard>
) : showInputApprove && !inputError ? ( ) : !userHasApprovedRouter && !inputError ? (
<ButtonLight <ButtonLight
onClick={() => { onClick={() => {
approveAmount(Field.INPUT) approveAmount(Field.INPUT)
...@@ -1218,14 +1194,12 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -1218,14 +1194,12 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
error={!!warningHigh} error={!!warningHigh}
> >
<Text fontSize={20} fontWeight={500}> <Text fontSize={20} fontWeight={500}>
{!account {generalError ||
? 'Connect Wallet' inputError ||
: generalError || outputError ||
inputError || recipientError ||
outputError || tradeError ||
recipientError || `${sending ? 'Send' : 'Swap'}${warningHigh ? ' Anyway' : ''}`}
tradeError ||
`${sending ? 'Send' : 'Swap'}${warningHigh ? ' Anyway' : ''}`}
</Text> </Text>
</ButtonError> </ButtonError>
)} )}
...@@ -1276,20 +1250,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -1276,20 +1250,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
<RowFixed> <RowFixed>
<TYPE.black color={theme.text1} fontSize={14}> <TYPE.black color={theme.text1} fontSize={14}>
{independentField === Field.INPUT {independentField === Field.INPUT
? slippageAdjustedAmounts[Field.OUTPUT] ? slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4) ?? '-'
? slippageAdjustedAmounts[Field.OUTPUT]?.lessThan( : slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4) ?? '-'}
new Fraction(JSBI.BigInt(1), JSBI.BigInt(10000))
)
? '<0.00001'
: slippageAdjustedAmounts[Field.OUTPUT]?.toFixed(5)
: '-'
: slippageAdjustedAmounts[Field.INPUT]
? slippageAdjustedAmounts[Field.INPUT]?.lessThan(
new Fraction(JSBI.BigInt(1), JSBI.BigInt(10000))
)
? '<0.00001'
: slippageAdjustedAmounts[Field.INPUT]?.toFixed(5)
: '-'}
</TYPE.black> </TYPE.black>
{parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && ( {parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && (
<TYPE.black fontSize={14} marginLeft={'4px'} color={theme.text1}> <TYPE.black fontSize={14} marginLeft={'4px'} color={theme.text1}>
...@@ -1314,11 +1276,9 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -1314,11 +1276,9 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
warningMedium={warningMedium} warningMedium={warningMedium}
warningHigh={warningHigh} warningHigh={warningHigh}
> >
{priceSlippage {priceSlippage?.lessThan(new Percent('1', '10000'))
? priceSlippage.toFixed(4) === '0.0000' ? '<0.01%'
? '<0.0001%' : `${priceSlippage?.toFixed(2)}%` ?? '-'}
: priceSlippage.toFixed(4) + '%'
: '-'}
</ErrorText> </ErrorText>
</RowBetween> </RowBetween>
<RowBetween> <RowBetween>
...@@ -1366,8 +1326,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro ...@@ -1366,8 +1326,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
<Text lineHeight="145.23%;" fontSize={16} fontWeight={400} color={theme.text1}> <Text lineHeight="145.23%;" fontSize={16} fontWeight={400} color={theme.text1}>
This trade will move the price by {slippageFromTrade.toFixed(2)}%. This pool probably doesn’t have This trade will move the price by {slippageFromTrade?.toFixed(2)}%. This pool probably doesn’t
enough liquidity to support this trade. have enough liquidity to support this trade.
</Text> </Text>
</AutoColumn> </AutoColumn>
</YellowCard> </YellowCard>
......
...@@ -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
...@@ -324,26 +324,27 @@ export function Updater() { ...@@ -324,26 +324,27 @@ 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)) { const cleanupFunctions = []
startListening(chainId, account, pairAddress) pairs.forEach(pair => {
return () => { startListening(chainId, account, pair.liquidityToken.address)
stopListening(chainId, account, pairAddress) cleanupFunctions.push(() => stopListening(chainId, account, pair.liquidityToken.address))
} })
return () => {
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,30 +41,31 @@ function useLocalStorage<T, S = T>( ...@@ -39,30 +41,31 @@ 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.chainId,
serializedToken => serializedToken.address,
new Token( serializedToken.decimals,
serializedToken.chainId, serializedToken.symbol,
serializedToken.address, serializedToken.name
serializedToken.decimals,
serializedToken.symbol,
serializedToken.name
)
) )
} }
...@@ -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>
......
...@@ -2,9 +2,9 @@ import React, { useReducer, useState, useCallback, useEffect, useContext } from ...@@ -2,9 +2,9 @@ import React, { useReducer, useState, useCallback, useEffect, useContext } from
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { RouteComponentProps, withRouter } from 'react-router-dom' import { RouteComponentProps, withRouter } from 'react-router-dom'
import { parseUnits, parseEther } from '@ethersproject/units' import { parseUnits, parseEther } from '@ethersproject/units'
import { MaxUint256, Zero } from '@ethersproject/constants' import { MaxUint256 } from '@ethersproject/constants'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { WETH, TokenAmount, JSBI, Percent, Route, Token, Pair, Price } from '@uniswap/sdk' import { WETH, TokenAmount, JSBI, Percent, Route, Token, Price } from '@uniswap/sdk'
import TokenLogo from '../../components/TokenLogo' import TokenLogo from '../../components/TokenLogo'
import DoubleLogo from '../../components/DoubleLogo' import DoubleLogo from '../../components/DoubleLogo'
...@@ -22,14 +22,15 @@ import Row, { AutoRow, RowBetween, RowFlat, RowFixed } from '../../components/Ro ...@@ -22,14 +22,15 @@ import Row, { AutoRow, RowBetween, RowFlat, RowFixed } from '../../components/Ro
import { useToken } from '../../contexts/Tokens' import { useToken } from '../../contexts/Tokens'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
import { useAddressAllowance } from '../../contexts/Allowances' import { useTokenAllowance } from '../../data/Allowances'
import { usePair, useTotalSupply } from '../../contexts/Pairs' import { useTotalSupply } from '../../data/TotalSupply'
import { useWeb3React, useTokenContract } from '../../hooks' import { useWeb3React, useTokenContract } from '../../hooks'
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions' import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
import { ROUTER_ADDRESS } from '../../constants' import { ROUTER_ADDRESS } from '../../constants'
import { getRouterContract, calculateGasMargin, isWETH } from '../../utils' import { getRouterContract, calculateGasMargin, calculateSlippageAmount } from '../../utils'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { usePair } from '../../data/Reserves'
import { useLocalStorageTokens } from '../../contexts/LocalStorage' import { useLocalStorageTokens } from '../../contexts/LocalStorage'
import { useAllTokens } from '../../contexts/Tokens' import { useAllTokens } from '../../contexts/Tokens'
...@@ -213,22 +214,12 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -213,22 +214,12 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
const tokenContractInput: Contract = useTokenContract(tokens[Field.INPUT]?.address) const tokenContractInput: Contract = useTokenContract(tokens[Field.INPUT]?.address)
const tokenContractOutput: Contract = useTokenContract(tokens[Field.OUTPUT]?.address) const tokenContractOutput: Contract = useTokenContract(tokens[Field.OUTPUT]?.address)
// check on pending approvals for token amounts // exchange data
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address) const pair = usePair(tokens[Field.INPUT], tokens[Field.OUTPUT])
const pendingApprovalOutput = usePendingApproval(tokens[Field.OUTPUT]?.address)
// exhchange data
const pair: Pair = usePair(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route: Route = pair ? new Route([pair], tokens[independentField]) : undefined const route: Route = pair ? new Route([pair], tokens[independentField]) : undefined
const totalSupply: TokenAmount = useTotalSupply(tokens[Field.INPUT], tokens[Field.OUTPUT]) const totalSupply: TokenAmount = useTotalSupply(pair?.liquidityToken)
const noLiquidity = // used to detect new exchange const noLiquidity = // used to detect new exchange
pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0)) !!pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0))
// state for amount approvals
const inputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.INPUT], ROUTER_ADDRESS)
const outputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.OUTPUT], ROUTER_ADDRESS)
const [showInputApprove, setShowInputApprove] = useState<boolean>(false)
const [showOutputApprove, setShowOutputApprove] = useState<boolean>(false)
// get user-pecific and token-specific lookup data // get user-pecific and token-specific lookup data
const userBalances: { [field: number]: TokenAmount } = { const userBalances: { [field: number]: TokenAmount } = {
...@@ -295,11 +286,27 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -295,11 +286,27 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
// get formatted amounts // get formatted amounts
const formattedAmounts = { const formattedAmounts = {
[independentField]: typedValue, [independentField]: typedValue,
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField]?.toSignificant(8) : '' [dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField]?.toSignificant(6) : ''
} }
// used for displaying approximate starting price in UI // check whether the user has approved the router on both tokens
const inputApproval: TokenAmount = useTokenAllowance(tokens[Field.INPUT], account, ROUTER_ADDRESS)
const outputApproval: TokenAmount = useTokenAllowance(tokens[Field.OUTPUT], account, ROUTER_ADDRESS)
const inputApproved =
tokens[Field.INPUT]?.equals(WETH[chainId]) ||
(!!inputApproval &&
!!parsedAmounts[Field.INPUT] &&
JSBI.greaterThanOrEqual(inputApproval.raw, parsedAmounts[Field.INPUT].raw))
const outputApproved =
tokens[Field.OUTPUT]?.equals(WETH[chainId]) ||
(!!outputApproval &&
!!parsedAmounts[Field.OUTPUT] &&
JSBI.greaterThanOrEqual(outputApproval.raw, parsedAmounts[Field.OUTPUT].raw))
// check on pending approvals for token amounts
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address)
const pendingApprovalOutput = usePendingApproval(tokens[Field.OUTPUT]?.address)
// used for displaying approximate starting price in UI
const derivedPrice = const derivedPrice =
parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT] &&
parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT] &&
...@@ -362,8 +369,11 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -362,8 +369,11 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
const [maxAmountInput, maxAmountOutput]: TokenAmount[] = [Field.INPUT, Field.OUTPUT].map(index => { const [maxAmountInput, maxAmountOutput]: TokenAmount[] = [Field.INPUT, Field.OUTPUT].map(index => {
const field = Field[index] const field = Field[index]
return !!userBalances[Field[field]] && return !!userBalances[Field[field]] &&
JSBI.greaterThan(userBalances[Field[field]].raw, isWETH(tokens[Field[field]]) ? MIN_ETHER.raw : JSBI.BigInt(0)) JSBI.greaterThan(
? isWETH(tokens[Field[field]]) userBalances[Field[field]].raw,
tokens[Field[field]]?.equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
)
? tokens[Field[field]]?.equals(WETH[chainId])
? userBalances[Field[field]].subtract(MIN_ETHER) ? userBalances[Field[field]].subtract(MIN_ETHER)
: userBalances[Field[field]] : userBalances[Field[field]]
: undefined : undefined
...@@ -377,18 +387,6 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -377,18 +387,6 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
: undefined : undefined
}) })
// monitor parsed amounts and update approve buttons
useEffect(() => {
setShowInputApprove(
parsedAmounts[Field.INPUT] && inputApproval && JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
)
setShowOutputApprove(
parsedAmounts[Field.OUTPUT] &&
outputApproval &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw)
)
}, [inputApproval, outputApproval, parsedAmounts])
// errors // errors
const [generalError, setGeneralError] = useState('') const [generalError, setGeneralError] = useState('')
const [inputError, setInputError] = useState('') const [inputError, setInputError] = useState('')
...@@ -402,6 +400,11 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -402,6 +400,11 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
setOutputError(null) setOutputError(null)
setIsValid(true) setIsValid(true)
if (!account) {
setGeneralError('Connect Wallet')
setIsValid(false)
}
if (noLiquidity && parsedAmounts[Field.INPUT] && JSBI.equal(parsedAmounts[Field.INPUT].raw, JSBI.BigInt(0))) { if (noLiquidity && parsedAmounts[Field.INPUT] && JSBI.equal(parsedAmounts[Field.INPUT].raw, JSBI.BigInt(0))) {
setGeneralError('Enter an amount') setGeneralError('Enter an amount')
setIsValid(false) setIsValid(false)
...@@ -436,57 +439,39 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -436,57 +439,39 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
setOutputError('Insufficient ' + tokens[Field.OUTPUT]?.symbol + ' balance') setOutputError('Insufficient ' + tokens[Field.OUTPUT]?.symbol + ' balance')
setIsValid(false) setIsValid(false)
} }
}, [noLiquidity, parsedAmounts, showInputApprove, showOutputApprove, tokens, userBalances]) }, [noLiquidity, parsedAmounts, tokens, userBalances, account])
// state for txn // state for txn
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
// format ETH value for transaction
function hex(value: JSBI) {
return BigNumber.from(value.toString())
}
// calculate slippage bounds based on current reserves
function calculateSlippageAmount(value: TokenAmount): JSBI[] {
if (value && value.raw) {
const offset = JSBI.divide(JSBI.multiply(JSBI.BigInt(ALLOWED_SLIPPAGE), value.raw), JSBI.BigInt(10000))
return [JSBI.subtract(value.raw, offset), JSBI.add(value.raw, offset)]
} else {
return null
}
}
async function onAdd() { async function onAdd() {
setAttemptingTxn(true) setAttemptingTxn(true)
const router = getRouterContract(chainId, library, account) const router = getRouterContract(chainId, library, account)
const minTokenInput = calculateSlippageAmount(parsedAmounts[Field.INPUT])[0] const minInput = calculateSlippageAmount(parsedAmounts[Field.INPUT], ALLOWED_SLIPPAGE)[0]
const minTokenOutput = calculateSlippageAmount(parsedAmounts[Field.OUTPUT])[0] const minOutput = calculateSlippageAmount(parsedAmounts[Field.OUTPUT], ALLOWED_SLIPPAGE)[0]
const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
let method, estimate, args, value let method, estimate, args, value
if (tokens[Field.INPUT] === WETH[chainId] || tokens[Field.OUTPUT] === WETH[chainId]) { // one of the tokens is ETH
if (tokens[Field.INPUT].equals(WETH[chainId]) || tokens[Field.OUTPUT].equals(WETH[chainId])) {
method = router.addLiquidityETH method = router.addLiquidityETH
estimate = router.estimateGas.addLiquidityETH estimate = router.estimateGas.addLiquidityETH
const outputIsETH = tokens[Field.OUTPUT].equals(WETH[chainId])
args = [ args = [
tokens[Field.OUTPUT] === WETH[chainId] ? tokens[Field.INPUT].address : tokens[Field.OUTPUT].address, // token tokens[outputIsETH ? Field.INPUT : Field.OUTPUT].address, // token
tokens[Field.OUTPUT] === WETH[chainId] // token desired parsedAmounts[outputIsETH ? Field.INPUT : Field.OUTPUT].raw.toString(), // token desired
? parsedAmounts[Field.INPUT].raw.toString() outputIsETH ? minInput.toString() : minOutput.toString(), // token min
: parsedAmounts[Field.OUTPUT].raw.toString(), outputIsETH ? minOutput.toString() : minInput.toString(), // eth min
tokens[Field.OUTPUT] === WETH[chainId] ? minTokenInput.toString() : minTokenOutput.toString(), // token min
tokens[Field.OUTPUT] === WETH[chainId] ? minTokenOutput.toString() : minTokenInput.toString(), // eth min
account, account,
deadline deadline
] ]
value = hex( value = BigNumber.from(parsedAmounts[outputIsETH ? Field.OUTPUT : Field.INPUT].raw.toString())
tokens[Field.OUTPUT] === WETH[chainId] // eth desired
? parsedAmounts[Field.OUTPUT].raw
: parsedAmounts[Field.INPUT].raw
)
} else { } else {
method = router.addLiquidity method = router.addLiquidity
estimate = router.estimateGas.addLiquidity estimate = router.estimateGas.addLiquidity
...@@ -495,21 +480,19 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -495,21 +480,19 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
tokens[Field.OUTPUT].address, tokens[Field.OUTPUT].address,
parsedAmounts[Field.INPUT].raw.toString(), parsedAmounts[Field.INPUT].raw.toString(),
parsedAmounts[Field.OUTPUT].raw.toString(), parsedAmounts[Field.OUTPUT].raw.toString(),
noLiquidity ? parsedAmounts[Field.INPUT].raw.toString() : minTokenInput.toString(), noLiquidity ? parsedAmounts[Field.INPUT].raw.toString() : minInput.toString(),
noLiquidity ? parsedAmounts[Field.OUTPUT].raw.toString() : minTokenOutput.toString(), noLiquidity ? parsedAmounts[Field.OUTPUT].raw.toString() : minOutput.toString(),
account, account,
deadline deadline
] ]
value = Zero value = null
} }
await estimate(...args, { await estimate(...args, value ? { value } : {})
value: value
})
.then(estimatedGasLimit => .then(estimatedGasLimit =>
method(...args, { method(...args, {
gasLimit: calculateGasMargin(estimatedGasLimit), ...(value ? { value } : {}),
value: value gasLimit: calculateGasMargin(estimatedGasLimit)
}).then(response => { }).then(response => {
setTxHash(response.hash) setTxHash(response.hash)
addTransaction( addTransaction(
...@@ -527,7 +510,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -527,7 +510,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
}) })
) )
.catch((e: Error) => { .catch((e: Error) => {
console.log(e) console.error(e)
setPendingConfirmation(true) setPendingConfirmation(true)
setAttemptingTxn(false) setAttemptingTxn(false)
setShowConfirm(false) setShowConfirm(false)
...@@ -592,7 +575,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -592,7 +575,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
<AutoColumn gap="20px"> <AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}> <RowFlat style={{ marginTop: '20px' }}>
<Text fontSize="48px" fontWeight={500} lineHeight="32px" marginRight={10}> <Text fontSize="48px" fontWeight={500} lineHeight="32px" marginRight={10}>
{liquidityMinted?.toFixed(6)} {liquidityMinted?.toSignificant(6)}
</Text> </Text>
<DoubleLogo a0={tokens[Field.INPUT]?.symbol || ''} a1={tokens[Field.OUTPUT]?.symbol || ''} size={30} /> <DoubleLogo a0={tokens[Field.INPUT]?.symbol || ''} a1={tokens[Field.OUTPUT]?.symbol || ''} size={30} />
</RowFlat> </RowFlat>
...@@ -616,14 +599,14 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -616,14 +599,14 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
<RowBetween> <RowBetween>
<TYPE.body>{tokens[Field.INPUT]?.symbol} Deposited</TYPE.body> <TYPE.body>{tokens[Field.INPUT]?.symbol} Deposited</TYPE.body>
<RowFixed> <RowFixed>
<TokenLogo address={tokens[Field.INPUT]?.address || ''} style={{ marginRight: '8px' }} /> <TokenLogo address={tokens[Field.INPUT]?.address} style={{ marginRight: '8px' }} />
<TYPE.body>{!!parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT].toSignificant(6)}</TYPE.body> <TYPE.body>{!!parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT].toSignificant(6)}</TYPE.body>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
<RowBetween> <RowBetween>
<TYPE.body>{tokens[Field.OUTPUT]?.symbol} Deposited</TYPE.body> <TYPE.body>{tokens[Field.OUTPUT]?.symbol} Deposited</TYPE.body>
<RowFixed> <RowFixed>
<TokenLogo address={tokens[Field.OUTPUT]?.address || ''} style={{ marginRight: '8px' }} /> <TokenLogo address={tokens[Field.OUTPUT]?.address} style={{ marginRight: '8px' }} />
<TYPE.body>{!!parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT].toSignificant(6)}</TYPE.body> <TYPE.body>{!!parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT].toSignificant(6)}</TYPE.body>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
...@@ -633,13 +616,13 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -633,13 +616,13 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
<TYPE.body> <TYPE.body>
{`1 ${tokens[Field.INPUT]?.symbol} = ${route?.midPrice && {`1 ${tokens[Field.INPUT]?.symbol} = ${route?.midPrice &&
route?.midPrice?.raw?.denominator && route?.midPrice?.raw?.denominator &&
route?.midPrice?.adjusted?.toFixed(8)} ${tokens[Field.OUTPUT]?.symbol}`} route?.midPrice?.adjusted?.toSignificant(4)} ${tokens[Field.OUTPUT]?.symbol}`}
</TYPE.body> </TYPE.body>
</RowBetween> </RowBetween>
)} )}
<RowBetween> <RowBetween>
<TYPE.body>Minted Pool Share:</TYPE.body> <TYPE.body>Minted Pool Share:</TYPE.body>
<TYPE.body>{noLiquidity ? '100%' : poolTokenPercentage?.toFixed(6) + '%'}</TYPE.body> <TYPE.body>{noLiquidity ? '100%' : poolTokenPercentage?.toSignificant(6) + '%'}</TYPE.body>
</RowBetween> </RowBetween>
<ButtonPrimary style={{ margin: '20px 0' }} onClick={onAdd}> <ButtonPrimary style={{ margin: '20px 0' }} onClick={onAdd}>
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
...@@ -696,7 +679,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -696,7 +679,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
</AutoColumn> </AutoColumn>
<AutoColumn justify="center"> <AutoColumn justify="center">
<TYPE.black> <TYPE.black>
{noLiquidity && derivedPrice ? '100' : poolTokenPercentage ? poolTokenPercentage?.toFixed(2) : '0.0'} {noLiquidity && derivedPrice ? '100' : poolTokenPercentage?.toSignificant(4) ?? '0'}
{'%'} {'%'}
</TYPE.black> </TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}> <Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
...@@ -791,37 +774,46 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -791,37 +774,46 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
</GreyCard> </GreyCard>
</> </>
)} )}
{showOutputApprove ? ( {isValid ? (
<ButtonLight !inputApproved ? (
onClick={() => { <ButtonLight
!pendingApprovalOutput && approveAmount(Field.OUTPUT) onClick={() => {
}} approveAmount(Field.INPUT)
> }}
{pendingApprovalOutput ? ( disabled={pendingApprovalInput}
<Dots>Approving {tokens[Field.OUTPUT]?.symbol}</Dots> >
) : ( {pendingApprovalInput ? (
'Approve ' + tokens[Field.OUTPUT]?.symbol <Dots>Approving {tokens[Field.INPUT]?.symbol}</Dots>
)} ) : (
</ButtonLight> 'Approve ' + tokens[Field.INPUT]?.symbol
) : showInputApprove ? ( )}
<ButtonLight </ButtonLight>
onClick={() => { ) : !outputApproved ? (
!pendingApprovalInput && approveAmount(Field.INPUT) <ButtonLight
}} onClick={() => {
> approveAmount(Field.OUTPUT)
{pendingApprovalInput ? ( }}
<Dots>Approving {tokens[Field.INPUT]?.symbol}</Dots> disabled={pendingApprovalOutput}
) : ( >
'Approve ' + tokens[Field.INPUT]?.symbol {pendingApprovalOutput ? (
)} <Dots>Approving {tokens[Field.OUTPUT]?.symbol}</Dots>
</ButtonLight> ) : (
'Approve ' + tokens[Field.OUTPUT]?.symbol
)}
</ButtonLight>
) : (
<ButtonPrimary
onClick={() => {
setShowConfirm(true)
}}
>
<Text fontSize={20} fontWeight={500}>
Supply
</Text>
</ButtonPrimary>
)
) : ( ) : (
<ButtonPrimary <ButtonPrimary disabled={true}>
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid}
>
<Text fontSize={20} fontWeight={500}> <Text fontSize={20} fontWeight={500}>
{generalError ? generalError : inputError ? inputError : outputError ? outputError : 'Supply'} {generalError ? generalError : inputError ? inputError : outputError ? outputError : 'Supply'}
</Text> </Text>
...@@ -832,12 +824,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) { ...@@ -832,12 +824,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
{!noLiquidity && ( {!noLiquidity && (
<FixedBottom> <FixedBottom>
<AutoColumn> <AutoColumn>
<PositionCard <PositionCard pair={pair} minimal={true} />
pairAddress={pair?.liquidityToken?.address}
token0={tokens[Field.INPUT]}
token1={tokens[Field.OUTPUT]}
minimal={true}
/>
</AutoColumn> </AutoColumn>
</FixedBottom> </FixedBottom>
)} )}
......
import React, { useReducer, useState, useCallback, useEffect, useContext } from 'react' import React, { useReducer, useState, useCallback, useEffect, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { parseUnits } from '@ethersproject/units' import { parseUnits } from '@ethersproject/units'
import { Zero } from '@ethersproject/constants'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { TokenAmount, JSBI, Route, WETH, Percent, Token, Pair } from '@uniswap/sdk' import { TokenAmount, JSBI, Route, WETH, Percent, Token } from '@uniswap/sdk'
import Slider from '../../components/Slider' import Slider from '../../components/Slider'
import TokenLogo from '../../components/TokenLogo' import TokenLogo from '../../components/TokenLogo'
...@@ -25,11 +24,15 @@ import { useWeb3React } from '../../hooks' ...@@ -25,11 +24,15 @@ import { useWeb3React } from '../../hooks'
import { useAllBalances } from '../../contexts/Balances' import { useAllBalances } from '../../contexts/Balances'
import { usePairContract } from '../../hooks' import { usePairContract } from '../../hooks'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { usePair, useTotalSupply } from '../../contexts/Pairs' import { useTotalSupply } from '../../data/TotalSupply'
import { splitSignature } from '@ethersproject/bytes' import { splitSignature } from '@ethersproject/bytes'
import { ROUTER_ADDRESS } from '../../constants' import { ROUTER_ADDRESS } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils' import { getRouterContract, calculateGasMargin, calculateSlippageAmount } from '../../utils'
import { usePair } from '../../data/Reserves'
// denominated in bips
const ALLOWED_SLIPPAGE = 50
// denominated in seconds // denominated in seconds
const DEADLINE_FROM_NOW = 60 * 20 const DEADLINE_FROM_NOW = 60 * 20
...@@ -165,14 +168,14 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -165,14 +168,14 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
[Field.TOKEN1]: outputToken [Field.TOKEN1]: outputToken
} }
const pair: Pair = usePair(inputToken, outputToken) const pair = usePair(inputToken, outputToken)
const pairContract: Contract = usePairContract(pair?.liquidityToken.address) const pairContract: Contract = usePairContract(pair?.liquidityToken.address)
// pool token data // pool token data
const totalPoolTokens: TokenAmount = useTotalSupply(tokens[Field.TOKEN0], tokens[Field.TOKEN1]) const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
const allBalances: TokenAmount[] = useAllBalances() const allBalances = useAllBalances()
const userLiquidity: TokenAmount = allBalances?.[account]?.[pair?.liquidityToken?.address] const userLiquidity = allBalances?.[account]?.[pair?.liquidityToken?.address]
// input state // input state
const [state, dispatch] = useReducer(reducer, initializeRemoveState(userLiquidity?.toExact(), token0, token1)) const [state, dispatch] = useReducer(reducer, initializeRemoveState(userLiquidity?.toExact(), token0, token1))
...@@ -266,10 +269,10 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -266,10 +269,10 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
pair.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, parsedAmounts[Field.LIQUIDITY], false) pair.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, parsedAmounts[Field.LIQUIDITY], false)
// derived percent for advanced mode // derived percent for advanced mode
const derivedPerecent = const derivedPercent =
userLiquidity &&
parsedAmounts[Field.LIQUIDITY] && parsedAmounts[Field.LIQUIDITY] &&
new Percent(parsedAmounts[Field.LIQUIDITY]?.raw, userLiquidity.raw).toFixed(0) userLiquidity &&
new Percent(parsedAmounts[Field.LIQUIDITY]?.raw, userLiquidity.raw)
const [override, setSliderOverride] = useState(false) // override slider internal value const [override, setSliderOverride] = useState(false) // override slider internal value
const handlePresetPercentage = newPercent => { const handlePresetPercentage = newPercent => {
...@@ -299,19 +302,19 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -299,19 +302,19 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
independentField === Field.LIQUIDITY independentField === Field.LIQUIDITY
? typedValue ? typedValue
: parsedAmounts[Field.LIQUIDITY] : parsedAmounts[Field.LIQUIDITY]
? parsedAmounts[Field.LIQUIDITY].toSignificant(8) ? parsedAmounts[Field.LIQUIDITY].toSignificant(6)
: '', : '',
[Field.TOKEN0]: [Field.TOKEN0]:
independentField === Field.TOKEN0 independentField === Field.TOKEN0
? typedValue ? typedValue
: parsedAmounts[Field.TOKEN0] : parsedAmounts[Field.TOKEN0]
? parsedAmounts[Field.TOKEN0].toSignificant(8) ? parsedAmounts[Field.TOKEN0].toSignificant(6)
: '', : '',
[Field.TOKEN1]: [Field.TOKEN1]:
independentField === Field.TOKEN1 independentField === Field.TOKEN1
? typedValue ? typedValue
: parsedAmounts[Field.TOKEN1] : parsedAmounts[Field.TOKEN1]
? parsedAmounts[Field.TOKEN1].toSignificant(8) ? parsedAmounts[Field.TOKEN1].toSignificant(6)
: '' : ''
} }
...@@ -439,18 +442,23 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -439,18 +442,23 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
let method, args, estimate let method, args, estimate
// removal with ETH // removal with ETH
if (tokens[Field.TOKEN0] === WETH[chainId] || tokens[Field.TOKEN1] === WETH[chainId]) { if (tokens[Field.TOKEN0].equals(WETH[chainId]) || tokens[Field.TOKEN1].equals(WETH[chainId])) {
method = router.removeLiquidityETHWithPermit method = router.removeLiquidityETHWithPermit
estimate = router.estimateGas.removeLiquidityETHWithPermit estimate = router.estimateGas.removeLiquidityETHWithPermit
const token0IsETH = tokens[Field.TOKEN0].equals(WETH[chainId])
args = [ args = [
tokens[Field.TOKEN1] === WETH[chainId] ? tokens[Field.TOKEN0].address : tokens[Field.TOKEN1].address, tokens[token0IsETH ? Field.TOKEN1 : Field.TOKEN0].address,
parsedAmounts[Field.LIQUIDITY].raw.toString(), parsedAmounts[Field.LIQUIDITY].raw.toString(),
tokens[Field.TOKEN1] === WETH[chainId] calculateSlippageAmount(
? parsedAmounts[Field.TOKEN0].raw.toString() parsedAmounts[token0IsETH ? Field.TOKEN1 : Field.TOKEN0],
: parsedAmounts[Field.TOKEN1].raw.toString(), ALLOWED_SLIPPAGE
tokens[Field.TOKEN1] === WETH[chainId] )[0].toString(),
? parsedAmounts[Field.TOKEN1].raw.toString() calculateSlippageAmount(
: parsedAmounts[Field.TOKEN0].raw.toString(), parsedAmounts[token0IsETH ? Field.TOKEN0 : Field.TOKEN1],
ALLOWED_SLIPPAGE
)[0].toString(),
account, account,
deadline, deadline,
false, false,
...@@ -467,8 +475,8 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -467,8 +475,8 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
tokens[Field.TOKEN0].address, tokens[Field.TOKEN0].address,
tokens[Field.TOKEN1].address, tokens[Field.TOKEN1].address,
parsedAmounts[Field.LIQUIDITY].raw.toString(), parsedAmounts[Field.LIQUIDITY].raw.toString(),
parsedAmounts[Field.TOKEN0].raw.toString(), calculateSlippageAmount(parsedAmounts[Field.TOKEN0], ALLOWED_SLIPPAGE)[0].toString(),
parsedAmounts[Field.TOKEN1].raw.toString(), calculateSlippageAmount(parsedAmounts[Field.TOKEN1], ALLOWED_SLIPPAGE)[0].toString(),
account, account,
deadline, deadline,
false, false,
...@@ -478,9 +486,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -478,9 +486,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
] ]
} }
await estimate(...args, { await estimate(...args)
value: Zero
})
.then(estimatedGasLimit => .then(estimatedGasLimit =>
method(...args, { method(...args, {
gasLimit: calculateGasMargin(estimatedGasLimit) gasLimit: calculateGasMargin(estimatedGasLimit)
...@@ -501,7 +507,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -501,7 +507,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
}) })
) )
.catch(e => { .catch(e => {
console.log(e) console.error(e)
resetModalState() resetModalState()
setShowConfirm(false) setShowConfirm(false)
}) })
...@@ -537,9 +543,9 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -537,9 +543,9 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
</RowBetween> </RowBetween>
<TYPE.italic fontSize={12} color={theme.text2} textAlign="left" padding={'12px 0 0 0'}> <TYPE.italic fontSize={12} color={theme.text2} textAlign="left" padding={'12px 0 0 0'}>
{`Output is estimated. You will receive at least ${parsedAmounts[Field.TOKEN0]?.toFixed(6)} ${ {`Output is estimated. You will receive at least ${parsedAmounts[Field.TOKEN0]?.toSignificant(6)} ${
tokens[Field.TOKEN0]?.symbol tokens[Field.TOKEN0]?.symbol
} and at least ${parsedAmounts[Field.TOKEN1]?.toFixed(6)} ${ } and at least ${parsedAmounts[Field.TOKEN1]?.toSignificant(6)} ${
tokens[Field.TOKEN1]?.symbol tokens[Field.TOKEN1]?.symbol
} or the transaction will revert.`} } or the transaction will revert.`}
</TYPE.italic> </TYPE.italic>
...@@ -570,7 +576,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -570,7 +576,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
Price Price
</Text> </Text>
<Text fontWeight={500} fontSize={16} color={theme.text1}> <Text fontWeight={500} fontSize={16} color={theme.text1}>
{`1 ${tokens[Field.TOKEN0]?.symbol} = ${route?.midPrice && route.midPrice.adjusted.toFixed(8)} ${ {`1 ${tokens[Field.TOKEN0]?.symbol} = ${route?.midPrice && route.midPrice.adjusted.toSignificant(6)} ${
tokens[Field.TOKEN1]?.symbol tokens[Field.TOKEN1]?.symbol
}`} }`}
</Text> </Text>
...@@ -633,11 +639,15 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -633,11 +639,15 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
</RowBetween> </RowBetween>
<Row style={{ alignItems: 'flex-end' }}> <Row style={{ alignItems: 'flex-end' }}>
<Text fontSize={72} fontWeight={500}> <Text fontSize={72} fontWeight={500}>
{derivedPerecent ? (parseInt(derivedPerecent) < 1 ? '<1' : derivedPerecent) : '0'}% {derivedPercent?.toFixed(0) === '0' ? '<1' : derivedPercent?.toFixed(0) ?? '0'}%
</Text> </Text>
</Row> </Row>
{!showAdvanced && ( {!showAdvanced && (
<Slider value={parseFloat(derivedPerecent)} onChange={handleSliderChange} override={override} /> <Slider
value={parseInt(derivedPercent?.toFixed(0) ?? '0')}
onChange={handleSliderChange}
override={override}
/>
)} )}
{!showAdvanced && ( {!showAdvanced && (
<RowBetween> <RowBetween>
...@@ -669,7 +679,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -669,7 +679,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
{formattedAmounts[Field.TOKEN0] ? formattedAmounts[Field.TOKEN0] : '-'} {formattedAmounts[Field.TOKEN0] ? formattedAmounts[Field.TOKEN0] : '-'}
</Text> </Text>
<RowFixed> <RowFixed>
<TokenLogo address={tokens[Field.TOKEN0]?.address || ''} style={{ marginRight: '12px' }} /> <TokenLogo address={tokens[Field.TOKEN0]?.address} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500}> <Text fontSize={24} fontWeight={500}>
{tokens[Field.TOKEN0]?.symbol} {tokens[Field.TOKEN0]?.symbol}
</Text> </Text>
...@@ -680,7 +690,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -680,7 +690,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
{formattedAmounts[Field.TOKEN1] ? formattedAmounts[Field.TOKEN1] : '-'} {formattedAmounts[Field.TOKEN1] ? formattedAmounts[Field.TOKEN1] : '-'}
</Text> </Text>
<RowFixed> <RowFixed>
<TokenLogo address={tokens[Field.TOKEN1]?.address || ''} style={{ marginRight: '12px' }} /> <TokenLogo address={tokens[Field.TOKEN1]?.address} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500}> <Text fontSize={24} fontWeight={500}>
{tokens[Field.TOKEN1]?.symbol} {tokens[Field.TOKEN1]?.symbol}
</Text> </Text>
...@@ -759,12 +769,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to ...@@ -759,12 +769,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
</Text> </Text>
</ButtonPrimary> </ButtonPrimary>
<FixedBottom> <FixedBottom>
<PositionCard <PositionCard pair={pair} minimal={true} />
pairAddress={pair?.liquidityToken.address}
token0={pair?.token0}
token1={pair?.token1}
minimal={true}
/>
</FixedBottom> </FixedBottom>
</div> </div>
</AutoColumn> </AutoColumn>
......
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