Commit 19b1e9e3 authored by Moody Salem's avatar Moody Salem Committed by GitHub

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