update swr hooks when block number changes (#771)
* key per-block SWR data with block number * fix + tweak copy and decrease sig figs for lp fee * don't use blocknumber as a key instead mutate when it changes * fix totalsupply + user liquidity sync issues * fix v1 trade checker logic * rough fix allowing removal of user added tokens probably needs a more comprehensive overhaul... * refactor SWR block updates to custom hook * typo * fix import error * fix footer css to work cross-broswer * disallow \ to be typing into amount inputs add test case for this add value assertions to all input integration tests * fix import error * remove console.log * clean up address input a bit trim whitespace from pasted addresses * fix input maxing remove spurious max output logic
This commit is contained in:
parent
b1ffab1890
commit
7bffea0692
@ -2,6 +2,8 @@ describe('Send', () => {
|
|||||||
beforeEach(() => cy.visit('/send'))
|
beforeEach(() => cy.visit('/send'))
|
||||||
|
|
||||||
it('can enter an amount into input', () => {
|
it('can enter an amount into input', () => {
|
||||||
cy.get('#sending-no-swap-input').type('0.001')
|
cy.get('#sending-no-swap-input')
|
||||||
|
.type('0.001')
|
||||||
|
.should('have.value', '0.001')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,19 +1,33 @@
|
|||||||
describe('Swap', () => {
|
describe('Swap', () => {
|
||||||
beforeEach(() => cy.visit('/swap'))
|
beforeEach(() => cy.visit('/swap'))
|
||||||
it('can enter an amount into input', () => {
|
it('can enter an amount into input', () => {
|
||||||
cy.get('#swap-currency-input .token-amount-input').type('0.001')
|
cy.get('#swap-currency-input .token-amount-input')
|
||||||
|
.type('0.001')
|
||||||
|
.should('have.value', '0.001')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('zero swap amount', () => {
|
it('zero swap amount', () => {
|
||||||
cy.get('#swap-currency-input .token-amount-input').type('0.0')
|
cy.get('#swap-currency-input .token-amount-input')
|
||||||
|
.type('0.0')
|
||||||
|
.should('have.value', '0.0')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('invalid swap amount', () => {
|
||||||
|
cy.get('#swap-currency-input .token-amount-input')
|
||||||
|
.type('\\')
|
||||||
|
.should('have.value', '')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can enter an amount into output', () => {
|
it('can enter an amount into output', () => {
|
||||||
cy.get('#swap-currency-output .token-amount-input').type('0.001')
|
cy.get('#swap-currency-output .token-amount-input')
|
||||||
|
.type('0.001')
|
||||||
|
.should('have.value', '0.001')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('zero output amount', () => {
|
it('zero output amount', () => {
|
||||||
cy.get('#swap-currency-output .token-amount-input').type('0.0')
|
cy.get('#swap-currency-output .token-amount-input')
|
||||||
|
.type('0.0')
|
||||||
|
.should('have.value', '0.0')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can swap ETH for DAI', () => {
|
it('can swap ETH for DAI', () => {
|
||||||
|
@ -76,17 +76,15 @@ export default function AddressInputPanel({
|
|||||||
const theme = useContext(ThemeContext)
|
const theme = useContext(ThemeContext)
|
||||||
|
|
||||||
const [input, setInput] = useState(initialInput ? initialInput : '')
|
const [input, setInput] = useState(initialInput ? initialInput : '')
|
||||||
|
const debouncedInput = useDebounce(input, 200)
|
||||||
|
|
||||||
const debouncedInput = useDebounce(input, 150)
|
const [data, setData] = useState<{ address: string; name: string }>({ address: undefined, name: undefined })
|
||||||
|
|
||||||
const [data, setData] = useState({ address: undefined, name: undefined })
|
|
||||||
const [error, setError] = useState<boolean>(false)
|
const [error, setError] = useState<boolean>(false)
|
||||||
|
|
||||||
// keep data and errors in sync
|
// keep data and errors in sync
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange({ address: data.address, name: data.name })
|
onChange({ address: data.address, name: data.name })
|
||||||
}, [onChange, data.address, data.name])
|
}, [onChange, data.address, data.name])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onError(error, input)
|
onError(error, input)
|
||||||
}, [onError, error, input])
|
}, [onError, error, input])
|
||||||
@ -94,55 +92,45 @@ export default function AddressInputPanel({
|
|||||||
// run parser on debounced input
|
// run parser on debounced input
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let stale = false
|
let stale = false
|
||||||
|
// if the input is an address, try to look up its name
|
||||||
if (isAddress(debouncedInput)) {
|
if (isAddress(debouncedInput)) {
|
||||||
try {
|
library
|
||||||
|
.lookupAddress(debouncedInput)
|
||||||
|
.then(name => {
|
||||||
|
if (stale) return
|
||||||
|
// if an ENS name exists, set it as the destination
|
||||||
|
if (name) {
|
||||||
|
setInput(name)
|
||||||
|
} else {
|
||||||
|
setData({ address: debouncedInput, name: '' })
|
||||||
|
setError(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (stale) return
|
||||||
|
setData({ address: debouncedInput, name: '' })
|
||||||
|
setError(null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// otherwise try to look up the address of the input, treated as an ENS name
|
||||||
|
else {
|
||||||
|
if (debouncedInput !== '') {
|
||||||
library
|
library
|
||||||
.lookupAddress(debouncedInput)
|
.resolveName(debouncedInput)
|
||||||
.then(name => {
|
.then(address => {
|
||||||
if (!stale) {
|
if (stale) return
|
||||||
// if an ENS name exists, set it as the destination
|
// if the debounced input name resolves to an address
|
||||||
if (name) {
|
if (address) {
|
||||||
setInput(name)
|
setData({ address: address, name: debouncedInput })
|
||||||
} else {
|
setError(null)
|
||||||
setData({ address: debouncedInput, name: '' })
|
} else {
|
||||||
setError(null)
|
setError(true)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!stale) {
|
if (stale) return
|
||||||
setData({ address: debouncedInput, name: '' })
|
setError(true)
|
||||||
setError(null)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
} catch {
|
|
||||||
setData({ address: debouncedInput, name: '' })
|
|
||||||
setError(null)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (debouncedInput !== '') {
|
|
||||||
try {
|
|
||||||
library
|
|
||||||
.resolveName(debouncedInput)
|
|
||||||
.then(address => {
|
|
||||||
if (!stale) {
|
|
||||||
// if the debounced input name resolves to an address
|
|
||||||
if (address) {
|
|
||||||
setData({ address: address, name: debouncedInput })
|
|
||||||
setError(null)
|
|
||||||
} else {
|
|
||||||
setError(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (!stale) {
|
|
||||||
setError(true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
setError(true)
|
|
||||||
}
|
|
||||||
} else if (debouncedInput === '') {
|
} else if (debouncedInput === '') {
|
||||||
setError(true)
|
setError(true)
|
||||||
}
|
}
|
||||||
@ -151,22 +139,13 @@ export default function AddressInputPanel({
|
|||||||
return () => {
|
return () => {
|
||||||
stale = true
|
stale = true
|
||||||
}
|
}
|
||||||
}, [debouncedInput, library, onChange, onError])
|
}, [debouncedInput, library])
|
||||||
|
|
||||||
function onInput(event) {
|
function onInput(event) {
|
||||||
if (event.target.value === '') {
|
setData({ address: undefined, name: undefined })
|
||||||
setData({ address: undefined, name: undefined })
|
setError(false)
|
||||||
}
|
|
||||||
|
|
||||||
if (data.address !== undefined || data.name !== undefined) {
|
|
||||||
setData({ address: undefined, name: undefined })
|
|
||||||
}
|
|
||||||
if (error !== undefined) {
|
|
||||||
setError(true)
|
|
||||||
}
|
|
||||||
const input = event.target.value
|
const input = event.target.value
|
||||||
const checksummedInput = isAddress(input)
|
const checksummedInput = isAddress(input.replace(/\s/g, '')) // delete whitespace
|
||||||
|
|
||||||
setInput(checksummedInput || input)
|
setInput(checksummedInput || input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BigNumber } from '@ethersproject/bignumber'
|
import { BigNumber } from '@ethersproject/bignumber'
|
||||||
import { MaxUint256 } from '@ethersproject/constants'
|
import { MaxUint256 } from '@ethersproject/constants'
|
||||||
import { Contract } from '@ethersproject/contracts'
|
import { Contract } from '@ethersproject/contracts'
|
||||||
import { parseEther, parseUnits } from '@ethersproject/units'
|
import { parseUnits } from '@ethersproject/units'
|
||||||
import { Fraction, JSBI, Percent, TokenAmount, TradeType, WETH } from '@uniswap/sdk'
|
import { Fraction, JSBI, Percent, TokenAmount, TradeType, WETH } from '@uniswap/sdk'
|
||||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||||
import { ArrowDown, ChevronDown, ChevronUp, Repeat } from 'react-feather'
|
import { ArrowDown, ChevronDown, ChevronUp, Repeat } from 'react-feather'
|
||||||
@ -74,6 +74,9 @@ const DEFAULT_DEADLINE_FROM_NOW = 60 * 20
|
|||||||
const ALLOWED_SLIPPAGE_MEDIUM = 100
|
const ALLOWED_SLIPPAGE_MEDIUM = 100
|
||||||
const ALLOWED_SLIPPAGE_HIGH = 500
|
const ALLOWED_SLIPPAGE_HIGH = 500
|
||||||
|
|
||||||
|
// used to ensure the user doesn't send so much ETH so they end up with <.01
|
||||||
|
const MIN_ETH: JSBI = JSBI.BigInt(`1${'0'.repeat(16)}`) // .01 ETH
|
||||||
|
|
||||||
interface ExchangePageProps extends RouteComponentProps {
|
interface ExchangePageProps extends RouteComponentProps {
|
||||||
sendingInput: boolean
|
sendingInput: boolean
|
||||||
params: QueryParams
|
params: QueryParams
|
||||||
@ -130,8 +133,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
|
|
||||||
// get user- and token-specific lookup data
|
// get user- and token-specific lookup data
|
||||||
const userBalances = {
|
const userBalances = {
|
||||||
[Field.INPUT]: allBalances?.[tokens[Field.INPUT]?.address]?.raw,
|
[Field.INPUT]: allBalances?.[account]?.[tokens[Field.INPUT]?.address],
|
||||||
[Field.OUTPUT]: allBalances?.[tokens[Field.OUTPUT]?.address]?.raw
|
[Field.OUTPUT]: allBalances?.[account]?.[tokens[Field.OUTPUT]?.address]
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the amount that the user typed
|
// parse the amount that the user typed
|
||||||
@ -251,19 +254,6 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onMaxOutput = useCallback(
|
|
||||||
(typedValue: string) => {
|
|
||||||
dispatch({
|
|
||||||
type: SwapAction.TYPE,
|
|
||||||
payload: {
|
|
||||||
field: Field.OUTPUT,
|
|
||||||
typedValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[dispatch]
|
|
||||||
)
|
|
||||||
|
|
||||||
// reset field if sending with with swap is cancled
|
// reset field if sending with with swap is cancled
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sending && !sendingWithSwap) {
|
if (sending && !sendingWithSwap) {
|
||||||
@ -271,39 +261,19 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
}
|
}
|
||||||
}, [onTokenSelection, sending, sendingWithSwap])
|
}, [onTokenSelection, sending, sendingWithSwap])
|
||||||
|
|
||||||
const MIN_ETHER: TokenAmount = chainId && new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
|
const maxAmountInput: TokenAmount =
|
||||||
|
!!userBalances[Field.INPUT] &&
|
||||||
let maxAmountInput: TokenAmount
|
!!tokens[Field.INPUT] &&
|
||||||
|
!!WETH[chainId] &&
|
||||||
try {
|
userBalances[Field.INPUT].greaterThan(
|
||||||
maxAmountInput =
|
new TokenAmount(tokens[Field.INPUT], tokens[Field.INPUT].equals(WETH[chainId]) ? MIN_ETH : '0')
|
||||||
!!userBalances[Field.INPUT] &&
|
)
|
||||||
!!tokens[Field.INPUT] &&
|
? tokens[Field.INPUT].equals(WETH[chainId])
|
||||||
WETH[chainId] &&
|
? userBalances[Field.INPUT].subtract(new TokenAmount(WETH[chainId], MIN_ETH))
|
||||||
JSBI.greaterThan(
|
: userBalances[Field.INPUT]
|
||||||
userBalances[Field.INPUT].raw,
|
: undefined
|
||||||
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]
|
|
||||||
: undefined
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
const atMaxAmountInput: boolean =
|
const atMaxAmountInput: boolean =
|
||||||
!!maxAmountInput && !!parsedAmounts[Field.INPUT]
|
!!maxAmountInput && !!parsedAmounts[Field.INPUT] ? maxAmountInput.equalTo(parsedAmounts[Field.INPUT]) : undefined
|
||||||
? JSBI.equal(maxAmountInput.raw, parsedAmounts[Field.INPUT].raw)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const maxAmountOutput: TokenAmount =
|
|
||||||
!!userBalances[Field.OUTPUT] && JSBI.greaterThan(userBalances[Field.OUTPUT].raw, JSBI.BigInt(0))
|
|
||||||
? userBalances[Field.OUTPUT]
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const atMaxAmountOutput: boolean =
|
|
||||||
!!maxAmountOutput && !!parsedAmounts[Field.OUTPUT]
|
|
||||||
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
function getSwapType(): SwapType {
|
function getSwapType(): SwapType {
|
||||||
if (tradeType === TradeType.EXACT_INPUT) {
|
if (tradeType === TradeType.EXACT_INPUT) {
|
||||||
@ -528,11 +498,11 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
const estimatedGas = await tokenContract.estimateGas.approve(ROUTER_ADDRESS, MaxUint256).catch(() => {
|
const estimatedGas = await tokenContract.estimateGas.approve(ROUTER_ADDRESS, MaxUint256).catch(() => {
|
||||||
// general fallback for tokens who restrict approval amounts
|
// general fallback for tokens who restrict approval amounts
|
||||||
useUserBalance = true
|
useUserBalance = true
|
||||||
return tokenContract.estimateGas.approve(ROUTER_ADDRESS, userBalances[field])
|
return tokenContract.estimateGas.approve(ROUTER_ADDRESS, userBalances[field].raw.toString())
|
||||||
})
|
})
|
||||||
|
|
||||||
tokenContract
|
tokenContract
|
||||||
.approve(ROUTER_ADDRESS, useUserBalance ? userBalances[field] : MaxUint256, {
|
.approve(ROUTER_ADDRESS, useUserBalance ? userBalances[field].raw.toString() : MaxUint256, {
|
||||||
gasLimit: calculateGasMargin(estimatedGas)
|
gasLimit: calculateGasMargin(estimatedGas)
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@ -585,7 +555,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
if (
|
if (
|
||||||
userBalances[Field.INPUT] &&
|
userBalances[Field.INPUT] &&
|
||||||
parsedAmounts[Field.INPUT] &&
|
parsedAmounts[Field.INPUT] &&
|
||||||
JSBI.lessThan(userBalances[Field.INPUT].raw, parsedAmounts[Field.INPUT]?.raw)
|
userBalances[Field.INPUT].lessThan(parsedAmounts[Field.INPUT])
|
||||||
) {
|
) {
|
||||||
setInputError('Insufficient ' + tokens[Field.INPUT]?.symbol + ' balance')
|
setInputError('Insufficient ' + tokens[Field.INPUT]?.symbol + ' balance')
|
||||||
setIsValid(false)
|
setIsValid(false)
|
||||||
@ -905,9 +875,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
|
]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
|
||||||
|
|
||||||
function _onTokenSelect(address: string) {
|
function _onTokenSelect(address: string) {
|
||||||
const balance = allBalances?.[account]?.[address]
|
|
||||||
// if no user balance - switch view to a send with swap
|
// if no user balance - switch view to a send with swap
|
||||||
const hasBalance = balance && JSBI.greaterThan(balance.raw, JSBI.BigInt(0))
|
const hasBalance = allBalances?.[account]?.[address]?.greaterThan('0')
|
||||||
if (!hasBalance && sending) {
|
if (!hasBalance && sending) {
|
||||||
onTokenSelection(Field.INPUT, null)
|
onTokenSelection(Field.INPUT, null)
|
||||||
onTokenSelection(Field.OUTPUT, address)
|
onTokenSelection(Field.OUTPUT, address)
|
||||||
@ -1047,11 +1016,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
field={Field.OUTPUT}
|
field={Field.OUTPUT}
|
||||||
value={formattedAmounts[Field.OUTPUT]}
|
value={formattedAmounts[Field.OUTPUT]}
|
||||||
onUserInput={onUserInput}
|
onUserInput={onUserInput}
|
||||||
onMax={() => {
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
maxAmountOutput && onMaxOutput(maxAmountOutput.toExact())
|
onMax={() => {}}
|
||||||
}}
|
atMax={true}
|
||||||
label={independentField === Field.INPUT && parsedAmounts[Field.OUTPUT] ? 'To (estimated)' : 'To'}
|
label={independentField === Field.INPUT && parsedAmounts[Field.OUTPUT] ? 'To (estimated)' : 'To'}
|
||||||
atMax={atMaxAmountOutput}
|
|
||||||
token={tokens[Field.OUTPUT]}
|
token={tokens[Field.OUTPUT]}
|
||||||
onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
|
onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
|
||||||
advanced={advanced}
|
advanced={advanced}
|
||||||
@ -1286,11 +1254,11 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
||||||
Liquidity Provider Fee
|
Liquidity Provider Fee
|
||||||
</TYPE.black>
|
</TYPE.black>
|
||||||
<QuestionHelper text="A portion of each trade (0.03%) goes to liquidity providers to incentivize liquidity on the protocol." />
|
<QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." />
|
||||||
</RowFixed>
|
</RowFixed>
|
||||||
<TYPE.black fontSize={14} color={theme.text1}>
|
<TYPE.black fontSize={14} color={theme.text1}>
|
||||||
{realizedLPFeeAmount
|
{realizedLPFeeAmount
|
||||||
? realizedLPFeeAmount?.toSignificant(6) + ' ' + tokens[Field.INPUT]?.symbol
|
? realizedLPFeeAmount?.toSignificant(4) + ' ' + tokens[Field.INPUT]?.symbol
|
||||||
: '-'}
|
: '-'}
|
||||||
</TYPE.black>
|
</TYPE.black>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
|
@ -9,7 +9,6 @@ const FooterFrame = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
width: 100%;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
@ -24,24 +23,11 @@ export default function Footer() {
|
|||||||
return (
|
return (
|
||||||
<FooterFrame>
|
<FooterFrame>
|
||||||
<form action="https://forms.gle/DaLuqvJsVhVaAM3J9" target="_blank">
|
<form action="https://forms.gle/DaLuqvJsVhVaAM3J9" target="_blank">
|
||||||
<ButtonSecondary
|
<ButtonSecondary p="8px 12px">
|
||||||
style={{
|
|
||||||
padding: ' 8px 12px',
|
|
||||||
marginRight: '8px',
|
|
||||||
width: 'fit-content'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Send size={16} style={{ marginRight: '8px' }} /> Feedback
|
<Send size={16} style={{ marginRight: '8px' }} /> Feedback
|
||||||
</ButtonSecondary>
|
</ButtonSecondary>
|
||||||
</form>
|
</form>
|
||||||
<ButtonSecondary
|
<ButtonSecondary onClick={toggleDarkMode} p="8px 12px" ml="0.5rem" width="min-content">
|
||||||
onClick={toggleDarkMode}
|
|
||||||
style={{
|
|
||||||
padding: ' 8px 12px',
|
|
||||||
marginRight: '0px',
|
|
||||||
width: 'fit-content'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{darkMode ? <Sun size={16} /> : <Moon size={16} />}
|
{darkMode ? <Sun size={16} /> : <Moon size={16} />}
|
||||||
</ButtonSecondary>
|
</ButtonSecondary>
|
||||||
</FooterFrame>
|
</FooterFrame>
|
||||||
|
@ -40,7 +40,7 @@ const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: s
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const inputRegex = RegExp(`^\\d*(?:\\\\.)?\\d*$`) // match escaped "." characters via in a non-capturing group
|
const inputRegex = RegExp(`^\\d*(?:\\\\[.])?\\d*$`) // match escaped "." characters via in a non-capturing group
|
||||||
|
|
||||||
export const Input = React.memo(function InnerInput({
|
export const Input = React.memo(function InnerInput({
|
||||||
value,
|
value,
|
||||||
|
@ -2,7 +2,7 @@ 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 } from '@uniswap/sdk'
|
import { Percent, Pair, JSBI } from '@uniswap/sdk'
|
||||||
|
|
||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { useTotalSupply } from '../../data/TotalSupply'
|
import { useTotalSupply } from '../../data/TotalSupply'
|
||||||
@ -47,23 +47,21 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
|
|||||||
const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
|
const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
|
||||||
|
|
||||||
const poolTokenPercentage =
|
const poolTokenPercentage =
|
||||||
!!userPoolBalance && !!totalPoolTokens ? new Percent(userPoolBalance.raw, totalPoolTokens.raw) : undefined
|
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||||
|
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
|
||||||
|
: undefined
|
||||||
|
|
||||||
const token0Deposited =
|
const [token0Deposited, token1Deposited] =
|
||||||
token0 &&
|
!!pair &&
|
||||||
totalPoolTokens &&
|
!!totalPoolTokens &&
|
||||||
userPoolBalance &&
|
!!userPoolBalance &&
|
||||||
pair &&
|
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||||
totalPoolTokens &&
|
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||||
pair.liquidityToken.equals(totalPoolTokens.token) &&
|
? [
|
||||||
pair.getLiquidityValue(token0, totalPoolTokens, userPoolBalance, false)
|
pair.getLiquidityValue(token0, totalPoolTokens, userPoolBalance, false),
|
||||||
const token1Deposited =
|
pair.getLiquidityValue(token1, totalPoolTokens, userPoolBalance, false)
|
||||||
token1 &&
|
]
|
||||||
totalPoolTokens &&
|
: [undefined, undefined]
|
||||||
userPoolBalance &&
|
|
||||||
totalPoolTokens &&
|
|
||||||
pair.liquidityToken.equals(totalPoolTokens.token) &&
|
|
||||||
pair.getLiquidityValue(token1, totalPoolTokens, userPoolBalance, false)
|
|
||||||
|
|
||||||
if (minimal) {
|
if (minimal) {
|
||||||
return (
|
return (
|
||||||
@ -87,7 +85,7 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
|
|||||||
</RowFixed>
|
</RowFixed>
|
||||||
<RowFixed>
|
<RowFixed>
|
||||||
<Text fontWeight={500} fontSize={20}>
|
<Text fontWeight={500} fontSize={20}>
|
||||||
{userPoolBalance ? userPoolBalance.toSignificant(5) : '-'}
|
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</RowFixed>
|
</RowFixed>
|
||||||
</FixedHeightRow>
|
</FixedHeightRow>
|
||||||
|
@ -28,10 +28,11 @@ import {
|
|||||||
useAllDummyPairs,
|
useAllDummyPairs,
|
||||||
useFetchTokenByAddress,
|
useFetchTokenByAddress,
|
||||||
useAddUserToken,
|
useAddUserToken,
|
||||||
useRemoveUserAddedToken
|
useRemoveUserAddedToken,
|
||||||
|
useUserAddedTokens
|
||||||
} from '../../state/user/hooks'
|
} from '../../state/user/hooks'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useToken, useAllTokens, ALL_TOKENS } from '../../hooks/Tokens'
|
import { useToken, useAllTokens } from '../../hooks/Tokens'
|
||||||
import QuestionHelper from '../Question'
|
import QuestionHelper from '../Question'
|
||||||
|
|
||||||
const TokenModalInfo = styled.div`
|
const TokenModalInfo = styled.div`
|
||||||
@ -183,6 +184,7 @@ function SearchModal({
|
|||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [sortDirection, setSortDirection] = useState(true)
|
const [sortDirection, setSortDirection] = useState(true)
|
||||||
|
|
||||||
|
const userAddedTokens = useUserAddedTokens()
|
||||||
const fetchTokenByAddress = useFetchTokenByAddress()
|
const fetchTokenByAddress = useFetchTokenByAddress()
|
||||||
const addToken = useAddUserToken()
|
const addToken = useAddUserToken()
|
||||||
const removeTokenByAddress = useRemoveUserAddedToken()
|
const removeTokenByAddress = useRemoveUserAddedToken()
|
||||||
@ -259,12 +261,8 @@ function SearchModal({
|
|||||||
|
|
||||||
const filteredTokenList = useMemo(() => {
|
const filteredTokenList = useMemo(() => {
|
||||||
return tokenList.filter(tokenEntry => {
|
return tokenList.filter(tokenEntry => {
|
||||||
const urlAdded = urlAddedTokens && urlAddedTokens.hasOwnProperty(tokenEntry.address)
|
const urlAdded = urlAddedTokens?.some(token => token.address === tokenEntry.address)
|
||||||
const customAdded =
|
const customAdded = userAddedTokens?.some(token => token.address === tokenEntry.address) && !urlAdded
|
||||||
tokenEntry.address !== 'ETH' &&
|
|
||||||
ALL_TOKENS[chainId] &&
|
|
||||||
!ALL_TOKENS[chainId].hasOwnProperty(tokenEntry.address) &&
|
|
||||||
!urlAdded
|
|
||||||
|
|
||||||
// if token import page dont show preset list, else show all
|
// if token import page dont show preset list, else show all
|
||||||
const include = !showTokenImport || (showTokenImport && customAdded && searchQuery !== '')
|
const include = !showTokenImport || (showTokenImport && customAdded && searchQuery !== '')
|
||||||
@ -287,7 +285,7 @@ function SearchModal({
|
|||||||
})
|
})
|
||||||
return regexMatches.some(m => m)
|
return regexMatches.some(m => m)
|
||||||
})
|
})
|
||||||
}, [tokenList, urlAddedTokens, chainId, showTokenImport, searchQuery])
|
}, [tokenList, urlAddedTokens, userAddedTokens, showTokenImport, searchQuery])
|
||||||
|
|
||||||
function _onTokenSelect(address) {
|
function _onTokenSelect(address) {
|
||||||
setSearchQuery('')
|
setSearchQuery('')
|
||||||
@ -457,9 +455,8 @@ function SearchModal({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(({ address, symbol, balance }) => {
|
.map(({ address, symbol, balance }) => {
|
||||||
const urlAdded = urlAddedTokens && urlAddedTokens.hasOwnProperty(address)
|
const urlAdded = urlAddedTokens?.some(token => token.address === address)
|
||||||
const customAdded =
|
const customAdded = userAddedTokens?.some(token => token.address === address) && !urlAdded
|
||||||
address !== 'ETH' && ALL_TOKENS[chainId] && !ALL_TOKENS[chainId].hasOwnProperty(address) && !urlAdded
|
|
||||||
|
|
||||||
const zeroBalance = balance && JSBI.equal(JSBI.BigInt(0), balance.raw)
|
const zeroBalance = balance && JSBI.equal(JSBI.BigInt(0), balance.raw)
|
||||||
|
|
||||||
@ -481,7 +478,8 @@ function SearchModal({
|
|||||||
</Text>
|
</Text>
|
||||||
<FadedSpan>
|
<FadedSpan>
|
||||||
<TYPE.blue fontWeight={500}>
|
<TYPE.blue fontWeight={500}>
|
||||||
{urlAdded && '(Added by URL)'} {customAdded && 'Added by user'}
|
{urlAdded && 'Added by URL'}
|
||||||
|
{customAdded && 'Added by user'}
|
||||||
</TYPE.blue>
|
</TYPE.blue>
|
||||||
{customAdded && (
|
{customAdded && (
|
||||||
<div
|
<div
|
||||||
|
@ -2,14 +2,11 @@ import { Contract } from '@ethersproject/contracts'
|
|||||||
import { Token, TokenAmount } from '@uniswap/sdk'
|
import { Token, TokenAmount } from '@uniswap/sdk'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
|
||||||
import { SWRKeys } from '.'
|
import { SWRKeys, useKeepSWRDataLiveAsBlocksArrive } from '.'
|
||||||
import { useTokenContract } from '../hooks'
|
import { useTokenContract } from '../hooks'
|
||||||
|
|
||||||
function getTokenAllowance(
|
function getTokenAllowance(contract: Contract, token: Token): (owner: string, spender: string) => Promise<TokenAmount> {
|
||||||
contract: Contract,
|
return async (owner: string, spender: string): Promise<TokenAmount> =>
|
||||||
token: Token
|
|
||||||
): (_: SWRKeys, __: number, ___: string, owner: string, spender: string) => Promise<TokenAmount> {
|
|
||||||
return async (_, __, ___, owner: string, spender: string): Promise<TokenAmount> =>
|
|
||||||
contract
|
contract
|
||||||
.allowance(owner, spender)
|
.allowance(owner, spender)
|
||||||
.then((balance: { toString: () => string }) => new TokenAmount(token, balance.toString()))
|
.then((balance: { toString: () => string }) => new TokenAmount(token, balance.toString()))
|
||||||
@ -17,16 +14,13 @@ function getTokenAllowance(
|
|||||||
|
|
||||||
export function useTokenAllowance(token?: Token, owner?: string, spender?: string): TokenAmount {
|
export function useTokenAllowance(token?: Token, owner?: string, spender?: string): TokenAmount {
|
||||||
const contract = useTokenContract(token?.address, false)
|
const contract = useTokenContract(token?.address, false)
|
||||||
const shouldFetch = !!contract && typeof owner === 'string' && typeof spender === 'string'
|
|
||||||
|
|
||||||
const { data } = useSWR(
|
const shouldFetch = !!contract && typeof owner === 'string' && typeof spender === 'string'
|
||||||
shouldFetch ? [SWRKeys.Allowances, token.chainId, token.address, owner, spender] : null,
|
const { data, mutate } = useSWR(
|
||||||
getTokenAllowance(contract, token),
|
shouldFetch ? [owner, spender, token.address, token.chainId, SWRKeys.Allowances] : null,
|
||||||
{
|
getTokenAllowance(contract, token)
|
||||||
dedupingInterval: 10 * 1000,
|
|
||||||
refreshInterval: 20 * 1000
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
useKeepSWRDataLiveAsBlocksArrive(mutate)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,16 @@ import { Token, TokenAmount, Pair } from '@uniswap/sdk'
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||||
|
|
||||||
|
import { SWRKeys, useKeepSWRDataLiveAsBlocksArrive } from '.'
|
||||||
import { useContract } from '../hooks'
|
import { useContract } from '../hooks'
|
||||||
import { SWRKeys } from '.'
|
|
||||||
|
|
||||||
function getReserves(contract: Contract, token0: Token, token1: Token): () => Promise<Pair | null> {
|
function getReserves(contract: Contract, tokenA: Token, tokenB: Token): () => Promise<Pair | null> {
|
||||||
return async (): Promise<Pair | null> =>
|
return async (): Promise<Pair | null> =>
|
||||||
contract
|
contract
|
||||||
.getReserves()
|
.getReserves()
|
||||||
.then(
|
.then(
|
||||||
({ reserve0, reserve1 }: { reserve0: { toString: () => string }; reserve1: { toString: () => string } }) => {
|
({ reserve0, reserve1 }: { reserve0: { toString: () => string }; reserve1: { toString: () => string } }) => {
|
||||||
|
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
|
||||||
return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
|
return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -26,21 +27,13 @@ function getReserves(contract: Contract, token0: Token, token1: Token): () => Pr
|
|||||||
* if pair already created (even if 0 reserves), return pair
|
* if pair already created (even if 0 reserves), return pair
|
||||||
*/
|
*/
|
||||||
export function usePair(tokenA?: Token, tokenB?: Token): undefined | Pair | null {
|
export function usePair(tokenA?: Token, tokenB?: Token): undefined | Pair | null {
|
||||||
const bothDefined = !!tokenA && !!tokenB
|
const pairAddress = !!tokenA && !!tokenB && !tokenA.equals(tokenB) ? Pair.getAddress(tokenA, tokenB) : undefined
|
||||||
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 contract = useContract(pairAddress, IUniswapV2PairABI, false)
|
||||||
|
|
||||||
const shouldFetch = !!contract
|
const shouldFetch = !!contract
|
||||||
const { data } = useSWR(
|
const key = shouldFetch ? [pairAddress, tokenA.chainId, SWRKeys.Reserves] : null
|
||||||
shouldFetch ? [SWRKeys.Reserves, token0.chainId, pairAddress] : null,
|
const { data, mutate } = useSWR(key, getReserves(contract, tokenA, tokenB))
|
||||||
getReserves(contract, token0, token1),
|
useKeepSWRDataLiveAsBlocksArrive(mutate)
|
||||||
{
|
|
||||||
dedupingInterval: 10 * 1000,
|
|
||||||
refreshInterval: 20 * 1000
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ import { Token, TokenAmount } from '@uniswap/sdk'
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { abi as IERC20ABI } from '@uniswap/v2-core/build/IERC20.json'
|
import { abi as IERC20ABI } from '@uniswap/v2-core/build/IERC20.json'
|
||||||
|
|
||||||
|
import { SWRKeys, useKeepSWRDataLiveAsBlocksArrive } from '.'
|
||||||
import { useContract } from '../hooks'
|
import { useContract } from '../hooks'
|
||||||
import { SWRKeys } from '.'
|
|
||||||
|
|
||||||
function getTotalSupply(contract: Contract, token: Token): () => Promise<TokenAmount> {
|
function getTotalSupply(contract: Contract, token: Token): () => Promise<TokenAmount> {
|
||||||
return async (): Promise<TokenAmount> =>
|
return async (): Promise<TokenAmount> =>
|
||||||
@ -15,14 +15,13 @@ function getTotalSupply(contract: Contract, token: Token): () => Promise<TokenAm
|
|||||||
|
|
||||||
export function useTotalSupply(token?: Token): TokenAmount {
|
export function useTotalSupply(token?: Token): TokenAmount {
|
||||||
const contract = useContract(token?.address, IERC20ABI, false)
|
const contract = useContract(token?.address, IERC20ABI, false)
|
||||||
|
|
||||||
const shouldFetch = !!contract
|
const shouldFetch = !!contract
|
||||||
const { data } = useSWR(
|
const { data, mutate } = useSWR(
|
||||||
shouldFetch ? [SWRKeys.TotalSupply, token.chainId, token.address] : null,
|
shouldFetch ? [token.address, token.chainId, SWRKeys.TotalSupply] : null,
|
||||||
getTotalSupply(contract, token),
|
getTotalSupply(contract, token)
|
||||||
{
|
|
||||||
dedupingInterval: 10 * 1000,
|
|
||||||
refreshInterval: 20 * 1000
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
useKeepSWRDataLiveAsBlocksArrive(mutate)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Contract } from '@ethersproject/contracts'
|
import { Contract } from '@ethersproject/contracts'
|
||||||
import { Token, TokenAmount, Pair, Trade, ChainId, WETH, Route, TradeType, Percent } from '@uniswap/sdk'
|
import { Token, TokenAmount, Pair, Trade, ChainId, WETH, Route, TradeType, Percent } from '@uniswap/sdk'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
import { useWeb3React } from '@web3-react/core'
|
||||||
|
|
||||||
import IUniswapV1Factory from '../constants/abis/v1_factory.json'
|
import IUniswapV1Factory from '../constants/abis/v1_factory.json'
|
||||||
import { V1_FACTORY_ADDRESS } from '../constants'
|
import { V1_FACTORY_ADDRESS } from '../constants'
|
||||||
@ -8,31 +9,35 @@ import { useContract } from '../hooks'
|
|||||||
import { SWRKeys } from '.'
|
import { SWRKeys } from '.'
|
||||||
import { useETHBalances, useTokenBalances } from '../state/wallet/hooks'
|
import { useETHBalances, useTokenBalances } from '../state/wallet/hooks'
|
||||||
|
|
||||||
function getV1PairAddress(contract: Contract): (_: SWRKeys, tokenAddress: string) => Promise<string> {
|
function getV1PairAddress(contract: Contract): (tokenAddress: string) => Promise<string> {
|
||||||
return async (_: SWRKeys, tokenAddress: string): Promise<string> => contract.getExchange(tokenAddress)
|
return async (tokenAddress: string): Promise<string> => contract.getExchange(tokenAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
function useV1PairAddress(tokenAddress: string) {
|
function useV1PairAddress(tokenAddress: string) {
|
||||||
const contract = useContract(V1_FACTORY_ADDRESS, IUniswapV1Factory, false)
|
const { chainId } = useWeb3React()
|
||||||
const shouldFetch = typeof tokenAddress === 'string' && !!contract
|
|
||||||
|
|
||||||
const { data } = useSWR(shouldFetch ? [SWRKeys.V1PairAddress, tokenAddress] : null, getV1PairAddress(contract), {
|
const contract = useContract(V1_FACTORY_ADDRESS, IUniswapV1Factory, false)
|
||||||
refreshInterval: 0 // don't need to update these
|
|
||||||
|
const shouldFetch = chainId === ChainId.MAINNET && typeof tokenAddress === 'string' && !!contract
|
||||||
|
const { data } = useSWR(shouldFetch ? [tokenAddress, SWRKeys.V1PairAddress] : null, getV1PairAddress(contract), {
|
||||||
|
// don't need to update this data
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: false
|
||||||
})
|
})
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
function useMockV1Pair(token?: Token) {
|
function useMockV1Pair(token?: Token) {
|
||||||
const mainnet = token?.chainId === ChainId.MAINNET
|
const isWETH = token?.equals(WETH[token?.chainId])
|
||||||
const isWETH = token?.equals(WETH[ChainId.MAINNET])
|
|
||||||
|
|
||||||
const v1PairAddress = useV1PairAddress(mainnet && !isWETH ? token?.address : undefined)
|
// will only return an address on mainnet, and not for WETH
|
||||||
|
const v1PairAddress = useV1PairAddress(isWETH ? undefined : token?.address)
|
||||||
const tokenBalance = useTokenBalances(v1PairAddress, [token])[token?.address]
|
const tokenBalance = useTokenBalances(v1PairAddress, [token])[token?.address]
|
||||||
const ETHBalance = useETHBalances([v1PairAddress])[v1PairAddress]
|
const ETHBalance = useETHBalances([v1PairAddress])[v1PairAddress]
|
||||||
|
|
||||||
return tokenBalance && ETHBalance
|
return tokenBalance && ETHBalance
|
||||||
? new Pair(tokenBalance, new TokenAmount(WETH[ChainId.MAINNET], ETHBalance.toString()))
|
? new Pair(tokenBalance, new TokenAmount(WETH[token?.chainId], ETHBalance.toString()))
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,9 +68,20 @@ export function useV1TradeLinkIfBetter(trade: Trade, minimumDelta: Percent = new
|
|||||||
trade.tradeType
|
trade.tradeType
|
||||||
)
|
)
|
||||||
|
|
||||||
const v1HasBetterRate = v1Trade?.slippage?.add(minimumDelta)?.lessThan(trade?.slippage)
|
let v1HasBetterTrade = false
|
||||||
|
if (v1Trade) {
|
||||||
|
if (trade.tradeType === TradeType.EXACT_INPUT) {
|
||||||
|
// check if the output amount on v1, discounted by minimumDelta, is greater than on v2
|
||||||
|
const discountedV1Output = v1Trade.outputAmount.multiply(new Percent('1').subtract(minimumDelta))
|
||||||
|
v1HasBetterTrade = discountedV1Output.greaterThan(trade.outputAmount)
|
||||||
|
} else {
|
||||||
|
// check if the input amount on v1, inflated by minimumDelta, is less than on v2
|
||||||
|
const inflatedV1Input = v1Trade.inputAmount.multiply(new Percent('1').add(minimumDelta))
|
||||||
|
v1HasBetterTrade = inflatedV1Input.lessThan(trade.inputAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return v1HasBetterRate
|
return v1HasBetterTrade
|
||||||
? `https://v1.uniswap.exchange/swap?inputCurrency=${
|
? `https://v1.uniswap.exchange/swap?inputCurrency=${
|
||||||
inputIsWETH ? 'ETH' : trade.route.input.address
|
inputIsWETH ? 'ETH' : trade.route.input.address
|
||||||
}&outputCurrency=${outputIsWETH ? 'ETH' : trade.route.output.address}`
|
}&outputCurrency=${outputIsWETH ? 'ETH' : trade.route.output.address}`
|
||||||
|
@ -1,6 +1,24 @@
|
|||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { responseInterface } from 'swr'
|
||||||
|
|
||||||
|
import { useBlockNumber } from '../state/application/hooks'
|
||||||
|
|
||||||
export enum SWRKeys {
|
export enum SWRKeys {
|
||||||
Allowances,
|
Allowances,
|
||||||
Reserves,
|
Reserves,
|
||||||
TotalSupply,
|
TotalSupply,
|
||||||
V1PairAddress
|
V1PairAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useKeepSWRDataLiveAsBlocksArrive(mutate: responseInterface<any, any>['mutate']) {
|
||||||
|
// because we don't care about the referential identity of mutate, just bind it to a ref
|
||||||
|
const mutateRef = useRef(mutate)
|
||||||
|
useEffect(() => {
|
||||||
|
mutateRef.current = mutate
|
||||||
|
})
|
||||||
|
// then, whenever a new block arrives, trigger a mutation
|
||||||
|
const blockNumber = useBlockNumber()
|
||||||
|
useEffect(() => {
|
||||||
|
mutateRef.current()
|
||||||
|
}, [blockNumber])
|
||||||
|
}
|
||||||
|
@ -56,7 +56,7 @@ export function useAllTokens(): { [address: string]: Token } {
|
|||||||
.reduce<{ [address: string]: Token }>((tokenMap, token) => {
|
.reduce<{ [address: string]: Token }>((tokenMap, token) => {
|
||||||
tokenMap[token.address] = token
|
tokenMap[token.address] = token
|
||||||
return tokenMap
|
return tokenMap
|
||||||
}, ALL_TOKENS?.[chainId] ?? {})
|
}, ALL_TOKENS[chainId] ?? {})
|
||||||
)
|
)
|
||||||
}, [userAddedTokens, chainId])
|
}, [userAddedTokens, chainId])
|
||||||
}
|
}
|
||||||
|
@ -275,17 +275,12 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
|||||||
// check for estimated liquidity minted
|
// check for estimated liquidity minted
|
||||||
const liquidityMinted: TokenAmount =
|
const liquidityMinted: TokenAmount =
|
||||||
!!pair &&
|
!!pair &&
|
||||||
|
!!totalSupply &&
|
||||||
!!parsedAmounts[Field.INPUT] &&
|
!!parsedAmounts[Field.INPUT] &&
|
||||||
!!parsedAmounts[Field.OUTPUT] &&
|
!!parsedAmounts[Field.OUTPUT] &&
|
||||||
!JSBI.equal(parsedAmounts[Field.INPUT].raw, JSBI.BigInt(0)) &&
|
!JSBI.equal(parsedAmounts[Field.INPUT].raw, JSBI.BigInt(0)) &&
|
||||||
!JSBI.equal(parsedAmounts[Field.OUTPUT].raw, JSBI.BigInt(0)) &&
|
!JSBI.equal(parsedAmounts[Field.OUTPUT].raw, JSBI.BigInt(0))
|
||||||
totalSupply &&
|
? pair.getLiquidityMinted(totalSupply, parsedAmounts[Field.INPUT], parsedAmounts[Field.OUTPUT])
|
||||||
totalSupply.token.equals(pair.liquidityToken) // if stale value for pair
|
|
||||||
? pair.getLiquidityMinted(
|
|
||||||
totalSupply ? totalSupply : new TokenAmount(pair?.liquidityToken, JSBI.BigInt(0)),
|
|
||||||
parsedAmounts[Field.INPUT],
|
|
||||||
parsedAmounts[Field.OUTPUT]
|
|
||||||
)
|
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const poolTokenPercentage: Percent =
|
const poolTokenPercentage: Percent =
|
||||||
|
@ -129,25 +129,30 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
|
|||||||
const pairContract: Contract = usePairContract(pair?.liquidityToken.address)
|
const pairContract: Contract = usePairContract(pair?.liquidityToken.address)
|
||||||
|
|
||||||
// pool token data
|
// pool token data
|
||||||
const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
|
|
||||||
|
|
||||||
const userLiquidity = useTokenBalance(account, pair?.liquidityToken)
|
const userLiquidity = useTokenBalance(account, pair?.liquidityToken)
|
||||||
|
const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
|
||||||
|
|
||||||
// input state
|
// input state
|
||||||
const [state, dispatch] = useReducer(reducer, initializeRemoveState(userLiquidity?.toExact(), token0, token1))
|
const [state, dispatch] = useReducer(reducer, initializeRemoveState(userLiquidity?.toExact(), token0, token1))
|
||||||
const { independentField, typedValue } = state
|
const { independentField, typedValue } = state
|
||||||
|
|
||||||
const TokensDeposited: { [field: number]: TokenAmount } = {
|
const tokensDeposited: { [field: number]: TokenAmount } = {
|
||||||
[Field.TOKEN0]:
|
[Field.TOKEN0]:
|
||||||
pair &&
|
pair &&
|
||||||
totalPoolTokens &&
|
totalPoolTokens &&
|
||||||
userLiquidity &&
|
userLiquidity &&
|
||||||
pair.getLiquidityValue(tokens[Field.TOKEN0], totalPoolTokens, userLiquidity, false),
|
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||||
|
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userLiquidity.raw)
|
||||||
|
? pair.getLiquidityValue(tokens[Field.TOKEN0], totalPoolTokens, userLiquidity, false)
|
||||||
|
: undefined,
|
||||||
[Field.TOKEN1]:
|
[Field.TOKEN1]:
|
||||||
pair &&
|
pair &&
|
||||||
totalPoolTokens &&
|
totalPoolTokens &&
|
||||||
userLiquidity &&
|
userLiquidity &&
|
||||||
pair.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, userLiquidity, false)
|
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||||
|
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userLiquidity.raw)
|
||||||
|
? pair.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, userLiquidity, false)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const route: Route = pair
|
const route: Route = pair
|
||||||
@ -168,12 +173,12 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
|
|||||||
if (typedValueParsed !== '0') {
|
if (typedValueParsed !== '0') {
|
||||||
const tokenAmount = new TokenAmount(tokens[Field.TOKEN0], typedValueParsed)
|
const tokenAmount = new TokenAmount(tokens[Field.TOKEN0], typedValueParsed)
|
||||||
if (
|
if (
|
||||||
TokensDeposited[Field.TOKEN0] &&
|
tokensDeposited[Field.TOKEN0] &&
|
||||||
JSBI.lessThanOrEqual(tokenAmount.raw, TokensDeposited[Field.TOKEN0].raw)
|
JSBI.lessThanOrEqual(tokenAmount.raw, tokensDeposited[Field.TOKEN0].raw)
|
||||||
) {
|
) {
|
||||||
poolTokenAmount = JSBI.divide(
|
poolTokenAmount = JSBI.divide(
|
||||||
JSBI.multiply(tokenAmount.raw, userLiquidity.raw),
|
JSBI.multiply(tokenAmount.raw, userLiquidity.raw),
|
||||||
TokensDeposited[Field.TOKEN0].raw
|
tokensDeposited[Field.TOKEN0].raw
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,12 +188,12 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
|
|||||||
if (typedValueParsed !== '0') {
|
if (typedValueParsed !== '0') {
|
||||||
const tokenAmount = new TokenAmount(tokens[Field.TOKEN1], typedValueParsed)
|
const tokenAmount = new TokenAmount(tokens[Field.TOKEN1], typedValueParsed)
|
||||||
if (
|
if (
|
||||||
TokensDeposited[Field.TOKEN1] &&
|
tokensDeposited[Field.TOKEN1] &&
|
||||||
JSBI.lessThanOrEqual(tokenAmount.raw, TokensDeposited[Field.TOKEN1].raw)
|
JSBI.lessThanOrEqual(tokenAmount.raw, tokensDeposited[Field.TOKEN1].raw)
|
||||||
) {
|
) {
|
||||||
poolTokenAmount = JSBI.divide(
|
poolTokenAmount = JSBI.divide(
|
||||||
JSBI.multiply(tokenAmount.raw, userLiquidity.raw),
|
JSBI.multiply(tokenAmount.raw, userLiquidity.raw),
|
||||||
TokensDeposited[Field.TOKEN1].raw
|
tokensDeposited[Field.TOKEN1].raw
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,25 +215,31 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
|
|||||||
|
|
||||||
// set parsed amounts based on live amount of liquidity
|
// set parsed amounts based on live amount of liquidity
|
||||||
parsedAmounts[Field.LIQUIDITY] =
|
parsedAmounts[Field.LIQUIDITY] =
|
||||||
pair && poolTokenAmount && userLiquidity && new TokenAmount(pair.liquidityToken, poolTokenAmount)
|
!!pair && !!poolTokenAmount ? new TokenAmount(pair.liquidityToken, poolTokenAmount) : undefined
|
||||||
|
|
||||||
parsedAmounts[Field.TOKEN0] =
|
parsedAmounts[Field.TOKEN0] =
|
||||||
totalPoolTokens &&
|
!!pair &&
|
||||||
pair &&
|
!!totalPoolTokens &&
|
||||||
parsedAmounts[Field.LIQUIDITY] &&
|
!!parsedAmounts[Field.LIQUIDITY] &&
|
||||||
pair.getLiquidityValue(tokens[Field.TOKEN0], totalPoolTokens, parsedAmounts[Field.LIQUIDITY], false)
|
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||||
|
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userLiquidity.raw)
|
||||||
|
? pair.getLiquidityValue(tokens[Field.TOKEN0], totalPoolTokens, parsedAmounts[Field.LIQUIDITY], false)
|
||||||
|
: undefined
|
||||||
|
|
||||||
parsedAmounts[Field.TOKEN1] =
|
parsedAmounts[Field.TOKEN1] =
|
||||||
totalPoolTokens &&
|
!!pair &&
|
||||||
pair &&
|
!!totalPoolTokens &&
|
||||||
parsedAmounts[Field.LIQUIDITY] &&
|
!!parsedAmounts[Field.LIQUIDITY] &&
|
||||||
pair.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, parsedAmounts[Field.LIQUIDITY], false)
|
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||||
|
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userLiquidity.raw)
|
||||||
|
? pair.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, parsedAmounts[Field.LIQUIDITY], false)
|
||||||
|
: undefined
|
||||||
|
|
||||||
// derived percent for advanced mode
|
// derived percent for advanced mode
|
||||||
const derivedPercent =
|
const derivedPercent =
|
||||||
parsedAmounts[Field.LIQUIDITY] &&
|
!!parsedAmounts[Field.LIQUIDITY] && !!userLiquidity
|
||||||
userLiquidity &&
|
? new Percent(parsedAmounts[Field.LIQUIDITY].raw, userLiquidity.raw)
|
||||||
new Percent(parsedAmounts[Field.LIQUIDITY]?.raw, userLiquidity.raw)
|
: undefined
|
||||||
|
|
||||||
const [override, setSliderOverride] = useState(false) // override slider internal value
|
const [override, setSliderOverride] = useState(false) // override slider internal value
|
||||||
const handlePresetPercentage = newPercent => {
|
const handlePresetPercentage = newPercent => {
|
||||||
|
Loading…
Reference in New Issue
Block a user