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,25 +33,21 @@ const StyledEthereumLogo = styled.img<{ size: string }>` ...@@ -35,25 +33,21 @@ 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()
if (currency instanceof Token) {
let path = '' let path = ''
const validated = isAddress(address) if (!NO_LOGO_ADDRESSES[currency.address]) {
// hard code to show ETH instead of WETH in UI path = getTokenLogoURL(currency.address)
if (validated === WETH[chainId].address) {
return <StyledEthereumLogo src={EthereumLogo} size={size} {...rest} />
} else if (!NO_LOGO_ADDRESSES[address] && validated) {
path = getTokenLogoURL(validated)
} else { } else {
return ( return (
<Emoji {...rest} size={size}> <Emoji {...rest} size={size}>
...@@ -67,13 +61,18 @@ export default function TokenLogo({ ...@@ -67,13 +61,18 @@ export default function TokenLogo({
return ( return (
<Image <Image
{...rest} {...rest}
// alt={address} alt={`${currency.name} Logo`}
src={path} src={path}
size={size} size={size}
onError={() => { onError={() => {
NO_LOGO_ADDRESSES[address] = true if (currency instanceof Token) {
NO_LOGO_ADDRESSES[currency.address] = true
}
refresh(i => i + 1) refresh(i => i + 1)
}} }}
/> />
) )
} else {
return <StyledEthereumLogo src={EthereumLogo} size={size} {...rest} />
}
} }
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">
{(SUGGESTED_BASES[chainId as ChainId] ?? []).map((token: Token) => {
return (
<BaseWrapper <BaseWrapper
onClick={() => selectedTokenAddress !== token.address && onSelect(token.address)} onClick={() => !currencyEquals(selectedCurrency, ETHER) && onSelect(ETHER)}
disable={selectedTokenAddress === token.address} disable={selectedCurrency === ETHER}
key={token.address}
> >
<TokenLogo address={token.address} style={{ marginRight: 8 }} /> <CurrencyLogo currency={ETHER} style={{ marginRight: 8 }} />
<Text fontWeight={500} fontSize={16}>
ETH
</Text>
</BaseWrapper>
{(SUGGESTED_BASES[chainId as ChainId] ?? []).map((token: Token) => {
const selected = currencyEquals(selectedCurrency, token)
return (
<BaseWrapper onClick={() => !selected && onSelect(token)} disable={selected} key={token.address}>
<CurrencyLogo currency={token} style={{ marginRight: 8 }} />
<Text fontWeight={500} fontSize={16}> <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'),
'0' new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin')
), ],
new TokenAmount( [USDC, USDT],
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'), [DAI, USDT]
'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,7 +15,8 @@ export function useAllTokens(): { [address: string]: Token } { ...@@ -15,7 +15,8 @@ export function useAllTokens(): { [address: string]: Token } {
return useMemo(() => { return useMemo(() => {
if (!chainId) return {} if (!chainId) return {}
const tokens = userAddedTokens return (
userAddedTokens
// reduce into all ALL_TOKENS filtered by the current chain // reduce into all ALL_TOKENS filtered by the current chain
.reduce<{ [address: string]: Token }>( .reduce<{ [address: string]: Token }>(
(tokenMap, token) => { (tokenMap, token) => {
...@@ -26,15 +27,7 @@ export function useAllTokens(): { [address: string]: Token } { ...@@ -26,15 +27,7 @@ export function useAllTokens(): { [address: string]: Token } {
// want to make a copy in every iteration // want to make a copy in every iteration
{ ...ALL_TOKENS[chainId as ChainId] } { ...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
} }
......
This diff is collapsed.
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')
}
}
This diff is collapsed.
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 ? (
hasPosition && pair ? (
<MinimalPositionCard pair={pair} border="1px solid #CED0D9" /> <MinimalPositionCard pair={pair} border="1px solid #CED0D9" />
) : ( ) : (
<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">You don’t have liquidity in this pool yet.</Text>
<StyledInternalLink to={`/add/${token0?.address}/${token1?.address}`}> <StyledInternalLink to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}>
<Text textAlign="center">Add liquidity.</Text> <Text textAlign="center">Add liquidity.</Text>
</StyledInternalLink> </StyledInternalLink>
</AutoColumn> </AutoColumn>
</LightCard> </LightCard>
) )
) : newPair ? ( ) : validPairNoLiquidity ? (
<LightCard padding="45px 10px"> <LightCard padding="45px 10px">
<AutoColumn gap="sm" justify="center"> <AutoColumn gap="sm" justify="center">
<Text textAlign="center">No pool found.</Text> <Text textAlign="center">No pool found.</Text>
<StyledInternalLink to={`/add/${token0Address}/${token1Address}`}>Create pool?</StyledInternalLink> <StyledInternalLink to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}>
Create pool.
</StyledInternalLink>
</AutoColumn> </AutoColumn>
</LightCard> </LightCard>
) : ( ) : pairState === PairState.INVALID ? (
<LightCard padding="45px 10px">
<AutoColumn gap="sm" justify="center">
<Text textAlign="center" fontWeight={500}>
Invalid pair.
</Text>
</AutoColumn>
</LightCard>
) : pairState === PairState.LOADING ? (
<LightCard padding="45px 10px"> <LightCard padding="45px 10px">
<AutoColumn gap="sm" justify="center">
<Text textAlign="center"> <Text textAlign="center">
{!account ? 'Connect to a wallet to find pools' : 'Select a token to find your liquidity.'} Loading
<Dots />
</Text> </Text>
</AutoColumn>
</LightCard> </LightCard>
) : null
) : (
prerequisiteMessage
)} )}
</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>
) )
......
This diff is collapsed.
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
This diff is collapsed.
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 &&
tokens[Field.TOKEN_A] &&
totalSupply && totalSupply &&
userLiquidity && userLiquidity &&
tokenA &&
// 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( ? new TokenAmount(tokenA, pair.getLiquidityValue(tokenA, totalSupply, userLiquidity, false).raw)
tokens[Field.TOKEN_A] as Token, : undefined
pair.getLiquidityValue(tokens[Field.TOKEN_A] as Token, totalSupply, userLiquidity, false).raw const liquidityValueB =
)
: undefined,
[Field.TOKEN_B]:
pair && pair &&
tokens[Field.TOKEN_B] &&
totalSupply && totalSupply &&
userLiquidity && userLiquidity &&
tokenB &&
// 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( ? new TokenAmount(tokenB, pair.getLiquidityValue(tokenB, totalSupply, userLiquidity, false).raw)
tokens[Field.TOKEN_B] as Token,
pair.getLiquidityValue(tokens[Field.TOKEN_B] as Token, totalSupply, userLiquidity, false).raw
)
: undefined : undefined
const liquidityValues: { [Field.CURRENCY_A]?: TokenAmount; [Field.CURRENCY_B]?: TokenAmount } = {
[Field.CURRENCY_A]: liquidityValueA,
[Field.CURRENCY_B]: liquidityValueB
} }
let percentToRemove: Percent = new Percent('0', '100') 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 } }) => {
const tokens = parseTokens(chainId, params?.tokens ?? '')
return {
independentField: Field.LIQUIDITY_PERCENT,
typedValue: '0',
[Field.TOKEN_A]: {
address: tokens[0]
},
[Field.TOKEN_B]: {
address: tokens[1]
}
}
})
.addCase(typeInput, (state, { payload: { field, typedValue } }) => {
return { return {
...state, ...state,
independentField: field, independentField: field,
......
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')
......
This diff is collapsed.
...@@ -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: ''
} }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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')
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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