feat(weth): support WETH across the site and use sdk 3.0 (#947)
* first pass of sdk 3.0 * second pass using weth * kill unused pool popup * get it compiling again * first pass of sdk 3.0 * switch to currencies * get it compiling after the big move merge * restore margin * clean up add liquidity more * fix a bunch of bugs * todo trade on v1 * show eth in currency list * allow selecting eth in the swap page * fix unit tests for swap page * test lint errors * fix failing integration tests * fix another couple of failing unit tests * handle selecting currency b when no currency a * improve the import pool page * clean up add liquidity for invalid pairs * bold * first pass at swap arguments for v1, some unit tests * fix some bugs in add liquidity, burn hook * fix last of ts errors in remove liquidity * support wrapping/unwrapping weth * kill a bunch of code including the dummy pairs * required pair prop in the position card * tests for the v1 swap arguments * do not say estimated on the wrap ui * show ETH instead of WETH in the pool summaries * small size socks * fix lint error * in burn, use currencies from the URL * fix some integration tests * both contain weth * receive eth/weth link * fix empty row * show wrapped only if one currency is weth * currency selects in the remove liquidity page
This commit is contained in:
parent
6287b95b92
commit
19b1e9e399
@ -32,13 +32,19 @@ describe('Add Liquidity', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('redirects /add/WETH-token to /add/ETH/token', () => {
|
||||
it('redirects /add/WETH-token to /add/WETH-address/token', () => {
|
||||
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should('contain', '/add/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('redirects /add/token-WETH to /add/token/ETH', () => {
|
||||
it('redirects /add/token-WETH to /add/token/WETH-address', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.url().should('contain', '/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -1,14 +1,34 @@
|
||||
describe('Remove Liquidity', () => {
|
||||
it('redirects', () => {
|
||||
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('eth remove', () => {
|
||||
cy.visit('/remove/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('eth remove swap order', () => {
|
||||
cy.visit('/remove/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'MKR')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('loads the two correct tokens', () => {
|
||||
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('does not crash if ETH is duplicated', () => {
|
||||
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('not.contain.text', 'ETH')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
|
22
package.json
22
package.json
@ -4,15 +4,15 @@
|
||||
"homepage": ".",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@ethersproject/address": "^5.0.0-beta.134",
|
||||
"@ethersproject/bignumber": "^5.0.0-beta.138",
|
||||
"@ethersproject/constants": "^5.0.0-beta.133",
|
||||
"@ethersproject/contracts": "^5.0.0-beta.151",
|
||||
"@ethersproject/experimental": "^5.0.0-beta.141",
|
||||
"@ethersproject/providers": "5.0.0-beta.162",
|
||||
"@ethersproject/strings": "^5.0.0-beta.136",
|
||||
"@ethersproject/units": "^5.0.0-beta.132",
|
||||
"@ethersproject/wallet": "^5.0.0-beta.141",
|
||||
"@ethersproject/address": "^5.0.1",
|
||||
"@ethersproject/bignumber": "^5.0.3",
|
||||
"@ethersproject/constants": "^5.0.1",
|
||||
"@ethersproject/contracts": "^5.0.1",
|
||||
"@ethersproject/experimental": "^5.0.0",
|
||||
"@ethersproject/providers": "^5.0.4",
|
||||
"@ethersproject/strings": "^5.0.1",
|
||||
"@ethersproject/units": "^5.0.1",
|
||||
"@ethersproject/wallet": "^5.0.1",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
@ -27,11 +27,11 @@
|
||||
"@types/react-router-dom": "^5.0.0",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/rebass": "^4.0.5",
|
||||
"@types/styled-components": "^4.2.0",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"@types/testing-library__cypress": "^5.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^2.31.0",
|
||||
"@typescript-eslint/parser": "^2.31.0",
|
||||
"@uniswap/sdk": "^2.0.5",
|
||||
"@uniswap/sdk": "3.0.1",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@web3-react/core": "^6.0.9",
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { Pair, Token } from '@uniswap/sdk'
|
||||
import { Currency, Pair } from '@uniswap/sdk'
|
||||
import React, { useState, useContext, useCallback } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import { darken } from 'polished'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import TokenSearchModal from '../SearchModal/TokenSearchModal'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import DoubleLogo from '../DoubleLogo'
|
||||
import { useCurrencyBalance } from '../../state/wallet/hooks'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { RowBetween } from '../Row'
|
||||
import { TYPE, CursorPointer } from '../../theme'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
@ -117,40 +116,36 @@ const StyledBalanceMax = styled.button`
|
||||
|
||||
interface CurrencyInputPanelProps {
|
||||
value: string
|
||||
field: string
|
||||
onUserInput: (field: string, val: string) => void
|
||||
onUserInput: (value: string) => void
|
||||
onMax?: () => void
|
||||
showMaxButton: boolean
|
||||
label?: string
|
||||
onTokenSelection?: (tokenAddress: string) => void
|
||||
token?: Token | null
|
||||
disableTokenSelect?: boolean
|
||||
onCurrencySelect?: (currency: Currency) => void
|
||||
currency?: Currency | null
|
||||
disableCurrencySelect?: boolean
|
||||
hideBalance?: boolean
|
||||
isExchange?: boolean
|
||||
pair?: Pair | null
|
||||
hideInput?: boolean
|
||||
showSendWithSwap?: boolean
|
||||
otherSelectedTokenAddress?: string | null
|
||||
otherCurrency?: Currency | null
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
}
|
||||
|
||||
export default function CurrencyInputPanel({
|
||||
value,
|
||||
field,
|
||||
onUserInput,
|
||||
onMax,
|
||||
showMaxButton,
|
||||
label = 'Input',
|
||||
onTokenSelection = null,
|
||||
token = null,
|
||||
disableTokenSelect = false,
|
||||
onCurrencySelect = null,
|
||||
currency = null,
|
||||
disableCurrencySelect = false,
|
||||
hideBalance = false,
|
||||
isExchange = false,
|
||||
pair = null, // used for double token logo
|
||||
hideInput = false,
|
||||
showSendWithSwap = false,
|
||||
otherSelectedTokenAddress = null,
|
||||
otherCurrency = null,
|
||||
id,
|
||||
showCommonBases
|
||||
}: CurrencyInputPanelProps) {
|
||||
@ -158,7 +153,7 @@ export default function CurrencyInputPanel({
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { account } = useActiveWeb3React()
|
||||
const userTokenBalance = useTokenBalanceTreatingWETHasETH(account, token)
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account, currency)
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
@ -183,8 +178,8 @@ export default function CurrencyInputPanel({
|
||||
fontSize={14}
|
||||
style={{ display: 'inline' }}
|
||||
>
|
||||
{!hideBalance && !!token && userTokenBalance
|
||||
? 'Balance: ' + userTokenBalance?.toSignificant(6)
|
||||
{!hideBalance && !!currency && selectedCurrencyBalance
|
||||
? 'Balance: ' + selectedCurrencyBalance?.toSignificant(6)
|
||||
: ' -'}
|
||||
</TYPE.body>
|
||||
</CursorPointer>
|
||||
@ -192,63 +187,62 @@ export default function CurrencyInputPanel({
|
||||
</RowBetween>
|
||||
</LabelRow>
|
||||
)}
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableTokenSelect}>
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}>
|
||||
{!hideInput && (
|
||||
<>
|
||||
<NumericalInput
|
||||
className="token-amount-input"
|
||||
value={value}
|
||||
onUserInput={val => {
|
||||
onUserInput(field, val)
|
||||
onUserInput(val)
|
||||
}}
|
||||
/>
|
||||
{account && !!token?.address && showMaxButton && label !== 'To' && (
|
||||
{account && currency && showMaxButton && label !== 'To' && (
|
||||
<StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<CurrencySelect
|
||||
selected={!!token}
|
||||
selected={!!currency}
|
||||
className="open-currency-select-button"
|
||||
onClick={() => {
|
||||
if (!disableTokenSelect) {
|
||||
if (!disableCurrencySelect) {
|
||||
setModalOpen(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Aligner>
|
||||
{isExchange ? (
|
||||
<DoubleLogo a0={pair?.token0.address} a1={pair?.token1.address} size={24} margin={true} />
|
||||
) : token?.address ? (
|
||||
<TokenLogo address={token?.address} size={'24px'} />
|
||||
{pair ? (
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
) : currency ? (
|
||||
<CurrencyLogo currency={currency} size={'24px'} />
|
||||
) : null}
|
||||
{isExchange ? (
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container">
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(token && token.symbol)}>
|
||||
{(token && token.symbol && token.symbol.length > 20
|
||||
? token.symbol.slice(0, 4) +
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
token.symbol.slice(token.symbol.length - 5, token.symbol.length)
|
||||
: token?.symbol) || t('selectToken')}
|
||||
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
|
||||
: currency?.symbol) || t('selectToken')}
|
||||
</StyledTokenName>
|
||||
)}
|
||||
{!disableTokenSelect && <StyledDropDown selected={!!token?.address} />}
|
||||
{!disableCurrencySelect && <StyledDropDown selected={!!currency} />}
|
||||
</Aligner>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
</Container>
|
||||
{!disableTokenSelect && (
|
||||
<TokenSearchModal
|
||||
{!disableCurrencySelect && (
|
||||
<CurrencySearchModal
|
||||
isOpen={modalOpen}
|
||||
onDismiss={handleDismissSearch}
|
||||
onTokenSelect={onTokenSelection}
|
||||
onCurrencySelect={onCurrencySelect}
|
||||
showSendWithSwap={showSendWithSwap}
|
||||
hiddenToken={token?.address}
|
||||
otherSelectedTokenAddress={otherSelectedTokenAddress}
|
||||
otherSelectedText={field === Field.INPUT ? 'Selected as output' : 'Selected as input'}
|
||||
hiddenCurrency={currency}
|
||||
otherSelectedCurrency={otherCurrency}
|
||||
showCommonBases={showCommonBases}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { isAddress } from '../../utils'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { WETH } from '@uniswap/sdk'
|
||||
import { Currency, Token } from '@uniswap/sdk'
|
||||
|
||||
import EthereumLogo from '../../assets/images/ethereum-logo.png'
|
||||
|
||||
@ -35,25 +33,21 @@ const StyledEthereumLogo = styled.img<{ size: string }>`
|
||||
border-radius: 24px;
|
||||
`
|
||||
|
||||
export default function TokenLogo({
|
||||
address,
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
size = '24px',
|
||||
...rest
|
||||
}: {
|
||||
address?: string
|
||||
currency?: Currency
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}) {
|
||||
const [, refresh] = useState<number>(0)
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
if (currency instanceof Token) {
|
||||
let path = ''
|
||||
const validated = isAddress(address)
|
||||
// hard code to show ETH instead of WETH in UI
|
||||
if (validated === WETH[chainId].address) {
|
||||
return <StyledEthereumLogo src={EthereumLogo} size={size} {...rest} />
|
||||
} else if (!NO_LOGO_ADDRESSES[address] && validated) {
|
||||
path = getTokenLogoURL(validated)
|
||||
if (!NO_LOGO_ADDRESSES[currency.address]) {
|
||||
path = getTokenLogoURL(currency.address)
|
||||
} else {
|
||||
return (
|
||||
<Emoji {...rest} size={size}>
|
||||
@ -67,13 +61,18 @@ export default function TokenLogo({
|
||||
return (
|
||||
<Image
|
||||
{...rest}
|
||||
// alt={address}
|
||||
alt={`${currency.name} Logo`}
|
||||
src={path}
|
||||
size={size}
|
||||
onError={() => {
|
||||
NO_LOGO_ADDRESSES[address] = true
|
||||
if (currency instanceof Token) {
|
||||
NO_LOGO_ADDRESSES[currency.address] = true
|
||||
}
|
||||
refresh(i => i + 1)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return <StyledEthereumLogo src={EthereumLogo} size={size} {...rest} />
|
||||
}
|
||||
}
|
@ -1,34 +1,40 @@
|
||||
import { Currency } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
const TokenWrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
|
||||
`
|
||||
|
||||
interface DoubleTokenLogoProps {
|
||||
interface DoubleCurrencyLogoProps {
|
||||
margin?: boolean
|
||||
size?: number
|
||||
a0?: string
|
||||
a1?: string
|
||||
currency0?: Currency
|
||||
currency1?: Currency
|
||||
}
|
||||
|
||||
const HigherLogo = styled(TokenLogo)`
|
||||
const HigherLogo = styled(CurrencyLogo)`
|
||||
z-index: 2;
|
||||
`
|
||||
const CoveredLogo = styled(TokenLogo)<{ sizeraw: number }>`
|
||||
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
|
||||
position: absolute;
|
||||
left: ${({ sizeraw }) => (sizeraw / 2).toString() + 'px'};
|
||||
`
|
||||
|
||||
export default function DoubleTokenLogo({ a0, a1, size = 16, margin = false }: DoubleTokenLogoProps) {
|
||||
export default function DoubleCurrencyLogo({
|
||||
currency0,
|
||||
currency1,
|
||||
size = 16,
|
||||
margin = false
|
||||
}: DoubleCurrencyLogoProps) {
|
||||
return (
|
||||
<TokenWrapper sizeraw={size} margin={margin}>
|
||||
{a0 && <HigherLogo address={a0} size={size.toString() + 'px'} />}
|
||||
{a1 && <CoveredLogo address={a1} size={size.toString() + 'px'} sizeraw={size} />}
|
||||
</TokenWrapper>
|
||||
<Wrapper sizeraw={size} margin={margin}>
|
||||
{currency0 && <HigherLogo currency={currency0} size={size.toString() + 'px'} />}
|
||||
{currency1 && <CoveredLogo currency={currency1} size={size.toString() + 'px'} sizeraw={size} />}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { Link, useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
import useParsedQueryString from '../../hooks/useParsedQueryString'
|
||||
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
|
||||
const VersionLabel = styled.span<{ enabled: boolean }>`
|
||||
padding: 0.35rem 0.6rem;
|
||||
@ -61,10 +62,15 @@ export default function VersionSwitch() {
|
||||
[versionSwitchAvailable]
|
||||
)
|
||||
|
||||
return (
|
||||
const toggle = (
|
||||
<VersionToggle enabled={versionSwitchAvailable} to={toggleDest} onClick={handleClick}>
|
||||
<VersionLabel enabled={version === Version.v2 || !versionSwitchAvailable}>V2</VersionLabel>
|
||||
<VersionLabel enabled={version === Version.v1 && versionSwitchAvailable}>V1</VersionLabel>
|
||||
</VersionToggle>
|
||||
)
|
||||
return versionSwitchAvailable ? (
|
||||
toggle
|
||||
) : (
|
||||
<MouseoverTooltip text="This page is only compatible with Uniswap V2.">{toggle}</MouseoverTooltip>
|
||||
)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChainId, WETH } from '@uniswap/sdk'
|
||||
import { ChainId } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { Text } from 'rebass'
|
||||
@ -11,7 +11,7 @@ import Wordmark from '../../assets/svg/wordmark.svg'
|
||||
import WordmarkDark from '../../assets/svg/wordmark_white.svg'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useDarkModeManager } from '../../state/user/hooks'
|
||||
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import { useETHBalances } from '../../state/wallet/hooks'
|
||||
|
||||
import { YellowCard } from '../Card'
|
||||
import Settings from '../Settings'
|
||||
@ -137,7 +137,7 @@ const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
|
||||
export default function Header() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const userEthBalance = useTokenBalanceTreatingWETHasETH(account, WETH[chainId])
|
||||
const userEthBalance = useETHBalances([account])[account]
|
||||
const [isDark] = useDarkModeManager()
|
||||
|
||||
return (
|
||||
|
@ -66,20 +66,6 @@ export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) {
|
||||
)
|
||||
}
|
||||
|
||||
export function CreatePoolTabs() {
|
||||
return (
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem' }}>
|
||||
<HistoryLink to="/pool">
|
||||
<StyledArrowLeft />
|
||||
</HistoryLink>
|
||||
<ActiveText>Create Pool</ActiveText>
|
||||
<QuestionHelper text={'Use this interface to create a new pool.'} />
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
export function FindPoolTabs() {
|
||||
return (
|
||||
<Tabs>
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { ChainId, Pair, Token } from '@uniswap/sdk'
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import { useMediaLayout } from 'use-media'
|
||||
|
||||
import { X } from 'react-feather'
|
||||
import { PopupContent } from '../../state/application/actions'
|
||||
import { useActivePopups, useRemovePopup } from '../../state/application/hooks'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
import DoubleTokenLogo from '../DoubleLogo'
|
||||
import Row from '../Row'
|
||||
import TxnPopup from '../TxnPopup'
|
||||
import { Text } from 'rebass'
|
||||
|
||||
const StyledClose = styled(X)`
|
||||
position: absolute;
|
||||
@ -72,52 +67,12 @@ const Popup = styled.div`
|
||||
`}
|
||||
`
|
||||
|
||||
function PoolPopup({
|
||||
token0,
|
||||
token1
|
||||
}: {
|
||||
token0: { address?: string; symbol?: string }
|
||||
token1: { address?: string; symbol?: string }
|
||||
}) {
|
||||
const pairAddress: string | null = useMemo(() => {
|
||||
if (!token0 || !token1) return null
|
||||
// just mock it out
|
||||
return Pair.getAddress(
|
||||
new Token(ChainId.MAINNET, token0.address, 18),
|
||||
new Token(ChainId.MAINNET, token1.address, 18)
|
||||
)
|
||||
}, [token0, token1])
|
||||
|
||||
return (
|
||||
<AutoColumn gap={'10px'}>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
Pool Imported
|
||||
</Text>
|
||||
<Row>
|
||||
<DoubleTokenLogo a0={token0?.address ?? ''} a1={token1?.address ?? ''} margin={true} />
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
UNI {token0?.symbol} / {token1?.symbol}
|
||||
</Text>
|
||||
</Row>
|
||||
{pairAddress ? (
|
||||
<ExternalLink href={`https://uniswap.info/pair/${pairAddress}`}>View on Uniswap Info.</ExternalLink>
|
||||
) : null}
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
||||
function PopupItem({ content, popKey }: { content: PopupContent; popKey: string }) {
|
||||
if ('txn' in content) {
|
||||
const {
|
||||
txn: { hash, success, summary }
|
||||
} = content
|
||||
return <TxnPopup popKey={popKey} hash={hash} success={success} summary={summary} />
|
||||
} else if ('poolAdded' in content) {
|
||||
const {
|
||||
poolAdded: { token0, token1 }
|
||||
} = content
|
||||
|
||||
return <PoolPopup token0={token0} token1={token1} />
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { AutoColumn } from '../Column'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { FixedHeightRow, HoverCard } from './index'
|
||||
import DoubleTokenLogo from '../DoubleLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
|
||||
@ -26,7 +26,7 @@ function V1PositionCard({ token, V1LiquidityBalance }: PositionCardProps) {
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<DoubleTokenLogo a0={token.address} margin={true} size={20} />
|
||||
<DoubleCurrencyLogo currency0={token} margin={true} size={20} />
|
||||
<Text fontWeight={500} fontSize={20} style={{ marginLeft: '' }}>
|
||||
{`${token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH`}
|
||||
</Text>
|
||||
|
@ -1,23 +1,24 @@
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { JSBI, Pair, Percent } from '@uniswap/sdk'
|
||||
import { darken } from 'polished'
|
||||
import React, { useState } from 'react'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Percent, Pair, JSBI } from '@uniswap/sdk'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { currencyId } from '../../pages/AddLiquidity/currencyId'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { unwrappedToken } from '../../utils/wrappedCurrency'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
|
||||
import Card, { GreyCard } from '../Card'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import DoubleLogo from '../DoubleLogo'
|
||||
import { Text } from 'rebass'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import { RowBetween, RowFixed, AutoRow } from '../Row'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
|
||||
export const FixedHeightRow = styled(RowBetween)`
|
||||
@ -32,20 +33,21 @@ export const HoverCard = styled(Card)`
|
||||
`
|
||||
|
||||
interface PositionCardProps {
|
||||
pair: Pair | undefined | null
|
||||
pair: Pair
|
||||
showUnwrapped?: boolean
|
||||
border?: string
|
||||
}
|
||||
|
||||
export function MinimalPositionCard({ pair, border }: PositionCardProps) {
|
||||
export function MinimalPositionCard({ pair, showUnwrapped = false, border }: PositionCardProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const token0 = pair?.token0
|
||||
const token1 = pair?.token1
|
||||
const currency0 = showUnwrapped ? pair.token0 : unwrappedToken(pair.token0)
|
||||
const currency1 = showUnwrapped ? pair.token1 : unwrappedToken(pair.token1)
|
||||
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
|
||||
const userPoolBalance = useTokenBalance(account, pair?.liquidityToken)
|
||||
const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
|
||||
const userPoolBalance = useTokenBalance(account, pair.liquidityToken)
|
||||
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
|
||||
|
||||
const [token0Deposited, token1Deposited] =
|
||||
!!pair &&
|
||||
@ -54,8 +56,8 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
? [
|
||||
pair.getLiquidityValue(token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(token1, totalPoolTokens, userPoolBalance, false)
|
||||
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false)
|
||||
]
|
||||
: [undefined, undefined]
|
||||
|
||||
@ -73,9 +75,9 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow onClick={() => setShowMore(!showMore)}>
|
||||
<RowFixed>
|
||||
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} />
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{token0?.symbol}/{token1?.symbol}
|
||||
{currency0.symbol}/{currency1.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
@ -87,7 +89,7 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
|
||||
<AutoColumn gap="4px">
|
||||
<FixedHeightRow>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500}>
|
||||
{token0?.symbol}:
|
||||
{currency0.symbol}:
|
||||
</Text>
|
||||
{token0Deposited ? (
|
||||
<RowFixed>
|
||||
@ -101,7 +103,7 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500}>
|
||||
{token1?.symbol}:
|
||||
{currency1.symbol}:
|
||||
</Text>
|
||||
{token1Deposited ? (
|
||||
<RowFixed>
|
||||
@ -124,13 +126,13 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
|
||||
export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const token0 = pair?.token0
|
||||
const token1 = pair?.token1
|
||||
const currency0 = unwrappedToken(pair.token0)
|
||||
const currency1 = unwrappedToken(pair.token1)
|
||||
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
|
||||
const userPoolBalance = useTokenBalance(account, pair?.liquidityToken)
|
||||
const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
|
||||
const userPoolBalance = useTokenBalance(account, pair.liquidityToken)
|
||||
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
|
||||
|
||||
const poolTokenPercentage =
|
||||
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
@ -144,8 +146,8 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
? [
|
||||
pair.getLiquidityValue(token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(token1, totalPoolTokens, userPoolBalance, false)
|
||||
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false)
|
||||
]
|
||||
: [undefined, undefined]
|
||||
|
||||
@ -154,9 +156,9 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow onClick={() => setShowMore(!showMore)} style={{ cursor: 'pointer' }}>
|
||||
<RowFixed>
|
||||
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} />
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{!token0 || !token1 ? <Dots>Loading</Dots> : `${token0.symbol}/${token1.symbol}`}
|
||||
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
@ -172,7 +174,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pooled {token0?.symbol}:
|
||||
Pooled {currency0.symbol}:
|
||||
</Text>
|
||||
</RowFixed>
|
||||
{token0Deposited ? (
|
||||
@ -180,7 +182,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token0Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token0?.address} />
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
@ -190,7 +192,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pooled {token1?.symbol}:
|
||||
Pooled {currency1.symbol}:
|
||||
</Text>
|
||||
</RowFixed>
|
||||
{token1Deposited ? (
|
||||
@ -198,7 +200,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token1Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token1?.address} />
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
@ -222,15 +224,15 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
</FixedHeightRow>
|
||||
|
||||
<AutoRow justify="center" marginTop={'10px'}>
|
||||
<ExternalLink href={`https://uniswap.info/pair/${pair?.liquidityToken.address}`}>
|
||||
<ExternalLink href={`https://uniswap.info/pair/${pair.liquidityToken.address}`}>
|
||||
View pool information ↗
|
||||
</ExternalLink>
|
||||
</AutoRow>
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonSecondary as={Link} to={`/add/${currencyId(token0)}/${currencyId(token1)}`} width="48%">
|
||||
<ButtonSecondary as={Link} to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`} width="48%">
|
||||
Add
|
||||
</ButtonSecondary>
|
||||
<ButtonSecondary as={Link} width="48%" to={`/remove/${token0?.address}-${token1?.address}`}>
|
||||
<ButtonSecondary as={Link} width="48%" to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}>
|
||||
Remove
|
||||
</ButtonSecondary>
|
||||
</RowBetween>
|
||||
|
@ -5,14 +5,13 @@ const Row = styled(Box)<{ align?: string; padding?: string; border?: string; bor
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
align-items: ${({ align }) => align && align};
|
||||
align-items: ${({ align }) => (align ? align : 'center')};
|
||||
padding: ${({ padding }) => padding};
|
||||
border: ${({ border }) => border};
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
`
|
||||
|
||||
export const RowBetween = styled(Row)<{ align?: string; padding?: string; border?: string; borderRadius?: string }>`
|
||||
export const RowBetween = styled(Row)`
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { ChainId, Token } from '@uniswap/sdk'
|
||||
import { ChainId, Currency, currencyEquals, ETHER, Token } from '@uniswap/sdk'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SUGGESTED_BASES } from '../../constants'
|
||||
import { AutoColumn } from '../Column'
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
import { AutoRow } from '../Row'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
const BaseWrapper = styled.div<{ disable?: boolean }>`
|
||||
border: 1px solid ${({ theme, disable }) => (disable ? 'transparent' : theme.bg3)};
|
||||
@ -28,11 +28,11 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
|
||||
export default function CommonBases({
|
||||
chainId,
|
||||
onSelect,
|
||||
selectedTokenAddress
|
||||
selectedCurrency
|
||||
}: {
|
||||
chainId: ChainId
|
||||
selectedTokenAddress: string
|
||||
onSelect: (tokenAddress: string) => void
|
||||
selectedCurrency?: Currency
|
||||
onSelect: (currency: Currency) => void
|
||||
}) {
|
||||
return (
|
||||
<AutoColumn gap="md">
|
||||
@ -43,14 +43,20 @@ export default function CommonBases({
|
||||
<QuestionHelper text="These tokens are commonly paired with other tokens." />
|
||||
</AutoRow>
|
||||
<AutoRow gap="4px">
|
||||
{(SUGGESTED_BASES[chainId as ChainId] ?? []).map((token: Token) => {
|
||||
return (
|
||||
<BaseWrapper
|
||||
onClick={() => selectedTokenAddress !== token.address && onSelect(token.address)}
|
||||
disable={selectedTokenAddress === token.address}
|
||||
key={token.address}
|
||||
onClick={() => !currencyEquals(selectedCurrency, ETHER) && onSelect(ETHER)}
|
||||
disable={selectedCurrency === ETHER}
|
||||
>
|
||||
<TokenLogo address={token.address} style={{ marginRight: 8 }} />
|
||||
<CurrencyLogo currency={ETHER} style={{ marginRight: 8 }} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
ETH
|
||||
</Text>
|
||||
</BaseWrapper>
|
||||
{(SUGGESTED_BASES[chainId as ChainId] ?? []).map((token: Token) => {
|
||||
const selected = currencyEquals(selectedCurrency, token)
|
||||
return (
|
||||
<BaseWrapper onClick={() => !selected && onSelect(token)} disable={selected} key={token.address}>
|
||||
<CurrencyLogo currency={token} style={{ marginRight: 8 }} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{token.symbol}
|
||||
</Text>
|
||||
|
@ -1,72 +1,72 @@
|
||||
import { JSBI, Token, TokenAmount } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount, currencyEquals, ETHER, JSBI, Token } from '@uniswap/sdk'
|
||||
import React, { CSSProperties, memo, useContext, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokens } from '../../hooks/Tokens'
|
||||
import { useAddUserToken, useRemoveUserAddedToken } from '../../state/user/hooks'
|
||||
import { useETHBalances } from '../../state/wallet/hooks'
|
||||
import { LinkStyledButton, TYPE } from '../../theme'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import Column, { AutoColumn } from '../Column'
|
||||
import { RowFixed } from '../Row'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import { FadedSpan, GreySpan, MenuItem, ModalInfo } from './styleds'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import { FadedSpan, MenuItem } from './styleds'
|
||||
import Loader from '../Loader'
|
||||
import { isDefaultToken, isCustomAddedToken } from '../../utils'
|
||||
|
||||
export default function TokenList({
|
||||
tokens,
|
||||
allTokenBalances,
|
||||
selectedToken,
|
||||
onTokenSelect,
|
||||
otherToken,
|
||||
showSendWithSwap,
|
||||
otherSelectedText
|
||||
function currencyKey(currency: Currency): string {
|
||||
return currency instanceof Token ? currency.address : currency === ETHER ? 'ETHER' : ''
|
||||
}
|
||||
|
||||
export default function CurrencyList({
|
||||
currencies,
|
||||
allBalances,
|
||||
selectedCurrency,
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
showSendWithSwap
|
||||
}: {
|
||||
tokens: Token[]
|
||||
selectedToken: string
|
||||
allTokenBalances: { [tokenAddress: string]: TokenAmount }
|
||||
onTokenSelect: (tokenAddress: string) => void
|
||||
otherToken: string
|
||||
currencies: Currency[]
|
||||
selectedCurrency: Currency
|
||||
allBalances: { [tokenAddress: string]: CurrencyAmount }
|
||||
onCurrencySelect: (currency: Currency) => void
|
||||
otherCurrency: Currency
|
||||
showSendWithSwap?: boolean
|
||||
otherSelectedText: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
const allTokens = useAllTokens()
|
||||
const addToken = useAddUserToken()
|
||||
const removeToken = useRemoveUserAddedToken()
|
||||
const ETHBalance = useETHBalances([account])[account]
|
||||
|
||||
const TokenRow = useMemo(() => {
|
||||
return memo(function TokenRow({ index, style }: { index: number; style: CSSProperties }) {
|
||||
const token = tokens[index]
|
||||
const { address, symbol } = token
|
||||
|
||||
const isDefault = isDefaultToken(token)
|
||||
const customAdded = isCustomAddedToken(allTokens, token)
|
||||
const balance = allTokenBalances[address]
|
||||
const CurrencyRow = useMemo(() => {
|
||||
return memo(function CurrencyRow({ index, style }: { index: number; style: CSSProperties }) {
|
||||
const currency = index === 0 ? Currency.ETHER : currencies[index - 1]
|
||||
const key = currencyKey(currency)
|
||||
const isDefault = isDefaultToken(currency)
|
||||
const customAdded = isCustomAddedToken(allTokens, currency)
|
||||
const balance = currency === ETHER ? ETHBalance : allBalances[key]
|
||||
|
||||
const zeroBalance = balance && JSBI.equal(JSBI.BigInt(0), balance.raw)
|
||||
|
||||
const isSelected = Boolean(selectedCurrency && currencyEquals(currency, selectedCurrency))
|
||||
const otherSelected = Boolean(otherCurrency && currencyEquals(otherCurrency, currency))
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
style={style}
|
||||
key={address}
|
||||
className={`token-item-${address}`}
|
||||
onClick={() => (selectedToken && selectedToken === address ? null : onTokenSelect(address))}
|
||||
disabled={selectedToken && selectedToken === address}
|
||||
selected={otherToken === address}
|
||||
className={`token-item-${key}`}
|
||||
onClick={() => (isSelected ? null : onCurrencySelect(currency))}
|
||||
disabled={isSelected}
|
||||
selected={otherSelected}
|
||||
>
|
||||
<RowFixed>
|
||||
<TokenLogo address={address} size={'24px'} style={{ marginRight: '14px' }} />
|
||||
<CurrencyLogo currency={currency} size={'24px'} style={{ marginRight: '14px' }} />
|
||||
<Column>
|
||||
<Text fontWeight={500}>
|
||||
{symbol}
|
||||
{otherToken === address && <GreySpan> ({otherSelectedText})</GreySpan>}
|
||||
</Text>
|
||||
<Text fontWeight={500}>{currency.symbol}</Text>
|
||||
<FadedSpan>
|
||||
{customAdded ? (
|
||||
<TYPE.main fontWeight={500}>
|
||||
@ -74,7 +74,7 @@ export default function TokenList({
|
||||
<LinkStyledButton
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
removeToken(chainId, address)
|
||||
if (currency instanceof Token) removeToken(chainId, currency.address)
|
||||
}}
|
||||
>
|
||||
(Remove)
|
||||
@ -87,7 +87,7 @@ export default function TokenList({
|
||||
<LinkStyledButton
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
addToken(token)
|
||||
if (currency instanceof Token) addToken(currency)
|
||||
}}
|
||||
>
|
||||
(Add)
|
||||
@ -122,35 +122,31 @@ export default function TokenList({
|
||||
)
|
||||
})
|
||||
}, [
|
||||
ETHBalance,
|
||||
account,
|
||||
addToken,
|
||||
allTokenBalances,
|
||||
allBalances,
|
||||
allTokens,
|
||||
chainId,
|
||||
onTokenSelect,
|
||||
otherSelectedText,
|
||||
otherToken,
|
||||
currencies,
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
removeToken,
|
||||
selectedToken,
|
||||
selectedCurrency,
|
||||
showSendWithSwap,
|
||||
theme.primary1,
|
||||
tokens
|
||||
theme.primary1
|
||||
])
|
||||
|
||||
if (tokens.length === 0) {
|
||||
return <ModalInfo>{t('noToken')}</ModalInfo>
|
||||
}
|
||||
|
||||
return (
|
||||
<FixedSizeList
|
||||
width="100%"
|
||||
height={500}
|
||||
itemCount={tokens.length}
|
||||
itemCount={currencies.length + 1}
|
||||
itemSize={56}
|
||||
style={{ flex: '1' }}
|
||||
itemKey={index => tokens[index].address}
|
||||
itemKey={index => currencyKey(currencies[index])}
|
||||
>
|
||||
{TokenRow}
|
||||
{CurrencyRow}
|
||||
</FixedSizeList>
|
||||
)
|
||||
}
|
57
src/components/SearchModal/TokenSearchModal.tsx → src/components/SearchModal/CurrencySearchModal.tsx
57
src/components/SearchModal/TokenSearchModal.tsx → src/components/SearchModal/CurrencySearchModal.tsx
@ -1,4 +1,4 @@
|
||||
import { Token } from '@uniswap/sdk'
|
||||
import { Currency, Token } from '@uniswap/sdk'
|
||||
import React, { KeyboardEvent, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -8,7 +8,7 @@ import Card from '../../components/Card'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokens, useToken } from '../../hooks/Tokens'
|
||||
import useInterval from '../../hooks/useInterval'
|
||||
import { useAllTokenBalancesTreatingWETHasETH, useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import { useAllTokenBalances, useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { CloseIcon, LinkStyledButton } from '../../theme'
|
||||
import { isAddress } from '../../utils'
|
||||
import Column from '../Column'
|
||||
@ -20,30 +20,28 @@ import CommonBases from './CommonBases'
|
||||
import { filterTokens } from './filtering'
|
||||
import { useTokenComparator } from './sorting'
|
||||
import { PaddedColumn, SearchInput } from './styleds'
|
||||
import TokenList from './TokenList'
|
||||
import CurrencyList from './CurrencyList'
|
||||
import SortButton from './SortButton'
|
||||
|
||||
interface TokenSearchModalProps {
|
||||
interface CurrencySearchModalProps {
|
||||
isOpen?: boolean
|
||||
onDismiss?: () => void
|
||||
hiddenToken?: string
|
||||
hiddenCurrency?: Currency
|
||||
showSendWithSwap?: boolean
|
||||
onTokenSelect?: (address: string) => void
|
||||
otherSelectedTokenAddress?: string
|
||||
otherSelectedText?: string
|
||||
onCurrencySelect?: (currency: Currency) => void
|
||||
otherSelectedCurrency?: Currency
|
||||
showCommonBases?: boolean
|
||||
}
|
||||
|
||||
export default function TokenSearchModal({
|
||||
export default function CurrencySearchModal({
|
||||
isOpen,
|
||||
onDismiss,
|
||||
onTokenSelect,
|
||||
hiddenToken,
|
||||
onCurrencySelect,
|
||||
hiddenCurrency,
|
||||
showSendWithSwap,
|
||||
otherSelectedTokenAddress,
|
||||
otherSelectedText,
|
||||
otherSelectedCurrency,
|
||||
showCommonBases = false
|
||||
}: TokenSearchModalProps) {
|
||||
}: CurrencySearchModalProps) {
|
||||
const { t } = useTranslation()
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
@ -55,8 +53,8 @@ export default function TokenSearchModal({
|
||||
|
||||
// if the current input is an address, and we don't have the token in context, try to fetch it and import
|
||||
const searchToken = useToken(searchQuery)
|
||||
const searchTokenBalance = useTokenBalanceTreatingWETHasETH(account, searchToken)
|
||||
const allTokenBalances_ = useAllTokenBalancesTreatingWETHasETH()
|
||||
const searchTokenBalance = useTokenBalance(account, searchToken)
|
||||
const allTokenBalances_ = useAllTokenBalances()
|
||||
const allTokenBalances = searchToken
|
||||
? {
|
||||
[searchToken.address]: searchTokenBalance
|
||||
@ -87,12 +85,12 @@ export default function TokenSearchModal({
|
||||
]
|
||||
}, [filteredTokens, searchQuery, searchToken, tokenComparator])
|
||||
|
||||
const handleTokenSelect = useCallback(
|
||||
(address: string) => {
|
||||
onTokenSelect(address)
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
onCurrencySelect(currency)
|
||||
onDismiss()
|
||||
},
|
||||
[onDismiss, onTokenSelect]
|
||||
[onDismiss, onCurrencySelect]
|
||||
)
|
||||
|
||||
// clear the input on open
|
||||
@ -129,11 +127,11 @@ export default function TokenSearchModal({
|
||||
filteredSortedTokens[0].symbol.toLowerCase() === searchQuery.trim().toLowerCase() ||
|
||||
filteredSortedTokens.length === 1
|
||||
) {
|
||||
handleTokenSelect(filteredSortedTokens[0].address)
|
||||
handleCurrencySelect(filteredSortedTokens[0])
|
||||
}
|
||||
}
|
||||
},
|
||||
[filteredSortedTokens, handleTokenSelect, searchQuery]
|
||||
[filteredSortedTokens, handleCurrencySelect, searchQuery]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -174,7 +172,7 @@ export default function TokenSearchModal({
|
||||
/>
|
||||
</Tooltip>
|
||||
{showCommonBases && (
|
||||
<CommonBases chainId={chainId} onSelect={handleTokenSelect} selectedTokenAddress={hiddenToken} />
|
||||
<CommonBases chainId={chainId} onSelect={handleCurrencySelect} selectedCurrency={hiddenCurrency} />
|
||||
)}
|
||||
<RowBetween>
|
||||
<Text fontSize={14} fontWeight={500}>
|
||||
@ -184,13 +182,12 @@ export default function TokenSearchModal({
|
||||
</RowBetween>
|
||||
</PaddedColumn>
|
||||
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
|
||||
<TokenList
|
||||
tokens={filteredSortedTokens}
|
||||
allTokenBalances={allTokenBalances}
|
||||
onTokenSelect={handleTokenSelect}
|
||||
otherSelectedText={otherSelectedText}
|
||||
otherToken={otherSelectedTokenAddress}
|
||||
selectedToken={hiddenToken}
|
||||
<CurrencyList
|
||||
currencies={filteredSortedTokens}
|
||||
allBalances={allTokenBalances}
|
||||
onCurrencySelect={handleCurrencySelect}
|
||||
otherCurrency={otherSelectedCurrency}
|
||||
selectedCurrency={hiddenCurrency}
|
||||
showSendWithSwap={showSendWithSwap}
|
||||
/>
|
||||
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
|
@ -1,5 +1,5 @@
|
||||
import { isAddress } from '../../utils'
|
||||
import { Pair, Token } from '@uniswap/sdk'
|
||||
import { Token } from '@uniswap/sdk'
|
||||
|
||||
export function filterTokens(tokens: Token[], search: string): Token[] {
|
||||
if (search.length === 0) return tokens
|
||||
@ -34,27 +34,3 @@ export function filterTokens(tokens: Token[], search: string): Token[] {
|
||||
return matchesSearch(symbol) || matchesSearch(name)
|
||||
})
|
||||
}
|
||||
|
||||
export function filterPairs(pairs: Pair[], search: string): Pair[] {
|
||||
if (search.trim().length === 0) return pairs
|
||||
|
||||
const addressSearch = isAddress(search)
|
||||
if (addressSearch) {
|
||||
return pairs.filter(p => {
|
||||
return (
|
||||
p.token0.address === addressSearch ||
|
||||
p.token1.address === addressSearch ||
|
||||
p.liquidityToken.address === addressSearch
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const lowerSearch = search.toLowerCase()
|
||||
return pairs.filter(pair => {
|
||||
const pairExpressionA = `${pair.token0.symbol}/${pair.token1.symbol}`.toLowerCase()
|
||||
if (pairExpressionA.startsWith(lowerSearch)) return true
|
||||
const pairExpressionB = `${pair.token1.symbol}/${pair.token0.symbol}`.toLowerCase()
|
||||
if (pairExpressionB.startsWith(lowerSearch)) return true
|
||||
return filterTokens([pair.token0, pair.token1], search).length > 0
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Token, TokenAmount, WETH, Pair } from '@uniswap/sdk'
|
||||
import { Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import { DUMMY_PAIRS_TO_PIN } from '../../constants'
|
||||
import { useAllTokenBalances } from '../../state/wallet/hooks'
|
||||
|
||||
// compare two token amounts with highest one coming first
|
||||
function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
|
||||
@ -16,26 +15,6 @@ function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// compare two pairs, favoring "pinned" pairs, and falling back to balances
|
||||
export function pairComparator(pairA: Pair, pairB: Pair, balanceA?: TokenAmount, balanceB?: TokenAmount) {
|
||||
const aShouldBePinned =
|
||||
DUMMY_PAIRS_TO_PIN[pairA?.token0?.chainId]?.some(
|
||||
dummyPairToPin => dummyPairToPin.liquidityToken.address === pairA?.liquidityToken?.address
|
||||
) ?? false
|
||||
const bShouldBePinned =
|
||||
DUMMY_PAIRS_TO_PIN[pairB?.token0?.chainId]?.some(
|
||||
dummyPairToPin => dummyPairToPin.liquidityToken.address === pairB?.liquidityToken?.address
|
||||
) ?? false
|
||||
|
||||
if (aShouldBePinned && !bShouldBePinned) {
|
||||
return -1
|
||||
} else if (!aShouldBePinned && bShouldBePinned) {
|
||||
return 1
|
||||
} else {
|
||||
return balanceComparator(balanceA, balanceB)
|
||||
}
|
||||
}
|
||||
|
||||
function getTokenComparator(
|
||||
weth: Token | undefined,
|
||||
balances: { [tokenAddress: string]: TokenAmount }
|
||||
@ -65,7 +44,7 @@ function getTokenComparator(
|
||||
export function useTokenComparator(inverted: boolean): (tokenA: Token, tokenB: Token) => number {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const weth = WETH[chainId]
|
||||
const balances = useAllTokenBalancesTreatingWETHasETH()
|
||||
const balances = useAllTokenBalances()
|
||||
const comparator = useMemo(() => getTokenComparator(weth, balances ?? {}), [balances, weth])
|
||||
return useMemo(() => {
|
||||
if (inverted) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Token } from '@uniswap/sdk'
|
||||
import { Currency, Token } from '@uniswap/sdk'
|
||||
import { transparentize } from 'polished'
|
||||
import React, { useMemo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
@ -11,7 +11,7 @@ import { ExternalLink, TYPE } from '../../theme'
|
||||
import { getEtherscanLink, isDefaultToken } from '../../utils'
|
||||
import PropsOfExcluding from '../../utils/props-of-excluding'
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
const Wrapper = styled.div<{ error: boolean }>`
|
||||
background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)};
|
||||
@ -103,7 +103,7 @@ export default function TokenWarningCard({ token, ...rest }: TokenWarningCardPro
|
||||
<QuestionHelper text={duplicateNameOrSymbol ? DUPLICATE_NAME_HELP_TEXT : HELP_TEXT} />
|
||||
</Row>
|
||||
<Row>
|
||||
<TokenLogo address={token.address} />
|
||||
<CurrencyLogo currency={token} />
|
||||
<div style={{ fontWeight: 500 }}>
|
||||
{token && token.name && token.symbol && token.name !== token.symbol
|
||||
? `${token.name} (${token.symbol})`
|
||||
@ -127,11 +127,13 @@ const WarningContainer = styled.div`
|
||||
padding-right: 1rem;
|
||||
`
|
||||
|
||||
export function TokenWarningCards({ tokens }: { tokens: { [field in Field]?: Token } }) {
|
||||
export function TokenWarningCards({ currencies }: { currencies: { [field in Field]?: Currency } }) {
|
||||
return (
|
||||
<WarningContainer>
|
||||
{Object.keys(tokens).map(field =>
|
||||
tokens[field] ? <TokenWarningCard style={{ marginBottom: 14 }} key={field} token={tokens[field]} /> : null
|
||||
{Object.keys(currencies).map(field =>
|
||||
currencies[field] instanceof Token ? (
|
||||
<TokenWarningCard style={{ marginBottom: 14 }} key={field} token={currencies[field]} />
|
||||
) : null
|
||||
)}
|
||||
</WarningContainer>
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Popover, { PopoverProps } from '../Popover'
|
||||
|
||||
@ -16,3 +16,16 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> {
|
||||
export default function Tooltip({ text, ...rest }: TooltipProps) {
|
||||
return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} />
|
||||
}
|
||||
|
||||
export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show'>) {
|
||||
const [show, setShow] = useState(false)
|
||||
const open = useCallback(() => setShow(true), [setShow])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
return (
|
||||
<Tooltip {...rest} show={show}>
|
||||
<div onMouseEnter={open} onMouseLeave={close}>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
@ -126,6 +126,12 @@ function recentTransactionsOnly(a: TransactionDetails) {
|
||||
return new Date().getTime() - a.addedTime < 86_400_000
|
||||
}
|
||||
|
||||
const SOCK = (
|
||||
<span role="img" aria-label="has socks emoji" style={{ marginTop: -4, marginBottom: -4 }}>
|
||||
🧦
|
||||
</span>
|
||||
)
|
||||
|
||||
export default function Web3Status() {
|
||||
const { t } = useTranslation()
|
||||
const { active, account, connector, error } = useWeb3React()
|
||||
@ -187,9 +193,10 @@ export default function Web3Status() {
|
||||
<Text>{pending?.length} Pending</Text> <Loader stroke="white" />
|
||||
</RowBetween>
|
||||
) : (
|
||||
<Text>
|
||||
{hasSocks ? '🧦' : ''} {ENSName || shortenAddress(account)}
|
||||
</Text>
|
||||
<>
|
||||
{hasSocks ? SOCK : null}
|
||||
<Text>{ENSName || shortenAddress(account)}</Text>
|
||||
</>
|
||||
)}
|
||||
{!hasPendingTransactions && getStatusIcon()}
|
||||
</Web3StatusConnected>
|
||||
|
@ -31,8 +31,10 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
|
||||
<RowFixed>
|
||||
<TYPE.black color={theme.text1} fontSize={14}>
|
||||
{isExactIn
|
||||
? `${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} ${trade.outputAmount.token.symbol}` ?? '-'
|
||||
: `${slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4)} ${trade.inputAmount.token.symbol}` ?? '-'}
|
||||
? `${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} ${trade.outputAmount.currency.symbol}` ??
|
||||
'-'
|
||||
: `${slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4)} ${trade.inputAmount.currency.symbol}` ??
|
||||
'-'}
|
||||
</TYPE.black>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
@ -54,7 +56,7 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
|
||||
<QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." />
|
||||
</RowFixed>
|
||||
<TYPE.black fontSize={14} color={theme.text1}>
|
||||
{realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.token.symbol}` : '-'}
|
||||
{realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.currency.symbol}` : '-'}
|
||||
</TYPE.black>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Percent, TokenAmount, Trade, TradeType } from '@uniswap/sdk'
|
||||
import { CurrencyAmount, Percent, Trade, TradeType } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { Repeat } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
@ -29,10 +29,10 @@ export default function SwapModalFooter({
|
||||
showInverted: boolean
|
||||
setShowInverted: (inverted: boolean) => void
|
||||
severity: number
|
||||
slippageAdjustedAmounts?: { [field in Field]?: TokenAmount }
|
||||
slippageAdjustedAmounts?: { [field in Field]?: CurrencyAmount }
|
||||
onSwap: () => any
|
||||
parsedAmounts?: { [field in Field]?: TokenAmount }
|
||||
realizedLPFee?: TokenAmount
|
||||
parsedAmounts?: { [field in Field]?: CurrencyAmount }
|
||||
realizedLPFee?: CurrencyAmount
|
||||
priceImpactWithoutFee?: Percent
|
||||
confirmText: string
|
||||
}) {
|
||||
@ -84,8 +84,8 @@ export default function SwapModalFooter({
|
||||
{parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && (
|
||||
<TYPE.black fontSize={14} marginLeft={'4px'}>
|
||||
{trade?.tradeType === TradeType.EXACT_INPUT
|
||||
? parsedAmounts[Field.OUTPUT]?.token?.symbol
|
||||
: parsedAmounts[Field.INPUT]?.token?.symbol}
|
||||
? parsedAmounts[Field.OUTPUT]?.currency?.symbol
|
||||
: parsedAmounts[Field.INPUT]?.currency?.symbol}
|
||||
</TYPE.black>
|
||||
)}
|
||||
</RowFixed>
|
||||
@ -107,7 +107,7 @@ export default function SwapModalFooter({
|
||||
<QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." />
|
||||
</RowFixed>
|
||||
<TYPE.black fontSize={14}>
|
||||
{realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.token?.symbol : '-'}
|
||||
{realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.currency?.symbol : '-'}
|
||||
</TYPE.black>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Token, TokenAmount } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { ArrowDown } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
@ -8,20 +8,20 @@ import { TYPE } from '../../theme'
|
||||
import { isAddress, shortenAddress } from '../../utils'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import { TruncatedText } from './styleds'
|
||||
|
||||
export default function SwapModalHeader({
|
||||
tokens,
|
||||
currencies,
|
||||
formattedAmounts,
|
||||
slippageAdjustedAmounts,
|
||||
priceImpactSeverity,
|
||||
independentField,
|
||||
recipient
|
||||
}: {
|
||||
tokens: { [field in Field]?: Token }
|
||||
currencies: { [field in Field]?: Currency }
|
||||
formattedAmounts: { [field in Field]?: string }
|
||||
slippageAdjustedAmounts: { [field in Field]?: TokenAmount }
|
||||
slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
priceImpactSeverity: number
|
||||
independentField: Field
|
||||
recipient: string | null
|
||||
@ -35,9 +35,9 @@ export default function SwapModalHeader({
|
||||
{formattedAmounts[Field.INPUT]}
|
||||
</TruncatedText>
|
||||
<RowFixed gap="4px">
|
||||
<TokenLogo address={tokens[Field.INPUT]?.address} size={'24px'} />
|
||||
<CurrencyLogo currency={currencies[Field.INPUT]} size={'24px'} />
|
||||
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
|
||||
{tokens[Field.INPUT]?.symbol}
|
||||
{currencies[Field.INPUT]?.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
@ -49,9 +49,9 @@ export default function SwapModalHeader({
|
||||
{formattedAmounts[Field.OUTPUT]}
|
||||
</TruncatedText>
|
||||
<RowFixed gap="4px">
|
||||
<TokenLogo address={tokens[Field.OUTPUT]?.address} size={'24px'} />
|
||||
<CurrencyLogo currency={currencies[Field.OUTPUT]} size={'24px'} />
|
||||
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
|
||||
{tokens[Field.OUTPUT]?.symbol}
|
||||
{currencies[Field.OUTPUT]?.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
@ -60,7 +60,7 @@ export default function SwapModalHeader({
|
||||
<TYPE.italic textAlign="left" style={{ width: '100%' }}>
|
||||
{`Output is estimated. You will receive at least `}
|
||||
<b>
|
||||
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {tokens[Field.OUTPUT]?.symbol}
|
||||
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {currencies[Field.OUTPUT]?.symbol}
|
||||
</b>
|
||||
{' or the transaction will revert.'}
|
||||
</TYPE.italic>
|
||||
@ -68,7 +68,7 @@ export default function SwapModalHeader({
|
||||
<TYPE.italic textAlign="left" style={{ width: '100%' }}>
|
||||
{`Input is estimated. You will sell at most `}
|
||||
<b>
|
||||
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {tokens[Field.INPUT]?.symbol}
|
||||
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {currencies[Field.INPUT]?.symbol}
|
||||
</b>
|
||||
{' or the transaction will revert.'}
|
||||
</TYPE.italic>
|
||||
|
@ -4,7 +4,7 @@ import { ChevronRight } from 'react-feather'
|
||||
import { Flex } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { TYPE } from '../../theme'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
export default memo(function SwapRoute({ trade }: { trade: Trade }) {
|
||||
const theme = useContext(ThemeContext)
|
||||
@ -24,7 +24,7 @@ export default memo(function SwapRoute({ trade }: { trade: Trade }) {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<Flex my="0.5rem" alignItems="center" style={{ flexShrink: 0 }}>
|
||||
<TokenLogo address={token.address} size="1.5rem" />
|
||||
<CurrencyLogo currency={token} size="1.5rem" />
|
||||
<TYPE.black fontSize={14} color={theme.text1} ml="0.5rem">
|
||||
{token.symbol}
|
||||
</TYPE.black>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Price, Token } from '@uniswap/sdk'
|
||||
import { Currency, Price } from '@uniswap/sdk'
|
||||
import { useContext } from 'react'
|
||||
import { Repeat } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
@ -8,21 +8,27 @@ import { StyledBalanceMaxMini } from './styleds'
|
||||
|
||||
interface TradePriceProps {
|
||||
price?: Price
|
||||
inputToken?: Token
|
||||
outputToken?: Token
|
||||
inputCurrency?: Currency
|
||||
outputCurrency?: Currency
|
||||
showInverted: boolean
|
||||
setShowInverted: (showInverted: boolean) => void
|
||||
}
|
||||
|
||||
export default function TradePrice({ price, inputToken, outputToken, showInverted, setShowInverted }: TradePriceProps) {
|
||||
export default function TradePrice({
|
||||
price,
|
||||
inputCurrency,
|
||||
outputCurrency,
|
||||
showInverted,
|
||||
setShowInverted
|
||||
}: TradePriceProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6)
|
||||
|
||||
const show = Boolean(inputToken && outputToken)
|
||||
const show = Boolean(inputCurrency && outputCurrency)
|
||||
const label = showInverted
|
||||
? `${outputToken?.symbol} per ${inputToken?.symbol}`
|
||||
: `${inputToken?.symbol} per ${outputToken?.symbol}`
|
||||
? `${outputCurrency?.symbol} per ${inputCurrency?.symbol}`
|
||||
: `${inputCurrency?.symbol} per ${outputCurrency?.symbol}`
|
||||
|
||||
return (
|
||||
<Text
|
||||
|
@ -46,7 +46,13 @@ class MiniRpcProvider implements AsyncSendable {
|
||||
.catch(error => callback(error, null))
|
||||
}
|
||||
|
||||
public readonly request = async (method: string, params?: unknown[] | object): Promise<unknown> => {
|
||||
public readonly request = async (
|
||||
method: string | { method: string; params: unknown[] },
|
||||
params?: unknown[] | object
|
||||
): Promise<unknown> => {
|
||||
if (typeof method !== 'string') {
|
||||
return this.request(method.method, method.params)
|
||||
}
|
||||
const response = await fetch(this.url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
|
279
src/constants/abis/weth.json
Normal file
279
src/constants/abis/weth.json
Normal file
@ -0,0 +1,279 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "guy",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "deposit",
|
||||
"outputs": [],
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "guy",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Withdrawal",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
import { ChainId, JSBI, Percent, Token, WETH, Pair, TokenAmount } from '@uniswap/sdk'
|
||||
import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk'
|
||||
|
||||
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
|
||||
import { COMP, DAI, MKR, USDC, USDT } from './tokens/mainnet'
|
||||
@ -36,38 +36,14 @@ export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = {
|
||||
[ChainId.MAINNET]: [...WETH_ONLY[ChainId.MAINNET], DAI, USDC, USDT]
|
||||
}
|
||||
|
||||
export const DUMMY_PAIRS_TO_PIN: { readonly [chainId in ChainId]?: Pair[] } = {
|
||||
export const PINNED_PAIRS: { readonly [chainId in ChainId]?: [Token, Token][] } = {
|
||||
[ChainId.MAINNET]: [
|
||||
new Pair(
|
||||
new TokenAmount(
|
||||
[
|
||||
new Token(ChainId.MAINNET, '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 8, 'cDAI', 'Compound Dai'),
|
||||
'0'
|
||||
),
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'),
|
||||
'0'
|
||||
)
|
||||
),
|
||||
new Pair(
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C'),
|
||||
'0'
|
||||
),
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD'),
|
||||
'0'
|
||||
)
|
||||
),
|
||||
new Pair(
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
|
||||
'0'
|
||||
),
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD'),
|
||||
'0'
|
||||
)
|
||||
)
|
||||
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin')
|
||||
],
|
||||
[USDC, USDT],
|
||||
[DAI, USDT]
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -14,14 +14,6 @@ export const ALL_TOKENS: AllTokens = [
|
||||
...KOVAN_TOKENS,
|
||||
...ROPSTEN_TOKENS
|
||||
]
|
||||
// remap WETH to ETH
|
||||
.map(token => {
|
||||
if (token.equals(WETH[token.chainId])) {
|
||||
;(token as any).symbol = 'ETH'
|
||||
;(token as any).name = 'Ether'
|
||||
}
|
||||
return token
|
||||
})
|
||||
// put into an object
|
||||
.reduce<AllTokens>(
|
||||
(tokenMap, token) => {
|
||||
|
@ -1,18 +1,33 @@
|
||||
import { Token, TokenAmount, Pair } from '@uniswap/sdk'
|
||||
import { TokenAmount, Pair, Currency } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||
import { Interface } from '@ethersproject/abi'
|
||||
import { useActiveWeb3React } from '../hooks'
|
||||
|
||||
import { useMultipleContractSingleData } from '../state/multicall/hooks'
|
||||
import { wrappedCurrency } from '../utils/wrappedCurrency'
|
||||
|
||||
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)
|
||||
|
||||
/*
|
||||
* if loading, return undefined
|
||||
* if no pair created yet, return null
|
||||
* if pair already created (even if 0 reserves), return pair
|
||||
*/
|
||||
export function usePairs(tokens: [Token | undefined, Token | undefined][]): (undefined | Pair | null)[] {
|
||||
export enum PairState {
|
||||
LOADING,
|
||||
NOT_EXISTS,
|
||||
EXISTS,
|
||||
INVALID
|
||||
}
|
||||
|
||||
export function usePairs(currencies: [Currency | undefined, Currency | undefined][]): [PairState, Pair | null][] {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
const tokens = useMemo(
|
||||
() =>
|
||||
currencies.map(([currencyA, currencyB]) => [
|
||||
wrappedCurrency(currencyA, chainId),
|
||||
wrappedCurrency(currencyB, chainId)
|
||||
]),
|
||||
[chainId, currencies]
|
||||
)
|
||||
|
||||
const pairAddresses = useMemo(
|
||||
() =>
|
||||
tokens.map(([tokenA, tokenB]) => {
|
||||
@ -29,15 +44,19 @@ export function usePairs(tokens: [Token | undefined, Token | undefined][]): (und
|
||||
const tokenA = tokens[i][0]
|
||||
const tokenB = tokens[i][1]
|
||||
|
||||
if (loading || !tokenA || !tokenB) return undefined
|
||||
if (!reserves) return null
|
||||
if (loading) return [PairState.LOADING, null]
|
||||
if (!tokenA || !tokenB || tokenA.equals(tokenB)) return [PairState.INVALID, null]
|
||||
if (!reserves) return [PairState.NOT_EXISTS, null]
|
||||
const { reserve0, reserve1 } = reserves
|
||||
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
|
||||
return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
|
||||
return [
|
||||
PairState.EXISTS,
|
||||
new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
|
||||
]
|
||||
})
|
||||
}, [results, tokens])
|
||||
}
|
||||
|
||||
export function usePair(tokenA?: Token, tokenB?: Token): undefined | Pair | null {
|
||||
export function usePair(tokenA?: Currency, tokenB?: Currency): [PairState, Pair | null] {
|
||||
return usePairs([[tokenA, tokenB]])[0]
|
||||
}
|
||||
|
@ -1,4 +1,20 @@
|
||||
import { JSBI, Pair, Percent, Route, Token, TokenAmount, Trade, TradeType, WETH } from '@uniswap/sdk'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import {
|
||||
BigintIsh,
|
||||
Currency,
|
||||
CurrencyAmount,
|
||||
currencyEquals,
|
||||
ETHER,
|
||||
JSBI,
|
||||
Pair,
|
||||
Percent,
|
||||
Route,
|
||||
Token,
|
||||
TokenAmount,
|
||||
Trade,
|
||||
TradeType,
|
||||
WETH
|
||||
} from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { useActiveWeb3React } from '../hooks'
|
||||
import { useAllTokens } from '../hooks/Tokens'
|
||||
@ -6,7 +22,6 @@ import { useV1FactoryContract } from '../hooks/useContract'
|
||||
import { Version } from '../hooks/useToggledVersion'
|
||||
import { NEVER_RELOAD, useSingleCallResult, useSingleContractMultipleData } from '../state/multicall/hooks'
|
||||
import { useETHBalances, useTokenBalance, useTokenBalances } from '../state/wallet/hooks'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
|
||||
export function useV1ExchangeAddress(tokenAddress?: string): string | undefined {
|
||||
const contract = useV1FactoryContract()
|
||||
@ -15,18 +30,25 @@ export function useV1ExchangeAddress(tokenAddress?: string): string | undefined
|
||||
return useSingleCallResult(contract, 'getExchange', inputs)?.result?.[0]
|
||||
}
|
||||
|
||||
class MockV1Pair extends Pair {}
|
||||
export class MockV1Pair extends Pair {
|
||||
constructor(etherAmount: BigintIsh, tokenAmount: TokenAmount) {
|
||||
super(tokenAmount, new TokenAmount(WETH[tokenAmount.token.chainId], etherAmount))
|
||||
}
|
||||
}
|
||||
|
||||
function useMockV1Pair(token?: Token): MockV1Pair | undefined {
|
||||
const isWETH: boolean = token && WETH[token.chainId] ? token.equals(WETH[token.chainId]) : false
|
||||
function useMockV1Pair(inputCurrency?: Currency): MockV1Pair | undefined {
|
||||
const token = inputCurrency instanceof Token ? inputCurrency : undefined
|
||||
|
||||
const isWETH = Boolean(token && token.equals(WETH[token.chainId]))
|
||||
const v1PairAddress = useV1ExchangeAddress(isWETH ? undefined : token?.address)
|
||||
const tokenBalance = useTokenBalance(v1PairAddress, token)
|
||||
const ETHBalance = useETHBalances([v1PairAddress])[v1PairAddress ?? '']
|
||||
|
||||
return tokenBalance && ETHBalance && token
|
||||
? new MockV1Pair(tokenBalance, new TokenAmount(WETH[token.chainId], ETHBalance.toString()))
|
||||
: undefined
|
||||
return useMemo(
|
||||
() =>
|
||||
token && tokenBalance && ETHBalance && inputCurrency ? new MockV1Pair(ETHBalance.raw, tokenBalance) : undefined,
|
||||
[ETHBalance, inputCurrency, token, tokenBalance]
|
||||
)
|
||||
}
|
||||
|
||||
// returns all v1 exchange addresses in the user's token list
|
||||
@ -41,8 +63,7 @@ export function useAllTokenV1Exchanges(): { [exchangeAddress: string]: Token } {
|
||||
() =>
|
||||
data?.reduce<{ [exchangeAddress: string]: Token }>((memo, { result }, ix) => {
|
||||
if (result?.[0] && result[0] !== AddressZero) {
|
||||
const token = allTokens[args[ix][0]]
|
||||
memo[result[0]] = token
|
||||
memo[result[0]] = allTokens[args[ix][0]]
|
||||
}
|
||||
return memo
|
||||
}, {}) ?? {},
|
||||
@ -56,13 +77,13 @@ export function useUserHasLiquidityInAllTokens(): boolean | undefined {
|
||||
|
||||
const exchanges = useAllTokenV1Exchanges()
|
||||
|
||||
const fakeLiquidityTokens = useMemo(
|
||||
const v1ExchangeLiquidityTokens = useMemo(
|
||||
() =>
|
||||
chainId ? Object.keys(exchanges).map(address => new Token(chainId, address, 18, 'UNI-V1', 'Uniswap V1')) : [],
|
||||
[chainId, exchanges]
|
||||
)
|
||||
|
||||
const balances = useTokenBalances(account ?? undefined, fakeLiquidityTokens)
|
||||
const balances = useTokenBalances(account ?? undefined, v1ExchangeLiquidityTokens)
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
@ -79,32 +100,30 @@ export function useUserHasLiquidityInAllTokens(): boolean | undefined {
|
||||
*/
|
||||
export function useV1Trade(
|
||||
isExactIn?: boolean,
|
||||
inputToken?: Token,
|
||||
outputToken?: Token,
|
||||
exactAmount?: TokenAmount
|
||||
inputCurrency?: Currency,
|
||||
outputCurrency?: Currency,
|
||||
exactAmount?: CurrencyAmount
|
||||
): Trade | undefined {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
// get the mock v1 pairs
|
||||
const inputPair = useMockV1Pair(inputToken)
|
||||
const outputPair = useMockV1Pair(outputToken)
|
||||
const inputPair = useMockV1Pair(inputCurrency)
|
||||
const outputPair = useMockV1Pair(outputCurrency)
|
||||
|
||||
const inputIsWETH = (inputToken && chainId && WETH[chainId] && inputToken.equals(WETH[chainId])) ?? false
|
||||
const outputIsWETH = (outputToken && chainId && WETH[chainId] && outputToken.equals(WETH[chainId])) ?? false
|
||||
const inputIsETH = inputCurrency === ETHER
|
||||
const outputIsETH = outputCurrency === ETHER
|
||||
|
||||
// construct a direct or through ETH v1 route
|
||||
let pairs: Pair[] = []
|
||||
if (inputIsWETH && outputPair) {
|
||||
if (inputIsETH && outputPair) {
|
||||
pairs = [outputPair]
|
||||
} else if (outputIsWETH && inputPair) {
|
||||
} else if (outputIsETH && inputPair) {
|
||||
pairs = [inputPair]
|
||||
}
|
||||
// if neither are WETH, it's token-to-token (if they both exist)
|
||||
// if neither are ETH, it's token-to-token (if they both exist)
|
||||
else if (inputPair && outputPair) {
|
||||
pairs = [inputPair, outputPair]
|
||||
}
|
||||
|
||||
const route = inputToken && pairs && pairs.length > 0 && new Route(pairs, inputToken)
|
||||
const route = inputCurrency && pairs && pairs.length > 0 && new Route(pairs, inputCurrency)
|
||||
let v1Trade: Trade | undefined
|
||||
try {
|
||||
v1Trade =
|
||||
@ -125,14 +144,13 @@ export function getTradeVersion(trade?: Trade): Version | undefined {
|
||||
// returns the v1 exchange against which a trade should be executed
|
||||
export function useV1TradeExchangeAddress(trade: Trade | undefined): string | undefined {
|
||||
const tokenAddress: string | undefined = useMemo(() => {
|
||||
const tradeVersion = getTradeVersion(trade)
|
||||
const isV1 = tradeVersion === Version.v1
|
||||
return isV1
|
||||
? trade &&
|
||||
WETH[trade.inputAmount.token.chainId] &&
|
||||
trade.inputAmount.token.equals(WETH[trade.inputAmount.token.chainId])
|
||||
if (!trade) return undefined
|
||||
const isV1 = getTradeVersion(trade) === Version.v1
|
||||
if (!isV1) return undefined
|
||||
return trade.inputAmount instanceof TokenAmount
|
||||
? trade.inputAmount.token.address
|
||||
: trade.outputAmount instanceof TokenAmount
|
||||
? trade.outputAmount.token.address
|
||||
: trade?.inputAmount?.token?.address
|
||||
: undefined
|
||||
}, [trade])
|
||||
return useV1ExchangeAddress(tokenAddress)
|
||||
@ -140,7 +158,8 @@ export function useV1TradeExchangeAddress(trade: Trade | undefined): string | un
|
||||
|
||||
const ZERO_PERCENT = new Percent('0')
|
||||
const ONE_HUNDRED_PERCENT = new Percent('1')
|
||||
// returns whether tradeB is better than tradeA by at least a threshold
|
||||
|
||||
// returns whether tradeB is better than tradeA by at least a threshold percentage amount
|
||||
export function isTradeBetter(
|
||||
tradeA: Trade | undefined,
|
||||
tradeB: Trade | undefined,
|
||||
@ -150,8 +169,8 @@ export function isTradeBetter(
|
||||
|
||||
if (
|
||||
tradeA.tradeType !== tradeB.tradeType ||
|
||||
!tradeA.inputAmount.token.equals(tradeB.inputAmount.token) ||
|
||||
!tradeB.outputAmount.token.equals(tradeB.outputAmount.token)
|
||||
!currencyEquals(tradeA.inputAmount.currency, tradeB.inputAmount.currency) ||
|
||||
!currencyEquals(tradeB.outputAmount.currency, tradeB.outputAmount.currency)
|
||||
) {
|
||||
throw new Error('Trades are not comparable')
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { parseBytes32String } from '@ethersproject/strings'
|
||||
import { ChainId, Token, WETH } from '@uniswap/sdk'
|
||||
import { ChainId, Currency, ETHER, Token } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { ALL_TOKENS } from '../constants/tokens'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
|
||||
@ -15,7 +15,8 @@ export function useAllTokens(): { [address: string]: Token } {
|
||||
|
||||
return useMemo(() => {
|
||||
if (!chainId) return {}
|
||||
const tokens = userAddedTokens
|
||||
return (
|
||||
userAddedTokens
|
||||
// reduce into all ALL_TOKENS filtered by the current chain
|
||||
.reduce<{ [address: string]: Token }>(
|
||||
(tokenMap, token) => {
|
||||
@ -26,15 +27,7 @@ export function useAllTokens(): { [address: string]: Token } {
|
||||
// want to make a copy in every iteration
|
||||
{ ...ALL_TOKENS[chainId as ChainId] }
|
||||
)
|
||||
|
||||
const weth = WETH[chainId as ChainId]
|
||||
if (weth) {
|
||||
// we have to replace it as a workaround because if it is automatically
|
||||
// fetched by address it will cause an invariant when used in constructing
|
||||
// pairs since we replace the name and symbol with 'ETH' and 'Ether'
|
||||
tokens[weth.address] = weth
|
||||
}
|
||||
return tokens
|
||||
)
|
||||
}, [userAddedTokens, chainId])
|
||||
}
|
||||
|
||||
@ -100,3 +93,9 @@ export function useToken(tokenAddress?: string): Token | undefined | null {
|
||||
tokenNameBytes32.result
|
||||
])
|
||||
}
|
||||
|
||||
export function useCurrency(currencyId: string | undefined): Currency | null | undefined {
|
||||
const isETH = currencyId?.toUpperCase() === 'ETH'
|
||||
const token = useToken(isETH ? undefined : currencyId)
|
||||
return isETH ? ETHER : token
|
||||
}
|
||||
|
@ -1,17 +1,22 @@
|
||||
import { Pair, Token, TokenAmount, Trade } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount, Pair, Token, Trade } from '@uniswap/sdk'
|
||||
import flatMap from 'lodash.flatmap'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { BASES_TO_CHECK_TRADES_AGAINST } from '../constants'
|
||||
import { usePairs } from '../data/Reserves'
|
||||
import { PairState, usePairs } from '../data/Reserves'
|
||||
import { wrappedCurrency } from '../utils/wrappedCurrency'
|
||||
|
||||
import { useActiveWeb3React } from './index'
|
||||
|
||||
function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
|
||||
function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
const bases: Token[] = chainId ? BASES_TO_CHECK_TRADES_AGAINST[chainId] : []
|
||||
|
||||
const [tokenA, tokenB] = chainId
|
||||
? [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
|
||||
: [undefined, undefined]
|
||||
|
||||
const allPairCombinations: [Token | undefined, Token | undefined][] = useMemo(
|
||||
() => [
|
||||
// the direct pair
|
||||
@ -34,9 +39,9 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
|
||||
Object.values(
|
||||
allPairs
|
||||
// filter out invalid pairs
|
||||
.filter((p): p is Pair => !!p)
|
||||
.filter((result): result is [PairState.EXISTS, Pair] => Boolean(result[0] === PairState.EXISTS && result[1]))
|
||||
// filter out duplicated pairs
|
||||
.reduce<{ [pairAddress: string]: Pair }>((memo, curr) => {
|
||||
.reduce<{ [pairAddress: string]: Pair }>((memo, [, curr]) => {
|
||||
memo[curr.liquidityToken.address] = memo[curr.liquidityToken.address] ?? curr
|
||||
return memo
|
||||
}, {})
|
||||
@ -48,27 +53,32 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
|
||||
/**
|
||||
* Returns the best trade for the exact amount of tokens in to the given token out
|
||||
*/
|
||||
export function useTradeExactIn(amountIn?: TokenAmount, tokenOut?: Token): Trade | null {
|
||||
const allowedPairs = useAllCommonPairs(amountIn?.token, tokenOut)
|
||||
export function useTradeExactIn(currencyAmountIn?: CurrencyAmount, currencyOut?: Currency): Trade | null {
|
||||
const allowedPairs = useAllCommonPairs(currencyAmountIn?.currency, currencyOut)
|
||||
|
||||
return useMemo(() => {
|
||||
if (amountIn && tokenOut && allowedPairs.length > 0) {
|
||||
return Trade.bestTradeExactIn(allowedPairs, amountIn, tokenOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null
|
||||
if (currencyAmountIn && currencyOut && allowedPairs.length > 0) {
|
||||
return (
|
||||
Trade.bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null
|
||||
)
|
||||
}
|
||||
return null
|
||||
}, [allowedPairs, amountIn, tokenOut])
|
||||
}, [allowedPairs, currencyAmountIn, currencyOut])
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best trade for the token in to the exact amount of token out
|
||||
*/
|
||||
export function useTradeExactOut(tokenIn?: Token, amountOut?: TokenAmount): Trade | null {
|
||||
const allowedPairs = useAllCommonPairs(tokenIn, amountOut?.token)
|
||||
export function useTradeExactOut(currencyIn?: Currency, currencyAmountOut?: CurrencyAmount): Trade | null {
|
||||
const allowedPairs = useAllCommonPairs(currencyIn, currencyAmountOut?.currency)
|
||||
|
||||
return useMemo(() => {
|
||||
if (tokenIn && amountOut && allowedPairs.length > 0) {
|
||||
return Trade.bestTradeExactOut(allowedPairs, tokenIn, amountOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null
|
||||
if (currencyIn && currencyAmountOut && allowedPairs.length > 0) {
|
||||
return (
|
||||
Trade.bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: 3, maxNumResults: 1 })[0] ??
|
||||
null
|
||||
)
|
||||
}
|
||||
return null
|
||||
}, [allowedPairs, tokenIn, amountOut])
|
||||
}, [allowedPairs, currencyIn, currencyAmountOut])
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MaxUint256 } from '@ethersproject/constants'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trade, WETH, TokenAmount } from '@uniswap/sdk'
|
||||
import { Trade, TokenAmount, CurrencyAmount, ETHER } from '@uniswap/sdk'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { ROUTER_ADDRESS } from '../constants'
|
||||
import { useTokenAllowance } from '../data/Allowances'
|
||||
@ -22,19 +22,18 @@ export enum ApprovalState {
|
||||
|
||||
// returns a variable indicating the state of the approval and a function which approves if necessary or early returns
|
||||
export function useApproveCallback(
|
||||
amountToApprove?: TokenAmount,
|
||||
amountToApprove?: CurrencyAmount,
|
||||
spender?: string
|
||||
): [ApprovalState, () => Promise<void>] {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const currentAllowance = useTokenAllowance(amountToApprove?.token, account ?? undefined, spender)
|
||||
const pendingApproval = useHasPendingApproval(amountToApprove?.token?.address, spender)
|
||||
const token = amountToApprove instanceof TokenAmount ? amountToApprove.token : undefined
|
||||
const currentAllowance = useTokenAllowance(token, account ?? undefined, spender)
|
||||
const pendingApproval = useHasPendingApproval(token?.address, spender)
|
||||
|
||||
// check the current approval status
|
||||
const approvalState: ApprovalState = useMemo(() => {
|
||||
if (!amountToApprove || !spender) return ApprovalState.UNKNOWN
|
||||
// we treat WETH as ETH which requires no approvals
|
||||
if (amountToApprove.token.equals(WETH[amountToApprove.token.chainId])) return ApprovalState.APPROVED
|
||||
if (amountToApprove.currency === ETHER) return ApprovalState.APPROVED
|
||||
// we might not have enough data to know whether or not we need to approve
|
||||
if (!currentAllowance) return ApprovalState.UNKNOWN
|
||||
|
||||
@ -46,7 +45,7 @@ export function useApproveCallback(
|
||||
: ApprovalState.APPROVED
|
||||
}, [amountToApprove, currentAllowance, pendingApproval, spender])
|
||||
|
||||
const tokenContract = useTokenContract(amountToApprove?.token?.address)
|
||||
const tokenContract = useTokenContract(token?.address)
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
const approve = useCallback(async (): Promise<void> => {
|
||||
@ -54,6 +53,10 @@ export function useApproveCallback(
|
||||
console.error('approve was called unnecessarily')
|
||||
return
|
||||
}
|
||||
if (!token) {
|
||||
console.error('no token')
|
||||
return
|
||||
}
|
||||
|
||||
if (!tokenContract) {
|
||||
console.error('tokenContract is null')
|
||||
@ -83,15 +86,15 @@ export function useApproveCallback(
|
||||
})
|
||||
.then((response: TransactionResponse) => {
|
||||
addTransaction(response, {
|
||||
summary: 'Approve ' + amountToApprove.token.symbol,
|
||||
approval: { tokenAddress: amountToApprove.token.address, spender: spender }
|
||||
summary: 'Approve ' + amountToApprove.currency.symbol,
|
||||
approval: { tokenAddress: token.address, spender: spender }
|
||||
})
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
console.debug('Failed to approve token', error)
|
||||
throw error
|
||||
})
|
||||
}, [approvalState, tokenContract, spender, amountToApprove, addTransaction])
|
||||
}, [approvalState, token, tokenContract, amountToApprove, spender, addTransaction])
|
||||
|
||||
return [approvalState, approve]
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
|
||||
// modified from https://usehooks.com/useKeyPress/
|
||||
export default function useBodyKeyDown(targetKey: string, onKeyDown: () => void, suppressOnKeyDown = false) {
|
||||
const downHandler = useCallback(
|
||||
event => {
|
||||
const {
|
||||
target: { tagName },
|
||||
key
|
||||
} = event
|
||||
if (key === targetKey && tagName === 'BODY' && !suppressOnKeyDown) {
|
||||
event.preventDefault()
|
||||
onKeyDown()
|
||||
}
|
||||
},
|
||||
[targetKey, onKeyDown, suppressOnKeyDown]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', downHandler)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', downHandler)
|
||||
}
|
||||
}, [downHandler])
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { ChainId } from '@uniswap/sdk'
|
||||
import { ChainId, WETH } from '@uniswap/sdk'
|
||||
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||
import { useMemo } from 'react'
|
||||
import { ERC20_BYTES32_ABI } from '../constants/abis/erc20'
|
||||
import UNISOCKS_ABI from '../constants/abis/unisocks.json'
|
||||
import ERC20_ABI from '../constants/abis/erc20.json'
|
||||
import WETH_ABI from '../constants/abis/weth.json'
|
||||
import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator'
|
||||
import { MULTICALL_ABI, MULTICALL_NETWORKS } from '../constants/multicall'
|
||||
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from '../constants/v1'
|
||||
@ -43,6 +44,11 @@ export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: b
|
||||
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
export function useWETHContract(withSignerIfPossible?: boolean): Contract | null {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return useContract(chainId ? WETH[chainId].address : undefined, WETH_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
export function useBytes32TokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null {
|
||||
return useContract(tokenAddress, ERC20_BYTES32_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { JSBI } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { useSingleCallResult } from '../state/multicall/hooks'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
|
||||
import { useActiveWeb3React } from './index'
|
||||
import { useSocksController } from './useContract'
|
||||
|
||||
@ -8,7 +8,7 @@ export default function useSocksBalance(): JSBI | undefined {
|
||||
const { account } = useActiveWeb3React()
|
||||
const socksContract = useSocksController()
|
||||
|
||||
const { result } = useSingleCallResult(socksContract, 'balanceOf', [account ?? undefined], { blocksPerFetch: 100 })
|
||||
const { result } = useSingleCallResult(socksContract, 'balanceOf', [account ?? undefined], NEVER_RELOAD)
|
||||
const data = result?.[0]
|
||||
return data ? JSBI.BigInt(data.toString()) : undefined
|
||||
}
|
||||
|
@ -1,61 +1,17 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { MaxUint256 } from '@ethersproject/constants'
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { Trade, TradeType, WETH } from '@uniswap/sdk'
|
||||
import { JSBI, Percent, Router, Trade, TradeType } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, ROUTER_ADDRESS } from '../constants'
|
||||
import { useTokenAllowance } from '../data/Allowances'
|
||||
import { BIPS_BASE, DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../constants'
|
||||
import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1'
|
||||
import { Field } from '../state/swap/actions'
|
||||
import { useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { calculateGasMargin, getRouterContract, shortenAddress, isAddress } from '../utils'
|
||||
import { computeSlippageAdjustedAmounts } from '../utils/prices'
|
||||
import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils'
|
||||
import v1SwapArguments from '../utils/v1SwapArguments'
|
||||
import { useActiveWeb3React } from './index'
|
||||
import { useV1ExchangeContract } from './useContract'
|
||||
import useENS from './useENS'
|
||||
import { Version } from './useToggledVersion'
|
||||
|
||||
enum SwapType {
|
||||
EXACT_TOKENS_FOR_TOKENS,
|
||||
EXACT_TOKENS_FOR_ETH,
|
||||
EXACT_ETH_FOR_TOKENS,
|
||||
TOKENS_FOR_EXACT_TOKENS,
|
||||
TOKENS_FOR_EXACT_ETH,
|
||||
ETH_FOR_EXACT_TOKENS,
|
||||
V1_EXACT_ETH_FOR_TOKENS,
|
||||
V1_EXACT_TOKENS_FOR_ETH,
|
||||
V1_EXACT_TOKENS_FOR_TOKENS,
|
||||
V1_ETH_FOR_EXACT_TOKENS,
|
||||
V1_TOKENS_FOR_EXACT_ETH,
|
||||
V1_TOKENS_FOR_EXACT_TOKENS
|
||||
}
|
||||
|
||||
function getSwapType(trade: Trade | undefined): SwapType | undefined {
|
||||
if (!trade) return undefined
|
||||
const chainId = trade.inputAmount.token.chainId
|
||||
const inputWETH = trade.inputAmount.token.equals(WETH[chainId])
|
||||
const outputWETH = trade.outputAmount.token.equals(WETH[chainId])
|
||||
const isExactIn = trade.tradeType === TradeType.EXACT_INPUT
|
||||
const isV1 = getTradeVersion(trade) === Version.v1
|
||||
if (isExactIn) {
|
||||
if (inputWETH) {
|
||||
return isV1 ? SwapType.V1_EXACT_ETH_FOR_TOKENS : SwapType.EXACT_ETH_FOR_TOKENS
|
||||
} else if (outputWETH) {
|
||||
return isV1 ? SwapType.V1_EXACT_TOKENS_FOR_ETH : SwapType.EXACT_TOKENS_FOR_ETH
|
||||
} else {
|
||||
return isV1 ? SwapType.V1_EXACT_TOKENS_FOR_TOKENS : SwapType.EXACT_TOKENS_FOR_TOKENS
|
||||
}
|
||||
} else {
|
||||
if (inputWETH) {
|
||||
return isV1 ? SwapType.V1_ETH_FOR_EXACT_TOKENS : SwapType.ETH_FOR_EXACT_TOKENS
|
||||
} else if (outputWETH) {
|
||||
return isV1 ? SwapType.V1_TOKENS_FOR_EXACT_ETH : SwapType.TOKENS_FOR_EXACT_ETH
|
||||
} else {
|
||||
return isV1 ? SwapType.V1_TOKENS_FOR_EXACT_TOKENS : SwapType.TOKENS_FOR_EXACT_TOKENS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns a function that will execute a swap, if the parameters are all valid
|
||||
// and the user has approved the slippage adjusted input amount for the trade
|
||||
export function useSwapCallback(
|
||||
@ -72,31 +28,10 @@ export function useSwapCallback(
|
||||
|
||||
const tradeVersion = getTradeVersion(trade)
|
||||
const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true)
|
||||
const inputAllowance = useTokenAllowance(
|
||||
trade?.inputAmount?.token,
|
||||
account ?? undefined,
|
||||
tradeVersion === Version.v1 ? v1Exchange?.address : ROUTER_ADDRESS
|
||||
)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!trade || !recipient || !library || !account || !tradeVersion || !chainId) return null
|
||||
|
||||
// will always be defined
|
||||
const {
|
||||
[Field.INPUT]: slippageAdjustedInput,
|
||||
[Field.OUTPUT]: slippageAdjustedOutput
|
||||
} = computeSlippageAdjustedAmounts(trade, allowedSlippage)
|
||||
|
||||
if (!slippageAdjustedInput || !slippageAdjustedOutput) return null
|
||||
|
||||
// no allowance
|
||||
if (
|
||||
!trade.inputAmount.token.equals(WETH[chainId]) &&
|
||||
(!inputAllowance || slippageAdjustedInput.greaterThan(inputAllowance))
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return async function onSwap() {
|
||||
const contract: Contract | null =
|
||||
tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange
|
||||
@ -104,123 +39,43 @@ export function useSwapCallback(
|
||||
throw new Error('Failed to get a swap contract')
|
||||
}
|
||||
|
||||
const path = trade.route.path.map(t => t.address)
|
||||
const swapMethods = []
|
||||
|
||||
const deadlineFromNow: number = Math.ceil(Date.now() / 1000) + deadline
|
||||
switch (tradeVersion) {
|
||||
case Version.v2:
|
||||
swapMethods.push(
|
||||
Router.swapCallParameters(trade, {
|
||||
feeOnTransfer: false,
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
|
||||
const swapType = getSwapType(trade)
|
||||
|
||||
// let estimate: Function, method: Function,
|
||||
let methodNames: string[],
|
||||
args: Array<string | string[] | number>,
|
||||
value: BigNumber | null = null
|
||||
switch (swapType) {
|
||||
case SwapType.EXACT_TOKENS_FOR_TOKENS:
|
||||
methodNames = ['swapExactTokensForTokens', 'swapExactTokensForTokensSupportingFeeOnTransferTokens']
|
||||
args = [
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
path,
|
||||
if (trade.tradeType === TradeType.EXACT_INPUT) {
|
||||
swapMethods.push(
|
||||
Router.swapCallParameters(trade, {
|
||||
feeOnTransfer: true,
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
deadlineFromNow
|
||||
]
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
}
|
||||
break
|
||||
case SwapType.TOKENS_FOR_EXACT_TOKENS:
|
||||
methodNames = ['swapTokensForExactTokens']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
path,
|
||||
case Version.v1:
|
||||
swapMethods.push(
|
||||
v1SwapArguments(trade, {
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
deadlineFromNow
|
||||
]
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
break
|
||||
case SwapType.EXACT_ETH_FOR_TOKENS:
|
||||
methodNames = ['swapExactETHForTokens', 'swapExactETHForTokensSupportingFeeOnTransferTokens']
|
||||
args = [slippageAdjustedOutput.raw.toString(), path, recipient, deadlineFromNow]
|
||||
value = BigNumber.from(slippageAdjustedInput.raw.toString())
|
||||
break
|
||||
case SwapType.TOKENS_FOR_EXACT_ETH:
|
||||
methodNames = ['swapTokensForExactETH']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
path,
|
||||
recipient,
|
||||
deadlineFromNow
|
||||
]
|
||||
break
|
||||
case SwapType.EXACT_TOKENS_FOR_ETH:
|
||||
methodNames = ['swapExactTokensForETH', 'swapExactTokensForETHSupportingFeeOnTransferTokens']
|
||||
args = [
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
path,
|
||||
recipient,
|
||||
deadlineFromNow
|
||||
]
|
||||
break
|
||||
case SwapType.ETH_FOR_EXACT_TOKENS:
|
||||
methodNames = ['swapETHForExactTokens']
|
||||
args = [slippageAdjustedOutput.raw.toString(), path, recipient, deadlineFromNow]
|
||||
value = BigNumber.from(slippageAdjustedInput.raw.toString())
|
||||
break
|
||||
case SwapType.V1_EXACT_ETH_FOR_TOKENS:
|
||||
methodNames = ['ethToTokenTransferInput']
|
||||
args = [slippageAdjustedOutput.raw.toString(), deadlineFromNow, recipient]
|
||||
value = BigNumber.from(slippageAdjustedInput.raw.toString())
|
||||
break
|
||||
case SwapType.V1_EXACT_TOKENS_FOR_TOKENS:
|
||||
methodNames = ['tokenToTokenTransferInput']
|
||||
args = [
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
1,
|
||||
deadlineFromNow,
|
||||
recipient,
|
||||
trade.outputAmount.token.address
|
||||
]
|
||||
break
|
||||
case SwapType.V1_EXACT_TOKENS_FOR_ETH:
|
||||
methodNames = ['tokenToEthTransferOutput']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
deadlineFromNow,
|
||||
recipient
|
||||
]
|
||||
break
|
||||
case SwapType.V1_ETH_FOR_EXACT_TOKENS:
|
||||
methodNames = ['ethToTokenTransferOutput']
|
||||
args = [slippageAdjustedOutput.raw.toString(), deadlineFromNow, recipient]
|
||||
value = BigNumber.from(slippageAdjustedInput.raw.toString())
|
||||
break
|
||||
case SwapType.V1_TOKENS_FOR_EXACT_ETH:
|
||||
methodNames = ['tokenToEthTransferOutput']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
deadlineFromNow,
|
||||
recipient
|
||||
]
|
||||
break
|
||||
case SwapType.V1_TOKENS_FOR_EXACT_TOKENS:
|
||||
methodNames = ['tokenToTokenTransferOutput']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
MaxUint256.toString(),
|
||||
deadlineFromNow,
|
||||
recipient,
|
||||
trade.outputAmount.token.address
|
||||
]
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unhandled swap type: ${swapType}`)
|
||||
}
|
||||
|
||||
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
|
||||
methodNames.map(methodName =>
|
||||
swapMethods.map(({ args, methodName, value }) =>
|
||||
contract.estimateGas[methodName](...args, value ? { value } : {})
|
||||
.then(calculateGasMargin)
|
||||
.catch(error => {
|
||||
@ -251,7 +106,7 @@ export function useSwapCallback(
|
||||
// if only 1 method exists, either:
|
||||
// a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist)
|
||||
// b) the token is FoT and the user specified an exact output, which is not allowed
|
||||
if (methodNames.length === 1) {
|
||||
if (swapMethods.length === 1) {
|
||||
throw Error(
|
||||
`An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify an exact input amount.`
|
||||
)
|
||||
@ -259,7 +114,7 @@ export function useSwapCallback(
|
||||
// if 2 methods exists, either:
|
||||
// a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist)
|
||||
// b) the token is FoT and is taking more than the specified slippage
|
||||
else if (methodNames.length === 2) {
|
||||
else if (swapMethods.length === 2) {
|
||||
throw Error(
|
||||
`An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify a slippage tolerance higher than the fee.`
|
||||
)
|
||||
@ -267,7 +122,7 @@ export function useSwapCallback(
|
||||
throw Error('This transaction would fail. Please contact support.')
|
||||
}
|
||||
} else {
|
||||
const methodName = methodNames[indexOfSuccessfulEstimation]
|
||||
const { methodName, args, value } = swapMethods[indexOfSuccessfulEstimation]
|
||||
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
|
||||
|
||||
return contract[methodName](...args, {
|
||||
@ -275,10 +130,10 @@ export function useSwapCallback(
|
||||
...(value ? { value } : {})
|
||||
})
|
||||
.then((response: any) => {
|
||||
const inputSymbol = trade.inputAmount.token.symbol
|
||||
const outputSymbol = trade.outputAmount.token.symbol
|
||||
const inputAmount = slippageAdjustedInput.toSignificant(3)
|
||||
const outputAmount = slippageAdjustedOutput.toSignificant(3)
|
||||
const inputSymbol = trade.inputAmount.currency.symbol
|
||||
const outputSymbol = trade.outputAmount.currency.symbol
|
||||
const inputAmount = trade.inputAmount.toSignificant(3)
|
||||
const outputAmount = trade.outputAmount.toSignificant(3)
|
||||
|
||||
const base = `Swap ${inputAmount} ${inputSymbol} for ${outputAmount} ${outputSymbol}`
|
||||
const withRecipient =
|
||||
@ -291,7 +146,7 @@ export function useSwapCallback(
|
||||
}`
|
||||
|
||||
const withVersion =
|
||||
tradeVersion === Version.v2 ? withRecipient : `${withRecipient} on ${tradeVersion.toUpperCase()}`
|
||||
tradeVersion === Version.v2 ? withRecipient : `${withRecipient} on ${(tradeVersion as any).toUpperCase()}`
|
||||
|
||||
addTransaction(response, {
|
||||
summary: withVersion
|
||||
@ -320,7 +175,6 @@ export function useSwapCallback(
|
||||
tradeVersion,
|
||||
chainId,
|
||||
allowedSlippage,
|
||||
inputAllowance,
|
||||
v1Exchange,
|
||||
deadline,
|
||||
recipientAddressOrName,
|
||||
|
67
src/hooks/useWrapCallback.ts
Normal file
67
src/hooks/useWrapCallback.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { Currency, currencyEquals, ETHER, WETH } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { tryParseAmount } from '../state/swap/hooks'
|
||||
import { useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { useCurrencyBalance } from '../state/wallet/hooks'
|
||||
import { useActiveWeb3React } from './index'
|
||||
import { useWETHContract } from './useContract'
|
||||
|
||||
export enum WrapType {
|
||||
NOT_APPLICABLE,
|
||||
WRAP,
|
||||
UNWRAP
|
||||
}
|
||||
|
||||
const NOT_APPLICABLE = { wrapType: WrapType.NOT_APPLICABLE }
|
||||
/**
|
||||
* Given the selected input and output currency, return a wrap callback
|
||||
* @param inputCurrency the selected input currency
|
||||
* @param outputCurrency the selected output currency
|
||||
* @param typedValue the user input value
|
||||
*/
|
||||
export default function useWrapCallback(
|
||||
inputCurrency: Currency | undefined,
|
||||
outputCurrency: Currency | undefined,
|
||||
typedValue: string | undefined
|
||||
): { wrapType: WrapType; execute?: undefined | (() => Promise<void>); error?: string } {
|
||||
const { chainId, account } = useActiveWeb3React()
|
||||
const wethContract = useWETHContract()
|
||||
const balance = useCurrencyBalance(account ?? undefined, inputCurrency)
|
||||
// we can always parse the amount typed as the input currency, since wrapping is 1:1
|
||||
const inputAmount = useMemo(() => tryParseAmount(typedValue, inputCurrency), [inputCurrency, typedValue])
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!wethContract || !chainId || !inputCurrency || !outputCurrency) return NOT_APPLICABLE
|
||||
|
||||
const sufficientBalance = inputAmount && balance && !balance.lessThan(inputAmount)
|
||||
|
||||
if (inputCurrency === ETHER && currencyEquals(WETH[chainId], outputCurrency)) {
|
||||
return {
|
||||
wrapType: WrapType.WRAP,
|
||||
execute:
|
||||
sufficientBalance && inputAmount
|
||||
? async () => {
|
||||
const txReceipt = await wethContract.deposit({ value: `0x${inputAmount.raw.toString(16)}` })
|
||||
addTransaction(txReceipt, { summary: `Wrap ${inputAmount.toSignificant(6)} ETH to WETH` })
|
||||
}
|
||||
: undefined,
|
||||
error: sufficientBalance ? undefined : 'Insufficient ETH balance'
|
||||
}
|
||||
} else if (currencyEquals(WETH[chainId], inputCurrency) && outputCurrency === ETHER) {
|
||||
return {
|
||||
wrapType: WrapType.UNWRAP,
|
||||
execute:
|
||||
sufficientBalance && inputAmount
|
||||
? async () => {
|
||||
const txReceipt = await wethContract.withdraw(`0x${inputAmount.raw.toString(16)}`)
|
||||
addTransaction(txReceipt, { summary: `Unwrap ${inputAmount.toSignificant(6)} WETH to ETH` })
|
||||
}
|
||||
: undefined,
|
||||
error: sufficientBalance ? undefined : 'Insufficient WETH balance'
|
||||
}
|
||||
} else {
|
||||
return NOT_APPLICABLE
|
||||
}
|
||||
}, [wethContract, chainId, inputCurrency, outputCurrency, inputAmount, balance, addTransaction])
|
||||
}
|
@ -1,52 +1,56 @@
|
||||
import { Fraction, Percent, Token, TokenAmount } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount, Fraction, Percent } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { ButtonPrimary } from '../../components/Button'
|
||||
import { RowBetween, RowFixed } from '../../components/Row'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import { Field } from '../../state/mint/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
export function ConfirmAddModalBottom({
|
||||
noLiquidity,
|
||||
price,
|
||||
tokens,
|
||||
currencies,
|
||||
parsedAmounts,
|
||||
poolTokenPercentage,
|
||||
onAdd
|
||||
}: {
|
||||
noLiquidity?: boolean
|
||||
price?: Fraction
|
||||
tokens: { [field in Field]?: Token }
|
||||
parsedAmounts: { [field in Field]?: TokenAmount }
|
||||
currencies: { [field in Field]?: Currency }
|
||||
parsedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
poolTokenPercentage?: Percent
|
||||
onAdd: () => void
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<RowBetween>
|
||||
<TYPE.body>{tokens[Field.TOKEN_A]?.symbol} Deposited</TYPE.body>
|
||||
<TYPE.body>{currencies[Field.CURRENCY_A]?.symbol} Deposited</TYPE.body>
|
||||
<RowFixed>
|
||||
<TokenLogo address={tokens[Field.TOKEN_A]?.address} style={{ marginRight: '8px' }} />
|
||||
<TYPE.body>{parsedAmounts[Field.TOKEN_A]?.toSignificant(6)}</TYPE.body>
|
||||
<CurrencyLogo currency={currencies[Field.CURRENCY_A]} style={{ marginRight: '8px' }} />
|
||||
<TYPE.body>{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</TYPE.body>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.body>{tokens[Field.TOKEN_B]?.symbol} Deposited</TYPE.body>
|
||||
<TYPE.body>{currencies[Field.CURRENCY_B]?.symbol} Deposited</TYPE.body>
|
||||
<RowFixed>
|
||||
<TokenLogo address={tokens[Field.TOKEN_B]?.address} style={{ marginRight: '8px' }} />
|
||||
<TYPE.body>{parsedAmounts[Field.TOKEN_B]?.toSignificant(6)}</TYPE.body>
|
||||
<CurrencyLogo currency={currencies[Field.CURRENCY_B]} style={{ marginRight: '8px' }} />
|
||||
<TYPE.body>{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</TYPE.body>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.body>Rates</TYPE.body>
|
||||
<TYPE.body>
|
||||
{`1 ${tokens[Field.TOKEN_A]?.symbol} = ${price?.toSignificant(4)} ${tokens[Field.TOKEN_B]?.symbol}`}
|
||||
{`1 ${currencies[Field.CURRENCY_A]?.symbol} = ${price?.toSignificant(4)} ${
|
||||
currencies[Field.CURRENCY_B]?.symbol
|
||||
}`}
|
||||
</TYPE.body>
|
||||
</RowBetween>
|
||||
<RowBetween style={{ justifyContent: 'flex-end' }}>
|
||||
<TYPE.body>
|
||||
{`1 ${tokens[Field.TOKEN_B]?.symbol} = ${price?.invert().toSignificant(4)} ${tokens[Field.TOKEN_A]?.symbol}`}
|
||||
{`1 ${currencies[Field.CURRENCY_B]?.symbol} = ${price?.invert().toSignificant(4)} ${
|
||||
currencies[Field.CURRENCY_A]?.symbol
|
||||
}`}
|
||||
</TYPE.body>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Fraction, Percent, Token } from '@uniswap/sdk'
|
||||
import { Currency, Fraction, Percent } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
@ -9,12 +9,12 @@ import { Field } from '../../state/mint/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
export const PoolPriceBar = ({
|
||||
tokens,
|
||||
currencies,
|
||||
noLiquidity,
|
||||
poolTokenPercentage,
|
||||
price
|
||||
}: {
|
||||
tokens: { [field in Field]?: Token }
|
||||
currencies: { [field in Field]?: Currency }
|
||||
noLiquidity?: boolean
|
||||
poolTokenPercentage?: Percent
|
||||
price?: Fraction
|
||||
@ -26,13 +26,13 @@ export const PoolPriceBar = ({
|
||||
<AutoColumn justify="center">
|
||||
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||
{tokens[Field.TOKEN_B]?.symbol} per {tokens[Field.TOKEN_A]?.symbol}
|
||||
{currencies[Field.CURRENCY_B]?.symbol} per {currencies[Field.CURRENCY_A]?.symbol}
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
<AutoColumn justify="center">
|
||||
<TYPE.black>{price?.invert().toSignificant(6) ?? '0'}</TYPE.black>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||
{tokens[Field.TOKEN_A]?.symbol} per {tokens[Field.TOKEN_B]?.symbol}
|
||||
{currencies[Field.CURRENCY_A]?.symbol} per {currencies[Field.CURRENCY_B]?.symbol}
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
<AutoColumn justify="center">
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { Token, ChainId, WETH } from '@uniswap/sdk'
|
||||
|
||||
export function currencyId(...args: [ChainId | undefined, string] | [Token]): string {
|
||||
if (args.length === 2) {
|
||||
const [chainId, tokenAddress] = args
|
||||
return chainId && tokenAddress === WETH[chainId].address ? 'ETH' : tokenAddress
|
||||
} else if (args.length === 1) {
|
||||
const [token] = args
|
||||
return currencyId(token.chainId, token.address)
|
||||
} else {
|
||||
throw new Error('unexpected call signature')
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { ChainId, Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import { Currency, currencyEquals, ETHER, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import React, { useCallback, useContext, useState } from 'react'
|
||||
import { Plus } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
@ -12,14 +12,15 @@ import { BlueCard, GreyCard, LightCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import DoubleLogo from '../../components/DoubleLogo'
|
||||
import DoubleCurrencyLogo from '../../components/DoubleLogo'
|
||||
import { AddRemoveTabs } from '../../components/NavigationTabs'
|
||||
import { MinimalPositionCard } from '../../components/PositionCard'
|
||||
import Row, { RowBetween, RowFlat } from '../../components/Row'
|
||||
|
||||
import { ROUTER_ADDRESS } from '../../constants'
|
||||
import { PairState } from '../../data/Reserves'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useToken } from '../../hooks/Tokens'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { Field } from '../../state/mint/actions'
|
||||
@ -30,18 +31,13 @@ import { useIsExpertMode, useUserDeadline, useUserSlippageTolerance } from '../.
|
||||
import { TYPE } from '../../theme'
|
||||
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
|
||||
import { maxAmountSpend } from '../../utils/maxAmountSpend'
|
||||
import { wrappedCurrency } from '../../utils/wrappedCurrency'
|
||||
import AppBody from '../AppBody'
|
||||
import { Dots, Wrapper } from '../Pool/styleds'
|
||||
import { ConfirmAddModalBottom } from './ConfirmAddModalBottom'
|
||||
import { currencyId } from './currencyId'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { PoolPriceBar } from './PoolPriceBar'
|
||||
|
||||
function useTokenByCurrencyId(chainId: ChainId | undefined, currencyId: string | undefined): Token | undefined {
|
||||
const isETH = currencyId?.toUpperCase() === 'ETH'
|
||||
const token = useToken(isETH ? undefined : currencyId)
|
||||
return isETH && chainId ? WETH[chainId] : token ?? undefined
|
||||
}
|
||||
|
||||
export default function AddLiquidity({
|
||||
match: {
|
||||
params: { currencyIdA, currencyIdB }
|
||||
@ -51,11 +47,16 @@ export default function AddLiquidity({
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const tokenA = useTokenByCurrencyId(chainId, currencyIdA)
|
||||
const tokenB = useTokenByCurrencyId(chainId, currencyIdB)
|
||||
const currencyA = useCurrency(currencyIdA)
|
||||
const currencyB = useCurrency(currencyIdB)
|
||||
|
||||
// toggle wallet when disconnected
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
const oneCurrencyIsWETH = Boolean(
|
||||
chainId &&
|
||||
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
|
||||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
|
||||
)
|
||||
|
||||
const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected
|
||||
|
||||
const expertMode = useIsExpertMode()
|
||||
|
||||
@ -63,30 +64,18 @@ export default function AddLiquidity({
|
||||
const { independentField, typedValue, otherTypedValue } = useMintState()
|
||||
const {
|
||||
dependentField,
|
||||
tokens,
|
||||
currencies,
|
||||
pair,
|
||||
tokenBalances,
|
||||
pairState,
|
||||
currencyBalances,
|
||||
parsedAmounts,
|
||||
price,
|
||||
noLiquidity,
|
||||
liquidityMinted,
|
||||
poolTokenPercentage,
|
||||
error
|
||||
} = useDerivedMintInfo(tokenA ?? undefined, tokenB ?? undefined)
|
||||
const { onUserInput } = useMintActionHandlers(noLiquidity)
|
||||
|
||||
const handleTokenAInput = useCallback(
|
||||
(field: string, value: string) => {
|
||||
return onUserInput(Field.TOKEN_A, value)
|
||||
},
|
||||
[onUserInput]
|
||||
)
|
||||
const handleTokenBInput = useCallback(
|
||||
(field: string, value: string) => {
|
||||
return onUserInput(Field.TOKEN_B, value)
|
||||
},
|
||||
[onUserInput]
|
||||
)
|
||||
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
|
||||
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
|
||||
|
||||
const isValid = !error
|
||||
|
||||
@ -106,14 +95,17 @@ export default function AddLiquidity({
|
||||
}
|
||||
|
||||
// get the max amounts user can add
|
||||
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.TOKEN_A, Field.TOKEN_B].reduce((accumulator, field) => {
|
||||
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
|
||||
(accumulator, field) => {
|
||||
return {
|
||||
...accumulator,
|
||||
[field]: maxAmountSpend(tokenBalances[field])
|
||||
[field]: maxAmountSpend(currencyBalances[field])
|
||||
}
|
||||
}, {})
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.TOKEN_A, Field.TOKEN_B].reduce(
|
||||
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
|
||||
(accumulator, field) => {
|
||||
return {
|
||||
...accumulator,
|
||||
@ -124,8 +116,8 @@ export default function AddLiquidity({
|
||||
)
|
||||
|
||||
// check whether the user has approved the router on the tokens
|
||||
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.TOKEN_A], ROUTER_ADDRESS)
|
||||
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.TOKEN_B], ROUTER_ADDRESS)
|
||||
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
|
||||
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
|
||||
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
@ -133,14 +125,14 @@ export default function AddLiquidity({
|
||||
if (!chainId || !library || !account) return
|
||||
const router = getRouterContract(chainId, library, account)
|
||||
|
||||
const { [Field.TOKEN_A]: parsedAmountA, [Field.TOKEN_B]: parsedAmountB } = parsedAmounts
|
||||
if (!parsedAmountA || !parsedAmountB || !tokenA || !tokenB) {
|
||||
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
|
||||
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
|
||||
return
|
||||
}
|
||||
|
||||
const amountsMin = {
|
||||
[Field.TOKEN_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
|
||||
[Field.TOKEN_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0]
|
||||
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
|
||||
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0]
|
||||
}
|
||||
|
||||
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
|
||||
@ -149,15 +141,15 @@ export default function AddLiquidity({
|
||||
method: (...args: any) => Promise<TransactionResponse>,
|
||||
args: Array<string | string[] | number>,
|
||||
value: BigNumber | null
|
||||
if (tokenA.equals(WETH[chainId]) || tokenB.equals(WETH[chainId])) {
|
||||
const tokenBIsETH = tokenB.equals(WETH[chainId])
|
||||
if (currencyA === ETHER || currencyB === ETHER) {
|
||||
const tokenBIsETH = currencyB === ETHER
|
||||
estimate = router.estimateGas.addLiquidityETH
|
||||
method = router.addLiquidityETH
|
||||
args = [
|
||||
(tokenBIsETH ? tokenA : tokenB).address, // token
|
||||
wrappedCurrency(tokenBIsETH ? currencyA : currencyB, chainId)?.address ?? '', // token
|
||||
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
|
||||
amountsMin[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].toString(), // token min
|
||||
amountsMin[tokenBIsETH ? Field.TOKEN_B : Field.TOKEN_A].toString(), // eth min
|
||||
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
|
||||
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
|
||||
account,
|
||||
deadlineFromNow
|
||||
]
|
||||
@ -166,12 +158,12 @@ export default function AddLiquidity({
|
||||
estimate = router.estimateGas.addLiquidity
|
||||
method = router.addLiquidity
|
||||
args = [
|
||||
tokenA.address,
|
||||
tokenB.address,
|
||||
wrappedCurrency(currencyA, chainId)?.address ?? '',
|
||||
wrappedCurrency(currencyB, chainId)?.address ?? '',
|
||||
parsedAmountA.raw.toString(),
|
||||
parsedAmountB.raw.toString(),
|
||||
amountsMin[Field.TOKEN_A].toString(),
|
||||
amountsMin[Field.TOKEN_B].toString(),
|
||||
amountsMin[Field.CURRENCY_A].toString(),
|
||||
amountsMin[Field.CURRENCY_B].toString(),
|
||||
account,
|
||||
deadlineFromNow
|
||||
]
|
||||
@ -190,13 +182,13 @@ export default function AddLiquidity({
|
||||
addTransaction(response, {
|
||||
summary:
|
||||
'Add ' +
|
||||
parsedAmounts[Field.TOKEN_A]?.toSignificant(3) +
|
||||
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.TOKEN_A]?.symbol +
|
||||
currencies[Field.CURRENCY_A]?.symbol +
|
||||
' and ' +
|
||||
parsedAmounts[Field.TOKEN_B]?.toSignificant(3) +
|
||||
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.TOKEN_B]?.symbol
|
||||
currencies[Field.CURRENCY_B]?.symbol
|
||||
})
|
||||
|
||||
setTxHash(response.hash)
|
||||
@ -204,7 +196,7 @@ export default function AddLiquidity({
|
||||
ReactGA.event({
|
||||
category: 'Liquidity',
|
||||
action: 'Add',
|
||||
label: [tokens[Field.TOKEN_A]?.symbol, tokens[Field.TOKEN_B]?.symbol].join('/')
|
||||
label: [currencies[Field.CURRENCY_A]?.symbol, currencies[Field.CURRENCY_B]?.symbol].join('/')
|
||||
})
|
||||
})
|
||||
)
|
||||
@ -223,9 +215,13 @@ export default function AddLiquidity({
|
||||
<LightCard mt="20px" borderRadius="20px">
|
||||
<RowFlat>
|
||||
<Text fontSize="48px" fontWeight={500} lineHeight="42px" marginRight={10}>
|
||||
{tokens[Field.TOKEN_A]?.symbol + '/' + tokens[Field.TOKEN_B]?.symbol}
|
||||
{currencies[Field.CURRENCY_A]?.symbol + '/' + currencies[Field.CURRENCY_B]?.symbol}
|
||||
</Text>
|
||||
<DoubleLogo a0={tokens[Field.TOKEN_A]?.address} a1={tokens[Field.TOKEN_B]?.address} size={30} />
|
||||
<DoubleCurrencyLogo
|
||||
currency0={currencies[Field.CURRENCY_A]}
|
||||
currency1={currencies[Field.CURRENCY_B]}
|
||||
size={30}
|
||||
/>
|
||||
</RowFlat>
|
||||
</LightCard>
|
||||
</AutoColumn>
|
||||
@ -235,11 +231,15 @@ export default function AddLiquidity({
|
||||
<Text fontSize="48px" fontWeight={500} lineHeight="42px" marginRight={10}>
|
||||
{liquidityMinted?.toSignificant(6)}
|
||||
</Text>
|
||||
<DoubleLogo a0={tokens[Field.TOKEN_A]?.address} a1={tokens[Field.TOKEN_B]?.address} size={30} />
|
||||
<DoubleCurrencyLogo
|
||||
currency0={currencies[Field.CURRENCY_A]}
|
||||
currency1={currencies[Field.CURRENCY_B]}
|
||||
size={30}
|
||||
/>
|
||||
</RowFlat>
|
||||
<Row>
|
||||
<Text fontSize="24px">
|
||||
{tokens[Field.TOKEN_A]?.symbol + '/' + tokens[Field.TOKEN_B]?.symbol + ' Pool Tokens'}
|
||||
{currencies[Field.CURRENCY_A]?.symbol + '/' + currencies[Field.CURRENCY_B]?.symbol + ' Pool Tokens'}
|
||||
</Text>
|
||||
</Row>
|
||||
<TYPE.italic fontSize={12} textAlign="left" padding={'8px 0 0 0 '}>
|
||||
@ -254,7 +254,7 @@ export default function AddLiquidity({
|
||||
return (
|
||||
<ConfirmAddModalBottom
|
||||
price={price}
|
||||
tokens={tokens}
|
||||
currencies={currencies}
|
||||
parsedAmounts={parsedAmounts}
|
||||
noLiquidity={noLiquidity}
|
||||
onAdd={onAdd}
|
||||
@ -263,37 +263,35 @@ export default function AddLiquidity({
|
||||
)
|
||||
}
|
||||
|
||||
const pendingText = `Supplying ${parsedAmounts[Field.TOKEN_A]?.toSignificant(6)} ${
|
||||
tokens[Field.TOKEN_A]?.symbol
|
||||
} and ${parsedAmounts[Field.TOKEN_B]?.toSignificant(6)} ${tokens[Field.TOKEN_B]?.symbol}`
|
||||
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
|
||||
currencies[Field.CURRENCY_A]?.symbol
|
||||
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
|
||||
|
||||
const handleTokenASelect = useCallback(
|
||||
(tokenAddress: string) => {
|
||||
const [tokenAId, tokenBId] = [
|
||||
currencyId(chainId, tokenAddress),
|
||||
tokenB ? currencyId(chainId, tokenB.address) : undefined
|
||||
]
|
||||
if (tokenAId === tokenBId) {
|
||||
history.push(`/add/${tokenAId}/${tokenA ? currencyId(chainId, tokenA.address) : ''}`)
|
||||
const handleCurrencyASelect = useCallback(
|
||||
(currencyA: Currency) => {
|
||||
const newCurrencyIdA = currencyId(currencyA)
|
||||
if (newCurrencyIdA === currencyIdB) {
|
||||
history.push(`/add/${currencyIdB}/${currencyIdA}`)
|
||||
} else {
|
||||
history.push(`/add/${tokenAId}/${tokenBId}`)
|
||||
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
|
||||
}
|
||||
},
|
||||
[chainId, tokenB, history, tokenA]
|
||||
[currencyIdB, history, currencyIdA]
|
||||
)
|
||||
const handleTokenBSelect = useCallback(
|
||||
(tokenAddress: string) => {
|
||||
const [tokenAId, tokenBId] = [
|
||||
tokenA ? currencyId(chainId, tokenA.address) : undefined,
|
||||
currencyId(chainId, tokenAddress)
|
||||
]
|
||||
if (tokenAId === tokenBId) {
|
||||
history.push(`/add/${tokenB ? currencyId(chainId, tokenB.address) : ''}/${tokenAId}`)
|
||||
const handleCurrencyBSelect = useCallback(
|
||||
(currencyB: Currency) => {
|
||||
const newCurrencyIdB = currencyId(currencyB)
|
||||
if (currencyIdA === newCurrencyIdB) {
|
||||
if (currencyIdB) {
|
||||
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
|
||||
} else {
|
||||
history.push(`/add/${currencyIdA ? currencyIdA : 'ETH'}/${currencyId(chainId, tokenAddress)}`)
|
||||
history.push(`/add/${newCurrencyIdB}`)
|
||||
}
|
||||
} else {
|
||||
history.push(`/add/${currencyIdA ? currencyIdA : 'ETH'}/${newCurrencyIdB}`)
|
||||
}
|
||||
},
|
||||
[tokenA, chainId, history, tokenB, currencyIdA]
|
||||
[currencyIdA, history, currencyIdB]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -307,7 +305,7 @@ export default function AddLiquidity({
|
||||
setShowConfirm(false)
|
||||
// if there was a tx hash, we want to clear the input
|
||||
if (txHash) {
|
||||
onUserInput(Field.TOKEN_A, '')
|
||||
onFieldAInput('')
|
||||
}
|
||||
setTxHash('')
|
||||
}}
|
||||
@ -337,16 +335,14 @@ export default function AddLiquidity({
|
||||
</ColumnCenter>
|
||||
)}
|
||||
<CurrencyInputPanel
|
||||
field={Field.TOKEN_A}
|
||||
value={formattedAmounts[Field.TOKEN_A]}
|
||||
onUserInput={handleTokenAInput}
|
||||
value={formattedAmounts[Field.CURRENCY_A]}
|
||||
onUserInput={onFieldAInput}
|
||||
onMax={() => {
|
||||
onUserInput(Field.TOKEN_A, maxAmounts[Field.TOKEN_A]?.toExact() ?? '')
|
||||
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
|
||||
}}
|
||||
onTokenSelection={handleTokenASelect}
|
||||
showMaxButton={!atMaxAmounts[Field.TOKEN_A]}
|
||||
token={tokens[Field.TOKEN_A]}
|
||||
pair={pair}
|
||||
onCurrencySelect={handleCurrencyASelect}
|
||||
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
|
||||
currency={currencies[Field.CURRENCY_A]}
|
||||
id="add-liquidity-input-tokena"
|
||||
showCommonBases
|
||||
/>
|
||||
@ -354,20 +350,18 @@ export default function AddLiquidity({
|
||||
<Plus size="16" color={theme.text2} />
|
||||
</ColumnCenter>
|
||||
<CurrencyInputPanel
|
||||
field={Field.TOKEN_B}
|
||||
value={formattedAmounts[Field.TOKEN_B]}
|
||||
onUserInput={handleTokenBInput}
|
||||
onTokenSelection={handleTokenBSelect}
|
||||
value={formattedAmounts[Field.CURRENCY_B]}
|
||||
onUserInput={onFieldBInput}
|
||||
onCurrencySelect={handleCurrencyBSelect}
|
||||
onMax={() => {
|
||||
onUserInput(Field.TOKEN_B, maxAmounts[Field.TOKEN_B]?.toExact() ?? '')
|
||||
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
|
||||
}}
|
||||
showMaxButton={!atMaxAmounts[Field.TOKEN_B]}
|
||||
token={tokens[Field.TOKEN_B]}
|
||||
pair={pair}
|
||||
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
|
||||
currency={currencies[Field.CURRENCY_B]}
|
||||
id="add-liquidity-input-tokenb"
|
||||
showCommonBases
|
||||
/>
|
||||
{tokens[Field.TOKEN_A] && tokens[Field.TOKEN_B] && (
|
||||
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
|
||||
<>
|
||||
<GreyCard padding="0px" borderRadius={'20px'}>
|
||||
<RowBetween padding="1rem">
|
||||
@ -377,7 +371,7 @@ export default function AddLiquidity({
|
||||
</RowBetween>{' '}
|
||||
<LightCard padding="1rem" borderRadius={'20px'}>
|
||||
<PoolPriceBar
|
||||
tokens={tokens}
|
||||
currencies={currencies}
|
||||
poolTokenPercentage={poolTokenPercentage}
|
||||
noLiquidity={noLiquidity}
|
||||
price={price}
|
||||
@ -404,9 +398,9 @@ export default function AddLiquidity({
|
||||
width={approvalB !== ApprovalState.APPROVED ? '48%' : '100%'}
|
||||
>
|
||||
{approvalA === ApprovalState.PENDING ? (
|
||||
<Dots>Approving {tokens[Field.TOKEN_A]?.symbol}</Dots>
|
||||
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
|
||||
) : (
|
||||
'Approve ' + tokens[Field.TOKEN_A]?.symbol
|
||||
'Approve ' + currencies[Field.CURRENCY_A]?.symbol
|
||||
)}
|
||||
</ButtonPrimary>
|
||||
)}
|
||||
@ -417,9 +411,9 @@ export default function AddLiquidity({
|
||||
width={approvalA !== ApprovalState.APPROVED ? '48%' : '100%'}
|
||||
>
|
||||
{approvalB === ApprovalState.PENDING ? (
|
||||
<Dots>Approving {tokens[Field.TOKEN_B]?.symbol}</Dots>
|
||||
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
|
||||
) : (
|
||||
'Approve ' + tokens[Field.TOKEN_B]?.symbol
|
||||
'Approve ' + currencies[Field.CURRENCY_B]?.symbol
|
||||
)}
|
||||
</ButtonPrimary>
|
||||
)}
|
||||
@ -430,7 +424,7 @@ export default function AddLiquidity({
|
||||
expertMode ? onAdd() : setShowConfirm(true)
|
||||
}}
|
||||
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
|
||||
error={!isValid && !!parsedAmounts[Field.TOKEN_A] && !!parsedAmounts[Field.TOKEN_B]}
|
||||
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
{error ?? 'Supply'}
|
||||
@ -442,9 +436,9 @@ export default function AddLiquidity({
|
||||
</Wrapper>
|
||||
</AppBody>
|
||||
|
||||
{pair && !noLiquidity ? (
|
||||
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
|
||||
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
|
||||
<MinimalPositionCard pair={pair} />
|
||||
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
|
||||
</AutoColumn>
|
||||
) : null}
|
||||
</>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { WETH } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom'
|
||||
import AddLiquidity from './index'
|
||||
@ -7,13 +6,6 @@ export function RedirectToAddLiquidity() {
|
||||
return <Redirect to="/add/" />
|
||||
}
|
||||
|
||||
function convertToCurrencyIds(address: string): string {
|
||||
if (Object.values(WETH).some(weth => weth.address === address)) {
|
||||
return 'ETH'
|
||||
}
|
||||
return address
|
||||
}
|
||||
|
||||
const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/
|
||||
export function RedirectOldAddLiquidityPathStructure(props: RouteComponentProps<{ currencyIdA: string }>) {
|
||||
const {
|
||||
@ -23,7 +15,7 @@ export function RedirectOldAddLiquidityPathStructure(props: RouteComponentProps<
|
||||
} = props
|
||||
const match = currencyIdA.match(OLD_PATH_STRUCTURE)
|
||||
if (match?.length) {
|
||||
return <Redirect to={`/add/${convertToCurrencyIds(match[1])}/${convertToCurrencyIds(match[2])}`} />
|
||||
return <Redirect to={`/add/${match[1]}/${match[2]}`} />
|
||||
}
|
||||
|
||||
return <AddLiquidity {...props} />
|
||||
|
@ -18,6 +18,7 @@ import RemoveV1Exchange from './MigrateV1/RemoveV1Exchange'
|
||||
import Pool from './Pool'
|
||||
import PoolFinder from './PoolFinder'
|
||||
import RemoveLiquidity from './RemoveLiquidity'
|
||||
import { RedirectOldRemoveLiquidityPathStructure } from './RemoveLiquidity/redirects'
|
||||
import Swap from './Swap'
|
||||
import { RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects'
|
||||
|
||||
@ -79,7 +80,8 @@ export default function App() {
|
||||
<Route exact path="/add" component={AddLiquidity} />
|
||||
<Route exact path="/add/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} />
|
||||
<Route exact path="/add/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} />
|
||||
<Route exact strict path="/remove/:tokens" component={RemoveLiquidity} />
|
||||
<Route exact strict path="/remove/:tokens" component={RedirectOldRemoveLiquidityPathStructure} />
|
||||
<Route exact strict path="/remove/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
|
||||
<Route exact strict path="/migrate/v1" component={MigrateV1} />
|
||||
<Route exact strict path="/migrate/v1/:address" component={MigrateV1Exchange} />
|
||||
<Route exact strict path="/remove/v1/:address" component={RemoveV1Exchange} />
|
||||
|
@ -1,17 +1,20 @@
|
||||
import { TransactionResponse } from '@ethersproject/abstract-provider'
|
||||
import { ChainId, Fraction, JSBI, Percent, Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import { Currency, Fraction, JSBI, Percent, Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Redirect, RouteComponentProps } from 'react-router'
|
||||
import { Text } from 'rebass'
|
||||
import { ButtonConfirmed } from '../../components/Button'
|
||||
import { PinkCard, YellowCard, LightCard } from '../../components/Card'
|
||||
import { LightCard, PinkCard, YellowCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import QuestionHelper from '../../components/QuestionHelper'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
|
||||
import { MIGRATOR_ADDRESS } from '../../constants/abis/migrator'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import { PairState, usePair } from '../../data/Reserves'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useToken } from '../../hooks/Tokens'
|
||||
@ -20,13 +23,10 @@ import { useV1ExchangeContract, useV2MigratorContract } from '../../hooks/useCon
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks'
|
||||
import { useIsTransactionPending, useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { useETHBalances, useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { TYPE, ExternalLink, BackArrow } from '../../theme'
|
||||
import { isAddress, getEtherscanLink } from '../../utils'
|
||||
import { BackArrow, ExternalLink, TYPE } from '../../theme'
|
||||
import { getEtherscanLink, isAddress } from '../../utils'
|
||||
import { BodyWrapper } from '../AppBody'
|
||||
import { EmptyState } from './EmptyState'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import { Text } from 'rebass'
|
||||
|
||||
const POOL_TOKEN_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
|
||||
const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
|
||||
@ -63,7 +63,7 @@ export function V1LiquidityInfo({
|
||||
return (
|
||||
<>
|
||||
<AutoRow style={{ justifyContent: 'flex-start', width: 'fit-content' }}>
|
||||
<TokenLogo size="24px" address={token.address} />
|
||||
<CurrencyLogo size="24px" currency={token} />
|
||||
<div style={{ marginLeft: '.75rem' }}>
|
||||
<TYPE.mediumHeader>
|
||||
{<FormattedPoolTokenAmount tokenAmount={liquidityTokenAmount} />}{' '}
|
||||
@ -80,7 +80,7 @@ export function V1LiquidityInfo({
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{tokenWorth.toSignificant(4)}
|
||||
</Text>
|
||||
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token.address} />
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={token} />
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween mb="1rem">
|
||||
@ -91,7 +91,7 @@ export function V1LiquidityInfo({
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{ethWorth.toSignificant(4)}
|
||||
</Text>
|
||||
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={WETH[chainId].address} />
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={Currency.ETHER} />
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
</>
|
||||
@ -104,10 +104,10 @@ function V1PairMigration({ liquidityTokenAmount, token }: { liquidityTokenAmount
|
||||
const exchangeETHBalance = useETHBalances([liquidityTokenAmount.token.address])?.[liquidityTokenAmount.token.address]
|
||||
const exchangeTokenBalance = useTokenBalance(liquidityTokenAmount.token.address, token)
|
||||
|
||||
const v2Pair = usePair(WETH[chainId as ChainId], token)
|
||||
const isFirstLiquidityProvider: boolean = v2Pair === null
|
||||
const [v2PairState, v2Pair] = usePair(chainId ? WETH[chainId] : undefined, token)
|
||||
const isFirstLiquidityProvider: boolean = v2PairState === PairState.NOT_EXISTS
|
||||
|
||||
const v2SpotPrice = v2Pair?.reserveOf(token)?.divide(v2Pair?.reserveOf(WETH[chainId as ChainId]))
|
||||
const v2SpotPrice = v2Pair?.reserveOf(token)?.divide(v2Pair?.reserveOf(WETH[chainId]))
|
||||
|
||||
const [confirmingMigration, setConfirmingMigration] = useState<boolean>(false)
|
||||
const [pendingMigrationHash, setPendingMigrationHash] = useState<string | null>(null)
|
||||
@ -126,7 +126,7 @@ function V1PairMigration({ liquidityTokenAmount, token }: { liquidityTokenAmount
|
||||
|
||||
const v1SpotPrice =
|
||||
exchangeTokenBalance && exchangeETHBalance
|
||||
? exchangeTokenBalance.divide(new Fraction(exchangeETHBalance, WEI_DENOM))
|
||||
? exchangeTokenBalance.divide(new Fraction(exchangeETHBalance.raw, WEI_DENOM))
|
||||
: null
|
||||
|
||||
const priceDifferenceFraction: Fraction | undefined =
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react'
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { Pair } from '@uniswap/sdk'
|
||||
import { Link } from 'react-router-dom'
|
||||
@ -17,7 +17,7 @@ import { AutoColumn } from '../../components/Column'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { usePairs } from '../../data/Reserves'
|
||||
import { useAllDummyPairs } from '../../state/user/hooks'
|
||||
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
|
||||
import AppBody from '../AppBody'
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
|
||||
@ -26,27 +26,33 @@ export default function Pool() {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
// fetch the user's balances of all tracked V2 LP tokens
|
||||
const v2DummyPairs = useAllDummyPairs()
|
||||
const trackedTokenPairs = useTrackedTokenPairs()
|
||||
const tokenPairsWithLiquidityTokens = useMemo(
|
||||
() => trackedTokenPairs.map(tokens => ({ liquidityToken: toV2LiquidityToken(tokens), tokens })),
|
||||
[trackedTokenPairs]
|
||||
)
|
||||
const liquidityTokens = useMemo(() => tokenPairsWithLiquidityTokens.map(tpwlt => tpwlt.liquidityToken), [
|
||||
tokenPairsWithLiquidityTokens
|
||||
])
|
||||
const [v2PairsBalances, fetchingV2PairBalances] = useTokenBalancesWithLoadingIndicator(
|
||||
account ?? undefined,
|
||||
v2DummyPairs?.map(p => p.liquidityToken)
|
||||
liquidityTokens
|
||||
)
|
||||
// fetch the reserves for all V2 pools in which the user has a balance
|
||||
const v2DummyPairsWithABalance = v2DummyPairs.filter(dummyPair =>
|
||||
v2PairsBalances[dummyPair.liquidityToken.address]?.greaterThan('0')
|
||||
)
|
||||
const v2Pairs = usePairs(
|
||||
v2DummyPairsWithABalance.map(V2DummyPairWithABalance => [
|
||||
V2DummyPairWithABalance.token0,
|
||||
V2DummyPairWithABalance.token1
|
||||
])
|
||||
)
|
||||
const v2IsLoading =
|
||||
fetchingV2PairBalances || v2Pairs?.length < v2DummyPairsWithABalance.length || v2Pairs?.some(V2Pair => !V2Pair)
|
||||
|
||||
const allV2PairsWithLiquidity = v2Pairs
|
||||
.filter((v2Pair): v2Pair is Pair => Boolean(v2Pair))
|
||||
.map(V2Pair => <FullPositionCard key={V2Pair.liquidityToken.address} pair={V2Pair} />)
|
||||
// fetch the reserves for all V2 pools in which the user has a balance
|
||||
const liquidityTokensWithBalances = useMemo(
|
||||
() =>
|
||||
tokenPairsWithLiquidityTokens.filter(({ liquidityToken }) =>
|
||||
v2PairsBalances[liquidityToken.address]?.greaterThan('0')
|
||||
),
|
||||
[tokenPairsWithLiquidityTokens, v2PairsBalances]
|
||||
)
|
||||
|
||||
const v2Pairs = usePairs(liquidityTokensWithBalances.map(({ tokens }) => tokens))
|
||||
const v2IsLoading =
|
||||
fetchingV2PairBalances || v2Pairs?.length < liquidityTokensWithBalances.length || v2Pairs?.some(V2Pair => !V2Pair)
|
||||
|
||||
const allV2PairsWithLiquidity = v2Pairs.map(([, pair]) => pair).filter((v2Pair): v2Pair is Pair => Boolean(v2Pair))
|
||||
|
||||
const hasV1Liquidity = useUserHasLiquidityInAllTokens()
|
||||
|
||||
@ -82,7 +88,11 @@ export default function Pool() {
|
||||
</TYPE.body>
|
||||
</LightCard>
|
||||
) : allV2PairsWithLiquidity?.length > 0 ? (
|
||||
<>{allV2PairsWithLiquidity}</>
|
||||
<>
|
||||
{allV2PairsWithLiquidity.map(v2Pair => (
|
||||
<FullPositionCard key={v2Pair.liquidityToken.address} pair={v2Pair} />
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<LightCard padding="40px">
|
||||
<TYPE.body color={theme.text3} textAlign="center">
|
||||
|
@ -1,22 +1,23 @@
|
||||
import { JSBI, Pair, Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import { Currency, ETHER, JSBI, TokenAmount } from '@uniswap/sdk'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Plus } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import { ButtonDropdownLight } from '../../components/Button'
|
||||
import { LightCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import { FindPoolTabs } from '../../components/NavigationTabs'
|
||||
import { MinimalPositionCard } from '../../components/PositionCard'
|
||||
import Row from '../../components/Row'
|
||||
import TokenSearchModal from '../../components/SearchModal/TokenSearchModal'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import CurrencySearchModal from '../../components/SearchModal/CurrencySearchModal'
|
||||
import { PairState, usePair } from '../../data/Reserves'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useToken } from '../../hooks/Tokens'
|
||||
import { usePairAdder } from '../../state/user/hooks'
|
||||
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { StyledInternalLink } from '../../theme'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import AppBody from '../AppBody'
|
||||
import { Dots } from '../Pool/styleds'
|
||||
|
||||
enum Fields {
|
||||
TOKEN0 = 0,
|
||||
@ -24,17 +25,15 @@ enum Fields {
|
||||
}
|
||||
|
||||
export default function PoolFinder() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const [showSearch, setShowSearch] = useState<boolean>(false)
|
||||
const [activeField, setActiveField] = useState<number>(Fields.TOKEN1)
|
||||
|
||||
const [token0Address, setToken0Address] = useState<string>(chainId ? WETH[chainId].address : '')
|
||||
const [token1Address, setToken1Address] = useState<string>()
|
||||
const token0: Token | null | undefined = useToken(token0Address)
|
||||
const token1: Token | null | undefined = useToken(token1Address)
|
||||
const [currency0, setCurrency0] = useState<Currency | null>(ETHER)
|
||||
const [currency1, setCurrency1] = useState<Currency | null>(null)
|
||||
|
||||
const pair: Pair | null | undefined = usePair(token0 ?? undefined, token1 ?? undefined)
|
||||
const [pairState, pair] = usePair(currency0 ?? undefined, currency1 ?? undefined)
|
||||
const addPair = usePairAdder()
|
||||
useEffect(() => {
|
||||
if (pair) {
|
||||
@ -42,16 +41,25 @@ export default function PoolFinder() {
|
||||
}
|
||||
}, [pair, addPair])
|
||||
|
||||
const newPair: boolean =
|
||||
pair === null ||
|
||||
(!!pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0)))
|
||||
const validPairNoLiquidity: boolean =
|
||||
pairState === PairState.NOT_EXISTS ||
|
||||
Boolean(
|
||||
pairState === PairState.EXISTS &&
|
||||
pair &&
|
||||
JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) &&
|
||||
JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0))
|
||||
)
|
||||
|
||||
const position: TokenAmount | undefined = useTokenBalanceTreatingWETHasETH(account ?? undefined, pair?.liquidityToken)
|
||||
const poolImported: boolean = !!position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
|
||||
const position: TokenAmount | undefined = useTokenBalance(account ?? undefined, pair?.liquidityToken)
|
||||
const hasPosition = Boolean(position && JSBI.greaterThan(position.raw, JSBI.BigInt(0)))
|
||||
|
||||
const handleTokenSelect = useCallback(
|
||||
(address: string) => {
|
||||
activeField === Fields.TOKEN0 ? setToken0Address(address) : setToken1Address(address)
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
if (activeField === Fields.TOKEN0) {
|
||||
setCurrency0(currency)
|
||||
} else {
|
||||
setCurrency1(currency)
|
||||
}
|
||||
},
|
||||
[activeField]
|
||||
)
|
||||
@ -60,6 +68,14 @@ export default function PoolFinder() {
|
||||
setShowSearch(false)
|
||||
}, [setShowSearch])
|
||||
|
||||
const prerequisiteMessage = (
|
||||
<LightCard padding="45px 10px">
|
||||
<Text textAlign="center">
|
||||
{!account ? 'Connect to a wallet to find pools' : 'Select a token to find your liquidity.'}
|
||||
</Text>
|
||||
</LightCard>
|
||||
)
|
||||
|
||||
return (
|
||||
<AppBody>
|
||||
<FindPoolTabs />
|
||||
@ -70,11 +86,11 @@ export default function PoolFinder() {
|
||||
setActiveField(Fields.TOKEN0)
|
||||
}}
|
||||
>
|
||||
{token0 ? (
|
||||
{currency0 ? (
|
||||
<Row>
|
||||
<TokenLogo address={token0Address} />
|
||||
<CurrencyLogo currency={currency0} />
|
||||
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
|
||||
{token0.symbol}
|
||||
{currency0.symbol}
|
||||
</Text>
|
||||
</Row>
|
||||
) : (
|
||||
@ -94,11 +110,11 @@ export default function PoolFinder() {
|
||||
setActiveField(Fields.TOKEN1)
|
||||
}}
|
||||
>
|
||||
{token1 ? (
|
||||
{currency1 ? (
|
||||
<Row>
|
||||
<TokenLogo address={token1Address} />
|
||||
<CurrencyLogo currency={currency1} />
|
||||
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
|
||||
{token1.symbol}
|
||||
{currency1.symbol}
|
||||
</Text>
|
||||
</Row>
|
||||
) : (
|
||||
@ -108,51 +124,68 @@ export default function PoolFinder() {
|
||||
)}
|
||||
</ButtonDropdownLight>
|
||||
|
||||
{poolImported && (
|
||||
{hasPosition && (
|
||||
<ColumnCenter
|
||||
style={{ justifyItems: 'center', backgroundColor: '', padding: '12px 0px', borderRadius: '12px' }}
|
||||
>
|
||||
<Text textAlign="center" fontWeight={500} color="">
|
||||
<Text textAlign="center" fontWeight={500}>
|
||||
Pool Found!
|
||||
</Text>
|
||||
</ColumnCenter>
|
||||
)}
|
||||
|
||||
{position ? (
|
||||
poolImported ? (
|
||||
{currency0 && currency1 ? (
|
||||
pairState === PairState.EXISTS ? (
|
||||
hasPosition && pair ? (
|
||||
<MinimalPositionCard pair={pair} border="1px solid #CED0D9" />
|
||||
) : (
|
||||
<LightCard padding="45px 10px">
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Text textAlign="center">You don’t have liquidity in this pool yet.</Text>
|
||||
<StyledInternalLink to={`/add/${token0?.address}/${token1?.address}`}>
|
||||
<StyledInternalLink to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}>
|
||||
<Text textAlign="center">Add liquidity.</Text>
|
||||
</StyledInternalLink>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
)
|
||||
) : newPair ? (
|
||||
) : validPairNoLiquidity ? (
|
||||
<LightCard padding="45px 10px">
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Text textAlign="center">No pool found.</Text>
|
||||
<StyledInternalLink to={`/add/${token0Address}/${token1Address}`}>Create pool?</StyledInternalLink>
|
||||
<StyledInternalLink to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}>
|
||||
Create pool.
|
||||
</StyledInternalLink>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
) : (
|
||||
) : pairState === PairState.INVALID ? (
|
||||
<LightCard padding="45px 10px">
|
||||
<Text textAlign="center">
|
||||
{!account ? 'Connect to a wallet to find pools' : 'Select a token to find your liquidity.'}
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Text textAlign="center" fontWeight={500}>
|
||||
Invalid pair.
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
) : pairState === PairState.LOADING ? (
|
||||
<LightCard padding="45px 10px">
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Text textAlign="center">
|
||||
Loading
|
||||
<Dots />
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
) : null
|
||||
) : (
|
||||
prerequisiteMessage
|
||||
)}
|
||||
</AutoColumn>
|
||||
|
||||
<TokenSearchModal
|
||||
<CurrencySearchModal
|
||||
isOpen={showSearch}
|
||||
onTokenSelect={handleTokenSelect}
|
||||
onCurrencySelect={handleCurrencySelect}
|
||||
onDismiss={handleSearchDismiss}
|
||||
showCommonBases
|
||||
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address}
|
||||
hiddenCurrency={(activeField === Fields.TOKEN0 ? currency1 : currency0) ?? undefined}
|
||||
/>
|
||||
</AppBody>
|
||||
)
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { splitSignature } from '@ethersproject/bytes'
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { Percent, WETH } from '@uniswap/sdk'
|
||||
import React, { useCallback, useContext, useState } from 'react'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Currency, currencyEquals, ETHER, Percent, WETH } from '@uniswap/sdk'
|
||||
import React, { useCallback, useContext, useMemo, useState } from 'react'
|
||||
import { ArrowDown, Plus } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { RouteComponentProps } from 'react-router'
|
||||
@ -12,35 +13,48 @@ import { LightCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import DoubleLogo from '../../components/DoubleLogo'
|
||||
import DoubleCurrencyLogo from '../../components/DoubleLogo'
|
||||
import { AddRemoveTabs } from '../../components/NavigationTabs'
|
||||
import { MinimalPositionCard } from '../../components/PositionCard'
|
||||
import Row, { RowBetween, RowFixed } from '../../components/Row'
|
||||
|
||||
import Slider from '../../components/Slider'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import { ROUTER_ADDRESS } from '../../constants'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import { usePairContract } from '../../hooks/useContract'
|
||||
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TYPE } from '../../theme'
|
||||
import { StyledInternalLink, TYPE } from '../../theme'
|
||||
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { wrappedCurrency } from '../../utils/wrappedCurrency'
|
||||
import AppBody from '../AppBody'
|
||||
import { ClickableText, MaxButton, Wrapper } from '../Pool/styleds'
|
||||
import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallback'
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
import { useDefaultsFromURLMatchParams, useBurnActionHandlers } from '../../state/burn/hooks'
|
||||
import { useBurnActionHandlers } from '../../state/burn/hooks'
|
||||
import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks'
|
||||
import { Field } from '../../state/burn/actions'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { useUserDeadline, useUserSlippageTolerance } from '../../state/user/hooks'
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
|
||||
export default function RemoveLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) {
|
||||
useDefaultsFromURLMatchParams(params)
|
||||
|
||||
export default function RemoveLiquidity({
|
||||
history,
|
||||
match: {
|
||||
params: { currencyIdA, currencyIdB }
|
||||
}
|
||||
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
|
||||
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
|
||||
currencyA,
|
||||
currencyB,
|
||||
chainId
|
||||
])
|
||||
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
// toggle wallet when disconnected
|
||||
@ -48,7 +62,7 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
|
||||
// burn state
|
||||
const { independentField, typedValue } = useBurnState()
|
||||
const { tokens, pair, route, parsedAmounts, error } = useDerivedBurnInfo()
|
||||
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
|
||||
const { onUserInput: _onUserInput } = useBurnActionHandlers()
|
||||
const isValid = !error
|
||||
|
||||
@ -70,23 +84,27 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
|
||||
[Field.LIQUIDITY]:
|
||||
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
|
||||
[Field.TOKEN_A]:
|
||||
independentField === Field.TOKEN_A ? typedValue : parsedAmounts[Field.TOKEN_A]?.toSignificant(6) ?? '',
|
||||
[Field.TOKEN_B]:
|
||||
independentField === Field.TOKEN_B ? typedValue : parsedAmounts[Field.TOKEN_B]?.toSignificant(6) ?? ''
|
||||
[Field.CURRENCY_A]:
|
||||
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
|
||||
[Field.CURRENCY_B]:
|
||||
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? ''
|
||||
}
|
||||
|
||||
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
|
||||
|
||||
// pair contract
|
||||
const pairContract: Contract = usePairContract(pair?.liquidityToken?.address)
|
||||
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
|
||||
|
||||
// allowance handling
|
||||
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number }>(null)
|
||||
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
|
||||
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
|
||||
async function onAttemptToApprove() {
|
||||
if (!pairContract || !pair || !library) throw new Error('missing dependencies')
|
||||
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
|
||||
if (!liquidityAmount) throw new Error('missing liquidity amount')
|
||||
// try to gather a signature for permission
|
||||
const nonce = await pairContract.nonces(account)
|
||||
|
||||
const deadlineForSignature: number = Math.ceil(Date.now() / 1000) + deadline
|
||||
|
||||
const EIP712Domain = [
|
||||
@ -111,7 +129,7 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
const message = {
|
||||
owner: account,
|
||||
spender: ROUTER_ADDRESS,
|
||||
value: parsedAmounts[Field.LIQUIDITY].raw.toString(),
|
||||
value: liquidityAmount.raw.toString(),
|
||||
nonce: nonce.toHexString(),
|
||||
deadline: deadlineForSignature
|
||||
}
|
||||
@ -153,32 +171,52 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
[_onUserInput]
|
||||
)
|
||||
|
||||
const onLiquidityInput = useCallback((typedValue: string): void => onUserInput(Field.LIQUIDITY, typedValue), [
|
||||
onUserInput
|
||||
])
|
||||
const onCurrencyAInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_A, typedValue), [
|
||||
onUserInput
|
||||
])
|
||||
const onCurrencyBInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_B, typedValue), [
|
||||
onUserInput
|
||||
])
|
||||
|
||||
// tx sending
|
||||
const addTransaction = useTransactionAdder()
|
||||
async function onRemove() {
|
||||
if (!chainId || !library || !account) throw new Error('missing dependencies')
|
||||
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
|
||||
if (!currencyAmountA || !currencyAmountB) {
|
||||
throw new Error('missing currency amounts')
|
||||
}
|
||||
const router = getRouterContract(chainId, library, account)
|
||||
|
||||
const amountsMin = {
|
||||
[Field.TOKEN_A]: calculateSlippageAmount(parsedAmounts[Field.TOKEN_A], allowedSlippage)[0],
|
||||
[Field.TOKEN_B]: calculateSlippageAmount(parsedAmounts[Field.TOKEN_B], allowedSlippage)[0]
|
||||
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
|
||||
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0]
|
||||
}
|
||||
|
||||
const tokenBIsETH = tokens[Field.TOKEN_B].equals(WETH[chainId])
|
||||
const oneTokenIsETH = tokens[Field.TOKEN_A].equals(WETH[chainId]) || tokenBIsETH
|
||||
if (!currencyA || !currencyB) throw new Error('missing tokens')
|
||||
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
|
||||
if (!liquidityAmount) throw new Error('missing liquidity amount')
|
||||
|
||||
const currencyBIsETH = currencyB === ETHER
|
||||
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
|
||||
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
|
||||
|
||||
if (!tokenA || !tokenB) throw new Error('could not wrap')
|
||||
|
||||
let methodNames: string[], args: Array<string | string[] | number | boolean>
|
||||
// we have approval, use normal remove liquidity
|
||||
if (approval === ApprovalState.APPROVED) {
|
||||
// removeLiquidityETH
|
||||
if (oneTokenIsETH) {
|
||||
if (oneCurrencyIsETH) {
|
||||
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
|
||||
args = [
|
||||
tokens[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].address,
|
||||
parsedAmounts[Field.LIQUIDITY].raw.toString(),
|
||||
amountsMin[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].toString(),
|
||||
amountsMin[tokenBIsETH ? Field.TOKEN_B : Field.TOKEN_A].toString(),
|
||||
currencyBIsETH ? tokenA.address : tokenB.address,
|
||||
liquidityAmount.raw.toString(),
|
||||
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
|
||||
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
|
||||
account,
|
||||
deadlineFromNow
|
||||
]
|
||||
@ -187,11 +225,11 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
else {
|
||||
methodNames = ['removeLiquidity']
|
||||
args = [
|
||||
tokens[Field.TOKEN_A].address,
|
||||
tokens[Field.TOKEN_B].address,
|
||||
parsedAmounts[Field.LIQUIDITY].raw.toString(),
|
||||
amountsMin[Field.TOKEN_A].toString(),
|
||||
amountsMin[Field.TOKEN_B].toString(),
|
||||
tokenA.address,
|
||||
tokenB.address,
|
||||
liquidityAmount.raw.toString(),
|
||||
amountsMin[Field.CURRENCY_A].toString(),
|
||||
amountsMin[Field.CURRENCY_B].toString(),
|
||||
account,
|
||||
deadlineFromNow
|
||||
]
|
||||
@ -200,13 +238,13 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
// we have a signataure, use permit versions of remove liquidity
|
||||
else if (signatureData !== null) {
|
||||
// removeLiquidityETHWithPermit
|
||||
if (oneTokenIsETH) {
|
||||
if (oneCurrencyIsETH) {
|
||||
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
|
||||
args = [
|
||||
tokens[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].address,
|
||||
parsedAmounts[Field.LIQUIDITY].raw.toString(),
|
||||
amountsMin[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].toString(),
|
||||
amountsMin[tokenBIsETH ? Field.TOKEN_B : Field.TOKEN_A].toString(),
|
||||
currencyBIsETH ? tokenA.address : tokenB.address,
|
||||
liquidityAmount.raw.toString(),
|
||||
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
|
||||
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
|
||||
account,
|
||||
signatureData.deadline,
|
||||
false,
|
||||
@ -219,11 +257,11 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
else {
|
||||
methodNames = ['removeLiquidityWithPermit']
|
||||
args = [
|
||||
tokens[Field.TOKEN_A].address,
|
||||
tokens[Field.TOKEN_B].address,
|
||||
parsedAmounts[Field.LIQUIDITY].raw.toString(),
|
||||
amountsMin[Field.TOKEN_A].toString(),
|
||||
amountsMin[Field.TOKEN_B].toString(),
|
||||
tokenA.address,
|
||||
tokenB.address,
|
||||
liquidityAmount.raw.toString(),
|
||||
amountsMin[Field.CURRENCY_A].toString(),
|
||||
amountsMin[Field.CURRENCY_B].toString(),
|
||||
account,
|
||||
signatureData.deadline,
|
||||
false,
|
||||
@ -233,7 +271,7 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
]
|
||||
}
|
||||
} else {
|
||||
console.error('Attempting to confirm without approval or a signature. Please contact support.')
|
||||
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
|
||||
}
|
||||
|
||||
const safeGasEstimates = await Promise.all(
|
||||
@ -261,19 +299,19 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
await router[methodName](...args, {
|
||||
gasLimit: safeGasEstimate
|
||||
})
|
||||
.then(response => {
|
||||
.then((response: TransactionResponse) => {
|
||||
setAttemptingTxn(false)
|
||||
|
||||
addTransaction(response, {
|
||||
summary:
|
||||
'Remove ' +
|
||||
parsedAmounts[Field.TOKEN_A]?.toSignificant(3) +
|
||||
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.TOKEN_A]?.symbol +
|
||||
currencyA?.symbol +
|
||||
' and ' +
|
||||
parsedAmounts[Field.TOKEN_B]?.toSignificant(3) +
|
||||
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.TOKEN_B]?.symbol
|
||||
currencyB?.symbol
|
||||
})
|
||||
|
||||
setTxHash(response.hash)
|
||||
@ -281,15 +319,13 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
ReactGA.event({
|
||||
category: 'Liquidity',
|
||||
action: 'Remove',
|
||||
label: [tokens[Field.TOKEN_A]?.symbol, tokens[Field.TOKEN_B]?.symbol].join('/')
|
||||
label: [currencyA?.symbol, currencyB?.symbol].join('/')
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error: Error) => {
|
||||
setAttemptingTxn(false)
|
||||
// we only care if the error is something _other_ than the user rejected the tx
|
||||
if (error?.code !== 4001) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -299,12 +335,12 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
|
||||
<RowBetween align="flex-end">
|
||||
<Text fontSize={24} fontWeight={500}>
|
||||
{parsedAmounts[Field.TOKEN_A]?.toSignificant(6)}
|
||||
{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}
|
||||
</Text>
|
||||
<RowFixed gap="4px">
|
||||
<TokenLogo address={tokens[Field.TOKEN_A]?.address} size={'24px'} />
|
||||
<CurrencyLogo currency={currencyA} size={'24px'} />
|
||||
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
|
||||
{tokens[Field.TOKEN_A]?.symbol}
|
||||
{currencyA?.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
@ -313,12 +349,12 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
</RowFixed>
|
||||
<RowBetween align="flex-end">
|
||||
<Text fontSize={24} fontWeight={500}>
|
||||
{parsedAmounts[Field.TOKEN_B]?.toSignificant(6)}
|
||||
{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}
|
||||
</Text>
|
||||
<RowFixed gap="4px">
|
||||
<TokenLogo address={tokens[Field.TOKEN_B]?.address} size={'24px'} />
|
||||
<CurrencyLogo currency={currencyB} size={'24px'} />
|
||||
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
|
||||
{tokens[Field.TOKEN_B]?.symbol}
|
||||
{currencyB?.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
@ -336,34 +372,29 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
<>
|
||||
<RowBetween>
|
||||
<Text color={theme.text2} fontWeight={500} fontSize={16}>
|
||||
{'UNI ' + tokens[Field.TOKEN_A]?.symbol + '/' + tokens[Field.TOKEN_B]?.symbol} Burned
|
||||
{'UNI ' + currencyA?.symbol + '/' + currencyB?.symbol} Burned
|
||||
</Text>
|
||||
<RowFixed>
|
||||
<DoubleLogo
|
||||
a0={tokens[Field.TOKEN_A]?.address || ''}
|
||||
a1={tokens[Field.TOKEN_B]?.address || ''}
|
||||
margin={true}
|
||||
/>
|
||||
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin={true} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
{route && (
|
||||
{pair && (
|
||||
<>
|
||||
<RowBetween>
|
||||
<Text color={theme.text2} fontWeight={500} fontSize={16}>
|
||||
Price
|
||||
</Text>
|
||||
<Text fontWeight={500} fontSize={16} color={theme.text1}>
|
||||
1 {tokens[Field.TOKEN_A]?.symbol} = {route.midPrice.toSignificant(6)} {tokens[Field.TOKEN_B]?.symbol}
|
||||
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
|
||||
</Text>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<Text fontWeight={500} fontSize={16} color={theme.text1}>
|
||||
1 {tokens[Field.TOKEN_B]?.symbol} = {route.midPrice.invert().toSignificant(6)}{' '}
|
||||
{tokens[Field.TOKEN_A]?.symbol}
|
||||
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
|
||||
</Text>
|
||||
</RowBetween>
|
||||
</>
|
||||
@ -377,9 +408,9 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
)
|
||||
}
|
||||
|
||||
const pendingText = `Removing ${parsedAmounts[Field.TOKEN_A]?.toSignificant(6)} ${
|
||||
tokens[Field.TOKEN_A]?.symbol
|
||||
} and ${parsedAmounts[Field.TOKEN_B]?.toSignificant(6)} ${tokens[Field.TOKEN_B]?.symbol}`
|
||||
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
|
||||
currencyA?.symbol
|
||||
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
|
||||
|
||||
const liquidityPercentChangeCallback = useCallback(
|
||||
(value: number) => {
|
||||
@ -388,6 +419,34 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
[onUserInput]
|
||||
)
|
||||
|
||||
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
|
||||
const oneCurrencyIsWETH = Boolean(
|
||||
chainId &&
|
||||
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
|
||||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
|
||||
)
|
||||
|
||||
const handleSelectCurrencyA = useCallback(
|
||||
(currency: Currency) => {
|
||||
if (currencyIdB && currencyId(currency) === currencyIdB) {
|
||||
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
|
||||
} else {
|
||||
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
|
||||
}
|
||||
},
|
||||
[currencyIdA, currencyIdB, history]
|
||||
)
|
||||
const handleSelectCurrencyB = useCallback(
|
||||
(currency: Currency) => {
|
||||
if (currencyIdA && currencyId(currency) === currencyIdA) {
|
||||
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
|
||||
} else {
|
||||
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
|
||||
}
|
||||
},
|
||||
[currencyIdA, currencyIdB, history]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBody>
|
||||
@ -463,26 +522,47 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
<AutoColumn gap="10px">
|
||||
<RowBetween>
|
||||
<Text fontSize={24} fontWeight={500}>
|
||||
{formattedAmounts[Field.TOKEN_A] || '-'}
|
||||
{formattedAmounts[Field.CURRENCY_A] || '-'}
|
||||
</Text>
|
||||
<RowFixed>
|
||||
<TokenLogo address={tokens[Field.TOKEN_A]?.address} style={{ marginRight: '12px' }} />
|
||||
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
|
||||
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokena-symbol">
|
||||
{tokens[Field.TOKEN_A]?.symbol}
|
||||
{currencyA?.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<Text fontSize={24} fontWeight={500}>
|
||||
{formattedAmounts[Field.TOKEN_B] || '-'}
|
||||
{formattedAmounts[Field.CURRENCY_B] || '-'}
|
||||
</Text>
|
||||
<RowFixed>
|
||||
<TokenLogo address={tokens[Field.TOKEN_B]?.address} style={{ marginRight: '12px' }} />
|
||||
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
|
||||
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokenb-symbol">
|
||||
{tokens[Field.TOKEN_B]?.symbol}
|
||||
{currencyB?.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
|
||||
<RowBetween style={{ justifyContent: 'flex-end' }}>
|
||||
{oneCurrencyIsETH ? (
|
||||
<StyledInternalLink
|
||||
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
|
||||
currencyB === ETHER ? WETH[chainId].address : currencyIdB
|
||||
}`}
|
||||
>
|
||||
Receive WETH
|
||||
</StyledInternalLink>
|
||||
) : oneCurrencyIsWETH ? (
|
||||
<StyledInternalLink
|
||||
to={`/remove/${
|
||||
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'ETH' : currencyIdA
|
||||
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'ETH' : currencyIdB}`}
|
||||
>
|
||||
Receive ETH
|
||||
</StyledInternalLink>
|
||||
) : null}
|
||||
</RowBetween>
|
||||
) : null}
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
</>
|
||||
@ -491,16 +571,14 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
{showDetailed && (
|
||||
<>
|
||||
<CurrencyInputPanel
|
||||
field={Field.LIQUIDITY}
|
||||
value={formattedAmounts[Field.LIQUIDITY]}
|
||||
onUserInput={onUserInput}
|
||||
onUserInput={onLiquidityInput}
|
||||
onMax={() => {
|
||||
onUserInput(Field.LIQUIDITY_PERCENT, '100')
|
||||
}}
|
||||
showMaxButton={!atMaxAmount}
|
||||
disableTokenSelect
|
||||
token={pair?.liquidityToken}
|
||||
isExchange={true}
|
||||
disableCurrencySelect
|
||||
currency={pair?.liquidityToken}
|
||||
pair={pair}
|
||||
id="liquidity-amount"
|
||||
/>
|
||||
@ -509,14 +587,13 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
</ColumnCenter>
|
||||
<CurrencyInputPanel
|
||||
hideBalance={true}
|
||||
field={Field.TOKEN_A}
|
||||
value={formattedAmounts[Field.TOKEN_A]}
|
||||
onUserInput={onUserInput}
|
||||
value={formattedAmounts[Field.CURRENCY_A]}
|
||||
onUserInput={onCurrencyAInput}
|
||||
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
|
||||
showMaxButton={!atMaxAmount}
|
||||
token={tokens[Field.TOKEN_A]}
|
||||
currency={currencyA}
|
||||
label={'Output'}
|
||||
disableTokenSelect
|
||||
onCurrencySelect={handleSelectCurrencyA}
|
||||
id="remove-liquidity-tokena"
|
||||
/>
|
||||
<ColumnCenter>
|
||||
@ -524,32 +601,36 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
</ColumnCenter>
|
||||
<CurrencyInputPanel
|
||||
hideBalance={true}
|
||||
field={Field.TOKEN_B}
|
||||
value={formattedAmounts[Field.TOKEN_B]}
|
||||
onUserInput={onUserInput}
|
||||
value={formattedAmounts[Field.CURRENCY_B]}
|
||||
onUserInput={onCurrencyBInput}
|
||||
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
|
||||
showMaxButton={!atMaxAmount}
|
||||
token={tokens[Field.TOKEN_B]}
|
||||
currency={currencyB}
|
||||
label={'Output'}
|
||||
disableTokenSelect
|
||||
onCurrencySelect={handleSelectCurrencyB}
|
||||
id="remove-liquidity-tokenb"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{route && (
|
||||
{pair && (
|
||||
<div style={{ padding: '10px 20px' }}>
|
||||
<RowBetween>
|
||||
Price:
|
||||
<div>
|
||||
1 {tokens[Field.TOKEN_A]?.symbol} = {route.midPrice.toSignificant(6)}{' '}
|
||||
{tokens[Field.TOKEN_B]?.symbol}
|
||||
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
|
||||
</div>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<div>
|
||||
1 {tokens[Field.TOKEN_B]?.symbol} = {route.midPrice.invert().toSignificant(6)}{' '}
|
||||
{tokens[Field.TOKEN_A]?.symbol}
|
||||
1 {currencyB?.symbol} ={' '}
|
||||
{tokenB
|
||||
? pair
|
||||
.priceOf(tokenB)
|
||||
.invert()
|
||||
.toSignificant(6)
|
||||
: '-'}{' '}
|
||||
{currencyA?.symbol}
|
||||
</div>
|
||||
</RowBetween>
|
||||
</div>
|
||||
@ -580,7 +661,7 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
setShowConfirm(true)
|
||||
}}
|
||||
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
|
||||
error={!isValid && !!parsedAmounts[Field.TOKEN_A] && !!parsedAmounts[Field.TOKEN_B]}
|
||||
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
|
||||
>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{error || 'Remove'}
|
||||
@ -595,7 +676,7 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
|
||||
{pair ? (
|
||||
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
|
||||
<MinimalPositionCard pair={pair} />
|
||||
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
|
||||
</AutoColumn>
|
||||
) : null}
|
||||
</>
|
||||
|
17
src/pages/RemoveLiquidity/redirects.tsx
Normal file
17
src/pages/RemoveLiquidity/redirects.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
import { RouteComponentProps, Redirect } from 'react-router-dom'
|
||||
|
||||
const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/
|
||||
|
||||
export function RedirectOldRemoveLiquidityPathStructure({
|
||||
match: {
|
||||
params: { tokens }
|
||||
}
|
||||
}: RouteComponentProps<{ tokens: string }>) {
|
||||
if (!OLD_PATH_STRUCTURE.test(tokens)) {
|
||||
return <Redirect to="/pool" />
|
||||
}
|
||||
const [currency0, currency1] = tokens.split('-')
|
||||
|
||||
return <Redirect to={`/remove/${currency0}/${currency1}`} />
|
||||
}
|
7
src/pages/RemoveLiquidity/tsconfig.json
Normal file
7
src/pages/RemoveLiquidity/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.strict.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../../../node_modules/eslint-plugin-react/lib/types.d.ts"
|
||||
]
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { JSBI, TokenAmount } from '@uniswap/sdk'
|
||||
import React, { useContext, useState, useEffect, useCallback } from 'react'
|
||||
import { CurrencyAmount, JSBI } from '@uniswap/sdk'
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { ArrowDown } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Text } from 'rebass'
|
||||
@ -13,23 +13,23 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import { SwapPoolTabs } from '../../components/NavigationTabs'
|
||||
import { AutoRow, RowBetween } from '../../components/Row'
|
||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
||||
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
||||
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
|
||||
import { ArrowWrapper, BottomGrouping, Dots, Wrapper } from '../../components/swap/styleds'
|
||||
import SwapModalFooter from '../../components/swap/SwapModalFooter'
|
||||
import SwapModalHeader from '../../components/swap/SwapModalHeader'
|
||||
import TradePrice from '../../components/swap/TradePrice'
|
||||
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
||||
import { TokenWarningCards } from '../../components/TokenWarningCard'
|
||||
|
||||
import { BETTER_TRADE_LINK_THRESHOLD, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
|
||||
import { getTradeVersion, isTradeBetter } from '../../data/V1'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useApproveCallbackFromTrade, ApprovalState } from '../../hooks/useApproveCallback'
|
||||
import { ApprovalState, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback'
|
||||
import useENSAddress from '../../hooks/useENSAddress'
|
||||
import { useSwapCallback } from '../../hooks/useSwapCallback'
|
||||
import { useWalletModalToggle, useToggleSettingsMenu } from '../../state/application/hooks'
|
||||
import { useExpertModeManager, useUserSlippageTolerance, useUserDeadline } from '../../state/user/hooks'
|
||||
|
||||
import { INITIAL_ALLOWED_SLIPPAGE, BETTER_TRADE_LINK_THRESHOLD } from '../../constants'
|
||||
import { getTradeVersion, isTradeBetter } from '../../data/V1'
|
||||
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
|
||||
import useWrapCallback, { WrapType } from '../../hooks/useWrapCallback'
|
||||
import { useToggleSettingsMenu, useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import {
|
||||
useDefaultsFromURLSearch,
|
||||
@ -37,6 +37,7 @@ import {
|
||||
useSwapActionHandlers,
|
||||
useSwapState
|
||||
} from '../../state/swap/hooks'
|
||||
import { useExpertModeManager, useUserDeadline, useUserSlippageTolerance } from '../../state/user/hooks'
|
||||
import { CursorPointer, LinkStyledButton, TYPE } from '../../theme'
|
||||
import { maxAmountSpend } from '../../utils/maxAmountSpend'
|
||||
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
||||
@ -62,14 +63,21 @@ export default function Swap() {
|
||||
|
||||
// swap state
|
||||
const { independentField, typedValue, recipient } = useSwapState()
|
||||
const { v1Trade, v2Trade, tokenBalances, parsedAmount, tokens, error } = useDerivedSwapInfo()
|
||||
const { v1Trade, v2Trade, currencyBalances, parsedAmount, currencies, error } = useDerivedSwapInfo()
|
||||
const { wrapType, execute: onWrap, error: wrapError } = useWrapCallback(
|
||||
currencies[Field.INPUT],
|
||||
currencies[Field.OUTPUT],
|
||||
typedValue
|
||||
)
|
||||
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
|
||||
const { address: recipientAddress } = useENSAddress(recipient)
|
||||
const toggledVersion = useToggledVersion()
|
||||
const trade =
|
||||
{
|
||||
const trade = showWrap
|
||||
? undefined
|
||||
: {
|
||||
[Version.v1]: v1Trade,
|
||||
[Version.v2]: v2Trade
|
||||
}[toggledVersion] ?? undefined
|
||||
}[toggledVersion]
|
||||
|
||||
const betterTradeLinkVersion: Version | undefined =
|
||||
toggledVersion === Version.v2 && isTradeBetter(v2Trade, v1Trade, BETTER_TRADE_LINK_THRESHOLD)
|
||||
@ -78,23 +86,28 @@ export default function Swap() {
|
||||
? Version.v2
|
||||
: undefined
|
||||
|
||||
const parsedAmounts = {
|
||||
const parsedAmounts = showWrap
|
||||
? {
|
||||
[Field.INPUT]: parsedAmount,
|
||||
[Field.OUTPUT]: parsedAmount
|
||||
}
|
||||
: {
|
||||
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
|
||||
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount
|
||||
}
|
||||
|
||||
const { onSwitchTokens, onTokenSelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
|
||||
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
|
||||
const isValid = !error
|
||||
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||
|
||||
const handleTypeInput = useCallback(
|
||||
(field, value) => {
|
||||
(value: string) => {
|
||||
onUserInput(Field.INPUT, value)
|
||||
},
|
||||
[onUserInput]
|
||||
)
|
||||
const handleTypeOutput = useCallback(
|
||||
(field, value) => {
|
||||
(value: string) => {
|
||||
onUserInput(Field.OUTPUT, value)
|
||||
},
|
||||
[onUserInput]
|
||||
@ -107,12 +120,14 @@ export default function Swap() {
|
||||
|
||||
const formattedAmounts = {
|
||||
[independentField]: typedValue,
|
||||
[dependentField]: parsedAmounts[dependentField]?.toSignificant(6) ?? ''
|
||||
[dependentField]: showWrap
|
||||
? parsedAmounts[independentField]?.toExact() ?? ''
|
||||
: parsedAmounts[dependentField]?.toSignificant(6) ?? ''
|
||||
}
|
||||
|
||||
const route = trade?.route
|
||||
const userHasSpecifiedInputOutput = Boolean(
|
||||
tokens[Field.INPUT] && tokens[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
|
||||
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
|
||||
)
|
||||
const noRoute = !route
|
||||
|
||||
@ -129,7 +144,7 @@ export default function Swap() {
|
||||
}
|
||||
}, [approval, approvalSubmitted])
|
||||
|
||||
const maxAmountInput: TokenAmount | undefined = maxAmountSpend(tokenBalances[Field.INPUT])
|
||||
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
|
||||
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
|
||||
|
||||
const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage)
|
||||
@ -160,9 +175,11 @@ export default function Swap() {
|
||||
: (recipientAddress ?? recipient) === account
|
||||
? 'Swap w/o Send + recipient'
|
||||
: 'Swap w/ Send',
|
||||
label: [trade?.inputAmount?.token?.symbol, trade?.outputAmount?.token?.symbol, getTradeVersion(trade)].join(
|
||||
'/'
|
||||
)
|
||||
label: [
|
||||
trade?.inputAmount?.currency?.symbol,
|
||||
trade?.outputAmount?.currency?.symbol,
|
||||
getTradeVersion(trade)
|
||||
].join('/')
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
@ -192,7 +209,7 @@ export default function Swap() {
|
||||
function modalHeader() {
|
||||
return (
|
||||
<SwapModalHeader
|
||||
tokens={tokens}
|
||||
currencies={currencies}
|
||||
formattedAmounts={formattedAmounts}
|
||||
slippageAdjustedAmounts={slippageAdjustedAmounts}
|
||||
priceImpactSeverity={priceImpactSeverity}
|
||||
@ -221,12 +238,12 @@ export default function Swap() {
|
||||
|
||||
// text to show while loading
|
||||
const pendingText = `Swapping ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${
|
||||
tokens[Field.INPUT]?.symbol
|
||||
} for ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
|
||||
currencies[Field.INPUT]?.symbol
|
||||
} for ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${currencies[Field.OUTPUT]?.symbol}`
|
||||
|
||||
return (
|
||||
<>
|
||||
<TokenWarningCards tokens={tokens} />
|
||||
<TokenWarningCards currencies={currencies} />
|
||||
<AppBody>
|
||||
<SwapPoolTabs active={'swap'} />
|
||||
<Wrapper id="swap-page">
|
||||
@ -250,20 +267,19 @@ export default function Swap() {
|
||||
|
||||
<AutoColumn gap={'md'}>
|
||||
<CurrencyInputPanel
|
||||
field={Field.INPUT}
|
||||
label={independentField === Field.OUTPUT ? 'From (estimated)' : 'From'}
|
||||
label={independentField === Field.OUTPUT && !showWrap ? 'From (estimated)' : 'From'}
|
||||
value={formattedAmounts[Field.INPUT]}
|
||||
showMaxButton={!atMaxAmountInput}
|
||||
token={tokens[Field.INPUT]}
|
||||
currency={currencies[Field.INPUT]}
|
||||
onUserInput={handleTypeInput}
|
||||
onMax={() => {
|
||||
maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact())
|
||||
}}
|
||||
onTokenSelection={address => {
|
||||
onCurrencySelect={currency => {
|
||||
setApprovalSubmitted(false) // reset 2 step UI for approvals
|
||||
onTokenSelection(Field.INPUT, address)
|
||||
onCurrencySelection(Field.INPUT, currency)
|
||||
}}
|
||||
otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address}
|
||||
otherCurrency={currencies[Field.OUTPUT]}
|
||||
id="swap-currency-input"
|
||||
/>
|
||||
|
||||
@ -277,10 +293,10 @@ export default function Swap() {
|
||||
setApprovalSubmitted(false) // reset 2 step UI for approvals
|
||||
onSwitchTokens()
|
||||
}}
|
||||
color={tokens[Field.INPUT] && tokens[Field.OUTPUT] ? theme.primary1 : theme.text2}
|
||||
color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.primary1 : theme.text2}
|
||||
/>
|
||||
</ArrowWrapper>
|
||||
{recipient === null ? (
|
||||
{recipient === null && !showWrap ? (
|
||||
<LinkStyledButton id="add-recipient-button" onClick={() => onChangeRecipient('')}>
|
||||
+ Add a send (optional)
|
||||
</LinkStyledButton>
|
||||
@ -289,18 +305,17 @@ export default function Swap() {
|
||||
</AutoColumn>
|
||||
</CursorPointer>
|
||||
<CurrencyInputPanel
|
||||
field={Field.OUTPUT}
|
||||
value={formattedAmounts[Field.OUTPUT]}
|
||||
onUserInput={handleTypeOutput}
|
||||
label={independentField === Field.INPUT ? 'To (estimated)' : 'To'}
|
||||
label={independentField === Field.INPUT && !showWrap ? 'To (estimated)' : 'To'}
|
||||
showMaxButton={false}
|
||||
token={tokens[Field.OUTPUT]}
|
||||
onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
|
||||
otherSelectedTokenAddress={tokens[Field.INPUT]?.address}
|
||||
currency={currencies[Field.OUTPUT]}
|
||||
onCurrencySelect={address => onCurrencySelection(Field.OUTPUT, address)}
|
||||
otherCurrency={currencies[Field.INPUT]}
|
||||
id="swap-currency-output"
|
||||
/>
|
||||
|
||||
{recipient !== null ? (
|
||||
{recipient !== null && !showWrap ? (
|
||||
<>
|
||||
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
|
||||
<ArrowWrapper clickable={false}>
|
||||
@ -314,6 +329,7 @@ export default function Swap() {
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{showWrap ? null : (
|
||||
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
|
||||
<AutoColumn gap="4px">
|
||||
<RowBetween align="center">
|
||||
@ -321,8 +337,8 @@ export default function Swap() {
|
||||
Price
|
||||
</Text>
|
||||
<TradePrice
|
||||
inputToken={tokens[Field.INPUT]}
|
||||
outputToken={tokens[Field.OUTPUT]}
|
||||
inputCurrency={currencies[Field.INPUT]}
|
||||
outputCurrency={currencies[Field.OUTPUT]}
|
||||
price={trade?.executionPrice}
|
||||
showInverted={showInverted}
|
||||
setShowInverted={setShowInverted}
|
||||
@ -341,10 +357,15 @@ export default function Swap() {
|
||||
)}
|
||||
</AutoColumn>
|
||||
</Card>
|
||||
)}
|
||||
</AutoColumn>
|
||||
<BottomGrouping>
|
||||
{!account ? (
|
||||
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
|
||||
) : showWrap ? (
|
||||
<ButtonPrimary disabled={Boolean(wrapError)} onClick={onWrap}>
|
||||
{wrapError ?? (wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
|
||||
</ButtonPrimary>
|
||||
) : noRoute && userHasSpecifiedInputOutput ? (
|
||||
<GreyCard style={{ textAlign: 'center' }}>
|
||||
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
|
||||
@ -362,7 +383,7 @@ export default function Swap() {
|
||||
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
|
||||
'Approved'
|
||||
) : (
|
||||
'Approve ' + tokens[Field.INPUT]?.symbol
|
||||
'Approve ' + currencies[Field.INPUT]?.symbol
|
||||
)}
|
||||
</ButtonPrimary>
|
||||
<ButtonError
|
||||
|
@ -1,25 +1,12 @@
|
||||
import { createAction } from '@reduxjs/toolkit'
|
||||
|
||||
export type PopupContent =
|
||||
| {
|
||||
export type PopupContent = {
|
||||
txn: {
|
||||
hash: string
|
||||
success?: boolean
|
||||
success: boolean
|
||||
summary?: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
poolAdded: {
|
||||
token0?: {
|
||||
address?: string
|
||||
symbol?: string
|
||||
}
|
||||
token1: {
|
||||
address?: string
|
||||
symbol?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('updateBlockNumber')
|
||||
export const toggleWalletModal = createAction<void>('toggleWalletModal')
|
||||
|
@ -1,15 +1,10 @@
|
||||
import { createAction } from '@reduxjs/toolkit'
|
||||
import { ChainId } from '@uniswap/sdk'
|
||||
|
||||
export enum Field {
|
||||
LIQUIDITY_PERCENT = 'LIQUIDITY_PERCENT',
|
||||
LIQUIDITY = 'LIQUIDITY',
|
||||
TOKEN_A = 'TOKEN_A',
|
||||
TOKEN_B = 'TOKEN_B'
|
||||
CURRENCY_A = 'CURRENCY_A',
|
||||
CURRENCY_B = 'CURRENCY_B'
|
||||
}
|
||||
|
||||
export const typeInput = createAction<{ field: Field; typedValue: string }>('typeInputBurn')
|
||||
export const setBurnDefaultsFromURLMatchParams = createAction<{
|
||||
chainId: ChainId
|
||||
params: { tokens: string }
|
||||
}>('setBurnDefaultsFromURLMatchParams')
|
||||
|
@ -1,94 +1,74 @@
|
||||
import { useEffect, useCallback, useMemo } from 'react'
|
||||
import { Currency, CurrencyAmount, JSBI, Pair, Percent, TokenAmount } from '@uniswap/sdk'
|
||||
import { useCallback } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { AppDispatch, AppState } from '../index'
|
||||
import { Field, setBurnDefaultsFromURLMatchParams, typeInput } from './actions'
|
||||
import { useToken } from '../../hooks/Tokens'
|
||||
import { Token, Pair, TokenAmount, Percent, JSBI, Route } from '@uniswap/sdk'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import { useTokenBalances } from '../wallet/hooks'
|
||||
import { tryParseAmount } from '../swap/hooks'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
|
||||
const ZERO = JSBI.BigInt(0)
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { wrappedCurrency } from '../../utils/wrappedCurrency'
|
||||
import { AppDispatch, AppState } from '../index'
|
||||
import { tryParseAmount } from '../swap/hooks'
|
||||
import { useTokenBalances } from '../wallet/hooks'
|
||||
import { Field, typeInput } from './actions'
|
||||
|
||||
export function useBurnState(): AppState['burn'] {
|
||||
return useSelector<AppState, AppState['burn']>(state => state.burn)
|
||||
}
|
||||
|
||||
export function useDerivedBurnInfo(): {
|
||||
tokens: { [field in Extract<Field, Field.TOKEN_A | Field.TOKEN_B>]?: Token }
|
||||
export function useDerivedBurnInfo(
|
||||
currencyA: Currency | undefined,
|
||||
currencyB: Currency | undefined
|
||||
): {
|
||||
pair?: Pair | null
|
||||
route?: Route
|
||||
parsedAmounts: {
|
||||
[Field.LIQUIDITY_PERCENT]: Percent
|
||||
[Field.LIQUIDITY]?: TokenAmount
|
||||
[Field.TOKEN_A]?: TokenAmount
|
||||
[Field.TOKEN_B]?: TokenAmount
|
||||
[Field.CURRENCY_A]?: CurrencyAmount
|
||||
[Field.CURRENCY_B]?: CurrencyAmount
|
||||
}
|
||||
error?: string
|
||||
} {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const {
|
||||
independentField,
|
||||
typedValue,
|
||||
[Field.TOKEN_A]: { address: tokenAAddress },
|
||||
[Field.TOKEN_B]: { address: tokenBAddress }
|
||||
} = useBurnState()
|
||||
|
||||
// tokens
|
||||
const tokenA = useToken(tokenAAddress)
|
||||
const tokenB = useToken(tokenBAddress)
|
||||
const tokens: { [field in Extract<Field, Field.TOKEN_A | Field.TOKEN_B>]?: Token } = useMemo(
|
||||
() => ({
|
||||
[Field.TOKEN_A]: tokenA ?? undefined,
|
||||
[Field.TOKEN_B]: tokenB ?? undefined
|
||||
}),
|
||||
[tokenA, tokenB]
|
||||
)
|
||||
const { independentField, typedValue } = useBurnState()
|
||||
|
||||
// pair + totalsupply
|
||||
const pair = usePair(tokens[Field.TOKEN_A], tokens[Field.TOKEN_B])
|
||||
const noLiquidity =
|
||||
pair === null || (!!pair && JSBI.equal(pair.reserve0.raw, ZERO) && JSBI.equal(pair.reserve1.raw, ZERO))
|
||||
|
||||
// route
|
||||
const route =
|
||||
!noLiquidity && pair && tokens[Field.TOKEN_A] ? new Route([pair], tokens[Field.TOKEN_A] as Token) : undefined
|
||||
const [, pair] = usePair(currencyA, currencyB)
|
||||
|
||||
// balances
|
||||
const relevantTokenBalances = useTokenBalances(account ?? undefined, [pair?.liquidityToken])
|
||||
const userLiquidity: undefined | TokenAmount = relevantTokenBalances?.[pair?.liquidityToken?.address ?? '']
|
||||
|
||||
const [tokenA, tokenB] = [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
|
||||
const tokens = {
|
||||
[Field.CURRENCY_A]: tokenA,
|
||||
[Field.CURRENCY_B]: tokenB,
|
||||
[Field.LIQUIDITY]: pair?.liquidityToken
|
||||
}
|
||||
|
||||
// liquidity values
|
||||
const totalSupply = useTotalSupply(pair?.liquidityToken)
|
||||
const liquidityValues: { [field in Extract<Field, Field.TOKEN_A | Field.TOKEN_B>]?: TokenAmount } = {
|
||||
[Field.TOKEN_A]:
|
||||
const liquidityValueA =
|
||||
pair &&
|
||||
tokens[Field.TOKEN_A] &&
|
||||
totalSupply &&
|
||||
userLiquidity &&
|
||||
tokenA &&
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalSupply.raw, userLiquidity.raw)
|
||||
? new TokenAmount(
|
||||
tokens[Field.TOKEN_A] as Token,
|
||||
pair.getLiquidityValue(tokens[Field.TOKEN_A] as Token, totalSupply, userLiquidity, false).raw
|
||||
)
|
||||
: undefined,
|
||||
[Field.TOKEN_B]:
|
||||
pair &&
|
||||
tokens[Field.TOKEN_B] &&
|
||||
totalSupply &&
|
||||
userLiquidity &&
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalSupply.raw, userLiquidity.raw)
|
||||
? new TokenAmount(
|
||||
tokens[Field.TOKEN_B] as Token,
|
||||
pair.getLiquidityValue(tokens[Field.TOKEN_B] as Token, totalSupply, userLiquidity, false).raw
|
||||
)
|
||||
? new TokenAmount(tokenA, pair.getLiquidityValue(tokenA, totalSupply, userLiquidity, false).raw)
|
||||
: undefined
|
||||
const liquidityValueB =
|
||||
pair &&
|
||||
totalSupply &&
|
||||
userLiquidity &&
|
||||
tokenB &&
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalSupply.raw, userLiquidity.raw)
|
||||
? new TokenAmount(tokenB, pair.getLiquidityValue(tokenB, totalSupply, userLiquidity, false).raw)
|
||||
: undefined
|
||||
const liquidityValues: { [Field.CURRENCY_A]?: TokenAmount; [Field.CURRENCY_B]?: TokenAmount } = {
|
||||
[Field.CURRENCY_A]: liquidityValueA,
|
||||
[Field.CURRENCY_B]: liquidityValueB
|
||||
}
|
||||
|
||||
let percentToRemove: Percent = new Percent('0', '100')
|
||||
@ -109,12 +89,9 @@ export function useDerivedBurnInfo(): {
|
||||
else {
|
||||
if (tokens[independentField]) {
|
||||
const independentAmount = tryParseAmount(typedValue, tokens[independentField])
|
||||
if (
|
||||
independentAmount &&
|
||||
liquidityValues[independentField] &&
|
||||
!independentAmount.greaterThan(liquidityValues[independentField] as TokenAmount)
|
||||
) {
|
||||
percentToRemove = new Percent(independentAmount.raw, (liquidityValues[independentField] as TokenAmount).raw)
|
||||
const liquidityValue = liquidityValues[independentField]
|
||||
if (independentAmount && liquidityValue && !independentAmount.greaterThan(liquidityValue)) {
|
||||
percentToRemove = new Percent(independentAmount.raw, liquidityValue.raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,27 +99,21 @@ export function useDerivedBurnInfo(): {
|
||||
const parsedAmounts: {
|
||||
[Field.LIQUIDITY_PERCENT]: Percent
|
||||
[Field.LIQUIDITY]?: TokenAmount
|
||||
[Field.TOKEN_A]?: TokenAmount
|
||||
[Field.TOKEN_B]?: TokenAmount
|
||||
[Field.CURRENCY_A]?: TokenAmount
|
||||
[Field.CURRENCY_B]?: TokenAmount
|
||||
} = {
|
||||
[Field.LIQUIDITY_PERCENT]: percentToRemove,
|
||||
[Field.LIQUIDITY]:
|
||||
userLiquidity && percentToRemove && percentToRemove.greaterThan('0')
|
||||
? new TokenAmount(userLiquidity.token, percentToRemove.multiply(userLiquidity.raw).quotient)
|
||||
: undefined,
|
||||
[Field.TOKEN_A]:
|
||||
tokens[Field.TOKEN_A] && percentToRemove && percentToRemove.greaterThan('0') && liquidityValues[Field.TOKEN_A]
|
||||
? new TokenAmount(
|
||||
tokens[Field.TOKEN_A] as Token,
|
||||
percentToRemove.multiply((liquidityValues[Field.TOKEN_A] as TokenAmount).raw).quotient
|
||||
)
|
||||
[Field.CURRENCY_A]:
|
||||
tokenA && percentToRemove && percentToRemove.greaterThan('0') && liquidityValueA
|
||||
? new TokenAmount(tokenA, percentToRemove.multiply(liquidityValueA.raw).quotient)
|
||||
: undefined,
|
||||
[Field.TOKEN_B]:
|
||||
tokens[Field.TOKEN_B] && percentToRemove && percentToRemove.greaterThan('0') && liquidityValues[Field.TOKEN_B]
|
||||
? new TokenAmount(
|
||||
tokens[Field.TOKEN_B] as Token,
|
||||
percentToRemove.multiply((liquidityValues[Field.TOKEN_B] as TokenAmount).raw).quotient
|
||||
)
|
||||
[Field.CURRENCY_B]:
|
||||
tokenB && percentToRemove && percentToRemove.greaterThan('0') && liquidityValueB
|
||||
? new TokenAmount(tokenB, percentToRemove.multiply(liquidityValueB.raw).quotient)
|
||||
: undefined
|
||||
}
|
||||
|
||||
@ -151,11 +122,11 @@ export function useDerivedBurnInfo(): {
|
||||
error = 'Connect Wallet'
|
||||
}
|
||||
|
||||
if (!parsedAmounts[Field.LIQUIDITY] || !parsedAmounts[Field.TOKEN_A] || !parsedAmounts[Field.TOKEN_B]) {
|
||||
if (!parsedAmounts[Field.LIQUIDITY] || !parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) {
|
||||
error = error ?? 'Enter an amount'
|
||||
}
|
||||
|
||||
return { tokens, pair, route, parsedAmounts, error }
|
||||
return { pair, parsedAmounts, error }
|
||||
}
|
||||
|
||||
export function useBurnActionHandlers(): {
|
||||
@ -174,13 +145,3 @@ export function useBurnActionHandlers(): {
|
||||
onUserInput
|
||||
}
|
||||
}
|
||||
|
||||
// updates the burn state to use the appropriate tokens, given the route
|
||||
export function useDefaultsFromURLMatchParams(params: { tokens: string }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
useEffect(() => {
|
||||
if (!chainId) return
|
||||
dispatch(setBurnDefaultsFromURLMatchParams({ chainId, params }))
|
||||
}, [dispatch, chainId, params])
|
||||
}
|
||||
|
@ -1,65 +1,18 @@
|
||||
import { createReducer } from '@reduxjs/toolkit'
|
||||
import { ChainId, WETH } from '@uniswap/sdk'
|
||||
import { isAddress } from '../../utils'
|
||||
|
||||
import { Field, setBurnDefaultsFromURLMatchParams, typeInput } from './actions'
|
||||
import { Field, typeInput } from './actions'
|
||||
|
||||
export interface BurnState {
|
||||
readonly independentField: Field
|
||||
readonly typedValue: string
|
||||
readonly [Field.TOKEN_A]: {
|
||||
readonly address: string
|
||||
}
|
||||
readonly [Field.TOKEN_B]: {
|
||||
readonly address: string
|
||||
}
|
||||
}
|
||||
|
||||
const initialState: BurnState = {
|
||||
independentField: Field.LIQUIDITY_PERCENT,
|
||||
typedValue: '0',
|
||||
[Field.TOKEN_A]: {
|
||||
address: ''
|
||||
},
|
||||
[Field.TOKEN_B]: {
|
||||
address: ''
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTokens(chainId: ChainId, tokens: string): string[] {
|
||||
return (
|
||||
tokens
|
||||
// split by '-'
|
||||
.split('-')
|
||||
// map to addresses
|
||||
.map((token): string =>
|
||||
isAddress(token) ? token : token.toLowerCase() === 'ETH'.toLowerCase() ? WETH[chainId]?.address ?? '' : ''
|
||||
)
|
||||
//remove duplicates
|
||||
.filter((token, i, array) => array.indexOf(token) === i)
|
||||
// add two empty elements for cases where the array is length 0
|
||||
.concat(['', ''])
|
||||
// only consider the first 2 elements
|
||||
.slice(0, 2)
|
||||
)
|
||||
typedValue: '0'
|
||||
}
|
||||
|
||||
export default createReducer<BurnState>(initialState, builder =>
|
||||
builder
|
||||
.addCase(setBurnDefaultsFromURLMatchParams, (state, { payload: { chainId, params } }) => {
|
||||
const tokens = parseTokens(chainId, params?.tokens ?? '')
|
||||
return {
|
||||
independentField: Field.LIQUIDITY_PERCENT,
|
||||
typedValue: '0',
|
||||
[Field.TOKEN_A]: {
|
||||
address: tokens[0]
|
||||
},
|
||||
[Field.TOKEN_B]: {
|
||||
address: tokens[1]
|
||||
}
|
||||
}
|
||||
})
|
||||
.addCase(typeInput, (state, { payload: { field, typedValue } }) => {
|
||||
builder.addCase(typeInput, (state, { payload: { field, typedValue } }) => {
|
||||
return {
|
||||
...state,
|
||||
independentField: field,
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { createAction } from '@reduxjs/toolkit'
|
||||
|
||||
export enum Field {
|
||||
TOKEN_A = 'TOKEN_A',
|
||||
TOKEN_B = 'TOKEN_B'
|
||||
CURRENCY_A = 'CURRENCY_A',
|
||||
CURRENCY_B = 'CURRENCY_B'
|
||||
}
|
||||
|
||||
export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('typeInputMint')
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { Currency, CurrencyAmount, JSBI, Pair, Percent, Price, TokenAmount } from '@uniswap/sdk'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { Token, TokenAmount, Route, JSBI, Price, Percent, Pair } from '@uniswap/sdk'
|
||||
import { PairState, usePair } from '../../data/Reserves'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { wrappedCurrency, wrappedCurrencyAmount } from '../../utils/wrappedCurrency'
|
||||
import { AppDispatch, AppState } from '../index'
|
||||
import { Field, typeInput } from './actions'
|
||||
import { useTokenBalancesTreatWETHAsETH } from '../wallet/hooks'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { tryParseAmount } from '../swap/hooks'
|
||||
import { useCurrencyBalances } from '../wallet/hooks'
|
||||
import { Field, typeInput } from './actions'
|
||||
|
||||
const ZERO = JSBI.BigInt(0)
|
||||
|
||||
@ -17,109 +18,98 @@ export function useMintState(): AppState['mint'] {
|
||||
}
|
||||
|
||||
export function useDerivedMintInfo(
|
||||
tokenA: Token | undefined,
|
||||
tokenB: Token | undefined
|
||||
currencyA: Currency | undefined,
|
||||
currencyB: Currency | undefined
|
||||
): {
|
||||
dependentField: Field
|
||||
tokens: { [field in Field]?: Token }
|
||||
currencies: { [field in Field]?: Currency }
|
||||
pair?: Pair | null
|
||||
tokenBalances: { [field in Field]?: TokenAmount }
|
||||
parsedAmounts: { [field in Field]?: TokenAmount }
|
||||
pairState: PairState
|
||||
currencyBalances: { [field in Field]?: CurrencyAmount }
|
||||
parsedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
price?: Price
|
||||
noLiquidity?: boolean
|
||||
liquidityMinted?: TokenAmount
|
||||
poolTokenPercentage?: Percent
|
||||
error?: string
|
||||
} {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const { independentField, typedValue, otherTypedValue } = useMintState()
|
||||
|
||||
const dependentField = independentField === Field.TOKEN_A ? Field.TOKEN_B : Field.TOKEN_A
|
||||
const dependentField = independentField === Field.CURRENCY_A ? Field.CURRENCY_B : Field.CURRENCY_A
|
||||
|
||||
// tokens
|
||||
const tokens: { [field in Field]?: Token } = useMemo(
|
||||
const currencies: { [field in Field]?: Currency } = useMemo(
|
||||
() => ({
|
||||
[Field.TOKEN_A]: tokenA,
|
||||
[Field.TOKEN_B]: tokenB
|
||||
[Field.CURRENCY_A]: currencyA ?? undefined,
|
||||
[Field.CURRENCY_B]: currencyB ?? undefined
|
||||
}),
|
||||
[tokenA, tokenB]
|
||||
[currencyA, currencyB]
|
||||
)
|
||||
|
||||
// pair
|
||||
const pair = usePair(tokens[Field.TOKEN_A], tokens[Field.TOKEN_B])
|
||||
const noLiquidity =
|
||||
pair === null || (!!pair && JSBI.equal(pair.reserve0.raw, ZERO) && JSBI.equal(pair.reserve1.raw, ZERO))
|
||||
|
||||
// route
|
||||
const route = useMemo(
|
||||
() =>
|
||||
!noLiquidity && pair && tokens[independentField] ? new Route([pair], tokens[Field.TOKEN_A] as Token) : undefined,
|
||||
[noLiquidity, pair, tokens, independentField]
|
||||
)
|
||||
const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B])
|
||||
const noLiquidity: boolean =
|
||||
pairState === PairState.NOT_EXISTS ||
|
||||
Boolean(pair && JSBI.equal(pair.reserve0.raw, ZERO) && JSBI.equal(pair.reserve1.raw, ZERO))
|
||||
|
||||
// balances
|
||||
const relevantTokenBalances = useTokenBalancesTreatWETHAsETH(account ?? undefined, [
|
||||
tokens[Field.TOKEN_A],
|
||||
tokens[Field.TOKEN_B]
|
||||
const balances = useCurrencyBalances(account ?? undefined, [
|
||||
currencies[Field.CURRENCY_A],
|
||||
currencies[Field.CURRENCY_B]
|
||||
])
|
||||
const tokenBalances: { [field in Field]?: TokenAmount } = {
|
||||
[Field.TOKEN_A]: relevantTokenBalances?.[tokens[Field.TOKEN_A]?.address ?? ''],
|
||||
[Field.TOKEN_B]: relevantTokenBalances?.[tokens[Field.TOKEN_B]?.address ?? '']
|
||||
const currencyBalances: { [field in Field]?: CurrencyAmount } = {
|
||||
[Field.CURRENCY_A]: balances[0],
|
||||
[Field.CURRENCY_B]: balances[1]
|
||||
}
|
||||
|
||||
// amounts
|
||||
const independentAmount = tryParseAmount(typedValue, tokens[independentField])
|
||||
const independentAmount = tryParseAmount(typedValue, currencies[independentField])
|
||||
const dependentAmount = useMemo(() => {
|
||||
if (noLiquidity && otherTypedValue && tokens[dependentField]) {
|
||||
return tryParseAmount(otherTypedValue, tokens[dependentField])
|
||||
} else if (route && independentAmount) {
|
||||
return dependentField === Field.TOKEN_B
|
||||
? route.midPrice.quote(independentAmount)
|
||||
: route.midPrice.invert().quote(independentAmount)
|
||||
if (noLiquidity && otherTypedValue && currencies[dependentField]) {
|
||||
return tryParseAmount(otherTypedValue, currencies[dependentField])
|
||||
} else if (independentAmount) {
|
||||
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
|
||||
const [tokenA, tokenB] = [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
|
||||
if (tokenA && tokenB && wrappedIndependentAmount && pair) {
|
||||
return dependentField === Field.CURRENCY_B
|
||||
? pair.priceOf(tokenA).quote(wrappedIndependentAmount)
|
||||
: pair.priceOf(tokenB).quote(wrappedIndependentAmount)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}, [noLiquidity, otherTypedValue, tokens, dependentField, independentAmount, route])
|
||||
const parsedAmounts = {
|
||||
[Field.TOKEN_A]: independentField === Field.TOKEN_A ? independentAmount : dependentAmount,
|
||||
[Field.TOKEN_B]: independentField === Field.TOKEN_A ? dependentAmount : independentAmount
|
||||
}, [noLiquidity, otherTypedValue, currencies, dependentField, independentAmount, currencyA, chainId, currencyB, pair])
|
||||
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = {
|
||||
[Field.CURRENCY_A]: independentField === Field.CURRENCY_A ? independentAmount : dependentAmount,
|
||||
[Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount
|
||||
}
|
||||
|
||||
const price = useMemo(() => {
|
||||
if (
|
||||
noLiquidity &&
|
||||
tokens[Field.TOKEN_A] &&
|
||||
tokens[Field.TOKEN_B] &&
|
||||
parsedAmounts[Field.TOKEN_A] &&
|
||||
parsedAmounts[Field.TOKEN_B]
|
||||
) {
|
||||
return new Price(
|
||||
tokens[Field.TOKEN_A] as Token,
|
||||
tokens[Field.TOKEN_B] as Token,
|
||||
(parsedAmounts[Field.TOKEN_A] as TokenAmount).raw,
|
||||
(parsedAmounts[Field.TOKEN_B] as TokenAmount).raw
|
||||
)
|
||||
} else if (route) {
|
||||
return route.midPrice
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
if (noLiquidity && currencyAAmount && currencyBAmount) {
|
||||
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}, [noLiquidity, tokens, parsedAmounts, route])
|
||||
}, [noLiquidity, parsedAmounts])
|
||||
|
||||
// liquidity minted
|
||||
const totalSupply = useTotalSupply(pair?.liquidityToken)
|
||||
const liquidityMinted = useMemo(() => {
|
||||
if (pair && totalSupply && parsedAmounts[Field.TOKEN_A] && parsedAmounts[Field.TOKEN_B]) {
|
||||
return pair.getLiquidityMinted(
|
||||
totalSupply,
|
||||
parsedAmounts[Field.TOKEN_A] as TokenAmount,
|
||||
parsedAmounts[Field.TOKEN_B] as TokenAmount
|
||||
)
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
const [tokenAmountA, tokenAmountB] = [
|
||||
wrappedCurrencyAmount(currencyAAmount, chainId),
|
||||
wrappedCurrencyAmount(currencyBAmount, chainId)
|
||||
]
|
||||
if (pair && totalSupply && tokenAmountA && tokenAmountB) {
|
||||
return pair.getLiquidityMinted(totalSupply, tokenAmountA, tokenAmountB)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}, [pair, totalSupply, parsedAmounts])
|
||||
}, [parsedAmounts, chainId, pair, totalSupply])
|
||||
|
||||
const poolTokenPercentage = useMemo(() => {
|
||||
if (liquidityMinted && totalSupply) {
|
||||
@ -134,29 +124,30 @@ export function useDerivedMintInfo(
|
||||
error = 'Connect Wallet'
|
||||
}
|
||||
|
||||
if (!parsedAmounts[Field.TOKEN_A] || !parsedAmounts[Field.TOKEN_B]) {
|
||||
if (pairState === PairState.INVALID) {
|
||||
error = error ?? 'Invalid pair'
|
||||
}
|
||||
|
||||
if (!parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) {
|
||||
error = error ?? 'Enter an amount'
|
||||
}
|
||||
|
||||
if (
|
||||
parsedAmounts[Field.TOKEN_A] &&
|
||||
tokenBalances?.[Field.TOKEN_A]?.lessThan(parsedAmounts[Field.TOKEN_A] as TokenAmount)
|
||||
) {
|
||||
error = 'Insufficient ' + tokens[Field.TOKEN_A]?.symbol + ' balance'
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
|
||||
if (currencyAAmount && currencyBalances?.[Field.CURRENCY_A]?.lessThan(currencyAAmount)) {
|
||||
error = 'Insufficient ' + currencies[Field.CURRENCY_A]?.symbol + ' balance'
|
||||
}
|
||||
|
||||
if (
|
||||
parsedAmounts[Field.TOKEN_B] &&
|
||||
tokenBalances?.[Field.TOKEN_B]?.lessThan(parsedAmounts[Field.TOKEN_B] as TokenAmount)
|
||||
) {
|
||||
error = 'Insufficient ' + tokens[Field.TOKEN_B]?.symbol + ' balance'
|
||||
if (currencyBAmount && currencyBalances?.[Field.CURRENCY_B]?.lessThan(currencyBAmount)) {
|
||||
error = 'Insufficient ' + currencies[Field.CURRENCY_B]?.symbol + ' balance'
|
||||
}
|
||||
|
||||
return {
|
||||
dependentField,
|
||||
tokens,
|
||||
currencies,
|
||||
pair,
|
||||
tokenBalances,
|
||||
pairState,
|
||||
currencyBalances,
|
||||
parsedAmounts,
|
||||
price,
|
||||
noLiquidity,
|
||||
@ -169,18 +160,26 @@ export function useDerivedMintInfo(
|
||||
export function useMintActionHandlers(
|
||||
noLiquidity: boolean | undefined
|
||||
): {
|
||||
onUserInput: (field: Field, typedValue: string) => void
|
||||
onFieldAInput: (typedValue: string) => void
|
||||
onFieldBInput: (typedValue: string) => void
|
||||
} {
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
|
||||
const onUserInput = useCallback(
|
||||
(field: Field, typedValue: string) => {
|
||||
dispatch(typeInput({ field, typedValue, noLiquidity: noLiquidity === true }))
|
||||
const onFieldAInput = useCallback(
|
||||
(typedValue: string) => {
|
||||
dispatch(typeInput({ field: Field.CURRENCY_A, typedValue, noLiquidity: noLiquidity === true }))
|
||||
},
|
||||
[dispatch, noLiquidity]
|
||||
)
|
||||
const onFieldBInput = useCallback(
|
||||
(typedValue: string) => {
|
||||
dispatch(typeInput({ field: Field.CURRENCY_B, typedValue, noLiquidity: noLiquidity === true }))
|
||||
},
|
||||
[dispatch, noLiquidity]
|
||||
)
|
||||
|
||||
return {
|
||||
onUserInput
|
||||
onFieldAInput,
|
||||
onFieldBInput
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ describe('mint reducer', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(reducer, {
|
||||
independentField: Field.TOKEN_A,
|
||||
independentField: Field.CURRENCY_A,
|
||||
typedValue: '',
|
||||
otherTypedValue: ''
|
||||
})
|
||||
@ -16,13 +16,13 @@ describe('mint reducer', () => {
|
||||
|
||||
describe('typeInput', () => {
|
||||
it('sets typed value', () => {
|
||||
store.dispatch(typeInput({ field: Field.TOKEN_A, typedValue: '1.0', noLiquidity: false }))
|
||||
expect(store.getState()).toEqual({ independentField: Field.TOKEN_A, typedValue: '1.0', otherTypedValue: '' })
|
||||
store.dispatch(typeInput({ field: Field.CURRENCY_A, typedValue: '1.0', noLiquidity: false }))
|
||||
expect(store.getState()).toEqual({ independentField: Field.CURRENCY_A, typedValue: '1.0', otherTypedValue: '' })
|
||||
})
|
||||
it('clears other value', () => {
|
||||
store.dispatch(typeInput({ field: Field.TOKEN_A, typedValue: '1.0', noLiquidity: false }))
|
||||
store.dispatch(typeInput({ field: Field.TOKEN_B, typedValue: '1.0', noLiquidity: false }))
|
||||
expect(store.getState()).toEqual({ independentField: Field.TOKEN_B, typedValue: '1.0', otherTypedValue: '' })
|
||||
store.dispatch(typeInput({ field: Field.CURRENCY_A, typedValue: '1.0', noLiquidity: false }))
|
||||
store.dispatch(typeInput({ field: Field.CURRENCY_B, typedValue: '1.0', noLiquidity: false }))
|
||||
expect(store.getState()).toEqual({ independentField: Field.CURRENCY_B, typedValue: '1.0', otherTypedValue: '' })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -8,7 +8,7 @@ export interface MintState {
|
||||
}
|
||||
|
||||
const initialState: MintState = {
|
||||
independentField: Field.TOKEN_A,
|
||||
independentField: Field.CURRENCY_A,
|
||||
typedValue: '',
|
||||
otherTypedValue: ''
|
||||
}
|
||||
|
@ -5,14 +5,14 @@ export enum Field {
|
||||
OUTPUT = 'OUTPUT'
|
||||
}
|
||||
|
||||
export const selectToken = createAction<{ field: Field; address: string }>('selectToken')
|
||||
export const switchTokens = createAction<void>('switchTokens')
|
||||
export const selectCurrency = createAction<{ field: Field; currencyId: string }>('selectCurrency')
|
||||
export const switchCurrencies = createAction<void>('switchCurrencies')
|
||||
export const typeInput = createAction<{ field: Field; typedValue: string }>('typeInput')
|
||||
export const replaceSwapState = createAction<{
|
||||
field: Field
|
||||
typedValue: string
|
||||
inputTokenAddress?: string
|
||||
outputTokenAddress?: string
|
||||
inputCurrencyId?: string
|
||||
outputCurrencyId?: string
|
||||
recipient: string | null
|
||||
}>('replaceSwapState')
|
||||
export const setRecipient = createAction<{ recipient: string | null }>('setRecipient')
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { ChainId, WETH } from '@uniswap/sdk'
|
||||
import { parse } from 'qs'
|
||||
import { Field } from './actions'
|
||||
import { queryParametersToSwapState } from './hooks'
|
||||
@ -11,12 +10,11 @@ describe('hooks', () => {
|
||||
parse(
|
||||
'?inputCurrency=ETH&outputCurrency=0x6b175474e89094c44da98b954eedeac495271d0f&exactAmount=20.5&exactField=outPUT',
|
||||
{ parseArrays: false, ignoreQueryPrefix: true }
|
||||
),
|
||||
ChainId.MAINNET
|
||||
)
|
||||
)
|
||||
).toEqual({
|
||||
[Field.OUTPUT]: { address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' },
|
||||
[Field.INPUT]: { address: WETH[ChainId.MAINNET].address },
|
||||
[Field.OUTPUT]: { currencyId: '0x6B175474E89094C44Da98b954EedeAC495271d0F' },
|
||||
[Field.INPUT]: { currencyId: 'ETH' },
|
||||
typedValue: '20.5',
|
||||
independentField: Field.OUTPUT,
|
||||
recipient: null
|
||||
@ -25,13 +23,10 @@ describe('hooks', () => {
|
||||
|
||||
test('does not duplicate eth for invalid output token', () => {
|
||||
expect(
|
||||
queryParametersToSwapState(
|
||||
parse('?outputCurrency=invalid', { parseArrays: false, ignoreQueryPrefix: true }),
|
||||
ChainId.MAINNET
|
||||
)
|
||||
queryParametersToSwapState(parse('?outputCurrency=invalid', { parseArrays: false, ignoreQueryPrefix: true }))
|
||||
).toEqual({
|
||||
[Field.INPUT]: { address: '' },
|
||||
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address },
|
||||
[Field.INPUT]: { currencyId: '' },
|
||||
[Field.OUTPUT]: { currencyId: 'ETH' },
|
||||
typedValue: '',
|
||||
independentField: Field.INPUT,
|
||||
recipient: null
|
||||
@ -41,12 +36,11 @@ describe('hooks', () => {
|
||||
test('output ETH only', () => {
|
||||
expect(
|
||||
queryParametersToSwapState(
|
||||
parse('?outputCurrency=eth&exactAmount=20.5', { parseArrays: false, ignoreQueryPrefix: true }),
|
||||
ChainId.MAINNET
|
||||
parse('?outputCurrency=eth&exactAmount=20.5', { parseArrays: false, ignoreQueryPrefix: true })
|
||||
)
|
||||
).toEqual({
|
||||
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address },
|
||||
[Field.INPUT]: { address: '' },
|
||||
[Field.OUTPUT]: { currencyId: 'ETH' },
|
||||
[Field.INPUT]: { currencyId: '' },
|
||||
typedValue: '20.5',
|
||||
independentField: Field.INPUT,
|
||||
recipient: null
|
||||
@ -56,12 +50,11 @@ describe('hooks', () => {
|
||||
test('invalid recipient', () => {
|
||||
expect(
|
||||
queryParametersToSwapState(
|
||||
parse('?outputCurrency=eth&exactAmount=20.5&recipient=abc', { parseArrays: false, ignoreQueryPrefix: true }),
|
||||
ChainId.MAINNET
|
||||
parse('?outputCurrency=eth&exactAmount=20.5&recipient=abc', { parseArrays: false, ignoreQueryPrefix: true })
|
||||
)
|
||||
).toEqual({
|
||||
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address },
|
||||
[Field.INPUT]: { address: '' },
|
||||
[Field.OUTPUT]: { currencyId: 'ETH' },
|
||||
[Field.INPUT]: { currencyId: '' },
|
||||
typedValue: '20.5',
|
||||
independentField: Field.INPUT,
|
||||
recipient: null
|
||||
@ -74,12 +67,11 @@ describe('hooks', () => {
|
||||
parse('?outputCurrency=eth&exactAmount=20.5&recipient=0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5', {
|
||||
parseArrays: false,
|
||||
ignoreQueryPrefix: true
|
||||
}),
|
||||
ChainId.MAINNET
|
||||
})
|
||||
)
|
||||
).toEqual({
|
||||
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address },
|
||||
[Field.INPUT]: { address: '' },
|
||||
[Field.OUTPUT]: { currencyId: 'ETH' },
|
||||
[Field.INPUT]: { currencyId: '' },
|
||||
typedValue: '20.5',
|
||||
independentField: Field.INPUT,
|
||||
recipient: '0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5'
|
||||
@ -91,12 +83,11 @@ describe('hooks', () => {
|
||||
parse('?outputCurrency=eth&exactAmount=20.5&recipient=bob.argent.xyz', {
|
||||
parseArrays: false,
|
||||
ignoreQueryPrefix: true
|
||||
}),
|
||||
ChainId.MAINNET
|
||||
})
|
||||
)
|
||||
).toEqual({
|
||||
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address },
|
||||
[Field.INPUT]: { address: '' },
|
||||
[Field.OUTPUT]: { currencyId: 'ETH' },
|
||||
[Field.INPUT]: { currencyId: '' },
|
||||
typedValue: '20.5',
|
||||
independentField: Field.INPUT,
|
||||
recipient: 'bob.argent.xyz'
|
||||
|
@ -1,19 +1,19 @@
|
||||
import useENS from '../../hooks/useENS'
|
||||
import { Version } from '../../hooks/useToggledVersion'
|
||||
import { parseUnits } from '@ethersproject/units'
|
||||
import { ChainId, JSBI, Token, TokenAmount, Trade, WETH } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount, ETHER, JSBI, Token, TokenAmount, Trade } from '@uniswap/sdk'
|
||||
import { ParsedQs } from 'qs'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useV1Trade } from '../../data/V1'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useToken } from '../../hooks/Tokens'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades'
|
||||
import useParsedQueryString from '../../hooks/useParsedQueryString'
|
||||
import { isAddress } from '../../utils'
|
||||
import { AppDispatch, AppState } from '../index'
|
||||
import { useTokenBalancesTreatWETHAsETH } from '../wallet/hooks'
|
||||
import { Field, replaceSwapState, selectToken, setRecipient, switchTokens, typeInput } from './actions'
|
||||
import { useCurrencyBalances } from '../wallet/hooks'
|
||||
import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions'
|
||||
import { SwapState } from './reducer'
|
||||
import useToggledVersion from '../../hooks/useToggledVersion'
|
||||
import { useUserSlippageTolerance } from '../user/hooks'
|
||||
@ -24,18 +24,18 @@ export function useSwapState(): AppState['swap'] {
|
||||
}
|
||||
|
||||
export function useSwapActionHandlers(): {
|
||||
onTokenSelection: (field: Field, address: string) => void
|
||||
onCurrencySelection: (field: Field, currency: Currency) => void
|
||||
onSwitchTokens: () => void
|
||||
onUserInput: (field: Field, typedValue: string) => void
|
||||
onChangeRecipient: (recipient: string | null) => void
|
||||
} {
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
const onTokenSelection = useCallback(
|
||||
(field: Field, address: string) => {
|
||||
const onCurrencySelection = useCallback(
|
||||
(field: Field, currency: Currency) => {
|
||||
dispatch(
|
||||
selectToken({
|
||||
selectCurrency({
|
||||
field,
|
||||
address
|
||||
currencyId: currency instanceof Token ? currency.address : currency === ETHER ? 'ETH' : ''
|
||||
})
|
||||
)
|
||||
},
|
||||
@ -43,7 +43,7 @@ export function useSwapActionHandlers(): {
|
||||
)
|
||||
|
||||
const onSwitchTokens = useCallback(() => {
|
||||
dispatch(switchTokens())
|
||||
dispatch(switchCurrencies())
|
||||
}, [dispatch])
|
||||
|
||||
const onUserInput = useCallback(
|
||||
@ -62,21 +62,23 @@ export function useSwapActionHandlers(): {
|
||||
|
||||
return {
|
||||
onSwitchTokens,
|
||||
onTokenSelection,
|
||||
onCurrencySelection,
|
||||
onUserInput,
|
||||
onChangeRecipient
|
||||
}
|
||||
}
|
||||
|
||||
// try to parse a user entered amount for a given token
|
||||
export function tryParseAmount(value?: string, token?: Token): TokenAmount | undefined {
|
||||
if (!value || !token) {
|
||||
export function tryParseAmount(value?: string, currency?: Currency): CurrencyAmount | undefined {
|
||||
if (!value || !currency) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const typedValueParsed = parseUnits(value, token.decimals).toString()
|
||||
const typedValueParsed = parseUnits(value, currency.decimals).toString()
|
||||
if (typedValueParsed !== '0') {
|
||||
return new TokenAmount(token, JSBI.BigInt(typedValueParsed))
|
||||
return currency instanceof Token
|
||||
? new TokenAmount(currency, JSBI.BigInt(typedValueParsed))
|
||||
: CurrencyAmount.ether(JSBI.BigInt(typedValueParsed))
|
||||
}
|
||||
} catch (error) {
|
||||
// should fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
|
||||
@ -88,9 +90,9 @@ export function tryParseAmount(value?: string, token?: Token): TokenAmount | und
|
||||
|
||||
// from the current swap inputs, compute the best trade and return it.
|
||||
export function useDerivedSwapInfo(): {
|
||||
tokens: { [field in Field]?: Token }
|
||||
tokenBalances: { [field in Field]?: TokenAmount }
|
||||
parsedAmount: TokenAmount | undefined
|
||||
currencies: { [field in Field]?: Currency }
|
||||
currencyBalances: { [field in Field]?: CurrencyAmount }
|
||||
parsedAmount: CurrencyAmount | undefined
|
||||
v2Trade: Trade | undefined
|
||||
error?: string
|
||||
v1Trade: Trade | undefined
|
||||
@ -102,41 +104,41 @@ export function useDerivedSwapInfo(): {
|
||||
const {
|
||||
independentField,
|
||||
typedValue,
|
||||
[Field.INPUT]: { address: tokenInAddress },
|
||||
[Field.OUTPUT]: { address: tokenOutAddress },
|
||||
[Field.INPUT]: { currencyId: inputCurrencyId },
|
||||
[Field.OUTPUT]: { currencyId: outputCurrencyId },
|
||||
recipient
|
||||
} = useSwapState()
|
||||
|
||||
const tokenIn = useToken(tokenInAddress)
|
||||
const tokenOut = useToken(tokenOutAddress)
|
||||
const inputCurrency = useCurrency(inputCurrencyId)
|
||||
const outputCurrency = useCurrency(outputCurrencyId)
|
||||
const recipientLookup = useENS(recipient ?? undefined)
|
||||
const to: string | null = (recipient === null ? account : recipientLookup.address) ?? null
|
||||
|
||||
const relevantTokenBalances = useTokenBalancesTreatWETHAsETH(account ?? undefined, [
|
||||
tokenIn ?? undefined,
|
||||
tokenOut ?? undefined
|
||||
const relevantTokenBalances = useCurrencyBalances(account ?? undefined, [
|
||||
inputCurrency ?? undefined,
|
||||
outputCurrency ?? undefined
|
||||
])
|
||||
|
||||
const isExactIn: boolean = independentField === Field.INPUT
|
||||
const parsedAmount = tryParseAmount(typedValue, (isExactIn ? tokenIn : tokenOut) ?? undefined)
|
||||
const parsedAmount = tryParseAmount(typedValue, (isExactIn ? inputCurrency : outputCurrency) ?? undefined)
|
||||
|
||||
const bestTradeExactIn = useTradeExactIn(isExactIn ? parsedAmount : undefined, tokenOut ?? undefined)
|
||||
const bestTradeExactOut = useTradeExactOut(tokenIn ?? undefined, !isExactIn ? parsedAmount : undefined)
|
||||
const bestTradeExactIn = useTradeExactIn(isExactIn ? parsedAmount : undefined, outputCurrency ?? undefined)
|
||||
const bestTradeExactOut = useTradeExactOut(inputCurrency ?? undefined, !isExactIn ? parsedAmount : undefined)
|
||||
|
||||
const v2Trade = isExactIn ? bestTradeExactIn : bestTradeExactOut
|
||||
|
||||
const tokenBalances = {
|
||||
[Field.INPUT]: relevantTokenBalances?.[tokenIn?.address ?? ''],
|
||||
[Field.OUTPUT]: relevantTokenBalances?.[tokenOut?.address ?? '']
|
||||
const currencyBalances = {
|
||||
[Field.INPUT]: relevantTokenBalances[0],
|
||||
[Field.OUTPUT]: relevantTokenBalances[1]
|
||||
}
|
||||
|
||||
const tokens: { [field in Field]?: Token } = {
|
||||
[Field.INPUT]: tokenIn ?? undefined,
|
||||
[Field.OUTPUT]: tokenOut ?? undefined
|
||||
const currencies: { [field in Field]?: Currency } = {
|
||||
[Field.INPUT]: inputCurrency ?? undefined,
|
||||
[Field.OUTPUT]: outputCurrency ?? undefined
|
||||
}
|
||||
|
||||
// get link to trade on v1, if a better rate exists
|
||||
const v1Trade = useV1Trade(isExactIn, tokens[Field.INPUT], tokens[Field.OUTPUT], parsedAmount)
|
||||
const v1Trade = useV1Trade(isExactIn, currencies[Field.INPUT], currencies[Field.OUTPUT], parsedAmount)
|
||||
|
||||
let error: string | undefined
|
||||
if (!account) {
|
||||
@ -147,7 +149,7 @@ export function useDerivedSwapInfo(): {
|
||||
error = error ?? 'Enter an amount'
|
||||
}
|
||||
|
||||
if (!tokens[Field.INPUT] || !tokens[Field.OUTPUT]) {
|
||||
if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) {
|
||||
error = error ?? 'Select a token'
|
||||
}
|
||||
|
||||
@ -162,9 +164,9 @@ export function useDerivedSwapInfo(): {
|
||||
const slippageAdjustedAmountsV1 =
|
||||
v1Trade && allowedSlippage && computeSlippageAdjustedAmounts(v1Trade, allowedSlippage)
|
||||
|
||||
// compare input balance to MAx input based on version
|
||||
// compare input balance to max input based on version
|
||||
const [balanceIn, amountIn] = [
|
||||
tokenBalances[Field.INPUT],
|
||||
currencyBalances[Field.INPUT],
|
||||
toggledVersion === Version.v1
|
||||
? slippageAdjustedAmountsV1
|
||||
? slippageAdjustedAmountsV1[Field.INPUT]
|
||||
@ -175,12 +177,12 @@ export function useDerivedSwapInfo(): {
|
||||
]
|
||||
|
||||
if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
|
||||
error = 'Insufficient ' + amountIn.token.symbol + ' balance'
|
||||
error = 'Insufficient ' + amountIn.currency.symbol + ' balance'
|
||||
}
|
||||
|
||||
return {
|
||||
tokens,
|
||||
tokenBalances,
|
||||
currencies,
|
||||
currencyBalances,
|
||||
parsedAmount,
|
||||
v2Trade: v2Trade ?? undefined,
|
||||
error,
|
||||
@ -188,14 +190,14 @@ export function useDerivedSwapInfo(): {
|
||||
}
|
||||
}
|
||||
|
||||
function parseCurrencyFromURLParameter(urlParam: any, chainId: number): string {
|
||||
function parseCurrencyFromURLParameter(urlParam: any): string {
|
||||
if (typeof urlParam === 'string') {
|
||||
const valid = isAddress(urlParam)
|
||||
if (valid) return valid
|
||||
if (urlParam.toLowerCase() === 'eth') return WETH[chainId as ChainId]?.address ?? ''
|
||||
if (valid === false) return WETH[chainId as ChainId]?.address ?? ''
|
||||
if (urlParam.toUpperCase() === 'ETH') return 'ETH'
|
||||
if (valid === false) return 'ETH'
|
||||
}
|
||||
return WETH[chainId as ChainId]?.address ?? ''
|
||||
return 'ETH' ?? ''
|
||||
}
|
||||
|
||||
function parseTokenAmountURLParameter(urlParam: any): string {
|
||||
@ -217,9 +219,9 @@ function validatedRecipient(recipient: any): string | null {
|
||||
return null
|
||||
}
|
||||
|
||||
export function queryParametersToSwapState(parsedQs: ParsedQs, chainId: ChainId): SwapState {
|
||||
let inputCurrency = parseCurrencyFromURLParameter(parsedQs.inputCurrency, chainId)
|
||||
let outputCurrency = parseCurrencyFromURLParameter(parsedQs.outputCurrency, chainId)
|
||||
export function queryParametersToSwapState(parsedQs: ParsedQs): SwapState {
|
||||
let inputCurrency = parseCurrencyFromURLParameter(parsedQs.inputCurrency)
|
||||
let outputCurrency = parseCurrencyFromURLParameter(parsedQs.outputCurrency)
|
||||
if (inputCurrency === outputCurrency) {
|
||||
if (typeof parsedQs.outputCurrency === 'string') {
|
||||
inputCurrency = ''
|
||||
@ -232,10 +234,10 @@ export function queryParametersToSwapState(parsedQs: ParsedQs, chainId: ChainId)
|
||||
|
||||
return {
|
||||
[Field.INPUT]: {
|
||||
address: inputCurrency
|
||||
currencyId: inputCurrency
|
||||
},
|
||||
[Field.OUTPUT]: {
|
||||
address: outputCurrency
|
||||
currencyId: outputCurrency
|
||||
},
|
||||
typedValue: parseTokenAmountURLParameter(parsedQs.exactAmount),
|
||||
independentField: parseIndependentFieldURLParameter(parsedQs.exactField),
|
||||
@ -251,14 +253,14 @@ export function useDefaultsFromURLSearch() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!chainId) return
|
||||
const parsed = queryParametersToSwapState(parsedQs, chainId)
|
||||
const parsed = queryParametersToSwapState(parsedQs)
|
||||
|
||||
dispatch(
|
||||
replaceSwapState({
|
||||
typedValue: parsed.typedValue,
|
||||
field: parsed.independentField,
|
||||
inputTokenAddress: parsed[Field.INPUT].address,
|
||||
outputTokenAddress: parsed[Field.OUTPUT].address,
|
||||
inputCurrencyId: parsed[Field.INPUT].currencyId,
|
||||
outputCurrencyId: parsed[Field.OUTPUT].currencyId,
|
||||
recipient: parsed.recipient
|
||||
})
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createStore, Store } from 'redux'
|
||||
import { Field, selectToken } from './actions'
|
||||
import { Field, selectCurrency } from './actions'
|
||||
import reducer, { SwapState } from './reducer'
|
||||
|
||||
describe('swap reducer', () => {
|
||||
@ -7,27 +7,29 @@ describe('swap reducer', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(reducer, {
|
||||
[Field.OUTPUT]: { address: '' },
|
||||
[Field.INPUT]: { address: '' },
|
||||
[Field.OUTPUT]: { currencyId: '' },
|
||||
[Field.INPUT]: { currencyId: '' },
|
||||
typedValue: '',
|
||||
independentField: Field.INPUT
|
||||
independentField: Field.INPUT,
|
||||
recipient: null
|
||||
})
|
||||
})
|
||||
|
||||
describe('selectToken', () => {
|
||||
it('changes token', () => {
|
||||
store.dispatch(
|
||||
selectToken({
|
||||
selectCurrency({
|
||||
field: Field.OUTPUT,
|
||||
address: '0x0000'
|
||||
currencyId: '0x0000'
|
||||
})
|
||||
)
|
||||
|
||||
expect(store.getState()).toEqual({
|
||||
[Field.OUTPUT]: { address: '0x0000' },
|
||||
[Field.INPUT]: { address: '' },
|
||||
[Field.OUTPUT]: { currencyId: '0x0000' },
|
||||
[Field.INPUT]: { currencyId: '' },
|
||||
typedValue: '',
|
||||
independentField: Field.INPUT
|
||||
independentField: Field.INPUT,
|
||||
recipient: null
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { createReducer } from '@reduxjs/toolkit'
|
||||
import { Field, replaceSwapState, selectToken, setRecipient, switchTokens, typeInput } from './actions'
|
||||
import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions'
|
||||
|
||||
export interface SwapState {
|
||||
readonly independentField: Field
|
||||
readonly typedValue: string
|
||||
readonly [Field.INPUT]: {
|
||||
readonly address: string | undefined
|
||||
readonly currencyId: string | undefined
|
||||
}
|
||||
readonly [Field.OUTPUT]: {
|
||||
readonly address: string | undefined
|
||||
readonly currencyId: string | undefined
|
||||
}
|
||||
// the typed recipient address, or null if swap should go to sender
|
||||
// the typed recipient address or ENS name, or null if swap should go to sender
|
||||
readonly recipient: string | null
|
||||
}
|
||||
|
||||
@ -18,10 +18,10 @@ const initialState: SwapState = {
|
||||
independentField: Field.INPUT,
|
||||
typedValue: '',
|
||||
[Field.INPUT]: {
|
||||
address: ''
|
||||
currencyId: ''
|
||||
},
|
||||
[Field.OUTPUT]: {
|
||||
address: ''
|
||||
currencyId: ''
|
||||
},
|
||||
recipient: null
|
||||
}
|
||||
@ -30,13 +30,13 @@ export default createReducer<SwapState>(initialState, builder =>
|
||||
builder
|
||||
.addCase(
|
||||
replaceSwapState,
|
||||
(state, { payload: { typedValue, recipient, field, inputTokenAddress, outputTokenAddress } }) => {
|
||||
(state, { payload: { typedValue, recipient, field, inputCurrencyId, outputCurrencyId } }) => {
|
||||
return {
|
||||
[Field.INPUT]: {
|
||||
address: inputTokenAddress
|
||||
currencyId: inputCurrencyId
|
||||
},
|
||||
[Field.OUTPUT]: {
|
||||
address: outputTokenAddress
|
||||
currencyId: outputCurrencyId
|
||||
},
|
||||
independentField: field,
|
||||
typedValue: typedValue,
|
||||
@ -44,30 +44,30 @@ export default createReducer<SwapState>(initialState, builder =>
|
||||
}
|
||||
}
|
||||
)
|
||||
.addCase(selectToken, (state, { payload: { address, field } }) => {
|
||||
.addCase(selectCurrency, (state, { payload: { currencyId, field } }) => {
|
||||
const otherField = field === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||
if (address === state[otherField].address) {
|
||||
if (currencyId === state[otherField].currencyId) {
|
||||
// the case where we have to swap the order
|
||||
return {
|
||||
...state,
|
||||
independentField: state.independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT,
|
||||
[field]: { address },
|
||||
[otherField]: { address: state[field].address }
|
||||
[field]: { currencyId: currencyId },
|
||||
[otherField]: { currencyId: state[field].currencyId }
|
||||
}
|
||||
} else {
|
||||
// the normal case
|
||||
return {
|
||||
...state,
|
||||
[field]: { address }
|
||||
[field]: { currencyId: currencyId }
|
||||
}
|
||||
}
|
||||
})
|
||||
.addCase(switchTokens, state => {
|
||||
.addCase(switchCurrencies, state => {
|
||||
return {
|
||||
...state,
|
||||
independentField: state.independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT,
|
||||
[Field.INPUT]: { address: state[Field.OUTPUT].address },
|
||||
[Field.OUTPUT]: { address: state[Field.INPUT].address }
|
||||
[Field.INPUT]: { currencyId: state[Field.OUTPUT].currencyId },
|
||||
[Field.OUTPUT]: { currencyId: state[Field.INPUT].currencyId }
|
||||
}
|
||||
})
|
||||
.addCase(typeInput, (state, { payload: { field, typedValue } }) => {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { ChainId, JSBI, Pair, Token, TokenAmount } from '@uniswap/sdk'
|
||||
import { ChainId, Pair, Token } from '@uniswap/sdk'
|
||||
import flatMap from 'lodash.flatmap'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
|
||||
import { BASES_TO_TRACK_LIQUIDITY_FOR, PINNED_PAIRS } from '../../constants'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokens } from '../../hooks/Tokens'
|
||||
@ -14,11 +15,10 @@ import {
|
||||
SerializedPair,
|
||||
SerializedToken,
|
||||
updateUserDarkMode,
|
||||
updateUserDeadline,
|
||||
updateUserExpertMode,
|
||||
updateUserSlippageTolerance,
|
||||
updateUserDeadline
|
||||
updateUserSlippageTolerance
|
||||
} from './actions'
|
||||
import { BASES_TO_TRACK_LIQUIDITY_FOR, DUMMY_PAIRS_TO_PIN } from '../../constants'
|
||||
|
||||
function serializeToken(token: Token): SerializedToken {
|
||||
return {
|
||||
@ -67,8 +67,7 @@ export function useDarkModeManager(): [boolean, () => void] {
|
||||
}
|
||||
|
||||
export function useIsExpertMode(): boolean {
|
||||
const userExpertMode = useSelector<AppState, AppState['user']['userExpertMode']>(state => state.user.userExpertMode)
|
||||
return userExpertMode
|
||||
return useSelector<AppState, AppState['user']['userExpertMode']>(state => state.user.userExpertMode)
|
||||
}
|
||||
|
||||
export function useExpertModeManager(): [boolean, () => void] {
|
||||
@ -144,8 +143,6 @@ export function useUserAddedTokens(): Token[] {
|
||||
}, [serializedTokensMap, chainId])
|
||||
}
|
||||
|
||||
const ZERO = JSBI.BigInt(0)
|
||||
|
||||
function serializePair(pair: Pair): SerializedPair {
|
||||
return {
|
||||
token0: serializeToken(pair.token0),
|
||||
@ -186,68 +183,79 @@ export function useTokenWarningDismissal(chainId?: number, token?: Token): [bool
|
||||
}, [chainId, token, dismissalState, dispatch])
|
||||
}
|
||||
|
||||
export function useAllDummyPairs(): Pair[] {
|
||||
/**
|
||||
* Given two tokens return the liquidity token that represents its liquidity shares
|
||||
* @param tokenA one of the two tokens
|
||||
* @param tokenB the other token
|
||||
*/
|
||||
export function toV2LiquidityToken([tokenA, tokenB]: [Token, Token]): Token {
|
||||
return new Token(tokenA.chainId, Pair.getAddress(tokenA, tokenB), 18, 'UNI-V2', 'Uniswap V2')
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the pairs of tokens that are tracked by the user for the current chain ID.
|
||||
*/
|
||||
export function useTrackedTokenPairs(): [Token, Token][] {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const tokens = useAllTokens()
|
||||
|
||||
// pinned pairs
|
||||
const pinnedPairs = useMemo(() => DUMMY_PAIRS_TO_PIN[chainId as ChainId] ?? [], [chainId])
|
||||
const pinnedPairs = useMemo(() => (chainId ? PINNED_PAIRS[chainId] ?? [] : []), [chainId])
|
||||
|
||||
// pairs for every token against every base
|
||||
const generatedPairs: Pair[] = useMemo(
|
||||
const generatedPairs: [Token, Token][] = useMemo(
|
||||
() =>
|
||||
flatMap(
|
||||
Object.values(tokens)
|
||||
// select only tokens on the current chain
|
||||
.filter(token => token.chainId === chainId),
|
||||
token => {
|
||||
chainId
|
||||
? flatMap(Object.keys(tokens), tokenAddress => {
|
||||
const token = tokens[tokenAddress]
|
||||
// for each token on the current chain,
|
||||
return (
|
||||
// loop though all bases on the current chain
|
||||
(BASES_TO_TRACK_LIQUIDITY_FOR[chainId as ChainId] ?? [])
|
||||
(BASES_TO_TRACK_LIQUIDITY_FOR[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))
|
||||
return [base, token]
|
||||
}
|
||||
})
|
||||
.filter(pair => !!pair) as Pair[]
|
||||
.filter((p): p is [Token, Token] => p !== null)
|
||||
)
|
||||
}
|
||||
),
|
||||
})
|
||||
: [],
|
||||
[tokens, chainId]
|
||||
)
|
||||
|
||||
// pairs saved by users
|
||||
const savedSerializedPairs = useSelector<AppState, AppState['user']['pairs']>(({ user: { pairs } }) => pairs)
|
||||
const userPairs: Pair[] = useMemo(
|
||||
() =>
|
||||
Object.values<SerializedPair>(savedSerializedPairs[chainId ?? -1] ?? {}).map(
|
||||
pair =>
|
||||
new Pair(
|
||||
new TokenAmount(deserializeToken(pair.token0), ZERO),
|
||||
new TokenAmount(deserializeToken(pair.token1), ZERO)
|
||||
)
|
||||
),
|
||||
[savedSerializedPairs, chainId]
|
||||
)
|
||||
|
||||
const userPairs: [Token, Token][] = useMemo(() => {
|
||||
if (!chainId || !savedSerializedPairs) return []
|
||||
const forChain = savedSerializedPairs[chainId]
|
||||
if (!forChain) return []
|
||||
|
||||
return Object.keys(forChain).map(pairId => {
|
||||
return [deserializeToken(forChain[pairId].token0), deserializeToken(forChain[pairId].token1)]
|
||||
})
|
||||
}, [savedSerializedPairs, chainId])
|
||||
|
||||
const combinedList = useMemo(() => userPairs.concat(generatedPairs).concat(pinnedPairs), [
|
||||
generatedPairs,
|
||||
pinnedPairs,
|
||||
userPairs
|
||||
])
|
||||
|
||||
return useMemo(() => {
|
||||
const cache: { [pairKey: string]: boolean } = {}
|
||||
return (
|
||||
pinnedPairs
|
||||
.concat(generatedPairs)
|
||||
.concat(userPairs)
|
||||
// filter out duplicate pairs
|
||||
.filter(pair => {
|
||||
const pairKey = `${pair.token0.address}:${pair.token1.address}`
|
||||
if (cache[pairKey]) {
|
||||
return false
|
||||
}
|
||||
return (cache[pairKey] = true)
|
||||
})
|
||||
)
|
||||
}, [pinnedPairs, generatedPairs, userPairs])
|
||||
// dedupes pairs of tokens in the combined list
|
||||
const keyed = combinedList.reduce<{ [key: string]: [Token, Token] }>((memo, [tokenA, tokenB]) => {
|
||||
const sorted = tokenA.sortsBefore(tokenB)
|
||||
const key = sorted ? `${tokenA.address}:${tokenB.address}` : `${tokenB.address}:${tokenA.address}`
|
||||
if (memo[key]) return memo
|
||||
memo[key] = sorted ? [tokenA, tokenB] : [tokenB, tokenA]
|
||||
return memo
|
||||
}, {})
|
||||
|
||||
return Object.keys(keyed).map(key => keyed[key])
|
||||
}, [combinedList])
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChainId, JSBI, Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount, ETHER, JSBI, Token, TokenAmount } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import ERC20_INTERFACE from '../../constants/abis/erc20'
|
||||
import { useAllTokens } from '../../hooks/Tokens'
|
||||
@ -10,7 +10,9 @@ import { useSingleContractMultipleData, useMultipleContractSingleData } from '..
|
||||
/**
|
||||
* Returns a map of the given addresses to their eventually consistent ETH balances.
|
||||
*/
|
||||
export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): { [address: string]: JSBI | undefined } {
|
||||
export function useETHBalances(
|
||||
uncheckedAddresses?: (string | undefined)[]
|
||||
): { [address: string]: CurrencyAmount | undefined } {
|
||||
const multicallContract = useMulticallContract()
|
||||
|
||||
const addresses: string[] = useMemo(
|
||||
@ -32,9 +34,9 @@ export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): { [
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
addresses.reduce<{ [address: string]: JSBI | undefined }>((memo, address, i) => {
|
||||
addresses.reduce<{ [address: string]: CurrencyAmount }>((memo, address, i) => {
|
||||
const value = results?.[i]?.result?.[0]
|
||||
if (value) memo[address] = JSBI.BigInt(value.toString())
|
||||
if (value) memo[address] = CurrencyAmount.ether(JSBI.BigInt(value.toString()))
|
||||
return memo
|
||||
}, {}),
|
||||
[addresses, results]
|
||||
@ -57,7 +59,7 @@ export function useTokenBalancesWithLoadingIndicator(
|
||||
|
||||
const balances = useMultipleContractSingleData(validatedTokenAddresses, ERC20_INTERFACE, 'balanceOf', [address])
|
||||
|
||||
const anyLoading = balances.some(callState => callState.loading)
|
||||
const anyLoading: boolean = useMemo(() => balances.some(callState => callState.loading), [balances])
|
||||
|
||||
return [
|
||||
useMemo(
|
||||
@ -85,45 +87,6 @@ export function useTokenBalances(
|
||||
return useTokenBalancesWithLoadingIndicator(address, tokens)[0]
|
||||
}
|
||||
|
||||
// contains the hacky logic to treat the WETH token input as if it's ETH to
|
||||
// maintain compatibility until we handle them separately.
|
||||
export function useTokenBalancesTreatWETHAsETH(
|
||||
address?: string,
|
||||
tokens?: (Token | undefined)[]
|
||||
): { [tokenAddress: string]: TokenAmount | undefined } {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { tokensWithoutWETH, includesWETH } = useMemo(() => {
|
||||
if (!tokens || tokens.length === 0) {
|
||||
return { includesWETH: false, tokensWithoutWETH: [] }
|
||||
}
|
||||
let includesWETH = false
|
||||
const tokensWithoutWETH = tokens.filter(t => {
|
||||
if (!chainId) return true
|
||||
const isWETH = t?.equals(WETH[chainId as ChainId]) ?? false
|
||||
if (isWETH) includesWETH = true
|
||||
return !isWETH
|
||||
})
|
||||
return { includesWETH, tokensWithoutWETH }
|
||||
}, [tokens, chainId])
|
||||
|
||||
const balancesWithoutWETH = useTokenBalances(address, tokensWithoutWETH)
|
||||
const ETHBalance = useETHBalances(includesWETH ? [address] : [])
|
||||
|
||||
return useMemo(() => {
|
||||
if (!chainId || !address) return {}
|
||||
if (includesWETH) {
|
||||
const weth = WETH[chainId as ChainId]
|
||||
const ethBalance = ETHBalance[address]
|
||||
return {
|
||||
...balancesWithoutWETH,
|
||||
...(ethBalance && weth ? { [weth.address]: new TokenAmount(weth, ethBalance) } : null)
|
||||
}
|
||||
} else {
|
||||
return balancesWithoutWETH
|
||||
}
|
||||
}, [balancesWithoutWETH, ETHBalance, includesWETH, address, chainId])
|
||||
}
|
||||
|
||||
// get the balance for a single token/account combo
|
||||
export function useTokenBalance(account?: string, token?: Token): TokenAmount | undefined {
|
||||
const tokenBalances = useTokenBalances(account, [token])
|
||||
@ -131,18 +94,39 @@ export function useTokenBalance(account?: string, token?: Token): TokenAmount |
|
||||
return tokenBalances[token.address]
|
||||
}
|
||||
|
||||
// mimics the behavior of useAddressBalance
|
||||
export function useTokenBalanceTreatingWETHasETH(account?: string, token?: Token): TokenAmount | undefined {
|
||||
const balances = useTokenBalancesTreatWETHAsETH(account, [token])
|
||||
if (!token) return
|
||||
return balances?.[token.address]
|
||||
export function useCurrencyBalances(
|
||||
account?: string,
|
||||
currencies?: (Currency | undefined)[]
|
||||
): (CurrencyAmount | undefined)[] {
|
||||
const tokens = useMemo(() => currencies?.filter((currency): currency is Token => currency instanceof Token) ?? [], [
|
||||
currencies
|
||||
])
|
||||
|
||||
const tokenBalances = useTokenBalances(account, tokens)
|
||||
const containsETH: boolean = useMemo(() => currencies?.some(currency => currency === ETHER) ?? false, [currencies])
|
||||
const ethBalance = useETHBalances(containsETH ? [account] : [])
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
currencies?.map(currency => {
|
||||
if (!account || !currency) return
|
||||
if (currency instanceof Token) return tokenBalances[currency.address]
|
||||
if (currency === ETHER) return ethBalance[account]
|
||||
return
|
||||
}) ?? [],
|
||||
[account, currencies, ethBalance, tokenBalances]
|
||||
)
|
||||
}
|
||||
|
||||
export function useCurrencyBalance(account?: string, currency?: Currency): CurrencyAmount | undefined {
|
||||
return useCurrencyBalances(account, [currency])[0]
|
||||
}
|
||||
|
||||
// mimics useAllBalances
|
||||
export function useAllTokenBalancesTreatingWETHasETH(): { [tokenAddress: string]: TokenAmount | undefined } {
|
||||
export function useAllTokenBalances(): { [tokenAddress: string]: TokenAmount | undefined } {
|
||||
const { account } = useActiveWeb3React()
|
||||
const allTokens = useAllTokens()
|
||||
const allTokensArray = useMemo(() => Object.values(allTokens ?? {}), [allTokens])
|
||||
const balances = useTokenBalancesTreatWETHAsETH(account ?? undefined, allTokensArray)
|
||||
const balances = useTokenBalances(account ?? undefined, allTokensArray)
|
||||
return balances ?? {}
|
||||
}
|
||||
|
7
src/utils/currencyId.ts
Normal file
7
src/utils/currencyId.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Currency, ETHER, Token } from '@uniswap/sdk'
|
||||
|
||||
export function currencyId(currency: Currency): string {
|
||||
if (currency === ETHER) return 'ETH'
|
||||
if (currency instanceof Token) return currency.address
|
||||
throw new Error('invalid currency')
|
||||
}
|
@ -6,7 +6,7 @@ import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'
|
||||
import { ROUTER_ADDRESS } from '../constants'
|
||||
import { ALL_TOKENS } from '../constants/tokens'
|
||||
import { ChainId, JSBI, Percent, TokenAmount, Token } from '@uniswap/sdk'
|
||||
import { ChainId, JSBI, Percent, Token, CurrencyAmount, Currency, ETHER } from '@uniswap/sdk'
|
||||
|
||||
// returns the checksummed address if the address is valid, otherwise returns false
|
||||
export function isAddress(value: any): string | false {
|
||||
@ -61,7 +61,7 @@ export function basisPointsToPercent(num: number): Percent {
|
||||
return new Percent(JSBI.BigInt(num), JSBI.BigInt(10000))
|
||||
}
|
||||
|
||||
export function calculateSlippageAmount(value: TokenAmount, slippage: number): [JSBI, JSBI] {
|
||||
export function calculateSlippageAmount(value: CurrencyAmount, slippage: number): [JSBI, JSBI] {
|
||||
if (slippage < 0 || slippage > 10000) {
|
||||
throw Error(`Unexpected slippage value: ${slippage}`)
|
||||
}
|
||||
@ -99,11 +99,12 @@ export function escapeRegExp(string: string): string {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
|
||||
}
|
||||
|
||||
export function isDefaultToken(token?: Token): boolean {
|
||||
return Boolean(token && ALL_TOKENS[token.chainId]?.[token.address])
|
||||
export function isDefaultToken(currency?: Currency): boolean {
|
||||
if (currency === ETHER) return true
|
||||
return Boolean(currency instanceof Token && ALL_TOKENS[currency.chainId]?.[currency.address])
|
||||
}
|
||||
|
||||
export function isCustomAddedToken(allTokens: { [address: string]: Token }, token?: Token): boolean {
|
||||
const isDefault = isDefaultToken(token)
|
||||
return Boolean(token && allTokens[token.address] && !isDefault)
|
||||
export function isCustomAddedToken(allTokens: { [address: string]: Token }, currency?: Currency): boolean {
|
||||
const isDefault = isDefaultToken(currency)
|
||||
return Boolean(!isDefault && currency instanceof Token && allTokens[currency.address])
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { JSBI, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import { CurrencyAmount, ETHER, JSBI } from '@uniswap/sdk'
|
||||
import { MIN_ETH } from '../constants'
|
||||
|
||||
/**
|
||||
* Given some token amount, return the max that can be spent of it
|
||||
* @param tokenAmount to return max of
|
||||
* @param currencyAmount to return max of
|
||||
*/
|
||||
export function maxAmountSpend(tokenAmount?: TokenAmount): TokenAmount | undefined {
|
||||
if (!tokenAmount) return
|
||||
if (tokenAmount.token.equals(WETH[tokenAmount.token.chainId])) {
|
||||
if (JSBI.greaterThan(tokenAmount.raw, MIN_ETH)) {
|
||||
return new TokenAmount(tokenAmount.token, JSBI.subtract(tokenAmount.raw, MIN_ETH))
|
||||
export function maxAmountSpend(currencyAmount?: CurrencyAmount): CurrencyAmount | undefined {
|
||||
if (!currencyAmount) return
|
||||
if (currencyAmount.currency === ETHER) {
|
||||
if (JSBI.greaterThan(currencyAmount.raw, MIN_ETH)) {
|
||||
return CurrencyAmount.ether(JSBI.subtract(currencyAmount.raw, MIN_ETH))
|
||||
} else {
|
||||
return new TokenAmount(tokenAmount.token, JSBI.BigInt(0))
|
||||
return CurrencyAmount.ether(JSBI.BigInt(0))
|
||||
}
|
||||
}
|
||||
return tokenAmount
|
||||
return currencyAmount
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { BLOCKED_PRICE_IMPACT_NON_EXPERT } from '../constants'
|
||||
import { Fraction, JSBI, Percent, TokenAmount, Trade } from '@uniswap/sdk'
|
||||
import { CurrencyAmount, Fraction, JSBI, Percent, TokenAmount, Trade } from '@uniswap/sdk'
|
||||
import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_LOW, ALLOWED_PRICE_IMPACT_MEDIUM } from '../constants'
|
||||
import { Field } from '../state/swap/actions'
|
||||
import { basisPointsToPercent } from './index'
|
||||
@ -11,7 +11,7 @@ const INPUT_FRACTION_AFTER_FEE = ONE_HUNDRED_PERCENT.subtract(BASE_FEE)
|
||||
// computes price breakdown for the trade
|
||||
export function computeTradePriceBreakdown(
|
||||
trade?: Trade
|
||||
): { priceImpactWithoutFee?: Percent; realizedLPFee?: TokenAmount } {
|
||||
): { priceImpactWithoutFee?: Percent; realizedLPFee?: CurrencyAmount } {
|
||||
// for each hop in our trade, take away the x*y=k price impact from 0.3% fees
|
||||
// e.g. for 3 tokens/2 hops: 1 - ((1 - .03) * (1-.03))
|
||||
const realizedLPFee = !trade
|
||||
@ -24,7 +24,7 @@ export function computeTradePriceBreakdown(
|
||||
)
|
||||
|
||||
// remove lp fees from price impact
|
||||
const priceImpactWithoutFeeFraction = trade && realizedLPFee ? trade.slippage.subtract(realizedLPFee) : undefined
|
||||
const priceImpactWithoutFeeFraction = trade && realizedLPFee ? trade.priceImpact.subtract(realizedLPFee) : undefined
|
||||
|
||||
// the x*y=k impact
|
||||
const priceImpactWithoutFeePercent = priceImpactWithoutFeeFraction
|
||||
@ -35,7 +35,9 @@ export function computeTradePriceBreakdown(
|
||||
const realizedLPFeeAmount =
|
||||
realizedLPFee &&
|
||||
trade &&
|
||||
new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
|
||||
(trade.inputAmount instanceof TokenAmount
|
||||
? new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
|
||||
: CurrencyAmount.ether(realizedLPFee.multiply(trade.inputAmount.raw).quotient))
|
||||
|
||||
return { priceImpactWithoutFee: priceImpactWithoutFeePercent, realizedLPFee: realizedLPFeeAmount }
|
||||
}
|
||||
@ -44,7 +46,7 @@ export function computeTradePriceBreakdown(
|
||||
export function computeSlippageAdjustedAmounts(
|
||||
trade: Trade | undefined,
|
||||
allowedSlippage: number
|
||||
): { [field in Field]?: TokenAmount } {
|
||||
): { [field in Field]?: CurrencyAmount } {
|
||||
const pct = basisPointsToPercent(allowedSlippage)
|
||||
return {
|
||||
[Field.INPUT]: trade?.maximumAmountIn(pct),
|
||||
@ -65,8 +67,10 @@ export function formatExecutionPrice(trade?: Trade, inverted?: boolean): string
|
||||
return ''
|
||||
}
|
||||
return inverted
|
||||
? `${trade.executionPrice.invert().toSignificant(6)} ${trade.inputAmount.token.symbol} / ${
|
||||
trade.outputAmount.token.symbol
|
||||
? `${trade.executionPrice.invert().toSignificant(6)} ${trade.inputAmount.currency.symbol} / ${
|
||||
trade.outputAmount.currency.symbol
|
||||
}`
|
||||
: `${trade.executionPrice.toSignificant(6)} ${trade.outputAmount.currency.symbol} / ${
|
||||
trade.inputAmount.currency.symbol
|
||||
}`
|
||||
: `${trade.executionPrice.toSignificant(6)} ${trade.outputAmount.token.symbol} / ${trade.inputAmount.token.symbol}`
|
||||
}
|
||||
|
106
src/utils/v1SwapArgument.test.ts
Normal file
106
src/utils/v1SwapArgument.test.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { CurrencyAmount, ETHER, Percent, Route, TokenAmount, Trade } from '@uniswap/sdk'
|
||||
import { DAI, USDC } from '../constants/tokens/mainnet'
|
||||
import { MockV1Pair } from '../data/V1'
|
||||
import v1SwapArguments from './v1SwapArguments'
|
||||
|
||||
describe('v1SwapArguments', () => {
|
||||
const USDC_WETH = new MockV1Pair('1000000', new TokenAmount(USDC, '1000000'))
|
||||
const DAI_WETH = new MockV1Pair('1000000', new TokenAmount(DAI, '1000000'))
|
||||
|
||||
// just some random address
|
||||
const TEST_RECIPIENT_ADDRESS = USDC_WETH.liquidityToken.address
|
||||
|
||||
function checkDeadline(hex: string | string[], ttl: number) {
|
||||
if (typeof hex !== 'string') throw new Error('invalid hex')
|
||||
const now = new Date().getTime() / 1000
|
||||
expect(parseInt(hex) - now).toBeGreaterThanOrEqual(ttl - 3)
|
||||
expect(parseInt(hex) - now).toBeLessThanOrEqual(ttl + 3)
|
||||
}
|
||||
|
||||
it('exact eth to token', () => {
|
||||
const trade = Trade.exactIn(new Route([USDC_WETH], ETHER), CurrencyAmount.ether('100'))
|
||||
const result = v1SwapArguments(trade, {
|
||||
recipient: TEST_RECIPIENT_ADDRESS,
|
||||
allowedSlippage: new Percent('1', '100'),
|
||||
ttl: 20 * 60
|
||||
})
|
||||
expect(result.methodName).toEqual('ethToTokenTransferInput')
|
||||
expect(result.args[0]).toEqual('0x62')
|
||||
expect(result.args[2]).toEqual(TEST_RECIPIENT_ADDRESS)
|
||||
checkDeadline(result.args[1], 20 * 60)
|
||||
expect(result.value).toEqual('0x64')
|
||||
})
|
||||
it('exact token to eth', () => {
|
||||
const trade = Trade.exactIn(new Route([USDC_WETH], USDC, ETHER), new TokenAmount(USDC, '100'))
|
||||
const result = v1SwapArguments(trade, {
|
||||
recipient: TEST_RECIPIENT_ADDRESS,
|
||||
allowedSlippage: new Percent('1', '100'),
|
||||
ttl: 20 * 60
|
||||
})
|
||||
expect(result.methodName).toEqual('tokenToEthTransferInput')
|
||||
expect(result.args[0]).toEqual('0x64')
|
||||
expect(result.args[1]).toEqual('0x62')
|
||||
checkDeadline(result.args[2], 20 * 60)
|
||||
expect(result.args[3]).toEqual(TEST_RECIPIENT_ADDRESS)
|
||||
expect(result.value).toEqual('0x0')
|
||||
})
|
||||
it('exact token to token', () => {
|
||||
const trade = Trade.exactIn(new Route([USDC_WETH, DAI_WETH], USDC), new TokenAmount(USDC, '100'))
|
||||
const result = v1SwapArguments(trade, {
|
||||
recipient: TEST_RECIPIENT_ADDRESS,
|
||||
allowedSlippage: new Percent('1', '100'),
|
||||
ttl: 20 * 60
|
||||
})
|
||||
expect(result.methodName).toEqual('tokenToTokenTransferInput')
|
||||
expect(result.args[0]).toEqual('0x64')
|
||||
expect(result.args[1]).toEqual('0x61')
|
||||
expect(result.args[2]).toEqual('0x1')
|
||||
expect(result.args[4]).toEqual(TEST_RECIPIENT_ADDRESS)
|
||||
expect(result.args[5]).toEqual(DAI.address)
|
||||
checkDeadline(result.args[3], 20 * 60)
|
||||
expect(result.value).toEqual('0x0')
|
||||
})
|
||||
it('eth to exact token', () => {
|
||||
const trade = Trade.exactOut(new Route([USDC_WETH], ETHER), new TokenAmount(USDC, '100'))
|
||||
const result = v1SwapArguments(trade, {
|
||||
recipient: TEST_RECIPIENT_ADDRESS,
|
||||
allowedSlippage: new Percent('1', '100'),
|
||||
ttl: 20 * 60
|
||||
})
|
||||
expect(result.methodName).toEqual('ethToTokenTransferOutput')
|
||||
expect(result.args[0]).toEqual('0x64')
|
||||
checkDeadline(result.args[1], 20 * 60)
|
||||
expect(result.args[2]).toEqual(TEST_RECIPIENT_ADDRESS)
|
||||
expect(result.value).toEqual('0x66')
|
||||
})
|
||||
it('token to exact eth', () => {
|
||||
const trade = Trade.exactOut(new Route([USDC_WETH], USDC, ETHER), CurrencyAmount.ether('100'))
|
||||
const result = v1SwapArguments(trade, {
|
||||
recipient: TEST_RECIPIENT_ADDRESS,
|
||||
allowedSlippage: new Percent('1', '100'),
|
||||
ttl: 20 * 60
|
||||
})
|
||||
expect(result.methodName).toEqual('tokenToEthTransferOutput')
|
||||
expect(result.args[0]).toEqual('0x64')
|
||||
expect(result.args[1]).toEqual('0x66')
|
||||
checkDeadline(result.args[2], 20 * 60)
|
||||
expect(result.args[3]).toEqual(TEST_RECIPIENT_ADDRESS)
|
||||
expect(result.value).toEqual('0x0')
|
||||
})
|
||||
it('token to exact token', () => {
|
||||
const trade = Trade.exactOut(new Route([USDC_WETH, DAI_WETH], USDC), new TokenAmount(DAI, '100'))
|
||||
const result = v1SwapArguments(trade, {
|
||||
recipient: TEST_RECIPIENT_ADDRESS,
|
||||
allowedSlippage: new Percent('1', '100'),
|
||||
ttl: 20 * 60
|
||||
})
|
||||
expect(result.methodName).toEqual('tokenToTokenTransferOutput')
|
||||
expect(result.args[0]).toEqual('0x64')
|
||||
expect(result.args[1]).toEqual('0x67')
|
||||
expect(result.args[2]).toEqual(`0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`)
|
||||
checkDeadline(result.args[3], 20 * 60)
|
||||
expect(result.args[4]).toEqual(TEST_RECIPIENT_ADDRESS)
|
||||
expect(result.args[5]).toEqual(DAI.address)
|
||||
expect(result.value).toEqual('0x0')
|
||||
})
|
||||
})
|
91
src/utils/v1SwapArguments.ts
Normal file
91
src/utils/v1SwapArguments.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { MaxUint256 } from '@ethersproject/constants'
|
||||
import { CurrencyAmount, ETHER, SwapParameters, Token, Trade, TradeOptions, TradeType } from '@uniswap/sdk'
|
||||
import { getTradeVersion } from '../data/V1'
|
||||
import { Version } from '../hooks/useToggledVersion'
|
||||
|
||||
function toHex(currencyAmount: CurrencyAmount): string {
|
||||
return `0x${currencyAmount.raw.toString(16)}`
|
||||
}
|
||||
|
||||
function deadlineFromNow(ttl: number): string {
|
||||
return `0x${(Math.floor(new Date().getTime() / 1000) + ttl).toString(16)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments to make for a swap
|
||||
* @param trade trade to get v1 arguments for swapping
|
||||
* @param options options for swapping
|
||||
*/
|
||||
export default function v1SwapArguments(trade: Trade, options: Omit<TradeOptions, 'feeOnTransfer'>): SwapParameters {
|
||||
if (getTradeVersion(trade) !== Version.v1) {
|
||||
throw new Error('invalid trade version')
|
||||
}
|
||||
if (trade.route.pairs.length > 2) {
|
||||
throw new Error('too many pairs')
|
||||
}
|
||||
const isExactIn = trade.tradeType === TradeType.EXACT_INPUT
|
||||
const inputETH = trade.inputAmount.currency === ETHER
|
||||
const outputETH = trade.outputAmount.currency === ETHER
|
||||
if (inputETH && outputETH) throw new Error('ETHER to ETHER')
|
||||
const minimumAmountOut = toHex(trade.minimumAmountOut(options.allowedSlippage))
|
||||
const maximumAmountIn = toHex(trade.maximumAmountIn(options.allowedSlippage))
|
||||
const deadline = deadlineFromNow(options.ttl)
|
||||
if (isExactIn) {
|
||||
if (inputETH) {
|
||||
return {
|
||||
methodName: 'ethToTokenTransferInput',
|
||||
args: [minimumAmountOut, deadline, options.recipient],
|
||||
value: maximumAmountIn
|
||||
}
|
||||
} else if (outputETH) {
|
||||
return {
|
||||
methodName: 'tokenToEthTransferInput',
|
||||
args: [maximumAmountIn, minimumAmountOut, deadline, options.recipient],
|
||||
value: '0x0'
|
||||
}
|
||||
} else {
|
||||
const outputToken = trade.outputAmount.currency
|
||||
// should never happen, needed for type check
|
||||
if (!(outputToken instanceof Token)) {
|
||||
throw new Error('token to token')
|
||||
}
|
||||
return {
|
||||
methodName: 'tokenToTokenTransferInput',
|
||||
args: [maximumAmountIn, minimumAmountOut, '0x1', deadline, options.recipient, outputToken.address],
|
||||
value: '0x0'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (inputETH) {
|
||||
return {
|
||||
methodName: 'ethToTokenTransferOutput',
|
||||
args: [minimumAmountOut, deadline, options.recipient],
|
||||
value: maximumAmountIn
|
||||
}
|
||||
} else if (outputETH) {
|
||||
return {
|
||||
methodName: 'tokenToEthTransferOutput',
|
||||
args: [minimumAmountOut, maximumAmountIn, deadline, options.recipient],
|
||||
value: '0x0'
|
||||
}
|
||||
} else {
|
||||
const output = trade.outputAmount.currency
|
||||
if (!(output instanceof Token)) {
|
||||
throw new Error('invalid output amount currency')
|
||||
}
|
||||
|
||||
return {
|
||||
methodName: 'tokenToTokenTransferOutput',
|
||||
args: [
|
||||
minimumAmountOut,
|
||||
maximumAmountIn,
|
||||
MaxUint256.toHexString(),
|
||||
deadline,
|
||||
options.recipient,
|
||||
output.address
|
||||
],
|
||||
value: '0x0'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
src/utils/wrappedCurrency.ts
Normal file
18
src/utils/wrappedCurrency.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ChainId, Currency, CurrencyAmount, ETHER, Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
|
||||
export function wrappedCurrency(currency: Currency | undefined, chainId: ChainId | undefined): Token | undefined {
|
||||
return chainId && currency === ETHER ? WETH[chainId] : currency instanceof Token ? currency : undefined
|
||||
}
|
||||
|
||||
export function wrappedCurrencyAmount(
|
||||
currencyAmount: CurrencyAmount | undefined,
|
||||
chainId: ChainId | undefined
|
||||
): TokenAmount | undefined {
|
||||
const token = currencyAmount && chainId ? wrappedCurrency(currencyAmount.currency, chainId) : undefined
|
||||
return token && currencyAmount ? new TokenAmount(token, currencyAmount.raw) : undefined
|
||||
}
|
||||
|
||||
export function unwrappedToken(token: Token): Currency {
|
||||
if (token.equals(WETH[token.chainId])) return ETHER
|
||||
return token
|
||||
}
|
Loading…
Reference in New Issue
Block a user