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

feat(weth): support WETH across the site and use sdk 3.0 (#947)

* first pass of sdk 3.0

* second pass using weth

* kill unused pool popup

* get it compiling again

* first pass of sdk 3.0

* switch to currencies

* get it compiling after the big move merge

* restore margin

* clean up add liquidity more

* fix a bunch of bugs

* todo trade on v1

* show eth in currency list

* allow selecting eth in the swap page

* fix unit tests for swap page

* test lint errors

* fix failing integration tests

* fix another couple of failing unit tests

* handle selecting currency b when no currency a

* improve the import pool page

* clean up add liquidity for invalid pairs

* bold

* first pass at swap arguments for v1, some unit tests

* fix some bugs in add liquidity, burn hook

* fix last of ts errors in remove liquidity

* support wrapping/unwrapping weth

* kill a bunch of code including the dummy pairs

* required pair prop in the position card

* tests for the v1 swap arguments

* do not say estimated on the wrap ui

* show ETH instead of WETH in the pool summaries

* small size socks

* fix lint error

* in burn, use currencies from the URL

* fix some integration tests

* both contain weth

* receive eth/weth link

* fix empty row

* show wrapped only if one currency is weth

* currency selects in the remove liquidity page
parent 6287b95b
...@@ -32,13 +32,19 @@ describe('Add Liquidity', () => { ...@@ -32,13 +32,19 @@ describe('Add Liquidity', () => {
) )
}) })
it('redirects /add/WETH-token to /add/ETH/token', () => { it('redirects /add/WETH-token to /add/WETH-address/token', () => {
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85') cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should('contain', '/add/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85') cy.url().should(
'contain',
'/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
)
}) })
it('redirects /add/token-WETH to /add/token/ETH', () => { it('redirects /add/token-WETH to /add/token/WETH-address', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab') cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.url().should('contain', '/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH') cy.url().should(
'contain',
'/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab'
)
}) })
}) })
describe('Remove Liquidity', () => { describe('Remove Liquidity', () => {
it('loads the two correct tokens', () => { it('redirects', () => {
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85') cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should(
'contain',
'/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
)
})
it('eth remove', () => {
cy.visit('/remove/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH') cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
}) })
it('eth remove swap order', () => {
cy.visit('/remove/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'MKR')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH')
})
it('loads the two correct tokens', () => {
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
})
it('does not crash if ETH is duplicated', () => { it('does not crash if ETH is duplicated', () => {
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab') cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH') cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('not.contain.text', 'ETH') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
}) })
it('token not in storage is loaded', () => { it('token not in storage is loaded', () => {
......
...@@ -4,15 +4,15 @@ ...@@ -4,15 +4,15 @@
"homepage": ".", "homepage": ".",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@ethersproject/address": "^5.0.0-beta.134", "@ethersproject/address": "^5.0.1",
"@ethersproject/bignumber": "^5.0.0-beta.138", "@ethersproject/bignumber": "^5.0.3",
"@ethersproject/constants": "^5.0.0-beta.133", "@ethersproject/constants": "^5.0.1",
"@ethersproject/contracts": "^5.0.0-beta.151", "@ethersproject/contracts": "^5.0.1",
"@ethersproject/experimental": "^5.0.0-beta.141", "@ethersproject/experimental": "^5.0.0",
"@ethersproject/providers": "5.0.0-beta.162", "@ethersproject/providers": "^5.0.4",
"@ethersproject/strings": "^5.0.0-beta.136", "@ethersproject/strings": "^5.0.1",
"@ethersproject/units": "^5.0.0-beta.132", "@ethersproject/units": "^5.0.1",
"@ethersproject/wallet": "^5.0.0-beta.141", "@ethersproject/wallet": "^5.0.1",
"@popperjs/core": "^2.4.4", "@popperjs/core": "^2.4.4",
"@reach/dialog": "^0.10.3", "@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3", "@reach/portal": "^0.10.3",
...@@ -27,11 +27,11 @@ ...@@ -27,11 +27,11 @@
"@types/react-router-dom": "^5.0.0", "@types/react-router-dom": "^5.0.0",
"@types/react-window": "^1.8.2", "@types/react-window": "^1.8.2",
"@types/rebass": "^4.0.5", "@types/rebass": "^4.0.5",
"@types/styled-components": "^4.2.0", "@types/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5", "@types/testing-library__cypress": "^5.0.5",
"@typescript-eslint/eslint-plugin": "^2.31.0", "@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0", "@typescript-eslint/parser": "^2.31.0",
"@uniswap/sdk": "^2.0.5", "@uniswap/sdk": "3.0.1",
"@uniswap/v2-core": "1.0.0", "@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0", "@uniswap/v2-periphery": "^1.1.0-beta.0",
"@web3-react/core": "^6.0.9", "@web3-react/core": "^6.0.9",
......
import { Pair, Token } from '@uniswap/sdk' import { Currency, Pair } from '@uniswap/sdk'
import React, { useState, useContext, useCallback } from 'react' import React, { useState, useContext, useCallback } from 'react'
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { darken } from 'polished' import { darken } from 'polished'
import { Field } from '../../state/swap/actions' import { useCurrencyBalance } from '../../state/wallet/hooks'
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks' import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
import TokenSearchModal from '../SearchModal/TokenSearchModal' import CurrencyLogo from '../CurrencyLogo'
import TokenLogo from '../TokenLogo' import DoubleCurrencyLogo from '../DoubleLogo'
import DoubleLogo from '../DoubleLogo'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
import { TYPE, CursorPointer } from '../../theme' import { TYPE, CursorPointer } from '../../theme'
import { Input as NumericalInput } from '../NumericalInput' import { Input as NumericalInput } from '../NumericalInput'
...@@ -117,40 +116,36 @@ const StyledBalanceMax = styled.button` ...@@ -117,40 +116,36 @@ const StyledBalanceMax = styled.button`
interface CurrencyInputPanelProps { interface CurrencyInputPanelProps {
value: string value: string
field: string onUserInput: (value: string) => void
onUserInput: (field: string, val: string) => void
onMax?: () => void onMax?: () => void
showMaxButton: boolean showMaxButton: boolean
label?: string label?: string
onTokenSelection?: (tokenAddress: string) => void onCurrencySelect?: (currency: Currency) => void
token?: Token | null currency?: Currency | null
disableTokenSelect?: boolean disableCurrencySelect?: boolean
hideBalance?: boolean hideBalance?: boolean
isExchange?: boolean
pair?: Pair | null pair?: Pair | null
hideInput?: boolean hideInput?: boolean
showSendWithSwap?: boolean showSendWithSwap?: boolean
otherSelectedTokenAddress?: string | null otherCurrency?: Currency | null
id: string id: string
showCommonBases?: boolean showCommonBases?: boolean
} }
export default function CurrencyInputPanel({ export default function CurrencyInputPanel({
value, value,
field,
onUserInput, onUserInput,
onMax, onMax,
showMaxButton, showMaxButton,
label = 'Input', label = 'Input',
onTokenSelection = null, onCurrencySelect = null,
token = null, currency = null,
disableTokenSelect = false, disableCurrencySelect = false,
hideBalance = false, hideBalance = false,
isExchange = false,
pair = null, // used for double token logo pair = null, // used for double token logo
hideInput = false, hideInput = false,
showSendWithSwap = false, showSendWithSwap = false,
otherSelectedTokenAddress = null, otherCurrency = null,
id, id,
showCommonBases showCommonBases
}: CurrencyInputPanelProps) { }: CurrencyInputPanelProps) {
...@@ -158,7 +153,7 @@ export default function CurrencyInputPanel({ ...@@ -158,7 +153,7 @@ export default function CurrencyInputPanel({
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const userTokenBalance = useTokenBalanceTreatingWETHasETH(account, token) const selectedCurrencyBalance = useCurrencyBalance(account, currency)
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const handleDismissSearch = useCallback(() => { const handleDismissSearch = useCallback(() => {
...@@ -183,8 +178,8 @@ export default function CurrencyInputPanel({ ...@@ -183,8 +178,8 @@ export default function CurrencyInputPanel({
fontSize={14} fontSize={14}
style={{ display: 'inline' }} style={{ display: 'inline' }}
> >
{!hideBalance && !!token && userTokenBalance {!hideBalance && !!currency && selectedCurrencyBalance
? 'Balance: ' + userTokenBalance?.toSignificant(6) ? 'Balance: ' + selectedCurrencyBalance?.toSignificant(6)
: ' -'} : ' -'}
</TYPE.body> </TYPE.body>
</CursorPointer> </CursorPointer>
...@@ -192,63 +187,62 @@ export default function CurrencyInputPanel({ ...@@ -192,63 +187,62 @@ export default function CurrencyInputPanel({
</RowBetween> </RowBetween>
</LabelRow> </LabelRow>
)} )}
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableTokenSelect}> <InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}>
{!hideInput && ( {!hideInput && (
<> <>
<NumericalInput <NumericalInput
className="token-amount-input" className="token-amount-input"
value={value} value={value}
onUserInput={val => { onUserInput={val => {
onUserInput(field, val) onUserInput(val)
}} }}
/> />
{account && !!token?.address && showMaxButton && label !== 'To' && ( {account && currency && showMaxButton && label !== 'To' && (
<StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax> <StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>
)} )}
</> </>
)} )}
<CurrencySelect <CurrencySelect
selected={!!token} selected={!!currency}
className="open-currency-select-button" className="open-currency-select-button"
onClick={() => { onClick={() => {
if (!disableTokenSelect) { if (!disableCurrencySelect) {
setModalOpen(true) setModalOpen(true)
} }
}} }}
> >
<Aligner> <Aligner>
{isExchange ? ( {pair ? (
<DoubleLogo a0={pair?.token0.address} a1={pair?.token1.address} size={24} margin={true} /> <DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
) : token?.address ? ( ) : currency ? (
<TokenLogo address={token?.address} size={'24px'} /> <CurrencyLogo currency={currency} size={'24px'} />
) : null} ) : null}
{isExchange ? ( {pair ? (
<StyledTokenName className="pair-name-container"> <StyledTokenName className="pair-name-container">
{pair?.token0.symbol}:{pair?.token1.symbol} {pair?.token0.symbol}:{pair?.token1.symbol}
</StyledTokenName> </StyledTokenName>
) : ( ) : (
<StyledTokenName className="token-symbol-container" active={Boolean(token && token.symbol)}> <StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
{(token && token.symbol && token.symbol.length > 20 {(currency && currency.symbol && currency.symbol.length > 20
? token.symbol.slice(0, 4) + ? currency.symbol.slice(0, 4) +
'...' + '...' +
token.symbol.slice(token.symbol.length - 5, token.symbol.length) currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
: token?.symbol) || t('selectToken')} : currency?.symbol) || t('selectToken')}
</StyledTokenName> </StyledTokenName>
)} )}
{!disableTokenSelect && <StyledDropDown selected={!!token?.address} />} {!disableCurrencySelect && <StyledDropDown selected={!!currency} />}
</Aligner> </Aligner>
</CurrencySelect> </CurrencySelect>
</InputRow> </InputRow>
</Container> </Container>
{!disableTokenSelect && ( {!disableCurrencySelect && (
<TokenSearchModal <CurrencySearchModal
isOpen={modalOpen} isOpen={modalOpen}
onDismiss={handleDismissSearch} onDismiss={handleDismissSearch}
onTokenSelect={onTokenSelection} onCurrencySelect={onCurrencySelect}
showSendWithSwap={showSendWithSwap} showSendWithSwap={showSendWithSwap}
hiddenToken={token?.address} hiddenCurrency={currency}
otherSelectedTokenAddress={otherSelectedTokenAddress} otherSelectedCurrency={otherCurrency}
otherSelectedText={field === Field.INPUT ? 'Selected as output' : 'Selected as input'}
showCommonBases={showCommonBases} showCommonBases={showCommonBases}
/> />
)} )}
......
import React, { useState } from 'react' import React, { useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { isAddress } from '../../utils' import { Currency, Token } from '@uniswap/sdk'
import { useActiveWeb3React } from '../../hooks'
import { WETH } from '@uniswap/sdk'
import EthereumLogo from '../../assets/images/ethereum-logo.png' import EthereumLogo from '../../assets/images/ethereum-logo.png'
...@@ -35,45 +33,46 @@ const StyledEthereumLogo = styled.img<{ size: string }>` ...@@ -35,45 +33,46 @@ const StyledEthereumLogo = styled.img<{ size: string }>`
border-radius: 24px; border-radius: 24px;
` `
export default function TokenLogo({ export default function CurrencyLogo({
address, currency,
size = '24px', size = '24px',
...rest ...rest
}: { }: {
address?: string currency?: Currency
size?: string size?: string
style?: React.CSSProperties style?: React.CSSProperties
}) { }) {
const [, refresh] = useState<number>(0) const [, refresh] = useState<number>(0)
const { chainId } = useActiveWeb3React()
let path = '' if (currency instanceof Token) {
const validated = isAddress(address) let path = ''
// hard code to show ETH instead of WETH in UI if (!NO_LOGO_ADDRESSES[currency.address]) {
if (validated === WETH[chainId].address) { path = getTokenLogoURL(currency.address)
return <StyledEthereumLogo src={EthereumLogo} size={size} {...rest} /> } else {
} else if (!NO_LOGO_ADDRESSES[address] && validated) { return (
path = getTokenLogoURL(validated) <Emoji {...rest} size={size}>
} else { <span role="img" aria-label="Thinking">
🤔
</span>
</Emoji>
)
}
return ( return (
<Emoji {...rest} size={size}> <Image
<span role="img" aria-label="Thinking"> {...rest}
🤔 alt={`${currency.name} Logo`}
</span> src={path}
</Emoji> size={size}
onError={() => {
if (currency instanceof Token) {
NO_LOGO_ADDRESSES[currency.address] = true
}
refresh(i => i + 1)
}}
/>
) )
} else {
return <StyledEthereumLogo src={EthereumLogo} size={size} {...rest} />
} }
return (
<Image
{...rest}
// alt={address}
src={path}
size={size}
onError={() => {
NO_LOGO_ADDRESSES[address] = true
refresh(i => i + 1)
}}
/>
)
} }
import { Currency } from '@uniswap/sdk'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import TokenLogo from '../TokenLogo' import CurrencyLogo from '../CurrencyLogo'
const TokenWrapper = styled.div<{ margin: boolean; sizeraw: number }>` const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
position: relative; position: relative;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'}; margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
` `
interface DoubleTokenLogoProps { interface DoubleCurrencyLogoProps {
margin?: boolean margin?: boolean
size?: number size?: number
a0?: string currency0?: Currency
a1?: string currency1?: Currency
} }
const HigherLogo = styled(TokenLogo)` const HigherLogo = styled(CurrencyLogo)`
z-index: 2; z-index: 2;
` `
const CoveredLogo = styled(TokenLogo)<{ sizeraw: number }>` const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
position: absolute; position: absolute;
left: ${({ sizeraw }) => (sizeraw / 2).toString() + 'px'}; left: ${({ sizeraw }) => (sizeraw / 2).toString() + 'px'};
` `
export default function DoubleTokenLogo({ a0, a1, size = 16, margin = false }: DoubleTokenLogoProps) { export default function DoubleCurrencyLogo({
currency0,
currency1,
size = 16,
margin = false
}: DoubleCurrencyLogoProps) {
return ( return (
<TokenWrapper sizeraw={size} margin={margin}> <Wrapper sizeraw={size} margin={margin}>
{a0 && <HigherLogo address={a0} size={size.toString() + 'px'} />} {currency0 && <HigherLogo currency={currency0} size={size.toString() + 'px'} />}
{a1 && <CoveredLogo address={a1} size={size.toString() + 'px'} sizeraw={size} />} {currency1 && <CoveredLogo currency={currency1} size={size.toString() + 'px'} sizeraw={size} />}
</TokenWrapper> </Wrapper>
) )
} }
...@@ -4,6 +4,7 @@ import { Link, useLocation } from 'react-router-dom' ...@@ -4,6 +4,7 @@ import { Link, useLocation } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import useParsedQueryString from '../../hooks/useParsedQueryString' import useParsedQueryString from '../../hooks/useParsedQueryString'
import useToggledVersion, { Version } from '../../hooks/useToggledVersion' import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
import { MouseoverTooltip } from '../Tooltip'
const VersionLabel = styled.span<{ enabled: boolean }>` const VersionLabel = styled.span<{ enabled: boolean }>`
padding: 0.35rem 0.6rem; padding: 0.35rem 0.6rem;
...@@ -61,10 +62,15 @@ export default function VersionSwitch() { ...@@ -61,10 +62,15 @@ export default function VersionSwitch() {
[versionSwitchAvailable] [versionSwitchAvailable]
) )
return ( const toggle = (
<VersionToggle enabled={versionSwitchAvailable} to={toggleDest} onClick={handleClick}> <VersionToggle enabled={versionSwitchAvailable} to={toggleDest} onClick={handleClick}>
<VersionLabel enabled={version === Version.v2 || !versionSwitchAvailable}>V2</VersionLabel> <VersionLabel enabled={version === Version.v2 || !versionSwitchAvailable}>V2</VersionLabel>
<VersionLabel enabled={version === Version.v1 && versionSwitchAvailable}>V1</VersionLabel> <VersionLabel enabled={version === Version.v1 && versionSwitchAvailable}>V1</VersionLabel>
</VersionToggle> </VersionToggle>
) )
return versionSwitchAvailable ? (
toggle
) : (
<MouseoverTooltip text="This page is only compatible with Uniswap V2.">{toggle}</MouseoverTooltip>
)
} }
import { ChainId, WETH } from '@uniswap/sdk' import { ChainId } from '@uniswap/sdk'
import React from 'react' import React from 'react'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -11,7 +11,7 @@ import Wordmark from '../../assets/svg/wordmark.svg' ...@@ -11,7 +11,7 @@ import Wordmark from '../../assets/svg/wordmark.svg'
import WordmarkDark from '../../assets/svg/wordmark_white.svg' import WordmarkDark from '../../assets/svg/wordmark_white.svg'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useDarkModeManager } from '../../state/user/hooks' import { useDarkModeManager } from '../../state/user/hooks'
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks' import { useETHBalances } from '../../state/wallet/hooks'
import { YellowCard } from '../Card' import { YellowCard } from '../Card'
import Settings from '../Settings' import Settings from '../Settings'
...@@ -137,7 +137,7 @@ const NETWORK_LABELS: { [chainId in ChainId]: string | null } = { ...@@ -137,7 +137,7 @@ const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
export default function Header() { export default function Header() {
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
const userEthBalance = useTokenBalanceTreatingWETHasETH(account, WETH[chainId]) const userEthBalance = useETHBalances([account])[account]
const [isDark] = useDarkModeManager() const [isDark] = useDarkModeManager()
return ( return (
......
...@@ -66,20 +66,6 @@ export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) { ...@@ -66,20 +66,6 @@ export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) {
) )
} }
export function CreatePoolTabs() {
return (
<Tabs>
<RowBetween style={{ padding: '1rem' }}>
<HistoryLink to="/pool">
<StyledArrowLeft />
</HistoryLink>
<ActiveText>Create Pool</ActiveText>
<QuestionHelper text={'Use this interface to create a new pool.'} />
</RowBetween>
</Tabs>
)
}
export function FindPoolTabs() { export function FindPoolTabs() {
return ( return (
<Tabs> <Tabs>
......
import { ChainId, Pair, Token } from '@uniswap/sdk' import React, { useContext } from 'react'
import React, { useContext, useMemo } from 'react'
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { useMediaLayout } from 'use-media' import { useMediaLayout } from 'use-media'
import { X } from 'react-feather' import { X } from 'react-feather'
import { PopupContent } from '../../state/application/actions' import { PopupContent } from '../../state/application/actions'
import { useActivePopups, useRemovePopup } from '../../state/application/hooks' import { useActivePopups, useRemovePopup } from '../../state/application/hooks'
import { ExternalLink } from '../../theme'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import DoubleTokenLogo from '../DoubleLogo'
import Row from '../Row'
import TxnPopup from '../TxnPopup' import TxnPopup from '../TxnPopup'
import { Text } from 'rebass'
const StyledClose = styled(X)` const StyledClose = styled(X)`
position: absolute; position: absolute;
...@@ -72,52 +67,12 @@ const Popup = styled.div` ...@@ -72,52 +67,12 @@ const Popup = styled.div`
`} `}
` `
function PoolPopup({
token0,
token1
}: {
token0: { address?: string; symbol?: string }
token1: { address?: string; symbol?: string }
}) {
const pairAddress: string | null = useMemo(() => {
if (!token0 || !token1) return null
// just mock it out
return Pair.getAddress(
new Token(ChainId.MAINNET, token0.address, 18),
new Token(ChainId.MAINNET, token1.address, 18)
)
}, [token0, token1])
return (
<AutoColumn gap={'10px'}>
<Text fontSize={20} fontWeight={500}>
Pool Imported
</Text>
<Row>
<DoubleTokenLogo a0={token0?.address ?? ''} a1={token1?.address ?? ''} margin={true} />
<Text fontSize={16} fontWeight={500}>
UNI {token0?.symbol} / {token1?.symbol}
</Text>
</Row>
{pairAddress ? (
<ExternalLink href={`https://uniswap.info/pair/${pairAddress}`}>View on Uniswap Info.</ExternalLink>
) : null}
</AutoColumn>
)
}
function PopupItem({ content, popKey }: { content: PopupContent; popKey: string }) { function PopupItem({ content, popKey }: { content: PopupContent; popKey: string }) {
if ('txn' in content) { if ('txn' in content) {
const { const {
txn: { hash, success, summary } txn: { hash, success, summary }
} = content } = content
return <TxnPopup popKey={popKey} hash={hash} success={success} summary={summary} /> return <TxnPopup popKey={popKey} hash={hash} success={success} summary={summary} />
} else if ('poolAdded' in content) {
const {
poolAdded: { token0, token1 }
} = content
return <PoolPopup token0={token0} token1={token1} />
} }
} }
......
...@@ -7,7 +7,7 @@ import { AutoColumn } from '../Column' ...@@ -7,7 +7,7 @@ import { AutoColumn } from '../Column'
import { ButtonSecondary } from '../Button' import { ButtonSecondary } from '../Button'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import { FixedHeightRow, HoverCard } from './index' import { FixedHeightRow, HoverCard } from './index'
import DoubleTokenLogo from '../DoubleLogo' import DoubleCurrencyLogo from '../DoubleLogo'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
...@@ -26,7 +26,7 @@ function V1PositionCard({ token, V1LiquidityBalance }: PositionCardProps) { ...@@ -26,7 +26,7 @@ function V1PositionCard({ token, V1LiquidityBalance }: PositionCardProps) {
<AutoColumn gap="12px"> <AutoColumn gap="12px">
<FixedHeightRow> <FixedHeightRow>
<RowFixed> <RowFixed>
<DoubleTokenLogo a0={token.address} margin={true} size={20} /> <DoubleCurrencyLogo currency0={token} margin={true} size={20} />
<Text fontWeight={500} fontSize={20} style={{ marginLeft: '' }}> <Text fontWeight={500} fontSize={20} style={{ marginLeft: '' }}>
{`${token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH`} {`${token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH`}
</Text> </Text>
......
import React, { useState } from 'react' import { JSBI, Pair, Percent } from '@uniswap/sdk'
import styled from 'styled-components'
import { darken } from 'polished' import { darken } from 'polished'
import React, { useState } from 'react'
import { ChevronDown, ChevronUp } from 'react-feather'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Percent, Pair, JSBI } from '@uniswap/sdk' import { Text } from 'rebass'
import styled from 'styled-components'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useTotalSupply } from '../../data/TotalSupply'
import { currencyId } from '../../pages/AddLiquidity/currencyId'
import { useTokenBalance } from '../../state/wallet/hooks' import { useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink } from '../../theme'
import { currencyId } from '../../utils/currencyId'
import { unwrappedToken } from '../../utils/wrappedCurrency'
import { ButtonSecondary } from '../Button'
import Card, { GreyCard } from '../Card' import Card, { GreyCard } from '../Card'
import TokenLogo from '../TokenLogo'
import DoubleLogo from '../DoubleLogo'
import { Text } from 'rebass'
import { ExternalLink } from '../../theme'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { ChevronDown, ChevronUp } from 'react-feather' import CurrencyLogo from '../CurrencyLogo'
import { ButtonSecondary } from '../Button' import DoubleCurrencyLogo from '../DoubleLogo'
import { RowBetween, RowFixed, AutoRow } from '../Row' import { AutoRow, RowBetween, RowFixed } from '../Row'
import { Dots } from '../swap/styleds' import { Dots } from '../swap/styleds'
export const FixedHeightRow = styled(RowBetween)` export const FixedHeightRow = styled(RowBetween)`
...@@ -32,20 +33,21 @@ export const HoverCard = styled(Card)` ...@@ -32,20 +33,21 @@ export const HoverCard = styled(Card)`
` `
interface PositionCardProps { interface PositionCardProps {
pair: Pair | undefined | null pair: Pair
showUnwrapped?: boolean
border?: string border?: string
} }
export function MinimalPositionCard({ pair, border }: PositionCardProps) { export function MinimalPositionCard({ pair, showUnwrapped = false, border }: PositionCardProps) {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const token0 = pair?.token0 const currency0 = showUnwrapped ? pair.token0 : unwrappedToken(pair.token0)
const token1 = pair?.token1 const currency1 = showUnwrapped ? pair.token1 : unwrappedToken(pair.token1)
const [showMore, setShowMore] = useState(false) const [showMore, setShowMore] = useState(false)
const userPoolBalance = useTokenBalance(account, pair?.liquidityToken) const userPoolBalance = useTokenBalance(account, pair.liquidityToken)
const totalPoolTokens = useTotalSupply(pair?.liquidityToken) const totalPoolTokens = useTotalSupply(pair.liquidityToken)
const [token0Deposited, token1Deposited] = const [token0Deposited, token1Deposited] =
!!pair && !!pair &&
...@@ -54,8 +56,8 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) { ...@@ -54,8 +56,8 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply // this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw) JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? [ ? [
pair.getLiquidityValue(token0, totalPoolTokens, userPoolBalance, false), pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
pair.getLiquidityValue(token1, totalPoolTokens, userPoolBalance, false) pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false)
] ]
: [undefined, undefined] : [undefined, undefined]
...@@ -73,9 +75,9 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) { ...@@ -73,9 +75,9 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
</FixedHeightRow> </FixedHeightRow>
<FixedHeightRow onClick={() => setShowMore(!showMore)}> <FixedHeightRow onClick={() => setShowMore(!showMore)}>
<RowFixed> <RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} /> <DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} />
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
{token0?.symbol}/{token1?.symbol} {currency0.symbol}/{currency1.symbol}
</Text> </Text>
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
...@@ -87,7 +89,7 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) { ...@@ -87,7 +89,7 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
<AutoColumn gap="4px"> <AutoColumn gap="4px">
<FixedHeightRow> <FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}> <Text color="#888D9B" fontSize={16} fontWeight={500}>
{token0?.symbol}: {currency0.symbol}:
</Text> </Text>
{token0Deposited ? ( {token0Deposited ? (
<RowFixed> <RowFixed>
...@@ -101,7 +103,7 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) { ...@@ -101,7 +103,7 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
</FixedHeightRow> </FixedHeightRow>
<FixedHeightRow> <FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}> <Text color="#888D9B" fontSize={16} fontWeight={500}>
{token1?.symbol}: {currency1.symbol}:
</Text> </Text>
{token1Deposited ? ( {token1Deposited ? (
<RowFixed> <RowFixed>
...@@ -124,13 +126,13 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) { ...@@ -124,13 +126,13 @@ export function MinimalPositionCard({ pair, border }: PositionCardProps) {
export default function FullPositionCard({ pair, border }: PositionCardProps) { export default function FullPositionCard({ pair, border }: PositionCardProps) {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const token0 = pair?.token0 const currency0 = unwrappedToken(pair.token0)
const token1 = pair?.token1 const currency1 = unwrappedToken(pair.token1)
const [showMore, setShowMore] = useState(false) const [showMore, setShowMore] = useState(false)
const userPoolBalance = useTokenBalance(account, pair?.liquidityToken) const userPoolBalance = useTokenBalance(account, pair.liquidityToken)
const totalPoolTokens = useTotalSupply(pair?.liquidityToken) const totalPoolTokens = useTotalSupply(pair.liquidityToken)
const poolTokenPercentage = const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw) !!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
...@@ -144,8 +146,8 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -144,8 +146,8 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply // this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw) JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? [ ? [
pair.getLiquidityValue(token0, totalPoolTokens, userPoolBalance, false), pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
pair.getLiquidityValue(token1, totalPoolTokens, userPoolBalance, false) pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false)
] ]
: [undefined, undefined] : [undefined, undefined]
...@@ -154,9 +156,9 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -154,9 +156,9 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
<AutoColumn gap="12px"> <AutoColumn gap="12px">
<FixedHeightRow onClick={() => setShowMore(!showMore)} style={{ cursor: 'pointer' }}> <FixedHeightRow onClick={() => setShowMore(!showMore)} style={{ cursor: 'pointer' }}>
<RowFixed> <RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} /> <DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} />
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
{!token0 || !token1 ? <Dots>Loading</Dots> : `${token0.symbol}/${token1.symbol}`} {!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
</Text> </Text>
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
...@@ -172,7 +174,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -172,7 +174,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
<FixedHeightRow> <FixedHeightRow>
<RowFixed> <RowFixed>
<Text fontSize={16} fontWeight={500}> <Text fontSize={16} fontWeight={500}>
Pooled {token0?.symbol}: Pooled {currency0.symbol}:
</Text> </Text>
</RowFixed> </RowFixed>
{token0Deposited ? ( {token0Deposited ? (
...@@ -180,7 +182,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -180,7 +182,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}> <Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token0Deposited?.toSignificant(6)} {token0Deposited?.toSignificant(6)}
</Text> </Text>
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token0?.address} /> <CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
</RowFixed> </RowFixed>
) : ( ) : (
'-' '-'
...@@ -190,7 +192,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -190,7 +192,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
<FixedHeightRow> <FixedHeightRow>
<RowFixed> <RowFixed>
<Text fontSize={16} fontWeight={500}> <Text fontSize={16} fontWeight={500}>
Pooled {token1?.symbol}: Pooled {currency1.symbol}:
</Text> </Text>
</RowFixed> </RowFixed>
{token1Deposited ? ( {token1Deposited ? (
...@@ -198,7 +200,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -198,7 +200,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}> <Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token1Deposited?.toSignificant(6)} {token1Deposited?.toSignificant(6)}
</Text> </Text>
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token1?.address} /> <CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
</RowFixed> </RowFixed>
) : ( ) : (
'-' '-'
...@@ -222,15 +224,15 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -222,15 +224,15 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
</FixedHeightRow> </FixedHeightRow>
<AutoRow justify="center" marginTop={'10px'}> <AutoRow justify="center" marginTop={'10px'}>
<ExternalLink href={`https://uniswap.info/pair/${pair?.liquidityToken.address}`}> <ExternalLink href={`https://uniswap.info/pair/${pair.liquidityToken.address}`}>
View pool information ↗ View pool information ↗
</ExternalLink> </ExternalLink>
</AutoRow> </AutoRow>
<RowBetween marginTop="10px"> <RowBetween marginTop="10px">
<ButtonSecondary as={Link} to={`/add/${currencyId(token0)}/${currencyId(token1)}`} width="48%"> <ButtonSecondary as={Link} to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`} width="48%">
Add Add
</ButtonSecondary> </ButtonSecondary>
<ButtonSecondary as={Link} width="48%" to={`/remove/${token0?.address}-${token1?.address}`}> <ButtonSecondary as={Link} width="48%" to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}>
Remove Remove
</ButtonSecondary> </ButtonSecondary>
</RowBetween> </RowBetween>
......
...@@ -5,14 +5,13 @@ const Row = styled(Box)<{ align?: string; padding?: string; border?: string; bor ...@@ -5,14 +5,13 @@ const Row = styled(Box)<{ align?: string; padding?: string; border?: string; bor
width: 100%; width: 100%;
display: flex; display: flex;
padding: 0; padding: 0;
align-items: center; align-items: ${({ align }) => (align ? align : 'center')};
align-items: ${({ align }) => align && align};
padding: ${({ padding }) => padding}; padding: ${({ padding }) => padding};
border: ${({ border }) => border}; border: ${({ border }) => border};
border-radius: ${({ borderRadius }) => borderRadius}; border-radius: ${({ borderRadius }) => borderRadius};
` `
export const RowBetween = styled(Row)<{ align?: string; padding?: string; border?: string; borderRadius?: string }>` export const RowBetween = styled(Row)`
justify-content: space-between; justify-content: space-between;
` `
......
import React from 'react' import React from 'react'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ChainId, Token } from '@uniswap/sdk' import { ChainId, Currency, currencyEquals, ETHER, Token } from '@uniswap/sdk'
import styled from 'styled-components' import styled from 'styled-components'
import { SUGGESTED_BASES } from '../../constants' import { SUGGESTED_BASES } from '../../constants'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import QuestionHelper from '../QuestionHelper' import QuestionHelper from '../QuestionHelper'
import { AutoRow } from '../Row' import { AutoRow } from '../Row'
import TokenLogo from '../TokenLogo' import CurrencyLogo from '../CurrencyLogo'
const BaseWrapper = styled.div<{ disable?: boolean }>` const BaseWrapper = styled.div<{ disable?: boolean }>`
border: 1px solid ${({ theme, disable }) => (disable ? 'transparent' : theme.bg3)}; border: 1px solid ${({ theme, disable }) => (disable ? 'transparent' : theme.bg3)};
...@@ -28,11 +28,11 @@ const BaseWrapper = styled.div<{ disable?: boolean }>` ...@@ -28,11 +28,11 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
export default function CommonBases({ export default function CommonBases({
chainId, chainId,
onSelect, onSelect,
selectedTokenAddress selectedCurrency
}: { }: {
chainId: ChainId chainId: ChainId
selectedTokenAddress: string selectedCurrency?: Currency
onSelect: (tokenAddress: string) => void onSelect: (currency: Currency) => void
}) { }) {
return ( return (
<AutoColumn gap="md"> <AutoColumn gap="md">
...@@ -43,14 +43,20 @@ export default function CommonBases({ ...@@ -43,14 +43,20 @@ export default function CommonBases({
<QuestionHelper text="These tokens are commonly paired with other tokens." /> <QuestionHelper text="These tokens are commonly paired with other tokens." />
</AutoRow> </AutoRow>
<AutoRow gap="4px"> <AutoRow gap="4px">
<BaseWrapper
onClick={() => !currencyEquals(selectedCurrency, ETHER) && onSelect(ETHER)}
disable={selectedCurrency === ETHER}
>
<CurrencyLogo currency={ETHER} style={{ marginRight: 8 }} />
<Text fontWeight={500} fontSize={16}>
ETH
</Text>
</BaseWrapper>
{(SUGGESTED_BASES[chainId as ChainId] ?? []).map((token: Token) => { {(SUGGESTED_BASES[chainId as ChainId] ?? []).map((token: Token) => {
const selected = currencyEquals(selectedCurrency, token)
return ( return (
<BaseWrapper <BaseWrapper onClick={() => !selected && onSelect(token)} disable={selected} key={token.address}>
onClick={() => selectedTokenAddress !== token.address && onSelect(token.address)} <CurrencyLogo currency={token} style={{ marginRight: 8 }} />
disable={selectedTokenAddress === token.address}
key={token.address}
>
<TokenLogo address={token.address} style={{ marginRight: 8 }} />
<Text fontWeight={500} fontSize={16}> <Text fontWeight={500} fontSize={16}>
{token.symbol} {token.symbol}
</Text> </Text>
......
import { JSBI, Token, TokenAmount } from '@uniswap/sdk' import { Currency, CurrencyAmount, currencyEquals, ETHER, JSBI, Token } from '@uniswap/sdk'
import React, { CSSProperties, memo, useContext, useMemo } from 'react' import React, { CSSProperties, memo, useContext, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { FixedSizeList } from 'react-window' import { FixedSizeList } from 'react-window'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useAllTokens } from '../../hooks/Tokens' import { useAllTokens } from '../../hooks/Tokens'
import { useAddUserToken, useRemoveUserAddedToken } from '../../state/user/hooks' import { useAddUserToken, useRemoveUserAddedToken } from '../../state/user/hooks'
import { useETHBalances } from '../../state/wallet/hooks'
import { LinkStyledButton, TYPE } from '../../theme' import { LinkStyledButton, TYPE } from '../../theme'
import { ButtonSecondary } from '../Button' import { ButtonSecondary } from '../Button'
import Column, { AutoColumn } from '../Column' import Column, { AutoColumn } from '../Column'
import { RowFixed } from '../Row' import { RowFixed } from '../Row'
import TokenLogo from '../TokenLogo' import CurrencyLogo from '../CurrencyLogo'
import { FadedSpan, GreySpan, MenuItem, ModalInfo } from './styleds' import { FadedSpan, MenuItem } from './styleds'
import Loader from '../Loader' import Loader from '../Loader'
import { isDefaultToken, isCustomAddedToken } from '../../utils' import { isDefaultToken, isCustomAddedToken } from '../../utils'
export default function TokenList({ function currencyKey(currency: Currency): string {
tokens, return currency instanceof Token ? currency.address : currency === ETHER ? 'ETHER' : ''
allTokenBalances, }
selectedToken,
onTokenSelect, export default function CurrencyList({
otherToken, currencies,
showSendWithSwap, allBalances,
otherSelectedText selectedCurrency,
onCurrencySelect,
otherCurrency,
showSendWithSwap
}: { }: {
tokens: Token[] currencies: Currency[]
selectedToken: string selectedCurrency: Currency
allTokenBalances: { [tokenAddress: string]: TokenAmount } allBalances: { [tokenAddress: string]: CurrencyAmount }
onTokenSelect: (tokenAddress: string) => void onCurrencySelect: (currency: Currency) => void
otherToken: string otherCurrency: Currency
showSendWithSwap?: boolean showSendWithSwap?: boolean
otherSelectedText: string
}) { }) {
const { t } = useTranslation()
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const allTokens = useAllTokens() const allTokens = useAllTokens()
const addToken = useAddUserToken() const addToken = useAddUserToken()
const removeToken = useRemoveUserAddedToken() const removeToken = useRemoveUserAddedToken()
const ETHBalance = useETHBalances([account])[account]
const TokenRow = useMemo(() => { const CurrencyRow = useMemo(() => {
return memo(function TokenRow({ index, style }: { index: number; style: CSSProperties }) { return memo(function CurrencyRow({ index, style }: { index: number; style: CSSProperties }) {
const token = tokens[index] const currency = index === 0 ? Currency.ETHER : currencies[index - 1]
const { address, symbol } = token const key = currencyKey(currency)
const isDefault = isDefaultToken(currency)
const isDefault = isDefaultToken(token) const customAdded = isCustomAddedToken(allTokens, currency)
const customAdded = isCustomAddedToken(allTokens, token) const balance = currency === ETHER ? ETHBalance : allBalances[key]
const balance = allTokenBalances[address]
const zeroBalance = balance && JSBI.equal(JSBI.BigInt(0), balance.raw) const zeroBalance = balance && JSBI.equal(JSBI.BigInt(0), balance.raw)
const isSelected = Boolean(selectedCurrency && currencyEquals(currency, selectedCurrency))
const otherSelected = Boolean(otherCurrency && currencyEquals(otherCurrency, currency))
return ( return (
<MenuItem <MenuItem
style={style} style={style}
key={address} className={`token-item-${key}`}
className={`token-item-${address}`} onClick={() => (isSelected ? null : onCurrencySelect(currency))}
onClick={() => (selectedToken && selectedToken === address ? null : onTokenSelect(address))} disabled={isSelected}
disabled={selectedToken && selectedToken === address} selected={otherSelected}
selected={otherToken === address}
> >
<RowFixed> <RowFixed>
<TokenLogo address={address} size={'24px'} style={{ marginRight: '14px' }} /> <CurrencyLogo currency={currency} size={'24px'} style={{ marginRight: '14px' }} />
<Column> <Column>
<Text fontWeight={500}> <Text fontWeight={500}>{currency.symbol}</Text>
{symbol}
{otherToken === address && <GreySpan> ({otherSelectedText})</GreySpan>}
</Text>
<FadedSpan> <FadedSpan>
{customAdded ? ( {customAdded ? (
<TYPE.main fontWeight={500}> <TYPE.main fontWeight={500}>
...@@ -74,7 +74,7 @@ export default function TokenList({ ...@@ -74,7 +74,7 @@ export default function TokenList({
<LinkStyledButton <LinkStyledButton
onClick={event => { onClick={event => {
event.stopPropagation() event.stopPropagation()
removeToken(chainId, address) if (currency instanceof Token) removeToken(chainId, currency.address)
}} }}
> >
(Remove) (Remove)
...@@ -87,7 +87,7 @@ export default function TokenList({ ...@@ -87,7 +87,7 @@ export default function TokenList({
<LinkStyledButton <LinkStyledButton
onClick={event => { onClick={event => {
event.stopPropagation() event.stopPropagation()
addToken(token) if (currency instanceof Token) addToken(currency)
}} }}
> >
(Add) (Add)
...@@ -122,35 +122,31 @@ export default function TokenList({ ...@@ -122,35 +122,31 @@ export default function TokenList({
) )
}) })
}, [ }, [
ETHBalance,
account, account,
addToken, addToken,
allTokenBalances, allBalances,
allTokens, allTokens,
chainId, chainId,
onTokenSelect, currencies,
otherSelectedText, onCurrencySelect,
otherToken, otherCurrency,
removeToken, removeToken,
selectedToken, selectedCurrency,
showSendWithSwap, showSendWithSwap,
theme.primary1, theme.primary1
tokens
]) ])
if (tokens.length === 0) {
return <ModalInfo>{t('noToken')}</ModalInfo>
}
return ( return (
<FixedSizeList <FixedSizeList
width="100%" width="100%"
height={500} height={500}
itemCount={tokens.length} itemCount={currencies.length + 1}
itemSize={56} itemSize={56}
style={{ flex: '1' }} style={{ flex: '1' }}
itemKey={index => tokens[index].address} itemKey={index => currencyKey(currencies[index])}
> >
{TokenRow} {CurrencyRow}
</FixedSizeList> </FixedSizeList>
) )
} }
import { Token } from '@uniswap/sdk' import { Currency, Token } from '@uniswap/sdk'
import React, { KeyboardEvent, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' import React, { KeyboardEvent, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
...@@ -8,7 +8,7 @@ import Card from '../../components/Card' ...@@ -8,7 +8,7 @@ import Card from '../../components/Card'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useAllTokens, useToken } from '../../hooks/Tokens' import { useAllTokens, useToken } from '../../hooks/Tokens'
import useInterval from '../../hooks/useInterval' import useInterval from '../../hooks/useInterval'
import { useAllTokenBalancesTreatingWETHasETH, useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks' import { useAllTokenBalances, useTokenBalance } from '../../state/wallet/hooks'
import { CloseIcon, LinkStyledButton } from '../../theme' import { CloseIcon, LinkStyledButton } from '../../theme'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import Column from '../Column' import Column from '../Column'
...@@ -20,30 +20,28 @@ import CommonBases from './CommonBases' ...@@ -20,30 +20,28 @@ import CommonBases from './CommonBases'
import { filterTokens } from './filtering' import { filterTokens } from './filtering'
import { useTokenComparator } from './sorting' import { useTokenComparator } from './sorting'
import { PaddedColumn, SearchInput } from './styleds' import { PaddedColumn, SearchInput } from './styleds'
import TokenList from './TokenList' import CurrencyList from './CurrencyList'
import SortButton from './SortButton' import SortButton from './SortButton'
interface TokenSearchModalProps { interface CurrencySearchModalProps {
isOpen?: boolean isOpen?: boolean
onDismiss?: () => void onDismiss?: () => void
hiddenToken?: string hiddenCurrency?: Currency
showSendWithSwap?: boolean showSendWithSwap?: boolean
onTokenSelect?: (address: string) => void onCurrencySelect?: (currency: Currency) => void
otherSelectedTokenAddress?: string otherSelectedCurrency?: Currency
otherSelectedText?: string
showCommonBases?: boolean showCommonBases?: boolean
} }
export default function TokenSearchModal({ export default function CurrencySearchModal({
isOpen, isOpen,
onDismiss, onDismiss,
onTokenSelect, onCurrencySelect,
hiddenToken, hiddenCurrency,
showSendWithSwap, showSendWithSwap,
otherSelectedTokenAddress, otherSelectedCurrency,
otherSelectedText,
showCommonBases = false showCommonBases = false
}: TokenSearchModalProps) { }: CurrencySearchModalProps) {
const { t } = useTranslation() const { t } = useTranslation()
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
...@@ -55,8 +53,8 @@ export default function TokenSearchModal({ ...@@ -55,8 +53,8 @@ export default function TokenSearchModal({
// if the current input is an address, and we don't have the token in context, try to fetch it and import // if the current input is an address, and we don't have the token in context, try to fetch it and import
const searchToken = useToken(searchQuery) const searchToken = useToken(searchQuery)
const searchTokenBalance = useTokenBalanceTreatingWETHasETH(account, searchToken) const searchTokenBalance = useTokenBalance(account, searchToken)
const allTokenBalances_ = useAllTokenBalancesTreatingWETHasETH() const allTokenBalances_ = useAllTokenBalances()
const allTokenBalances = searchToken const allTokenBalances = searchToken
? { ? {
[searchToken.address]: searchTokenBalance [searchToken.address]: searchTokenBalance
...@@ -87,12 +85,12 @@ export default function TokenSearchModal({ ...@@ -87,12 +85,12 @@ export default function TokenSearchModal({
] ]
}, [filteredTokens, searchQuery, searchToken, tokenComparator]) }, [filteredTokens, searchQuery, searchToken, tokenComparator])
const handleTokenSelect = useCallback( const handleCurrencySelect = useCallback(
(address: string) => { (currency: Currency) => {
onTokenSelect(address) onCurrencySelect(currency)
onDismiss() onDismiss()
}, },
[onDismiss, onTokenSelect] [onDismiss, onCurrencySelect]
) )
// clear the input on open // clear the input on open
...@@ -129,11 +127,11 @@ export default function TokenSearchModal({ ...@@ -129,11 +127,11 @@ export default function TokenSearchModal({
filteredSortedTokens[0].symbol.toLowerCase() === searchQuery.trim().toLowerCase() || filteredSortedTokens[0].symbol.toLowerCase() === searchQuery.trim().toLowerCase() ||
filteredSortedTokens.length === 1 filteredSortedTokens.length === 1
) { ) {
handleTokenSelect(filteredSortedTokens[0].address) handleCurrencySelect(filteredSortedTokens[0])
} }
} }
}, },
[filteredSortedTokens, handleTokenSelect, searchQuery] [filteredSortedTokens, handleCurrencySelect, searchQuery]
) )
return ( return (
...@@ -174,7 +172,7 @@ export default function TokenSearchModal({ ...@@ -174,7 +172,7 @@ export default function TokenSearchModal({
/> />
</Tooltip> </Tooltip>
{showCommonBases && ( {showCommonBases && (
<CommonBases chainId={chainId} onSelect={handleTokenSelect} selectedTokenAddress={hiddenToken} /> <CommonBases chainId={chainId} onSelect={handleCurrencySelect} selectedCurrency={hiddenCurrency} />
)} )}
<RowBetween> <RowBetween>
<Text fontSize={14} fontWeight={500}> <Text fontSize={14} fontWeight={500}>
...@@ -184,13 +182,12 @@ export default function TokenSearchModal({ ...@@ -184,13 +182,12 @@ export default function TokenSearchModal({
</RowBetween> </RowBetween>
</PaddedColumn> </PaddedColumn>
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} /> <div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
<TokenList <CurrencyList
tokens={filteredSortedTokens} currencies={filteredSortedTokens}
allTokenBalances={allTokenBalances} allBalances={allTokenBalances}
onTokenSelect={handleTokenSelect} onCurrencySelect={handleCurrencySelect}
otherSelectedText={otherSelectedText} otherCurrency={otherSelectedCurrency}
otherToken={otherSelectedTokenAddress} selectedCurrency={hiddenCurrency}
selectedToken={hiddenToken}
showSendWithSwap={showSendWithSwap} showSendWithSwap={showSendWithSwap}
/> />
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} /> <div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
......
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import { Pair, Token } from '@uniswap/sdk' import { Token } from '@uniswap/sdk'
export function filterTokens(tokens: Token[], search: string): Token[] { export function filterTokens(tokens: Token[], search: string): Token[] {
if (search.length === 0) return tokens if (search.length === 0) return tokens
...@@ -34,27 +34,3 @@ export function filterTokens(tokens: Token[], search: string): Token[] { ...@@ -34,27 +34,3 @@ export function filterTokens(tokens: Token[], search: string): Token[] {
return matchesSearch(symbol) || matchesSearch(name) return matchesSearch(symbol) || matchesSearch(name)
}) })
} }
export function filterPairs(pairs: Pair[], search: string): Pair[] {
if (search.trim().length === 0) return pairs
const addressSearch = isAddress(search)
if (addressSearch) {
return pairs.filter(p => {
return (
p.token0.address === addressSearch ||
p.token1.address === addressSearch ||
p.liquidityToken.address === addressSearch
)
})
}
const lowerSearch = search.toLowerCase()
return pairs.filter(pair => {
const pairExpressionA = `${pair.token0.symbol}/${pair.token1.symbol}`.toLowerCase()
if (pairExpressionA.startsWith(lowerSearch)) return true
const pairExpressionB = `${pair.token1.symbol}/${pair.token0.symbol}`.toLowerCase()
if (pairExpressionB.startsWith(lowerSearch)) return true
return filterTokens([pair.token0, pair.token1], search).length > 0
})
}
import { Token, TokenAmount, WETH, Pair } from '@uniswap/sdk' import { Token, TokenAmount, WETH } from '@uniswap/sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks' import { useAllTokenBalances } from '../../state/wallet/hooks'
import { DUMMY_PAIRS_TO_PIN } from '../../constants'
// compare two token amounts with highest one coming first // compare two token amounts with highest one coming first
function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) { function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
...@@ -16,26 +15,6 @@ function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) { ...@@ -16,26 +15,6 @@ function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
return 0 return 0
} }
// compare two pairs, favoring "pinned" pairs, and falling back to balances
export function pairComparator(pairA: Pair, pairB: Pair, balanceA?: TokenAmount, balanceB?: TokenAmount) {
const aShouldBePinned =
DUMMY_PAIRS_TO_PIN[pairA?.token0?.chainId]?.some(
dummyPairToPin => dummyPairToPin.liquidityToken.address === pairA?.liquidityToken?.address
) ?? false
const bShouldBePinned =
DUMMY_PAIRS_TO_PIN[pairB?.token0?.chainId]?.some(
dummyPairToPin => dummyPairToPin.liquidityToken.address === pairB?.liquidityToken?.address
) ?? false
if (aShouldBePinned && !bShouldBePinned) {
return -1
} else if (!aShouldBePinned && bShouldBePinned) {
return 1
} else {
return balanceComparator(balanceA, balanceB)
}
}
function getTokenComparator( function getTokenComparator(
weth: Token | undefined, weth: Token | undefined,
balances: { [tokenAddress: string]: TokenAmount } balances: { [tokenAddress: string]: TokenAmount }
...@@ -65,7 +44,7 @@ function getTokenComparator( ...@@ -65,7 +44,7 @@ function getTokenComparator(
export function useTokenComparator(inverted: boolean): (tokenA: Token, tokenB: Token) => number { export function useTokenComparator(inverted: boolean): (tokenA: Token, tokenB: Token) => number {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const weth = WETH[chainId] const weth = WETH[chainId]
const balances = useAllTokenBalancesTreatingWETHasETH() const balances = useAllTokenBalances()
const comparator = useMemo(() => getTokenComparator(weth, balances ?? {}), [balances, weth]) const comparator = useMemo(() => getTokenComparator(weth, balances ?? {}), [balances, weth])
return useMemo(() => { return useMemo(() => {
if (inverted) { if (inverted) {
......
import { Token } from '@uniswap/sdk' import { Currency, Token } from '@uniswap/sdk'
import { transparentize } from 'polished' import { transparentize } from 'polished'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
...@@ -11,7 +11,7 @@ import { ExternalLink, TYPE } from '../../theme' ...@@ -11,7 +11,7 @@ import { ExternalLink, TYPE } from '../../theme'
import { getEtherscanLink, isDefaultToken } from '../../utils' import { getEtherscanLink, isDefaultToken } from '../../utils'
import PropsOfExcluding from '../../utils/props-of-excluding' import PropsOfExcluding from '../../utils/props-of-excluding'
import QuestionHelper from '../QuestionHelper' import QuestionHelper from '../QuestionHelper'
import TokenLogo from '../TokenLogo' import CurrencyLogo from '../CurrencyLogo'
const Wrapper = styled.div<{ error: boolean }>` const Wrapper = styled.div<{ error: boolean }>`
background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)}; background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)};
...@@ -103,7 +103,7 @@ export default function TokenWarningCard({ token, ...rest }: TokenWarningCardPro ...@@ -103,7 +103,7 @@ export default function TokenWarningCard({ token, ...rest }: TokenWarningCardPro
<QuestionHelper text={duplicateNameOrSymbol ? DUPLICATE_NAME_HELP_TEXT : HELP_TEXT} /> <QuestionHelper text={duplicateNameOrSymbol ? DUPLICATE_NAME_HELP_TEXT : HELP_TEXT} />
</Row> </Row>
<Row> <Row>
<TokenLogo address={token.address} /> <CurrencyLogo currency={token} />
<div style={{ fontWeight: 500 }}> <div style={{ fontWeight: 500 }}>
{token && token.name && token.symbol && token.name !== token.symbol {token && token.name && token.symbol && token.name !== token.symbol
? `${token.name} (${token.symbol})` ? `${token.name} (${token.symbol})`
...@@ -127,11 +127,13 @@ const WarningContainer = styled.div` ...@@ -127,11 +127,13 @@ const WarningContainer = styled.div`
padding-right: 1rem; padding-right: 1rem;
` `
export function TokenWarningCards({ tokens }: { tokens: { [field in Field]?: Token } }) { export function TokenWarningCards({ currencies }: { currencies: { [field in Field]?: Currency } }) {
return ( return (
<WarningContainer> <WarningContainer>
{Object.keys(tokens).map(field => {Object.keys(currencies).map(field =>
tokens[field] ? <TokenWarningCard style={{ marginBottom: 14 }} key={field} token={tokens[field]} /> : null currencies[field] instanceof Token ? (
<TokenWarningCard style={{ marginBottom: 14 }} key={field} token={currencies[field]} />
) : null
)} )}
</WarningContainer> </WarningContainer>
) )
......
import React from 'react' import React, { useCallback, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import Popover, { PopoverProps } from '../Popover' import Popover, { PopoverProps } from '../Popover'
...@@ -16,3 +16,16 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> { ...@@ -16,3 +16,16 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> {
export default function Tooltip({ text, ...rest }: TooltipProps) { export default function Tooltip({ text, ...rest }: TooltipProps) {
return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} /> return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} />
} }
export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show'>) {
const [show, setShow] = useState(false)
const open = useCallback(() => setShow(true), [setShow])
const close = useCallback(() => setShow(false), [setShow])
return (
<Tooltip {...rest} show={show}>
<div onMouseEnter={open} onMouseLeave={close}>
{children}
</div>
</Tooltip>
)
}
...@@ -126,6 +126,12 @@ function recentTransactionsOnly(a: TransactionDetails) { ...@@ -126,6 +126,12 @@ function recentTransactionsOnly(a: TransactionDetails) {
return new Date().getTime() - a.addedTime < 86_400_000 return new Date().getTime() - a.addedTime < 86_400_000
} }
const SOCK = (
<span role="img" aria-label="has socks emoji" style={{ marginTop: -4, marginBottom: -4 }}>
🧦
</span>
)
export default function Web3Status() { export default function Web3Status() {
const { t } = useTranslation() const { t } = useTranslation()
const { active, account, connector, error } = useWeb3React() const { active, account, connector, error } = useWeb3React()
...@@ -187,9 +193,10 @@ export default function Web3Status() { ...@@ -187,9 +193,10 @@ export default function Web3Status() {
<Text>{pending?.length} Pending</Text> <Loader stroke="white" /> <Text>{pending?.length} Pending</Text> <Loader stroke="white" />
</RowBetween> </RowBetween>
) : ( ) : (
<Text> <>
{hasSocks ? '🧦' : ''} {ENSName || shortenAddress(account)} {hasSocks ? SOCK : null}
</Text> <Text>{ENSName || shortenAddress(account)}</Text>
</>
)} )}
{!hasPendingTransactions && getStatusIcon()} {!hasPendingTransactions && getStatusIcon()}
</Web3StatusConnected> </Web3StatusConnected>
......
...@@ -31,8 +31,10 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag ...@@ -31,8 +31,10 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
<RowFixed> <RowFixed>
<TYPE.black color={theme.text1} fontSize={14}> <TYPE.black color={theme.text1} fontSize={14}>
{isExactIn {isExactIn
? `${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} ${trade.outputAmount.token.symbol}` ?? '-' ? `${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} ${trade.outputAmount.currency.symbol}` ??
: `${slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4)} ${trade.inputAmount.token.symbol}` ?? '-'} '-'
: `${slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4)} ${trade.inputAmount.currency.symbol}` ??
'-'}
</TYPE.black> </TYPE.black>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
...@@ -54,7 +56,7 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag ...@@ -54,7 +56,7 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
<QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." /> <QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." />
</RowFixed> </RowFixed>
<TYPE.black fontSize={14} color={theme.text1}> <TYPE.black fontSize={14} color={theme.text1}>
{realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.token.symbol}` : '-'} {realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.currency.symbol}` : '-'}
</TYPE.black> </TYPE.black>
</RowBetween> </RowBetween>
</AutoColumn> </AutoColumn>
......
import { Percent, TokenAmount, Trade, TradeType } from '@uniswap/sdk' import { CurrencyAmount, Percent, Trade, TradeType } from '@uniswap/sdk'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { Repeat } from 'react-feather' import { Repeat } from 'react-feather'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -29,10 +29,10 @@ export default function SwapModalFooter({ ...@@ -29,10 +29,10 @@ export default function SwapModalFooter({
showInverted: boolean showInverted: boolean
setShowInverted: (inverted: boolean) => void setShowInverted: (inverted: boolean) => void
severity: number severity: number
slippageAdjustedAmounts?: { [field in Field]?: TokenAmount } slippageAdjustedAmounts?: { [field in Field]?: CurrencyAmount }
onSwap: () => any onSwap: () => any
parsedAmounts?: { [field in Field]?: TokenAmount } parsedAmounts?: { [field in Field]?: CurrencyAmount }
realizedLPFee?: TokenAmount realizedLPFee?: CurrencyAmount
priceImpactWithoutFee?: Percent priceImpactWithoutFee?: Percent
confirmText: string confirmText: string
}) { }) {
...@@ -84,8 +84,8 @@ export default function SwapModalFooter({ ...@@ -84,8 +84,8 @@ export default function SwapModalFooter({
{parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && ( {parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && (
<TYPE.black fontSize={14} marginLeft={'4px'}> <TYPE.black fontSize={14} marginLeft={'4px'}>
{trade?.tradeType === TradeType.EXACT_INPUT {trade?.tradeType === TradeType.EXACT_INPUT
? parsedAmounts[Field.OUTPUT]?.token?.symbol ? parsedAmounts[Field.OUTPUT]?.currency?.symbol
: parsedAmounts[Field.INPUT]?.token?.symbol} : parsedAmounts[Field.INPUT]?.currency?.symbol}
</TYPE.black> </TYPE.black>
)} )}
</RowFixed> </RowFixed>
...@@ -107,7 +107,7 @@ export default function SwapModalFooter({ ...@@ -107,7 +107,7 @@ export default function SwapModalFooter({
<QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." /> <QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." />
</RowFixed> </RowFixed>
<TYPE.black fontSize={14}> <TYPE.black fontSize={14}>
{realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.token?.symbol : '-'} {realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.currency?.symbol : '-'}
</TYPE.black> </TYPE.black>
</RowBetween> </RowBetween>
</AutoColumn> </AutoColumn>
......
import { Token, TokenAmount } from '@uniswap/sdk' import { Currency, CurrencyAmount } from '@uniswap/sdk'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { ArrowDown } from 'react-feather' import { ArrowDown } from 'react-feather'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -8,20 +8,20 @@ import { TYPE } from '../../theme' ...@@ -8,20 +8,20 @@ import { TYPE } from '../../theme'
import { isAddress, shortenAddress } from '../../utils' import { isAddress, shortenAddress } from '../../utils'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import TokenLogo from '../TokenLogo' import CurrencyLogo from '../CurrencyLogo'
import { TruncatedText } from './styleds' import { TruncatedText } from './styleds'
export default function SwapModalHeader({ export default function SwapModalHeader({
tokens, currencies,
formattedAmounts, formattedAmounts,
slippageAdjustedAmounts, slippageAdjustedAmounts,
priceImpactSeverity, priceImpactSeverity,
independentField, independentField,
recipient recipient
}: { }: {
tokens: { [field in Field]?: Token } currencies: { [field in Field]?: Currency }
formattedAmounts: { [field in Field]?: string } formattedAmounts: { [field in Field]?: string }
slippageAdjustedAmounts: { [field in Field]?: TokenAmount } slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount }
priceImpactSeverity: number priceImpactSeverity: number
independentField: Field independentField: Field
recipient: string | null recipient: string | null
...@@ -35,9 +35,9 @@ export default function SwapModalHeader({ ...@@ -35,9 +35,9 @@ export default function SwapModalHeader({
{formattedAmounts[Field.INPUT]} {formattedAmounts[Field.INPUT]}
</TruncatedText> </TruncatedText>
<RowFixed gap="4px"> <RowFixed gap="4px">
<TokenLogo address={tokens[Field.INPUT]?.address} size={'24px'} /> <CurrencyLogo currency={currencies[Field.INPUT]} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}> <Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.INPUT]?.symbol} {currencies[Field.INPUT]?.symbol}
</Text> </Text>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
...@@ -49,9 +49,9 @@ export default function SwapModalHeader({ ...@@ -49,9 +49,9 @@ export default function SwapModalHeader({
{formattedAmounts[Field.OUTPUT]} {formattedAmounts[Field.OUTPUT]}
</TruncatedText> </TruncatedText>
<RowFixed gap="4px"> <RowFixed gap="4px">
<TokenLogo address={tokens[Field.OUTPUT]?.address} size={'24px'} /> <CurrencyLogo currency={currencies[Field.OUTPUT]} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}> <Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.OUTPUT]?.symbol} {currencies[Field.OUTPUT]?.symbol}
</Text> </Text>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
...@@ -60,7 +60,7 @@ export default function SwapModalHeader({ ...@@ -60,7 +60,7 @@ export default function SwapModalHeader({
<TYPE.italic textAlign="left" style={{ width: '100%' }}> <TYPE.italic textAlign="left" style={{ width: '100%' }}>
{`Output is estimated. You will receive at least `} {`Output is estimated. You will receive at least `}
<b> <b>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {tokens[Field.OUTPUT]?.symbol} {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {currencies[Field.OUTPUT]?.symbol}
</b> </b>
{' or the transaction will revert.'} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
...@@ -68,7 +68,7 @@ export default function SwapModalHeader({ ...@@ -68,7 +68,7 @@ export default function SwapModalHeader({
<TYPE.italic textAlign="left" style={{ width: '100%' }}> <TYPE.italic textAlign="left" style={{ width: '100%' }}>
{`Input is estimated. You will sell at most `} {`Input is estimated. You will sell at most `}
<b> <b>
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {tokens[Field.INPUT]?.symbol} {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {currencies[Field.INPUT]?.symbol}
</b> </b>
{' or the transaction will revert.'} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
......
...@@ -4,7 +4,7 @@ import { ChevronRight } from 'react-feather' ...@@ -4,7 +4,7 @@ import { ChevronRight } from 'react-feather'
import { Flex } from 'rebass' import { Flex } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import TokenLogo from '../TokenLogo' import CurrencyLogo from '../CurrencyLogo'
export default memo(function SwapRoute({ trade }: { trade: Trade }) { export default memo(function SwapRoute({ trade }: { trade: Trade }) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
...@@ -24,7 +24,7 @@ export default memo(function SwapRoute({ trade }: { trade: Trade }) { ...@@ -24,7 +24,7 @@ export default memo(function SwapRoute({ trade }: { trade: Trade }) {
return ( return (
<Fragment key={i}> <Fragment key={i}>
<Flex my="0.5rem" alignItems="center" style={{ flexShrink: 0 }}> <Flex my="0.5rem" alignItems="center" style={{ flexShrink: 0 }}>
<TokenLogo address={token.address} size="1.5rem" /> <CurrencyLogo currency={token} size="1.5rem" />
<TYPE.black fontSize={14} color={theme.text1} ml="0.5rem"> <TYPE.black fontSize={14} color={theme.text1} ml="0.5rem">
{token.symbol} {token.symbol}
</TYPE.black> </TYPE.black>
......
import React from 'react' import React from 'react'
import { Price, Token } from '@uniswap/sdk' import { Currency, Price } from '@uniswap/sdk'
import { useContext } from 'react' import { useContext } from 'react'
import { Repeat } from 'react-feather' import { Repeat } from 'react-feather'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -8,21 +8,27 @@ import { StyledBalanceMaxMini } from './styleds' ...@@ -8,21 +8,27 @@ import { StyledBalanceMaxMini } from './styleds'
interface TradePriceProps { interface TradePriceProps {
price?: Price price?: Price
inputToken?: Token inputCurrency?: Currency
outputToken?: Token outputCurrency?: Currency
showInverted: boolean showInverted: boolean
setShowInverted: (showInverted: boolean) => void setShowInverted: (showInverted: boolean) => void
} }
export default function TradePrice({ price, inputToken, outputToken, showInverted, setShowInverted }: TradePriceProps) { export default function TradePrice({
price,
inputCurrency,
outputCurrency,
showInverted,
setShowInverted
}: TradePriceProps) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6) const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6)
const show = Boolean(inputToken && outputToken) const show = Boolean(inputCurrency && outputCurrency)
const label = showInverted const label = showInverted
? `${outputToken?.symbol} per ${inputToken?.symbol}` ? `${outputCurrency?.symbol} per ${inputCurrency?.symbol}`
: `${inputToken?.symbol} per ${outputToken?.symbol}` : `${inputCurrency?.symbol} per ${outputCurrency?.symbol}`
return ( return (
<Text <Text
......
...@@ -46,7 +46,13 @@ class MiniRpcProvider implements AsyncSendable { ...@@ -46,7 +46,13 @@ class MiniRpcProvider implements AsyncSendable {
.catch(error => callback(error, null)) .catch(error => callback(error, null))
} }
public readonly request = async (method: string, params?: unknown[] | object): Promise<unknown> => { public readonly request = async (
method: string | { method: string; params: unknown[] },
params?: unknown[] | object
): Promise<unknown> => {
if (typeof method !== 'string') {
return this.request(method.method, method.params)
}
const response = await fetch(this.url, { const response = await fetch(this.url, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
......
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "guy",
"type": "address"
},
{
"name": "wad",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "src",
"type": "address"
},
{
"name": "dst",
"type": "address"
},
{
"name": "wad",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "wad",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "dst",
"type": "address"
},
{
"name": "wad",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "deposit",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "address"
},
{
"name": "",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "src",
"type": "address"
},
{
"indexed": true,
"name": "guy",
"type": "address"
},
{
"indexed": false,
"name": "wad",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "src",
"type": "address"
},
{
"indexed": true,
"name": "dst",
"type": "address"
},
{
"indexed": false,
"name": "wad",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "dst",
"type": "address"
},
{
"indexed": false,
"name": "wad",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "src",
"type": "address"
},
{
"indexed": false,
"name": "wad",
"type": "uint256"
}
],
"name": "Withdrawal",
"type": "event"
}
]
\ No newline at end of file
import { ChainId, JSBI, Percent, Token, WETH, Pair, TokenAmount } from '@uniswap/sdk' import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors' import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
import { COMP, DAI, MKR, USDC, USDT } from './tokens/mainnet' import { COMP, DAI, MKR, USDC, USDT } from './tokens/mainnet'
...@@ -36,38 +36,14 @@ export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = { ...@@ -36,38 +36,14 @@ export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = {
[ChainId.MAINNET]: [...WETH_ONLY[ChainId.MAINNET], DAI, USDC, USDT] [ChainId.MAINNET]: [...WETH_ONLY[ChainId.MAINNET], DAI, USDC, USDT]
} }
export const DUMMY_PAIRS_TO_PIN: { readonly [chainId in ChainId]?: Pair[] } = { export const PINNED_PAIRS: { readonly [chainId in ChainId]?: [Token, Token][] } = {
[ChainId.MAINNET]: [ [ChainId.MAINNET]: [
new Pair( [
new TokenAmount( new Token(ChainId.MAINNET, '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 8, 'cDAI', 'Compound Dai'),
new Token(ChainId.MAINNET, '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 8, 'cDAI', 'Compound Dai'), new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin')
'0' ],
), [USDC, USDT],
new TokenAmount( [DAI, USDT]
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'),
'0'
)
),
new Pair(
new TokenAmount(
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C'),
'0'
),
new TokenAmount(
new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD'),
'0'
)
),
new Pair(
new TokenAmount(
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
'0'
),
new TokenAmount(
new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD'),
'0'
)
)
] ]
} }
......
...@@ -14,14 +14,6 @@ export const ALL_TOKENS: AllTokens = [ ...@@ -14,14 +14,6 @@ export const ALL_TOKENS: AllTokens = [
...KOVAN_TOKENS, ...KOVAN_TOKENS,
...ROPSTEN_TOKENS ...ROPSTEN_TOKENS
] ]
// remap WETH to ETH
.map(token => {
if (token.equals(WETH[token.chainId])) {
;(token as any).symbol = 'ETH'
;(token as any).name = 'Ether'
}
return token
})
// put into an object // put into an object
.reduce<AllTokens>( .reduce<AllTokens>(
(tokenMap, token) => { (tokenMap, token) => {
......
import { Token, TokenAmount, Pair } from '@uniswap/sdk' import { TokenAmount, Pair, Currency } from '@uniswap/sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json' import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { Interface } from '@ethersproject/abi' import { Interface } from '@ethersproject/abi'
import { useActiveWeb3React } from '../hooks'
import { useMultipleContractSingleData } from '../state/multicall/hooks' import { useMultipleContractSingleData } from '../state/multicall/hooks'
import { wrappedCurrency } from '../utils/wrappedCurrency'
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI) const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)
/* export enum PairState {
* if loading, return undefined LOADING,
* if no pair created yet, return null NOT_EXISTS,
* if pair already created (even if 0 reserves), return pair EXISTS,
*/ INVALID
export function usePairs(tokens: [Token | undefined, Token | undefined][]): (undefined | Pair | null)[] { }
export function usePairs(currencies: [Currency | undefined, Currency | undefined][]): [PairState, Pair | null][] {
const { chainId } = useActiveWeb3React()
const tokens = useMemo(
() =>
currencies.map(([currencyA, currencyB]) => [
wrappedCurrency(currencyA, chainId),
wrappedCurrency(currencyB, chainId)
]),
[chainId, currencies]
)
const pairAddresses = useMemo( const pairAddresses = useMemo(
() => () =>
tokens.map(([tokenA, tokenB]) => { tokens.map(([tokenA, tokenB]) => {
...@@ -29,15 +44,19 @@ export function usePairs(tokens: [Token | undefined, Token | undefined][]): (und ...@@ -29,15 +44,19 @@ export function usePairs(tokens: [Token | undefined, Token | undefined][]): (und
const tokenA = tokens[i][0] const tokenA = tokens[i][0]
const tokenB = tokens[i][1] const tokenB = tokens[i][1]
if (loading || !tokenA || !tokenB) return undefined if (loading) return [PairState.LOADING, null]
if (!reserves) return null if (!tokenA || !tokenB || tokenA.equals(tokenB)) return [PairState.INVALID, null]
if (!reserves) return [PairState.NOT_EXISTS, null]
const { reserve0, reserve1 } = reserves const { reserve0, reserve1 } = reserves
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString())) return [
PairState.EXISTS,
new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
]
}) })
}, [results, tokens]) }, [results, tokens])
} }
export function usePair(tokenA?: Token, tokenB?: Token): undefined | Pair | null { export function usePair(tokenA?: Currency, tokenB?: Currency): [PairState, Pair | null] {
return usePairs([[tokenA, tokenB]])[0] return usePairs([[tokenA, tokenB]])[0]
} }
import { JSBI, Pair, Percent, Route, Token, TokenAmount, Trade, TradeType, WETH } from '@uniswap/sdk' import { AddressZero } from '@ethersproject/constants'
import {
BigintIsh,
Currency,
CurrencyAmount,
currencyEquals,
ETHER,
JSBI,
Pair,
Percent,
Route,
Token,
TokenAmount,
Trade,
TradeType,
WETH
} from '@uniswap/sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useActiveWeb3React } from '../hooks' import { useActiveWeb3React } from '../hooks'
import { useAllTokens } from '../hooks/Tokens' import { useAllTokens } from '../hooks/Tokens'
...@@ -6,7 +22,6 @@ import { useV1FactoryContract } from '../hooks/useContract' ...@@ -6,7 +22,6 @@ import { useV1FactoryContract } from '../hooks/useContract'
import { Version } from '../hooks/useToggledVersion' import { Version } from '../hooks/useToggledVersion'
import { NEVER_RELOAD, useSingleCallResult, useSingleContractMultipleData } from '../state/multicall/hooks' import { NEVER_RELOAD, useSingleCallResult, useSingleContractMultipleData } from '../state/multicall/hooks'
import { useETHBalances, useTokenBalance, useTokenBalances } from '../state/wallet/hooks' import { useETHBalances, useTokenBalance, useTokenBalances } from '../state/wallet/hooks'
import { AddressZero } from '@ethersproject/constants'
export function useV1ExchangeAddress(tokenAddress?: string): string | undefined { export function useV1ExchangeAddress(tokenAddress?: string): string | undefined {
const contract = useV1FactoryContract() const contract = useV1FactoryContract()
...@@ -15,18 +30,25 @@ export function useV1ExchangeAddress(tokenAddress?: string): string | undefined ...@@ -15,18 +30,25 @@ export function useV1ExchangeAddress(tokenAddress?: string): string | undefined
return useSingleCallResult(contract, 'getExchange', inputs)?.result?.[0] return useSingleCallResult(contract, 'getExchange', inputs)?.result?.[0]
} }
class MockV1Pair extends Pair {} export class MockV1Pair extends Pair {
constructor(etherAmount: BigintIsh, tokenAmount: TokenAmount) {
super(tokenAmount, new TokenAmount(WETH[tokenAmount.token.chainId], etherAmount))
}
}
function useMockV1Pair(token?: Token): MockV1Pair | undefined { function useMockV1Pair(inputCurrency?: Currency): MockV1Pair | undefined {
const isWETH: boolean = token && WETH[token.chainId] ? token.equals(WETH[token.chainId]) : false const token = inputCurrency instanceof Token ? inputCurrency : undefined
const isWETH = Boolean(token && token.equals(WETH[token.chainId]))
const v1PairAddress = useV1ExchangeAddress(isWETH ? undefined : token?.address) const v1PairAddress = useV1ExchangeAddress(isWETH ? undefined : token?.address)
const tokenBalance = useTokenBalance(v1PairAddress, token) const tokenBalance = useTokenBalance(v1PairAddress, token)
const ETHBalance = useETHBalances([v1PairAddress])[v1PairAddress ?? ''] const ETHBalance = useETHBalances([v1PairAddress])[v1PairAddress ?? '']
return tokenBalance && ETHBalance && token return useMemo(
? new MockV1Pair(tokenBalance, new TokenAmount(WETH[token.chainId], ETHBalance.toString())) () =>
: undefined token && tokenBalance && ETHBalance && inputCurrency ? new MockV1Pair(ETHBalance.raw, tokenBalance) : undefined,
[ETHBalance, inputCurrency, token, tokenBalance]
)
} }
// returns all v1 exchange addresses in the user's token list // returns all v1 exchange addresses in the user's token list
...@@ -41,8 +63,7 @@ export function useAllTokenV1Exchanges(): { [exchangeAddress: string]: Token } { ...@@ -41,8 +63,7 @@ export function useAllTokenV1Exchanges(): { [exchangeAddress: string]: Token } {
() => () =>
data?.reduce<{ [exchangeAddress: string]: Token }>((memo, { result }, ix) => { data?.reduce<{ [exchangeAddress: string]: Token }>((memo, { result }, ix) => {
if (result?.[0] && result[0] !== AddressZero) { if (result?.[0] && result[0] !== AddressZero) {
const token = allTokens[args[ix][0]] memo[result[0]] = allTokens[args[ix][0]]
memo[result[0]] = token
} }
return memo return memo
}, {}) ?? {}, }, {}) ?? {},
...@@ -56,13 +77,13 @@ export function useUserHasLiquidityInAllTokens(): boolean | undefined { ...@@ -56,13 +77,13 @@ export function useUserHasLiquidityInAllTokens(): boolean | undefined {
const exchanges = useAllTokenV1Exchanges() const exchanges = useAllTokenV1Exchanges()
const fakeLiquidityTokens = useMemo( const v1ExchangeLiquidityTokens = useMemo(
() => () =>
chainId ? Object.keys(exchanges).map(address => new Token(chainId, address, 18, 'UNI-V1', 'Uniswap V1')) : [], chainId ? Object.keys(exchanges).map(address => new Token(chainId, address, 18, 'UNI-V1', 'Uniswap V1')) : [],
[chainId, exchanges] [chainId, exchanges]
) )
const balances = useTokenBalances(account ?? undefined, fakeLiquidityTokens) const balances = useTokenBalances(account ?? undefined, v1ExchangeLiquidityTokens)
return useMemo( return useMemo(
() => () =>
...@@ -79,32 +100,30 @@ export function useUserHasLiquidityInAllTokens(): boolean | undefined { ...@@ -79,32 +100,30 @@ export function useUserHasLiquidityInAllTokens(): boolean | undefined {
*/ */
export function useV1Trade( export function useV1Trade(
isExactIn?: boolean, isExactIn?: boolean,
inputToken?: Token, inputCurrency?: Currency,
outputToken?: Token, outputCurrency?: Currency,
exactAmount?: TokenAmount exactAmount?: CurrencyAmount
): Trade | undefined { ): Trade | undefined {
const { chainId } = useActiveWeb3React()
// get the mock v1 pairs // get the mock v1 pairs
const inputPair = useMockV1Pair(inputToken) const inputPair = useMockV1Pair(inputCurrency)
const outputPair = useMockV1Pair(outputToken) const outputPair = useMockV1Pair(outputCurrency)
const inputIsWETH = (inputToken && chainId && WETH[chainId] && inputToken.equals(WETH[chainId])) ?? false const inputIsETH = inputCurrency === ETHER
const outputIsWETH = (outputToken && chainId && WETH[chainId] && outputToken.equals(WETH[chainId])) ?? false const outputIsETH = outputCurrency === ETHER
// construct a direct or through ETH v1 route // construct a direct or through ETH v1 route
let pairs: Pair[] = [] let pairs: Pair[] = []
if (inputIsWETH && outputPair) { if (inputIsETH && outputPair) {
pairs = [outputPair] pairs = [outputPair]
} else if (outputIsWETH && inputPair) { } else if (outputIsETH && inputPair) {
pairs = [inputPair] pairs = [inputPair]
} }
// if neither are WETH, it's token-to-token (if they both exist) // if neither are ETH, it's token-to-token (if they both exist)
else if (inputPair && outputPair) { else if (inputPair && outputPair) {
pairs = [inputPair, outputPair] pairs = [inputPair, outputPair]
} }
const route = inputToken && pairs && pairs.length > 0 && new Route(pairs, inputToken) const route = inputCurrency && pairs && pairs.length > 0 && new Route(pairs, inputCurrency)
let v1Trade: Trade | undefined let v1Trade: Trade | undefined
try { try {
v1Trade = v1Trade =
...@@ -125,14 +144,13 @@ export function getTradeVersion(trade?: Trade): Version | undefined { ...@@ -125,14 +144,13 @@ export function getTradeVersion(trade?: Trade): Version | undefined {
// returns the v1 exchange against which a trade should be executed // returns the v1 exchange against which a trade should be executed
export function useV1TradeExchangeAddress(trade: Trade | undefined): string | undefined { export function useV1TradeExchangeAddress(trade: Trade | undefined): string | undefined {
const tokenAddress: string | undefined = useMemo(() => { const tokenAddress: string | undefined = useMemo(() => {
const tradeVersion = getTradeVersion(trade) if (!trade) return undefined
const isV1 = tradeVersion === Version.v1 const isV1 = getTradeVersion(trade) === Version.v1
return isV1 if (!isV1) return undefined
? trade && return trade.inputAmount instanceof TokenAmount
WETH[trade.inputAmount.token.chainId] && ? trade.inputAmount.token.address
trade.inputAmount.token.equals(WETH[trade.inputAmount.token.chainId]) : trade.outputAmount instanceof TokenAmount
? trade.outputAmount.token.address ? trade.outputAmount.token.address
: trade?.inputAmount?.token?.address
: undefined : undefined
}, [trade]) }, [trade])
return useV1ExchangeAddress(tokenAddress) return useV1ExchangeAddress(tokenAddress)
...@@ -140,7 +158,8 @@ export function useV1TradeExchangeAddress(trade: Trade | undefined): string | un ...@@ -140,7 +158,8 @@ export function useV1TradeExchangeAddress(trade: Trade | undefined): string | un
const ZERO_PERCENT = new Percent('0') const ZERO_PERCENT = new Percent('0')
const ONE_HUNDRED_PERCENT = new Percent('1') const ONE_HUNDRED_PERCENT = new Percent('1')
// returns whether tradeB is better than tradeA by at least a threshold
// returns whether tradeB is better than tradeA by at least a threshold percentage amount
export function isTradeBetter( export function isTradeBetter(
tradeA: Trade | undefined, tradeA: Trade | undefined,
tradeB: Trade | undefined, tradeB: Trade | undefined,
...@@ -150,8 +169,8 @@ export function isTradeBetter( ...@@ -150,8 +169,8 @@ export function isTradeBetter(
if ( if (
tradeA.tradeType !== tradeB.tradeType || tradeA.tradeType !== tradeB.tradeType ||
!tradeA.inputAmount.token.equals(tradeB.inputAmount.token) || !currencyEquals(tradeA.inputAmount.currency, tradeB.inputAmount.currency) ||
!tradeB.outputAmount.token.equals(tradeB.outputAmount.token) !currencyEquals(tradeB.outputAmount.currency, tradeB.outputAmount.currency)
) { ) {
throw new Error('Trades are not comparable') throw new Error('Trades are not comparable')
} }
......
import { parseBytes32String } from '@ethersproject/strings' import { parseBytes32String } from '@ethersproject/strings'
import { ChainId, Token, WETH } from '@uniswap/sdk' import { ChainId, Currency, ETHER, Token } from '@uniswap/sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { ALL_TOKENS } from '../constants/tokens' import { ALL_TOKENS } from '../constants/tokens'
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks' import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
...@@ -15,26 +15,19 @@ export function useAllTokens(): { [address: string]: Token } { ...@@ -15,26 +15,19 @@ export function useAllTokens(): { [address: string]: Token } {
return useMemo(() => { return useMemo(() => {
if (!chainId) return {} if (!chainId) return {}
const tokens = userAddedTokens return (
// reduce into all ALL_TOKENS filtered by the current chain userAddedTokens
.reduce<{ [address: string]: Token }>( // reduce into all ALL_TOKENS filtered by the current chain
(tokenMap, token) => { .reduce<{ [address: string]: Token }>(
tokenMap[token.address] = token (tokenMap, token) => {
return tokenMap tokenMap[token.address] = token
}, return tokenMap
// must make a copy because reduce modifies the map, and we do not },
// want to make a copy in every iteration // must make a copy because reduce modifies the map, and we do not
{ ...ALL_TOKENS[chainId as ChainId] } // want to make a copy in every iteration
) { ...ALL_TOKENS[chainId as ChainId] }
)
const weth = WETH[chainId as ChainId] )
if (weth) {
// we have to replace it as a workaround because if it is automatically
// fetched by address it will cause an invariant when used in constructing
// pairs since we replace the name and symbol with 'ETH' and 'Ether'
tokens[weth.address] = weth
}
return tokens
}, [userAddedTokens, chainId]) }, [userAddedTokens, chainId])
} }
...@@ -100,3 +93,9 @@ export function useToken(tokenAddress?: string): Token | undefined | null { ...@@ -100,3 +93,9 @@ export function useToken(tokenAddress?: string): Token | undefined | null {
tokenNameBytes32.result tokenNameBytes32.result
]) ])
} }
export function useCurrency(currencyId: string | undefined): Currency | null | undefined {
const isETH = currencyId?.toUpperCase() === 'ETH'
const token = useToken(isETH ? undefined : currencyId)
return isETH ? ETHER : token
}
import { Pair, Token, TokenAmount, Trade } from '@uniswap/sdk' import { Currency, CurrencyAmount, Pair, Token, Trade } from '@uniswap/sdk'
import flatMap from 'lodash.flatmap' import flatMap from 'lodash.flatmap'
import { useMemo } from 'react' import { useMemo } from 'react'
import { BASES_TO_CHECK_TRADES_AGAINST } from '../constants' import { BASES_TO_CHECK_TRADES_AGAINST } from '../constants'
import { usePairs } from '../data/Reserves' import { PairState, usePairs } from '../data/Reserves'
import { wrappedCurrency } from '../utils/wrappedCurrency'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] { function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const bases: Token[] = chainId ? BASES_TO_CHECK_TRADES_AGAINST[chainId] : [] const bases: Token[] = chainId ? BASES_TO_CHECK_TRADES_AGAINST[chainId] : []
const [tokenA, tokenB] = chainId
? [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
: [undefined, undefined]
const allPairCombinations: [Token | undefined, Token | undefined][] = useMemo( const allPairCombinations: [Token | undefined, Token | undefined][] = useMemo(
() => [ () => [
// the direct pair // the direct pair
...@@ -34,9 +39,9 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] { ...@@ -34,9 +39,9 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
Object.values( Object.values(
allPairs allPairs
// filter out invalid pairs // filter out invalid pairs
.filter((p): p is Pair => !!p) .filter((result): result is [PairState.EXISTS, Pair] => Boolean(result[0] === PairState.EXISTS && result[1]))
// filter out duplicated pairs // filter out duplicated pairs
.reduce<{ [pairAddress: string]: Pair }>((memo, curr) => { .reduce<{ [pairAddress: string]: Pair }>((memo, [, curr]) => {
memo[curr.liquidityToken.address] = memo[curr.liquidityToken.address] ?? curr memo[curr.liquidityToken.address] = memo[curr.liquidityToken.address] ?? curr
return memo return memo
}, {}) }, {})
...@@ -48,27 +53,32 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] { ...@@ -48,27 +53,32 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
/** /**
* Returns the best trade for the exact amount of tokens in to the given token out * Returns the best trade for the exact amount of tokens in to the given token out
*/ */
export function useTradeExactIn(amountIn?: TokenAmount, tokenOut?: Token): Trade | null { export function useTradeExactIn(currencyAmountIn?: CurrencyAmount, currencyOut?: Currency): Trade | null {
const allowedPairs = useAllCommonPairs(amountIn?.token, tokenOut) const allowedPairs = useAllCommonPairs(currencyAmountIn?.currency, currencyOut)
return useMemo(() => { return useMemo(() => {
if (amountIn && tokenOut && allowedPairs.length > 0) { if (currencyAmountIn && currencyOut && allowedPairs.length > 0) {
return Trade.bestTradeExactIn(allowedPairs, amountIn, tokenOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null return (
Trade.bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null
)
} }
return null return null
}, [allowedPairs, amountIn, tokenOut]) }, [allowedPairs, currencyAmountIn, currencyOut])
} }
/** /**
* Returns the best trade for the token in to the exact amount of token out * Returns the best trade for the token in to the exact amount of token out
*/ */
export function useTradeExactOut(tokenIn?: Token, amountOut?: TokenAmount): Trade | null { export function useTradeExactOut(currencyIn?: Currency, currencyAmountOut?: CurrencyAmount): Trade | null {
const allowedPairs = useAllCommonPairs(tokenIn, amountOut?.token) const allowedPairs = useAllCommonPairs(currencyIn, currencyAmountOut?.currency)
return useMemo(() => { return useMemo(() => {
if (tokenIn && amountOut && allowedPairs.length > 0) { if (currencyIn && currencyAmountOut && allowedPairs.length > 0) {
return Trade.bestTradeExactOut(allowedPairs, tokenIn, amountOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null return (
Trade.bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: 3, maxNumResults: 1 })[0] ??
null
)
} }
return null return null
}, [allowedPairs, tokenIn, amountOut]) }, [allowedPairs, currencyIn, currencyAmountOut])
} }
import { MaxUint256 } from '@ethersproject/constants' import { MaxUint256 } from '@ethersproject/constants'
import { TransactionResponse } from '@ethersproject/providers' import { TransactionResponse } from '@ethersproject/providers'
import { Trade, WETH, TokenAmount } from '@uniswap/sdk' import { Trade, TokenAmount, CurrencyAmount, ETHER } from '@uniswap/sdk'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { ROUTER_ADDRESS } from '../constants' import { ROUTER_ADDRESS } from '../constants'
import { useTokenAllowance } from '../data/Allowances' import { useTokenAllowance } from '../data/Allowances'
...@@ -22,19 +22,18 @@ export enum ApprovalState { ...@@ -22,19 +22,18 @@ export enum ApprovalState {
// returns a variable indicating the state of the approval and a function which approves if necessary or early returns // returns a variable indicating the state of the approval and a function which approves if necessary or early returns
export function useApproveCallback( export function useApproveCallback(
amountToApprove?: TokenAmount, amountToApprove?: CurrencyAmount,
spender?: string spender?: string
): [ApprovalState, () => Promise<void>] { ): [ApprovalState, () => Promise<void>] {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const token = amountToApprove instanceof TokenAmount ? amountToApprove.token : undefined
const currentAllowance = useTokenAllowance(amountToApprove?.token, account ?? undefined, spender) const currentAllowance = useTokenAllowance(token, account ?? undefined, spender)
const pendingApproval = useHasPendingApproval(amountToApprove?.token?.address, spender) const pendingApproval = useHasPendingApproval(token?.address, spender)
// check the current approval status // check the current approval status
const approvalState: ApprovalState = useMemo(() => { const approvalState: ApprovalState = useMemo(() => {
if (!amountToApprove || !spender) return ApprovalState.UNKNOWN if (!amountToApprove || !spender) return ApprovalState.UNKNOWN
// we treat WETH as ETH which requires no approvals if (amountToApprove.currency === ETHER) return ApprovalState.APPROVED
if (amountToApprove.token.equals(WETH[amountToApprove.token.chainId])) return ApprovalState.APPROVED
// we might not have enough data to know whether or not we need to approve // we might not have enough data to know whether or not we need to approve
if (!currentAllowance) return ApprovalState.UNKNOWN if (!currentAllowance) return ApprovalState.UNKNOWN
...@@ -46,7 +45,7 @@ export function useApproveCallback( ...@@ -46,7 +45,7 @@ export function useApproveCallback(
: ApprovalState.APPROVED : ApprovalState.APPROVED
}, [amountToApprove, currentAllowance, pendingApproval, spender]) }, [amountToApprove, currentAllowance, pendingApproval, spender])
const tokenContract = useTokenContract(amountToApprove?.token?.address) const tokenContract = useTokenContract(token?.address)
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const approve = useCallback(async (): Promise<void> => { const approve = useCallback(async (): Promise<void> => {
...@@ -54,6 +53,10 @@ export function useApproveCallback( ...@@ -54,6 +53,10 @@ export function useApproveCallback(
console.error('approve was called unnecessarily') console.error('approve was called unnecessarily')
return return
} }
if (!token) {
console.error('no token')
return
}
if (!tokenContract) { if (!tokenContract) {
console.error('tokenContract is null') console.error('tokenContract is null')
...@@ -83,15 +86,15 @@ export function useApproveCallback( ...@@ -83,15 +86,15 @@ export function useApproveCallback(
}) })
.then((response: TransactionResponse) => { .then((response: TransactionResponse) => {
addTransaction(response, { addTransaction(response, {
summary: 'Approve ' + amountToApprove.token.symbol, summary: 'Approve ' + amountToApprove.currency.symbol,
approval: { tokenAddress: amountToApprove.token.address, spender: spender } approval: { tokenAddress: token.address, spender: spender }
}) })
}) })
.catch((error: Error) => { .catch((error: Error) => {
console.debug('Failed to approve token', error) console.debug('Failed to approve token', error)
throw error throw error
}) })
}, [approvalState, tokenContract, spender, amountToApprove, addTransaction]) }, [approvalState, token, tokenContract, amountToApprove, spender, addTransaction])
return [approvalState, approve] return [approvalState, approve]
} }
......
import { useCallback, useEffect } from 'react'
// modified from https://usehooks.com/useKeyPress/
export default function useBodyKeyDown(targetKey: string, onKeyDown: () => void, suppressOnKeyDown = false) {
const downHandler = useCallback(
event => {
const {
target: { tagName },
key
} = event
if (key === targetKey && tagName === 'BODY' && !suppressOnKeyDown) {
event.preventDefault()
onKeyDown()
}
},
[targetKey, onKeyDown, suppressOnKeyDown]
)
useEffect(() => {
window.addEventListener('keydown', downHandler)
return () => {
window.removeEventListener('keydown', downHandler)
}
}, [downHandler])
}
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { ChainId } from '@uniswap/sdk' import { ChainId, WETH } from '@uniswap/sdk'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json' import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { useMemo } from 'react' import { useMemo } from 'react'
import { ERC20_BYTES32_ABI } from '../constants/abis/erc20' import { ERC20_BYTES32_ABI } from '../constants/abis/erc20'
import UNISOCKS_ABI from '../constants/abis/unisocks.json' import UNISOCKS_ABI from '../constants/abis/unisocks.json'
import ERC20_ABI from '../constants/abis/erc20.json' import ERC20_ABI from '../constants/abis/erc20.json'
import WETH_ABI from '../constants/abis/weth.json'
import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator' import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator'
import { MULTICALL_ABI, MULTICALL_NETWORKS } from '../constants/multicall' import { MULTICALL_ABI, MULTICALL_NETWORKS } from '../constants/multicall'
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from '../constants/v1' import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from '../constants/v1'
...@@ -43,6 +44,11 @@ export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: b ...@@ -43,6 +44,11 @@ export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: b
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible) return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible)
} }
export function useWETHContract(withSignerIfPossible?: boolean): Contract | null {
const { chainId } = useActiveWeb3React()
return useContract(chainId ? WETH[chainId].address : undefined, WETH_ABI, withSignerIfPossible)
}
export function useBytes32TokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null { export function useBytes32TokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null {
return useContract(tokenAddress, ERC20_BYTES32_ABI, withSignerIfPossible) return useContract(tokenAddress, ERC20_BYTES32_ABI, withSignerIfPossible)
} }
......
import { JSBI } from '@uniswap/sdk' import { JSBI } from '@uniswap/sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useSingleCallResult } from '../state/multicall/hooks' import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
import { useSocksController } from './useContract' import { useSocksController } from './useContract'
...@@ -8,7 +8,7 @@ export default function useSocksBalance(): JSBI | undefined { ...@@ -8,7 +8,7 @@ export default function useSocksBalance(): JSBI | undefined {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const socksContract = useSocksController() const socksContract = useSocksController()
const { result } = useSingleCallResult(socksContract, 'balanceOf', [account ?? undefined], { blocksPerFetch: 100 }) const { result } = useSingleCallResult(socksContract, 'balanceOf', [account ?? undefined], NEVER_RELOAD)
const data = result?.[0] const data = result?.[0]
return data ? JSBI.BigInt(data.toString()) : undefined return data ? JSBI.BigInt(data.toString()) : undefined
} }
......
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 ? (
<MinimalPositionCard pair={pair} border="1px solid #CED0D9" /> hasPosition && pair ? (
) : ( <MinimalPositionCard pair={pair} border="1px solid #CED0D9" />
) : (
<LightCard padding="45px 10px">
<AutoColumn gap="sm" justify="center">
<Text textAlign="center">You don’t have liquidity in this pool yet.</Text>
<StyledInternalLink to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}>
<Text textAlign="center">Add liquidity.</Text>
</StyledInternalLink>
</AutoColumn>
</LightCard>
)
) : validPairNoLiquidity ? (
<LightCard padding="45px 10px"> <LightCard padding="45px 10px">
<AutoColumn gap="sm" justify="center"> <AutoColumn gap="sm" justify="center">
<Text textAlign="center">You don’t have liquidity in this pool yet.</Text> <Text textAlign="center">No pool found.</Text>
<StyledInternalLink to={`/add/${token0?.address}/${token1?.address}`}> <StyledInternalLink to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}>
<Text textAlign="center">Add liquidity.</Text> Create pool.
</StyledInternalLink> </StyledInternalLink>
</AutoColumn> </AutoColumn>
</LightCard> </LightCard>
) ) : pairState === PairState.INVALID ? (
) : newPair ? ( <LightCard padding="45px 10px">
<LightCard padding="45px 10px"> <AutoColumn gap="sm" justify="center">
<AutoColumn gap="sm" justify="center"> <Text textAlign="center" fontWeight={500}>
<Text textAlign="center">No pool found.</Text> Invalid pair.
<StyledInternalLink to={`/add/${token0Address}/${token1Address}`}>Create pool?</StyledInternalLink> </Text>
</AutoColumn> </AutoColumn>
</LightCard> </LightCard>
) : pairState === PairState.LOADING ? (
<LightCard padding="45px 10px">
<AutoColumn gap="sm" justify="center">
<Text textAlign="center">
Loading
<Dots />
</Text>
</AutoColumn>
</LightCard>
) : null
) : ( ) : (
<LightCard padding="45px 10px"> prerequisiteMessage
<Text textAlign="center">
{!account ? 'Connect to a wallet to find pools' : 'Select a token to find your liquidity.'}
</Text>
</LightCard>
)} )}
</AutoColumn> </AutoColumn>
<TokenSearchModal <CurrencySearchModal
isOpen={showSearch} isOpen={showSearch}
onTokenSelect={handleTokenSelect} onCurrencySelect={handleCurrencySelect}
onDismiss={handleSearchDismiss} onDismiss={handleSearchDismiss}
showCommonBases showCommonBases
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address} hiddenCurrency={(activeField === Fields.TOKEN0 ? currency1 : currency0) ?? undefined}
/> />
</AppBody> </AppBody>
) )
......
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 && totalSupply &&
tokens[Field.TOKEN_A] && userLiquidity &&
totalSupply && tokenA &&
userLiquidity && // this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply JSBI.greaterThanOrEqual(totalSupply.raw, userLiquidity.raw)
JSBI.greaterThanOrEqual(totalSupply.raw, userLiquidity.raw) ? new TokenAmount(tokenA, pair.getLiquidityValue(tokenA, totalSupply, userLiquidity, false).raw)
? new TokenAmount( : undefined
tokens[Field.TOKEN_A] as Token, const liquidityValueB =
pair.getLiquidityValue(tokens[Field.TOKEN_A] as Token, totalSupply, userLiquidity, false).raw pair &&
) totalSupply &&
: undefined, userLiquidity &&
[Field.TOKEN_B]: tokenB &&
pair && // this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
tokens[Field.TOKEN_B] && JSBI.greaterThanOrEqual(totalSupply.raw, userLiquidity.raw)
totalSupply && ? new TokenAmount(tokenB, pair.getLiquidityValue(tokenB, totalSupply, userLiquidity, false).raw)
userLiquidity && : undefined
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply const liquidityValues: { [Field.CURRENCY_A]?: TokenAmount; [Field.CURRENCY_B]?: TokenAmount } = {
JSBI.greaterThanOrEqual(totalSupply.raw, userLiquidity.raw) [Field.CURRENCY_A]: liquidityValueA,
? new TokenAmount( [Field.CURRENCY_B]: liquidityValueB
tokens[Field.TOKEN_B] as Token,
pair.getLiquidityValue(tokens[Field.TOKEN_B] as Token, totalSupply, userLiquidity, false).raw
)
: undefined
} }
let percentToRemove: Percent = new Percent('0', '100') let percentToRemove: Percent = new Percent('0', '100')
...@@ -109,12 +89,9 @@ export function useDerivedBurnInfo(): { ...@@ -109,12 +89,9 @@ export function useDerivedBurnInfo(): {
else { else {
if (tokens[independentField]) { if (tokens[independentField]) {
const independentAmount = tryParseAmount(typedValue, tokens[independentField]) const independentAmount = tryParseAmount(typedValue, tokens[independentField])
if ( const liquidityValue = liquidityValues[independentField]
independentAmount && if (independentAmount && liquidityValue && !independentAmount.greaterThan(liquidityValue)) {
liquidityValues[independentField] && percentToRemove = new Percent(independentAmount.raw, liquidityValue.raw)
!independentAmount.greaterThan(liquidityValues[independentField] as TokenAmount)
) {
percentToRemove = new Percent(independentAmount.raw, (liquidityValues[independentField] as TokenAmount).raw)
} }
} }
} }
...@@ -122,27 +99,21 @@ export function useDerivedBurnInfo(): { ...@@ -122,27 +99,21 @@ export function useDerivedBurnInfo(): {
const parsedAmounts: { const parsedAmounts: {
[Field.LIQUIDITY_PERCENT]: Percent [Field.LIQUIDITY_PERCENT]: Percent
[Field.LIQUIDITY]?: TokenAmount [Field.LIQUIDITY]?: TokenAmount
[Field.TOKEN_A]?: TokenAmount [Field.CURRENCY_A]?: TokenAmount
[Field.TOKEN_B]?: TokenAmount [Field.CURRENCY_B]?: TokenAmount
} = { } = {
[Field.LIQUIDITY_PERCENT]: percentToRemove, [Field.LIQUIDITY_PERCENT]: percentToRemove,
[Field.LIQUIDITY]: [Field.LIQUIDITY]:
userLiquidity && percentToRemove && percentToRemove.greaterThan('0') userLiquidity && percentToRemove && percentToRemove.greaterThan('0')
? new TokenAmount(userLiquidity.token, percentToRemove.multiply(userLiquidity.raw).quotient) ? new TokenAmount(userLiquidity.token, percentToRemove.multiply(userLiquidity.raw).quotient)
: undefined, : undefined,
[Field.TOKEN_A]: [Field.CURRENCY_A]:
tokens[Field.TOKEN_A] && percentToRemove && percentToRemove.greaterThan('0') && liquidityValues[Field.TOKEN_A] tokenA && percentToRemove && percentToRemove.greaterThan('0') && liquidityValueA
? new TokenAmount( ? new TokenAmount(tokenA, percentToRemove.multiply(liquidityValueA.raw).quotient)
tokens[Field.TOKEN_A] as Token,
percentToRemove.multiply((liquidityValues[Field.TOKEN_A] as TokenAmount).raw).quotient
)
: undefined, : undefined,
[Field.TOKEN_B]: [Field.CURRENCY_B]:
tokens[Field.TOKEN_B] && percentToRemove && percentToRemove.greaterThan('0') && liquidityValues[Field.TOKEN_B] tokenB && percentToRemove && percentToRemove.greaterThan('0') && liquidityValueB
? new TokenAmount( ? new TokenAmount(tokenB, percentToRemove.multiply(liquidityValueB.raw).quotient)
tokens[Field.TOKEN_B] as Token,
percentToRemove.multiply((liquidityValues[Field.TOKEN_B] as TokenAmount).raw).quotient
)
: undefined : undefined
} }
...@@ -151,11 +122,11 @@ export function useDerivedBurnInfo(): { ...@@ -151,11 +122,11 @@ export function useDerivedBurnInfo(): {
error = 'Connect Wallet' error = 'Connect Wallet'
} }
if (!parsedAmounts[Field.LIQUIDITY] || !parsedAmounts[Field.TOKEN_A] || !parsedAmounts[Field.TOKEN_B]) { if (!parsedAmounts[Field.LIQUIDITY] || !parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) {
error = error ?? 'Enter an amount' error = error ?? 'Enter an amount'
} }
return { tokens, pair, route, parsedAmounts, error } return { pair, parsedAmounts, error }
} }
export function useBurnActionHandlers(): { export function useBurnActionHandlers(): {
...@@ -174,13 +145,3 @@ export function useBurnActionHandlers(): { ...@@ -174,13 +145,3 @@ export function useBurnActionHandlers(): {
onUserInput onUserInput
} }
} }
// updates the burn state to use the appropriate tokens, given the route
export function useDefaultsFromURLMatchParams(params: { tokens: string }) {
const { chainId } = useActiveWeb3React()
const dispatch = useDispatch<AppDispatch>()
useEffect(() => {
if (!chainId) return
dispatch(setBurnDefaultsFromURLMatchParams({ chainId, params }))
}, [dispatch, chainId, params])
}
import { createReducer } from '@reduxjs/toolkit' import { createReducer } from '@reduxjs/toolkit'
import { ChainId, WETH } from '@uniswap/sdk' import { Field, typeInput } from './actions'
import { isAddress } from '../../utils'
import { Field, setBurnDefaultsFromURLMatchParams, typeInput } from './actions'
export interface BurnState { export interface BurnState {
readonly independentField: Field readonly independentField: Field
readonly typedValue: string readonly typedValue: string
readonly [Field.TOKEN_A]: {
readonly address: string
}
readonly [Field.TOKEN_B]: {
readonly address: string
}
} }
const initialState: BurnState = { const initialState: BurnState = {
independentField: Field.LIQUIDITY_PERCENT, independentField: Field.LIQUIDITY_PERCENT,
typedValue: '0', typedValue: '0'
[Field.TOKEN_A]: {
address: ''
},
[Field.TOKEN_B]: {
address: ''
}
}
export function parseTokens(chainId: ChainId, tokens: string): string[] {
return (
tokens
// split by '-'
.split('-')
// map to addresses
.map((token): string =>
isAddress(token) ? token : token.toLowerCase() === 'ETH'.toLowerCase() ? WETH[chainId]?.address ?? '' : ''
)
//remove duplicates
.filter((token, i, array) => array.indexOf(token) === i)
// add two empty elements for cases where the array is length 0
.concat(['', ''])
// only consider the first 2 elements
.slice(0, 2)
)
} }
export default createReducer<BurnState>(initialState, builder => export default createReducer<BurnState>(initialState, builder =>
builder builder.addCase(typeInput, (state, { payload: { field, typedValue } }) => {
.addCase(setBurnDefaultsFromURLMatchParams, (state, { payload: { chainId, params } }) => { return {
const tokens = parseTokens(chainId, params?.tokens ?? '') ...state,
return { independentField: field,
independentField: Field.LIQUIDITY_PERCENT, typedValue
typedValue: '0', }
[Field.TOKEN_A]: { })
address: tokens[0]
},
[Field.TOKEN_B]: {
address: tokens[1]
}
}
})
.addCase(typeInput, (state, { payload: { field, typedValue } }) => {
return {
...state,
independentField: field,
typedValue
}
})
) )
import { createAction } from '@reduxjs/toolkit' import { createAction } from '@reduxjs/toolkit'
export enum Field { export enum Field {
TOKEN_A = 'TOKEN_A', CURRENCY_A = 'CURRENCY_A',
TOKEN_B = 'TOKEN_B' CURRENCY_B = 'CURRENCY_B'
} }
export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('typeInputMint') export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('typeInputMint')
......
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: ''
} }
......
...@@ -5,14 +5,14 @@ export enum Field { ...@@ -5,14 +5,14 @@ export enum Field {
OUTPUT = 'OUTPUT' OUTPUT = 'OUTPUT'
} }
export const selectToken = createAction<{ field: Field; address: string }>('selectToken') export const selectCurrency = createAction<{ field: Field; currencyId: string }>('selectCurrency')
export const switchTokens = createAction<void>('switchTokens') export const switchCurrencies = createAction<void>('switchCurrencies')
export const typeInput = createAction<{ field: Field; typedValue: string }>('typeInput') export const typeInput = createAction<{ field: Field; typedValue: string }>('typeInput')
export const replaceSwapState = createAction<{ export const replaceSwapState = createAction<{
field: Field field: Field
typedValue: string typedValue: string
inputTokenAddress?: string inputCurrencyId?: string
outputTokenAddress?: string outputCurrencyId?: string
recipient: string | null recipient: string | null
}>('replaceSwapState') }>('replaceSwapState')
export const setRecipient = createAction<{ recipient: string | null }>('setRecipient') export const setRecipient = createAction<{ recipient: string | null }>('setRecipient')
import { ChainId, WETH } from '@uniswap/sdk'
import { parse } from 'qs' import { parse } from 'qs'
import { Field } from './actions' import { Field } from './actions'
import { queryParametersToSwapState } from './hooks' import { queryParametersToSwapState } from './hooks'
...@@ -11,12 +10,11 @@ describe('hooks', () => { ...@@ -11,12 +10,11 @@ describe('hooks', () => {
parse( parse(
'?inputCurrency=ETH&outputCurrency=0x6b175474e89094c44da98b954eedeac495271d0f&exactAmount=20.5&exactField=outPUT', '?inputCurrency=ETH&outputCurrency=0x6b175474e89094c44da98b954eedeac495271d0f&exactAmount=20.5&exactField=outPUT',
{ parseArrays: false, ignoreQueryPrefix: true } { parseArrays: false, ignoreQueryPrefix: true }
), )
ChainId.MAINNET
) )
).toEqual({ ).toEqual({
[Field.OUTPUT]: { address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' }, [Field.OUTPUT]: { currencyId: '0x6B175474E89094C44Da98b954EedeAC495271d0F' },
[Field.INPUT]: { address: WETH[ChainId.MAINNET].address }, [Field.INPUT]: { currencyId: 'ETH' },
typedValue: '20.5', typedValue: '20.5',
independentField: Field.OUTPUT, independentField: Field.OUTPUT,
recipient: null recipient: null
...@@ -25,13 +23,10 @@ describe('hooks', () => { ...@@ -25,13 +23,10 @@ describe('hooks', () => {
test('does not duplicate eth for invalid output token', () => { test('does not duplicate eth for invalid output token', () => {
expect( expect(
queryParametersToSwapState( queryParametersToSwapState(parse('?outputCurrency=invalid', { parseArrays: false, ignoreQueryPrefix: true }))
parse('?outputCurrency=invalid', { parseArrays: false, ignoreQueryPrefix: true }),
ChainId.MAINNET
)
).toEqual({ ).toEqual({
[Field.INPUT]: { address: '' }, [Field.INPUT]: { currencyId: '' },
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address }, [Field.OUTPUT]: { currencyId: 'ETH' },
typedValue: '', typedValue: '',
independentField: Field.INPUT, independentField: Field.INPUT,
recipient: null recipient: null
...@@ -41,12 +36,11 @@ describe('hooks', () => { ...@@ -41,12 +36,11 @@ describe('hooks', () => {
test('output ETH only', () => { test('output ETH only', () => {
expect( expect(
queryParametersToSwapState( queryParametersToSwapState(
parse('?outputCurrency=eth&exactAmount=20.5', { parseArrays: false, ignoreQueryPrefix: true }), parse('?outputCurrency=eth&exactAmount=20.5', { parseArrays: false, ignoreQueryPrefix: true })
ChainId.MAINNET
) )
).toEqual({ ).toEqual({
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address }, [Field.OUTPUT]: { currencyId: 'ETH' },
[Field.INPUT]: { address: '' }, [Field.INPUT]: { currencyId: '' },
typedValue: '20.5', typedValue: '20.5',
independentField: Field.INPUT, independentField: Field.INPUT,
recipient: null recipient: null
...@@ -56,12 +50,11 @@ describe('hooks', () => { ...@@ -56,12 +50,11 @@ describe('hooks', () => {
test('invalid recipient', () => { test('invalid recipient', () => {
expect( expect(
queryParametersToSwapState( queryParametersToSwapState(
parse('?outputCurrency=eth&exactAmount=20.5&recipient=abc', { parseArrays: false, ignoreQueryPrefix: true }), parse('?outputCurrency=eth&exactAmount=20.5&recipient=abc', { parseArrays: false, ignoreQueryPrefix: true })
ChainId.MAINNET
) )
).toEqual({ ).toEqual({
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address }, [Field.OUTPUT]: { currencyId: 'ETH' },
[Field.INPUT]: { address: '' }, [Field.INPUT]: { currencyId: '' },
typedValue: '20.5', typedValue: '20.5',
independentField: Field.INPUT, independentField: Field.INPUT,
recipient: null recipient: null
...@@ -74,12 +67,11 @@ describe('hooks', () => { ...@@ -74,12 +67,11 @@ describe('hooks', () => {
parse('?outputCurrency=eth&exactAmount=20.5&recipient=0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5', { parse('?outputCurrency=eth&exactAmount=20.5&recipient=0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5', {
parseArrays: false, parseArrays: false,
ignoreQueryPrefix: true ignoreQueryPrefix: true
}), })
ChainId.MAINNET
) )
).toEqual({ ).toEqual({
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address }, [Field.OUTPUT]: { currencyId: 'ETH' },
[Field.INPUT]: { address: '' }, [Field.INPUT]: { currencyId: '' },
typedValue: '20.5', typedValue: '20.5',
independentField: Field.INPUT, independentField: Field.INPUT,
recipient: '0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5' recipient: '0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5'
...@@ -91,12 +83,11 @@ describe('hooks', () => { ...@@ -91,12 +83,11 @@ describe('hooks', () => {
parse('?outputCurrency=eth&exactAmount=20.5&recipient=bob.argent.xyz', { parse('?outputCurrency=eth&exactAmount=20.5&recipient=bob.argent.xyz', {
parseArrays: false, parseArrays: false,
ignoreQueryPrefix: true ignoreQueryPrefix: true
}), })
ChainId.MAINNET
) )
).toEqual({ ).toEqual({
[Field.OUTPUT]: { address: WETH[ChainId.MAINNET].address }, [Field.OUTPUT]: { currencyId: 'ETH' },
[Field.INPUT]: { address: '' }, [Field.INPUT]: { currencyId: '' },
typedValue: '20.5', typedValue: '20.5',
independentField: Field.INPUT, independentField: Field.INPUT,
recipient: 'bob.argent.xyz' recipient: 'bob.argent.xyz'
......
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.
import { ChainId, Currency, CurrencyAmount, ETHER, Token, TokenAmount, WETH } from '@uniswap/sdk'
export function wrappedCurrency(currency: Currency | undefined, chainId: ChainId | undefined): Token | undefined {
return chainId && currency === ETHER ? WETH[chainId] : currency instanceof Token ? currency : undefined
}
export function wrappedCurrencyAmount(
currencyAmount: CurrencyAmount | undefined,
chainId: ChainId | undefined
): TokenAmount | undefined {
const token = currencyAmount && chainId ? wrappedCurrency(currencyAmount.currency, chainId) : undefined
return token && currencyAmount ? new TokenAmount(token, currencyAmount.raw) : undefined
}
export function unwrappedToken(token: Token): Currency {
if (token.equals(WETH[token.chainId])) return ETHER
return token
}
This 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