Commit 344b4340 authored by Moody Salem's avatar Moody Salem Committed by GitHub

improvement(pool): simplify pool flow, remove pool search modal (#941)

* deleting some code first

* strict, some refactoring

* denser common bases

* more add liquidity refactoring

* add liquidity paths working

* show common bases in the token selects

* fix the ability to select duplicate tokens

* useless rename

* try to handle alllll the duplicate token edge cases

* think i got them all lol

* remove common bases header

* Revert "remove common bases header"

This reverts commit 6ac4565d

* fix and add integration tests

* make gap between rows smaller

* get integration tests actually running again

* try another format of the command, upgrade serve

* frozen lockfile on install

* try the cypress github action

* install cypress in ci

* remove redundant ignore-scripts command

* use a specific github commit for the pinata action

* fix a bug in the multicall reducer, improve token list rendering performance

* improve the enter key on the token search modal

* stop using history.push

* fix linting errors

* position card cleanup before updating to match mock
parent eeef306b
...@@ -39,14 +39,14 @@ jobs: ...@@ -39,14 +39,14 @@ jobs:
node-version: '12' node-version: '12'
- name: Install dependencies - name: Install dependencies
run: yarn install --ignore-scripts --frozen-lockfile run: yarn install --frozen-lockfile
- name: Build the IPFS bundle - name: Build the IPFS bundle
run: yarn build run: yarn build
- name: Pin to IPFS - name: Pin to IPFS
id: upload id: upload
uses: anantaramdas/ipfs-pinata-deploy-action@v1.5.2 uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
with: with:
pin-name: Uniswap ${{ needs.bump_version.outputs.new_tag }} pin-name: Uniswap ${{ needs.bump_version.outputs.new_tag }}
path: './build' path: './build'
......
...@@ -26,7 +26,9 @@ jobs: ...@@ -26,7 +26,9 @@ jobs:
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- run: yarn install - run: yarn install --frozen-lockfile
- run: yarn cypress install
- run: yarn build
- run: yarn integration-test - run: yarn integration-test
unit-tests: unit-tests:
...@@ -48,7 +50,7 @@ jobs: ...@@ -48,7 +50,7 @@ jobs:
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- run: yarn install --ignore-scripts --frozen-lockfile - run: yarn install --frozen-lockfile
- run: yarn test - run: yarn test
lint: lint:
...@@ -70,6 +72,6 @@ jobs: ...@@ -70,6 +72,6 @@ jobs:
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- run: yarn install --ignore-scripts --frozen-lockfile - run: yarn install --frozen-lockfile
- run: yarn lint - run: yarn lint
...@@ -16,4 +16,29 @@ describe('Add Liquidity', () => { ...@@ -16,4 +16,29 @@ describe('Add Liquidity', () => {
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL') cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR') cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
}) })
it('single token can be selected', () => {
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
})
it('redirects /add/token-token to add/token/token', () => {
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should(
'contain',
'/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
)
})
it('redirects /add/WETH-token to /add/ETH/token', () => {
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should('contain', '/add/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
})
it('redirects /add/token-WETH to /add/token/ETH', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.url().should('contain', '/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH')
})
}) })
...@@ -10,11 +10,6 @@ describe('Landing Page', () => { ...@@ -10,11 +10,6 @@ describe('Landing Page', () => {
cy.url().should('include', '/swap') cy.url().should('include', '/swap')
}) })
it('allows navigation to send', () => {
cy.get('#send-nav-link').click()
cy.url().should('include', '/send')
})
it('allows navigation to pool', () => { it('allows navigation to pool', () => {
cy.get('#pool-nav-link').click() cy.get('#pool-nav-link').click()
cy.url().should('include', '/pool') cy.url().should('include', '/pool')
......
describe('Pool', () => { describe('Pool', () => {
beforeEach(() => cy.visit('/pool')) beforeEach(() => cy.visit('/pool'))
it('can search for a pool', () => { it('add liquidity links to /add/ETH', () => {
cy.get('#join-pool-button').click() cy.get('#join-pool-button').click()
cy.get('#token-search-input').type('DAI', { delay: 200 }) cy.url().should('contain', '/add/ETH')
}) })
it('can import a pool', () => { it('import pool links to /import', () => {
cy.get('#join-pool-button').click() cy.get('#import-pool-link').click()
cy.get('#import-pool-link').click({ force: true }) // blocked by the grid element in the search box cy.url().should('contain', '/find')
cy.url().should('include', '/find')
}) })
}) })
describe('Send', () => { describe('Send', () => {
beforeEach(() => cy.visit('/send'))
it('should redirect', () => { it('should redirect', () => {
cy.visit('/send')
cy.url().should('include', '/swap') cy.url().should('include', '/swap')
}) })
it('should redirect with url params', () => {
cy.visit('/send?outputCurrency=ETH&recipient=bob.argent.xyz')
cy.url().should('contain', '/swap?outputCurrency=ETH&recipient=bob.argent.xyz')
})
}) })
...@@ -85,10 +85,7 @@ ...@@ -85,10 +85,7 @@
"test": "react-scripts test --env=jsdom", "test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"lint:fix": "eslint 'src/**/*.{js,jsx,ts,tsx}' --fix", "integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'"
"cy:run": "cypress run",
"serve:build": "serve -s build -l 3000",
"integration-test": "yarn build && start-server-and-test 'yarn run serve:build' http://localhost:3000 cy:run"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
......
...@@ -21,6 +21,7 @@ const Base = styled(RebassButton)<{ ...@@ -21,6 +21,7 @@ const Base = styled(RebassButton)<{
outline: none; outline: none;
border: 1px solid transparent; border: 1px solid transparent;
color: white; color: white;
text-decoration: none;
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-wrap: nowrap; flex-wrap: nowrap;
......
...@@ -132,6 +132,7 @@ interface CurrencyInputPanelProps { ...@@ -132,6 +132,7 @@ interface CurrencyInputPanelProps {
showSendWithSwap?: boolean showSendWithSwap?: boolean
otherSelectedTokenAddress?: string | null otherSelectedTokenAddress?: string | null
id: string id: string
showCommonBases?: boolean
} }
export default function CurrencyInputPanel({ export default function CurrencyInputPanel({
...@@ -150,7 +151,8 @@ export default function CurrencyInputPanel({ ...@@ -150,7 +151,8 @@ export default function CurrencyInputPanel({
hideInput = false, hideInput = false,
showSendWithSwap = false, showSendWithSwap = false,
otherSelectedTokenAddress = null, otherSelectedTokenAddress = null,
id id,
showCommonBases
}: CurrencyInputPanelProps) { }: CurrencyInputPanelProps) {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -247,6 +249,7 @@ export default function CurrencyInputPanel({ ...@@ -247,6 +249,7 @@ export default function CurrencyInputPanel({
hiddenToken={token?.address} hiddenToken={token?.address}
otherSelectedTokenAddress={otherSelectedTokenAddress} otherSelectedTokenAddress={otherSelectedTokenAddress}
otherSelectedText={field === Field.INPUT ? 'Selected as output' : 'Selected as input'} otherSelectedText={field === Field.INPUT ? 'Selected as output' : 'Selected as input'}
showCommonBases={showCommonBases}
/> />
)} )}
</InputPanel> </InputPanel>
......
...@@ -12,7 +12,7 @@ const TokenWrapper = styled.div<{ margin: boolean; sizeraw: number }>` ...@@ -12,7 +12,7 @@ const TokenWrapper = styled.div<{ margin: boolean; sizeraw: number }>`
interface DoubleTokenLogoProps { interface DoubleTokenLogoProps {
margin?: boolean margin?: boolean
size?: number size?: number
a0: string a0?: string
a1?: string a1?: string
} }
...@@ -27,7 +27,7 @@ const CoveredLogo = styled(TokenLogo)<{ sizeraw: number }>` ...@@ -27,7 +27,7 @@ const CoveredLogo = styled(TokenLogo)<{ sizeraw: number }>`
export default function DoubleTokenLogo({ a0, a1, size = 16, margin = false }: DoubleTokenLogoProps) { export default function DoubleTokenLogo({ a0, a1, size = 16, margin = false }: DoubleTokenLogoProps) {
return ( return (
<TokenWrapper sizeraw={size} margin={margin}> <TokenWrapper sizeraw={size} margin={margin}>
<HigherLogo address={a0} size={size.toString() + 'px'} /> {a0 && <HigherLogo address={a0} size={size.toString() + 'px'} />}
{a1 && <CoveredLogo address={a1} size={size.toString() + 'px'} sizeraw={size} />} {a1 && <CoveredLogo address={a1} size={size.toString() + 'px'} sizeraw={size} />}
</TokenWrapper> </TokenWrapper>
) )
......
...@@ -14,9 +14,7 @@ import { useActiveWeb3React } from '../../hooks' ...@@ -14,9 +14,7 @@ import { useActiveWeb3React } from '../../hooks'
import { useDarkModeManager } from '../../state/user/hooks' import { useDarkModeManager } from '../../state/user/hooks'
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks' import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink } from '../../theme'
import { YellowCard } from '../Card' import { YellowCard } from '../Card'
import { AutoColumn } from '../Column'
import Settings from '../Settings' import Settings from '../Settings'
import Menu from '../Menu' import Menu from '../Menu'
...@@ -107,26 +105,6 @@ const UniIcon = styled(HistoryLink)<{ to: string }>` ...@@ -107,26 +105,6 @@ const UniIcon = styled(HistoryLink)<{ to: string }>`
} }
` `
const MigrateBanner = styled(AutoColumn)`
width: 100%;
padding: 12px 0;
display: flex;
justify-content: center;
background-color: ${({ theme }) => theme.primary5};
color: ${({ theme }) => theme.primaryText1};
font-weight: 400;
text-align: center;
pointer-events: auto;
a {
color: ${({ theme }) => theme.primaryText1};
}
${({ theme }) => theme.mediaWidth.upToSmall`
padding: 0;
display: none;
`};
`
const HeaderControls = styled.div` const HeaderControls = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
...@@ -153,17 +131,6 @@ export default function Header() { ...@@ -153,17 +131,6 @@ export default function Header() {
return ( return (
<HeaderFrame> <HeaderFrame>
<MigrateBanner>
Uniswap V2 is live! Read the&nbsp;
<ExternalLink href="https://uniswap.org/blog/launch-uniswap-v2/">
<b>blog post ↗</b>
</ExternalLink>
&nbsp;or&nbsp;
<StyledInternalLink to="/migrate/v1">
<b>migrate your liquidity ↗</b>
</StyledInternalLink>
.
</MigrateBanner>
<RowBetween style={{ alignItems: 'flex-start' }} padding="1rem 1rem 0 1rem"> <RowBetween style={{ alignItems: 'flex-start' }} padding="1rem 1rem 0 1rem">
<HeaderElement> <HeaderElement>
<Title> <Title>
......
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { RouteComponentProps, withRouter } from 'react-router-dom' import { Link, RouteComponentProps, withRouter } from 'react-router-dom'
import { Token, TokenAmount, WETH } from '@uniswap/sdk' import { Token, TokenAmount, WETH } from '@uniswap/sdk'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -16,7 +16,7 @@ interface PositionCardProps extends RouteComponentProps<{}> { ...@@ -16,7 +16,7 @@ interface PositionCardProps extends RouteComponentProps<{}> {
V1LiquidityBalance: TokenAmount V1LiquidityBalance: TokenAmount
} }
function V1PositionCard({ token, V1LiquidityBalance, history }: PositionCardProps) { function V1PositionCard({ token, V1LiquidityBalance }: PositionCardProps) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
...@@ -47,21 +47,15 @@ function V1PositionCard({ token, V1LiquidityBalance, history }: PositionCardProp ...@@ -47,21 +47,15 @@ function V1PositionCard({ token, V1LiquidityBalance, history }: PositionCardProp
<AutoColumn gap="8px"> <AutoColumn gap="8px">
<RowBetween marginTop="10px"> <RowBetween marginTop="10px">
<ButtonSecondary <ButtonSecondary width="68%" as={Link} to={`/migrate/v1/${V1LiquidityBalance.token.address}`}>
width="68%"
onClick={() => {
history.push(`/migrate/v1/${V1LiquidityBalance.token.address}`)
}}
>
Migrate Migrate
</ButtonSecondary> </ButtonSecondary>
<ButtonSecondary <ButtonSecondary
style={{ backgroundColor: 'transparent' }} style={{ backgroundColor: 'transparent' }}
width="28%" width="28%"
onClick={() => { as={Link}
history.push(`/remove/v1/${V1LiquidityBalance.token.address}`) to={`/remove/v1/${V1LiquidityBalance.token.address}`}
}}
> >
Remove Remove
</ButtonSecondary> </ButtonSecondary>
......
import React, { useState } from 'react' import React, { useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { darken } from 'polished' import { darken } from 'polished'
import { RouteComponentProps, withRouter } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Percent, Pair, JSBI } from '@uniswap/sdk' import { Percent, Pair, JSBI } from '@uniswap/sdk'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useTotalSupply } from '../../data/TotalSupply' import { useTotalSupply } from '../../data/TotalSupply'
import { currencyId } from '../../pages/AddLiquidity/currencyId'
import { useTokenBalance } from '../../state/wallet/hooks' import { useTokenBalance } from '../../state/wallet/hooks'
import Card, { GreyCard } from '../Card' import Card, { GreyCard } from '../Card'
import TokenLogo from '../TokenLogo' import TokenLogo from '../TokenLogo'
import DoubleLogo from '../DoubleLogo' import DoubleLogo from '../DoubleLogo'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ExternalLink } from '../../theme/components' import { ExternalLink } from '../../theme'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { ChevronDown, ChevronUp } from 'react-feather' import { ChevronDown, ChevronUp } from 'react-feather'
import { ButtonSecondary } from '../Button' import { ButtonSecondary } from '../Button'
...@@ -30,13 +31,12 @@ export const HoverCard = styled(Card)` ...@@ -30,13 +31,12 @@ export const HoverCard = styled(Card)`
} }
` `
interface PositionCardProps extends RouteComponentProps<{}> { interface PositionCardProps {
pair: Pair pair: Pair | undefined | null
minimal?: boolean
border?: string border?: string
} }
function PositionCard({ pair, history, border, minimal = false }: PositionCardProps) { export function MinimalPositionCard({ pair, border }: PositionCardProps) {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const token0 = pair?.token0 const token0 = pair?.token0
...@@ -47,11 +47,6 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr ...@@ -47,11 +47,6 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
const userPoolBalance = useTokenBalance(account, pair?.liquidityToken) const userPoolBalance = useTokenBalance(account, pair?.liquidityToken)
const totalPoolTokens = useTotalSupply(pair?.liquidityToken) const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
: undefined
const [token0Deposited, token1Deposited] = const [token0Deposited, token1Deposited] =
!!pair && !!pair &&
!!totalPoolTokens && !!totalPoolTokens &&
...@@ -64,7 +59,6 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr ...@@ -64,7 +59,6 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
] ]
: [undefined, undefined] : [undefined, undefined]
if (minimal) {
return ( return (
<> <>
{userPoolBalance && ( {userPoolBalance && (
...@@ -125,7 +119,36 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr ...@@ -125,7 +119,36 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
)} )}
</> </>
) )
} else }
export default function FullPositionCard({ pair, border }: PositionCardProps) {
const { account } = useActiveWeb3React()
const token0 = pair?.token0
const token1 = pair?.token1
const [showMore, setShowMore] = useState(false)
const userPoolBalance = useTokenBalance(account, pair?.liquidityToken)
const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
: undefined
const [token0Deposited, token1Deposited] =
!!pair &&
!!totalPoolTokens &&
!!userPoolBalance &&
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? [
pair.getLiquidityValue(token0, totalPoolTokens, userPoolBalance, false),
pair.getLiquidityValue(token1, totalPoolTokens, userPoolBalance, false)
]
: [undefined, undefined]
return ( return (
<HoverCard border={border}> <HoverCard border={border}>
<AutoColumn gap="12px"> <AutoColumn gap="12px">
...@@ -204,20 +227,10 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr ...@@ -204,20 +227,10 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
</ExternalLink> </ExternalLink>
</AutoRow> </AutoRow>
<RowBetween marginTop="10px"> <RowBetween marginTop="10px">
<ButtonSecondary <ButtonSecondary as={Link} to={`/add/${currencyId(token0)}/${currencyId(token1)}`} width="48%">
width="48%"
onClick={() => {
history.push('/add/' + token0?.address + '-' + token1?.address)
}}
>
Add Add
</ButtonSecondary> </ButtonSecondary>
<ButtonSecondary <ButtonSecondary as={Link} width="48%" to={`/remove/${token0?.address}-${token1?.address}`}>
width="48%"
onClick={() => {
history.push('/remove/' + token0?.address + '-' + token1?.address)
}}
>
Remove Remove
</ButtonSecondary> </ButtonSecondary>
</RowBetween> </RowBetween>
...@@ -227,5 +240,3 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr ...@@ -227,5 +240,3 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
</HoverCard> </HoverCard>
) )
} }
export default withRouter(PositionCard)
import React from 'react' import React from 'react'
import { Text } from 'rebass' import { Text } from 'rebass'
import { Token } from '@uniswap/sdk' import { ChainId, Token } from '@uniswap/sdk'
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 TokenLogo from '../TokenLogo'
import { BaseWrapper } from './styleds'
const BaseWrapper = styled.div<{ disable?: boolean }>`
border: 1px solid ${({ theme, disable }) => (disable ? 'transparent' : theme.bg3)};
border-radius: 10px;
display: flex;
padding: 6px;
align-items: center;
:hover {
cursor: ${({ disable }) => !disable && 'pointer'};
background-color: ${({ theme, disable }) => !disable && theme.bg2};
}
background-color: ${({ theme, disable }) => disable && theme.bg3};
opacity: ${({ disable }) => disable && '0.4'};
`
export default function CommonBases({ export default function CommonBases({
chainId, chainId,
onSelect, onSelect,
selectedTokenAddress selectedTokenAddress
}: { }: {
chainId: number chainId: ChainId
selectedTokenAddress: string selectedTokenAddress: string
onSelect: (tokenAddress: string) => void onSelect: (tokenAddress: string) => void
}) { }) {
return ( return (
<AutoColumn gap="md"> <AutoColumn gap="md">
<AutoRow> <AutoRow>
<Text fontWeight={500} fontSize={16}> <Text fontWeight={500} fontSize={14}>
Common Bases Common bases
</Text> </Text>
<QuestionHelper text="These tokens are commonly used in pairs." /> <QuestionHelper text="These tokens are commonly paired with other tokens." />
</AutoRow> </AutoRow>
<AutoRow gap="10px"> <AutoRow gap="4px">
{(SUGGESTED_BASES[chainId] ?? []).map((token: Token) => { {(SUGGESTED_BASES[chainId as ChainId] ?? []).map((token: Token) => {
return ( return (
<BaseWrapper <BaseWrapper
gap="6px"
onClick={() => selectedTokenAddress !== token.address && onSelect(token.address)} onClick={() => selectedTokenAddress !== token.address && onSelect(token.address)}
disable={selectedTokenAddress === token.address} disable={selectedTokenAddress === token.address}
key={token.address} key={token.address}
> >
<TokenLogo address={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, Pair, TokenAmount } from '@uniswap/sdk'
import React from 'react'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'
import { ButtonPrimary } from '../Button'
import DoubleTokenLogo from '../DoubleLogo'
import { RowFixed } from '../Row'
import { MenuItem, ModalInfo } from './styleds'
export default function PairList({
pairs,
focusTokenAddress,
pairBalances,
onSelectPair,
onAddLiquidity = onSelectPair
}: {
pairs: Pair[]
focusTokenAddress?: string
pairBalances: { [pairAddress: string]: TokenAmount }
onSelectPair: (pair: Pair) => void
onAddLiquidity: (pair: Pair) => void
}) {
if (pairs.length === 0) {
return <ModalInfo>No Pools Found</ModalInfo>
}
return (
<FixedSizeList itemSize={56} height={500} itemCount={pairs.length} width="100%" style={{ flex: '1' }}>
{({ index, style }) => {
const pair = pairs[index]
// the focused token is shown first
const tokenA = focusTokenAddress === pair.token1.address ? pair.token1 : pair.token0
const tokenB = tokenA === pair.token0 ? pair.token1 : pair.token0
const pairAddress = pair.liquidityToken.address
const balance = pairBalances[pairAddress]?.toSignificant(6)
const zeroBalance = pairBalances[pairAddress]?.raw && JSBI.equal(pairBalances[pairAddress].raw, JSBI.BigInt(0))
const selectPair = () => onSelectPair(pair)
const addLiquidity = () => onAddLiquidity(pair)
return (
<MenuItem style={style} onClick={selectPair}>
<RowFixed>
<DoubleTokenLogo a0={tokenA.address} a1={tokenB.address} size={24} margin={true} />
<Text fontWeight={500} fontSize={16}>{`${tokenA.symbol}/${tokenB.symbol}`}</Text>
</RowFixed>
<ButtonPrimary padding={'6px 8px'} width={'fit-content'} borderRadius={'12px'} onClick={addLiquidity}>
{balance ? (zeroBalance ? 'Join' : 'Add Liquidity') : 'Join'}
</ButtonPrimary>
</MenuItem>
)
}}
</FixedSizeList>
)
}
import { Pair } from '@uniswap/sdk'
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { isMobile } from 'react-device-detect'
import { useTranslation } from 'react-i18next'
import { RouteComponentProps, withRouter } from 'react-router-dom'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
import Card from '../../components/Card'
import { useActiveWeb3React } from '../../hooks'
import { useAllTokens } from '../../hooks/Tokens'
import { useAllDummyPairs } from '../../state/user/hooks'
import { useTokenBalances } from '../../state/wallet/hooks'
import { CloseIcon, StyledInternalLink } from '../../theme/components'
import { isAddress } from '../../utils'
import Column from '../Column'
import Modal from '../Modal'
import QuestionHelper from '../QuestionHelper'
import { AutoRow, RowBetween } from '../Row'
import { filterPairs } from './filtering'
import PairList from './PairList'
import { pairComparator } from './sorting'
import { PaddedColumn, SearchInput } from './styleds'
interface PairSearchModalProps extends RouteComponentProps {
isOpen?: boolean
onDismiss?: () => void
}
function PairSearchModal({ history, isOpen, onDismiss }: PairSearchModalProps) {
const { t } = useTranslation()
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const [searchQuery, setSearchQuery] = useState<string>('')
const allTokens = useAllTokens()
const allPairs = useAllDummyPairs()
const allPairBalances = useTokenBalances(
account,
allPairs.map(p => p.liquidityToken)
)
// clear the input on open
useEffect(() => {
if (isOpen) setSearchQuery('')
}, [isOpen, setSearchQuery])
// manage focus on modal show
const inputRef = useRef<HTMLInputElement>()
function onInput(event) {
const input = event.target.value
const checksummedInput = isAddress(input)
setSearchQuery(checksummedInput || input)
}
const filteredPairs = useMemo(() => {
return filterPairs(allPairs, searchQuery)
}, [allPairs, searchQuery])
const sortedPairList = useMemo(() => {
const query = searchQuery.toLowerCase()
const queryMatches = (pair: Pair): boolean =>
pair.token0.symbol.toLowerCase() === query || pair.token1.symbol.toLowerCase() === query
return filteredPairs.sort((a, b): number => {
const [aMatches, bMatches] = [queryMatches(a), queryMatches(b)]
if (aMatches && !bMatches) return -1
if (bMatches && !aMatches) return 1
const balanceA = allPairBalances[a.liquidityToken.address]
const balanceB = allPairBalances[b.liquidityToken.address]
return pairComparator(a, b, balanceA, balanceB)
})
}, [searchQuery, filteredPairs, allPairBalances])
const selectPair = useCallback(
(pair: Pair) => {
history.push(`/add/${pair.token0.address}-${pair.token1.address}`)
},
[history]
)
const focusedToken = Object.values(allTokens ?? {}).filter(token => {
return token.symbol.toLowerCase() === searchQuery || searchQuery === token.address
})[0]
return (
<Modal
isOpen={isOpen}
onDismiss={onDismiss}
maxHeight={70}
initialFocusRef={isMobile ? undefined : inputRef}
minHeight={70}
>
<Column style={{ width: '100%' }}>
<PaddedColumn gap="20px">
<RowBetween>
<Text fontWeight={500} fontSize={16}>
Select a pool
<QuestionHelper text="Find a pair by searching for its name below." />
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
<SearchInput
type="text"
id="token-search-input"
placeholder={t('tokenSearchPlaceholder')}
value={searchQuery}
ref={inputRef}
onChange={onInput}
/>
<RowBetween>
<Text fontSize={14} fontWeight={500}>
Pool Name
</Text>
</RowBetween>
</PaddedColumn>
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
<PairList
pairs={sortedPairList}
focusTokenAddress={focusedToken?.address}
onAddLiquidity={selectPair}
onSelectPair={selectPair}
pairBalances={allPairBalances}
/>
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
<Card>
<AutoRow justify={'center'}>
<div>
<Text fontWeight={500}>
{!isMobile && "Don't see a pool? "}
<StyledInternalLink to="/find">{!isMobile ? 'Import it.' : 'Import pool.'}</StyledInternalLink>
</Text>
</div>
</AutoRow>
</Card>
</Column>
</Modal>
)
}
export default withRouter(PairSearchModal)
import { JSBI, Token, TokenAmount } from '@uniswap/sdk' import { JSBI, Token, TokenAmount } from '@uniswap/sdk'
import React, { useContext } from 'react' import React, { CSSProperties, memo, useContext, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FixedSizeList } from 'react-window' import { FixedSizeList } from 'react-window'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -40,20 +40,8 @@ export default function TokenList({ ...@@ -40,20 +40,8 @@ export default function TokenList({
const addToken = useAddUserToken() const addToken = useAddUserToken()
const removeToken = useRemoveUserAddedToken() const removeToken = useRemoveUserAddedToken()
if (tokens.length === 0) { const TokenRow = useMemo(() => {
return <ModalInfo>{t('noToken')}</ModalInfo> return memo(function TokenRow({ index, style }: { index: number; style: CSSProperties }) {
}
return (
<FixedSizeList
width="100%"
height={500}
itemCount={tokens.length}
itemSize={56}
style={{ flex: '1' }}
itemKey={index => tokens[index].address}
>
{({ index, style }) => {
const token = tokens[index] const token = tokens[index]
const { address, symbol } = token const { address, symbol } = token
...@@ -132,7 +120,37 @@ export default function TokenList({ ...@@ -132,7 +120,37 @@ export default function TokenList({
</AutoColumn> </AutoColumn>
</MenuItem> </MenuItem>
) )
}} })
}, [
account,
addToken,
allTokenBalances,
allTokens,
chainId,
onTokenSelect,
otherSelectedText,
otherToken,
removeToken,
selectedToken,
showSendWithSwap,
theme.primary1,
tokens
])
if (tokens.length === 0) {
return <ModalInfo>{t('noToken')}</ModalInfo>
}
return (
<FixedSizeList
width="100%"
height={500}
itemCount={tokens.length}
itemSize={56}
style={{ flex: '1' }}
itemKey={index => tokens[index].address}
>
{TokenRow}
</FixedSizeList> </FixedSizeList>
) )
} }
import { Token } from '@uniswap/sdk' import { Token } from '@uniswap/sdk'
import React, { 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'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -9,7 +9,7 @@ import { useActiveWeb3React } from '../../hooks' ...@@ -9,7 +9,7 @@ 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 { useAllTokenBalancesTreatingWETHasETH, useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
import { CloseIcon, LinkStyledButton } from '../../theme/components' import { CloseIcon, LinkStyledButton } from '../../theme'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import Column from '../Column' import Column from '../Column'
import Modal from '../Modal' import Modal from '../Modal'
...@@ -122,6 +122,20 @@ export default function TokenSearchModal({ ...@@ -122,6 +122,20 @@ export default function TokenSearchModal({
false false
) )
const handleEnter = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && filteredSortedTokens.length > 0) {
if (
filteredSortedTokens[0].symbol.toLowerCase() === searchQuery.trim().toLowerCase() ||
filteredSortedTokens.length === 1
) {
handleTokenSelect(filteredSortedTokens[0].address)
}
}
},
[filteredSortedTokens, handleTokenSelect, searchQuery]
)
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
...@@ -131,7 +145,7 @@ export default function TokenSearchModal({ ...@@ -131,7 +145,7 @@ export default function TokenSearchModal({
minHeight={70} minHeight={70}
> >
<Column style={{ width: '100%' }}> <Column style={{ width: '100%' }}>
<PaddedColumn gap="20px"> <PaddedColumn gap="14px">
<RowBetween> <RowBetween>
<Text fontWeight={500} fontSize={16}> <Text fontWeight={500} fontSize={16}>
Select a token Select a token
...@@ -156,6 +170,7 @@ export default function TokenSearchModal({ ...@@ -156,6 +170,7 @@ export default function TokenSearchModal({
onChange={handleInput} onChange={handleInput}
onFocus={closeTooltip} onFocus={closeTooltip}
onBlur={closeTooltip} onBlur={closeTooltip}
onKeyDown={handleEnter}
/> />
</Tooltip> </Tooltip>
{showCommonBases && ( {showCommonBases && (
......
import styled from 'styled-components' import styled from 'styled-components'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { AutoRow, RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
export const ModalInfo = styled.div` export const ModalInfo = styled.div`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
...@@ -61,21 +61,6 @@ export const MenuItem = styled(RowBetween)` ...@@ -61,21 +61,6 @@ export const MenuItem = styled(RowBetween)`
opacity: ${({ disabled, selected }) => (disabled || selected ? 0.5 : 1)}; opacity: ${({ disabled, selected }) => (disabled || selected ? 0.5 : 1)};
` `
export const BaseWrapper = styled(AutoRow)<{ disable?: boolean }>`
border: 1px solid ${({ theme, disable }) => (disable ? 'transparent' : theme.bg3)};
padding: 0 6px;
border-radius: 10px;
width: 120px;
:hover {
cursor: ${({ disable }) => !disable && 'pointer'};
background-color: ${({ theme, disable }) => !disable && theme.bg2};
}
background-color: ${({ theme, disable }) => disable && theme.bg3};
opacity: ${({ disable }) => disable && '0.4'};
`
export const SearchInput = styled(Input)` export const SearchInput = styled(Input)`
transition: border 100ms; transition: border 100ms;
:focus { :focus {
......
import { Fraction, Percent, Token, TokenAmount } from '@uniswap/sdk'
import React from 'react'
import { Text } from 'rebass'
import { ButtonPrimary } from '../../components/Button'
import { RowBetween, RowFixed } from '../../components/Row'
import TokenLogo from '../../components/TokenLogo'
import { Field } from '../../state/mint/actions'
import { TYPE } from '../../theme'
export function ConfirmAddModalBottom({
noLiquidity,
price,
tokens,
parsedAmounts,
poolTokenPercentage,
onAdd
}: {
noLiquidity?: boolean
price?: Fraction
tokens: { [field in Field]?: Token }
parsedAmounts: { [field in Field]?: TokenAmount }
poolTokenPercentage?: Percent
onAdd: () => void
}) {
return (
<>
<RowBetween>
<TYPE.body>{tokens[Field.TOKEN_A]?.symbol} Deposited</TYPE.body>
<RowFixed>
<TokenLogo address={tokens[Field.TOKEN_A]?.address} style={{ marginRight: '8px' }} />
<TYPE.body>{parsedAmounts[Field.TOKEN_A]?.toSignificant(6)}</TYPE.body>
</RowFixed>
</RowBetween>
<RowBetween>
<TYPE.body>{tokens[Field.TOKEN_B]?.symbol} Deposited</TYPE.body>
<RowFixed>
<TokenLogo address={tokens[Field.TOKEN_B]?.address} style={{ marginRight: '8px' }} />
<TYPE.body>{parsedAmounts[Field.TOKEN_B]?.toSignificant(6)}</TYPE.body>
</RowFixed>
</RowBetween>
<RowBetween>
<TYPE.body>Rates</TYPE.body>
<TYPE.body>
{`1 ${tokens[Field.TOKEN_A]?.symbol} = ${price?.toSignificant(4)} ${tokens[Field.TOKEN_B]?.symbol}`}
</TYPE.body>
</RowBetween>
<RowBetween style={{ justifyContent: 'flex-end' }}>
<TYPE.body>
{`1 ${tokens[Field.TOKEN_B]?.symbol} = ${price?.invert().toSignificant(4)} ${tokens[Field.TOKEN_A]?.symbol}`}
</TYPE.body>
</RowBetween>
<RowBetween>
<TYPE.body>Share of Pool:</TYPE.body>
<TYPE.body>{noLiquidity ? '100' : poolTokenPercentage?.toSignificant(4)}%</TYPE.body>
</RowBetween>
<ButtonPrimary style={{ margin: '20px 0 0 0' }} onClick={onAdd}>
<Text fontWeight={500} fontSize={20}>
{noLiquidity ? 'Create Pool & Supply' : 'Confirm Supply'}
</Text>
</ButtonPrimary>
</>
)
}
import { Fraction, Percent, Token } from '@uniswap/sdk'
import React, { useContext } from 'react'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
import { AutoColumn } from '../../components/Column'
import { AutoRow } from '../../components/Row'
import { ONE_BIPS } from '../../constants'
import { Field } from '../../state/mint/actions'
import { TYPE } from '../../theme'
export const PoolPriceBar = ({
tokens,
noLiquidity,
poolTokenPercentage,
price
}: {
tokens: { [field in Field]?: Token }
noLiquidity?: boolean
poolTokenPercentage?: Percent
price?: Fraction
}) => {
const theme = useContext(ThemeContext)
return (
<AutoColumn gap="md">
<AutoRow justify="space-around" gap="4px">
<AutoColumn justify="center">
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
{tokens[Field.TOKEN_B]?.symbol} per {tokens[Field.TOKEN_A]?.symbol}
</Text>
</AutoColumn>
<AutoColumn justify="center">
<TYPE.black>{price?.invert().toSignificant(6) ?? '0'}</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
{tokens[Field.TOKEN_A]?.symbol} per {tokens[Field.TOKEN_B]?.symbol}
</Text>
</AutoColumn>
<AutoColumn justify="center">
<TYPE.black>
{noLiquidity && price
? '100'
: (poolTokenPercentage?.lessThan(ONE_BIPS) ? '<0.01' : poolTokenPercentage?.toFixed(2)) ?? '0'}
%
</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
Share of Pool
</Text>
</AutoColumn>
</AutoRow>
</AutoColumn>
)
}
import { Token, ChainId, WETH } from '@uniswap/sdk'
export function currencyId(...args: [ChainId | undefined, string] | [Token]): string {
if (args.length === 2) {
const [chainId, tokenAddress] = args
return chainId && tokenAddress === WETH[chainId].address ? 'ETH' : tokenAddress
} else if (args.length === 1) {
const [token] = args
return currencyId(token.chainId, token.address)
} else {
throw new Error('unexpected call signature')
}
}
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { TokenAmount, WETH } from '@uniswap/sdk' import { TransactionResponse } from '@ethersproject/providers'
import React, { useContext, useState } from 'react' import { ChainId, Token, TokenAmount, WETH } from '@uniswap/sdk'
import React, { useCallback, useContext, useState } from 'react'
import { Plus } from 'react-feather' import { Plus } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router-dom' import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { ButtonLight, ButtonPrimary, ButtonError } from '../../components/Button' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import { BlueCard, GreyCard, LightCard } from '../../components/Card' import { BlueCard, GreyCard, LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import ConfirmationModal from '../../components/ConfirmationModal' import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import DoubleLogo from '../../components/DoubleLogo' import DoubleLogo from '../../components/DoubleLogo'
import PositionCard from '../../components/PositionCard' import { AddRemoveTabs } from '../../components/NavigationTabs'
import Row, { AutoRow, RowBetween, RowFixed, RowFlat } from '../../components/Row' import { MinimalPositionCard } from '../../components/PositionCard'
import Row, { RowBetween, RowFlat } from '../../components/Row'
import TokenLogo from '../../components/TokenLogo'
import { ROUTER_ADDRESS, MIN_ETH, ONE_BIPS } from '../../constants' import { ROUTER_ADDRESS } from '../../constants'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useToken } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/mint/actions'
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks'
import { useTransactionAdder } from '../../state/transactions/hooks' import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserDeadline, useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils' import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import AppBody from '../AppBody' import AppBody from '../AppBody'
import { Dots, Wrapper } from '../Pool/styleds' import { Dots, Wrapper } from '../Pool/styleds'
import { import { ConfirmAddModalBottom } from './ConfirmAddModalBottom'
useDefaultsFromURLMatchParams, import { currencyId } from './currencyId'
useMintState, import { PoolPriceBar } from './PoolPriceBar'
useDerivedMintInfo,
useMintActionHandlers
} from '../../state/mint/hooks'
import { Field } from '../../state/mint/actions'
import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallback'
import { useWalletModalToggle } from '../../state/application/hooks'
import { useUserSlippageTolerance, useUserDeadline, useIsExpertMode } from '../../state/user/hooks'
import { AddRemoveTabs } from '../../components/NavigationTabs'
export default function AddLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) { function useTokenByCurrencyId(chainId: ChainId | undefined, currencyId: string | undefined): Token | undefined {
useDefaultsFromURLMatchParams(params) const isETH = currencyId?.toUpperCase() === 'ETH'
const token = useToken(isETH ? undefined : currencyId)
return isETH && chainId ? WETH[chainId] : token ?? undefined
}
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB }
},
history
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, library } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const tokenA = useTokenByCurrencyId(chainId, currencyIdA)
const tokenB = useTokenByCurrencyId(chainId, currencyIdB)
// toggle wallet when disconnected // toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle() const toggleWalletModal = useWalletModalToggle()
...@@ -61,8 +72,21 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps< ...@@ -61,8 +72,21 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
liquidityMinted, liquidityMinted,
poolTokenPercentage, poolTokenPercentage,
error error
} = useDerivedMintInfo() } = useDerivedMintInfo(tokenA ?? undefined, tokenB ?? undefined)
const { onUserInput } = useMintActionHandlers() const { onUserInput } = useMintActionHandlers(noLiquidity)
const handleTokenAInput = useCallback(
(field: string, value: string) => {
return onUserInput(Field.TOKEN_A, value)
},
[onUserInput]
)
const handleTokenBInput = useCallback(
(field: string, value: string) => {
return onUserInput(Field.TOKEN_B, value)
},
[onUserInput]
)
const isValid = !error const isValid = !error
...@@ -85,17 +109,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps< ...@@ -85,17 +109,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.TOKEN_A, Field.TOKEN_B].reduce((accumulator, field) => { const maxAmounts: { [field in Field]?: TokenAmount } = [Field.TOKEN_A, Field.TOKEN_B].reduce((accumulator, field) => {
return { return {
...accumulator, ...accumulator,
[field]: [field]: maxAmountSpend(tokenBalances[field])
!!tokenBalances[field] &&
!!tokens[field] &&
!!WETH[chainId] &&
tokenBalances[field].greaterThan(
new TokenAmount(tokens[field], tokens[field].equals(WETH[chainId]) ? MIN_ETH : '0')
)
? tokens[field].equals(WETH[chainId])
? tokenBalances[field].subtract(new TokenAmount(WETH[chainId], MIN_ETH))
: tokenBalances[field]
: undefined
} }
}, {}) }, {})
...@@ -103,7 +117,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps< ...@@ -103,7 +117,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
(accumulator, field) => { (accumulator, field) => {
return { return {
...accumulator, ...accumulator,
[field]: maxAmounts[field] && parsedAmounts[field] ? maxAmounts[field].equalTo(parsedAmounts[field]) : undefined [field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0')
} }
}, },
{} {}
...@@ -114,38 +128,48 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps< ...@@ -114,38 +128,48 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.TOKEN_B], ROUTER_ADDRESS) const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.TOKEN_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
async function onAdd() { async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account) const router = getRouterContract(chainId, library, account)
const { [Field.TOKEN_A]: parsedAmountA, [Field.TOKEN_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !tokenA || !tokenB) {
return
}
const amountsMin = { const amountsMin = {
[Field.TOKEN_A]: calculateSlippageAmount(parsedAmounts[Field.TOKEN_A], noLiquidity ? 0 : allowedSlippage)[0], [Field.TOKEN_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.TOKEN_B]: calculateSlippageAmount(parsedAmounts[Field.TOKEN_B], noLiquidity ? 0 : allowedSlippage)[0] [Field.TOKEN_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0]
} }
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate, method: Function, args: Array<string | string[] | number>, value: BigNumber | null let estimate,
if (tokens[Field.TOKEN_A].equals(WETH[chainId]) || tokens[Field.TOKEN_B].equals(WETH[chainId])) { method: (...args: any) => Promise<TransactionResponse>,
const tokenBIsETH = tokens[Field.TOKEN_B].equals(WETH[chainId]) args: Array<string | string[] | number>,
value: BigNumber | null
if (tokenA.equals(WETH[chainId]) || tokenB.equals(WETH[chainId])) {
const tokenBIsETH = tokenB.equals(WETH[chainId])
estimate = router.estimateGas.addLiquidityETH estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH method = router.addLiquidityETH
args = [ args = [
tokens[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].address, // token (tokenBIsETH ? tokenA : tokenB).address, // token
parsedAmounts[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].raw.toString(), // token desired (tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].toString(), // token min amountsMin[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].toString(), // token min
amountsMin[tokenBIsETH ? Field.TOKEN_B : Field.TOKEN_A].toString(), // eth min amountsMin[tokenBIsETH ? Field.TOKEN_B : Field.TOKEN_A].toString(), // eth min
account, account,
deadlineFromNow deadlineFromNow
] ]
value = BigNumber.from(parsedAmounts[tokenBIsETH ? Field.TOKEN_B : Field.TOKEN_A].raw.toString()) value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
} else { } else {
estimate = router.estimateGas.addLiquidity estimate = router.estimateGas.addLiquidity
method = router.addLiquidity method = router.addLiquidity
args = [ args = [
tokens[Field.TOKEN_A].address, tokenA.address,
tokens[Field.TOKEN_B].address, tokenB.address,
parsedAmounts[Field.TOKEN_A].raw.toString(), parsedAmountA.raw.toString(),
parsedAmounts[Field.TOKEN_B].raw.toString(), parsedAmountB.raw.toString(),
amountsMin[Field.TOKEN_A].toString(), amountsMin[Field.TOKEN_A].toString(),
amountsMin[Field.TOKEN_B].toString(), amountsMin[Field.TOKEN_B].toString(),
account, account,
...@@ -228,76 +252,14 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps< ...@@ -228,76 +252,14 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
const modalBottom = () => { const modalBottom = () => {
return ( return (
<> <ConfirmAddModalBottom
<RowBetween> price={price}
<TYPE.body>{tokens[Field.TOKEN_A]?.symbol} Deposited</TYPE.body> tokens={tokens}
<RowFixed> parsedAmounts={parsedAmounts}
<TokenLogo address={tokens[Field.TOKEN_A]?.address} style={{ marginRight: '8px' }} /> noLiquidity={noLiquidity}
<TYPE.body>{parsedAmounts[Field.TOKEN_A]?.toSignificant(6)}</TYPE.body> onAdd={onAdd}
</RowFixed> poolTokenPercentage={poolTokenPercentage}
</RowBetween> />
<RowBetween>
<TYPE.body>{tokens[Field.TOKEN_B]?.symbol} Deposited</TYPE.body>
<RowFixed>
<TokenLogo address={tokens[Field.TOKEN_B]?.address} style={{ marginRight: '8px' }} />
<TYPE.body>{parsedAmounts[Field.TOKEN_B]?.toSignificant(6)}</TYPE.body>
</RowFixed>
</RowBetween>
<RowBetween>
<TYPE.body>Rates</TYPE.body>
<TYPE.body>
{`1 ${tokens[Field.TOKEN_A]?.symbol} = ${price?.toSignificant(4)} ${tokens[Field.TOKEN_B]?.symbol}`}
</TYPE.body>
</RowBetween>
<RowBetween style={{ justifyContent: 'flex-end' }}>
<TYPE.body>
{`1 ${tokens[Field.TOKEN_B]?.symbol} = ${price?.invert().toSignificant(4)} ${
tokens[Field.TOKEN_A]?.symbol
}`}
</TYPE.body>
</RowBetween>
<RowBetween>
<TYPE.body>Share of Pool:</TYPE.body>
<TYPE.body>{noLiquidity ? '100' : poolTokenPercentage?.toSignificant(4)}%</TYPE.body>
</RowBetween>
<ButtonPrimary style={{ margin: '20px 0 0 0' }} onClick={onAdd}>
<Text fontWeight={500} fontSize={20}>
{noLiquidity ? 'Create Pool & Supply' : 'Confirm Supply'}
</Text>
</ButtonPrimary>
</>
)
}
const PriceBar = () => {
return (
<AutoColumn gap="md">
<AutoRow justify="space-around" gap="4px">
<AutoColumn justify="center">
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
{tokens[Field.TOKEN_B]?.symbol} per {tokens[Field.TOKEN_A]?.symbol}
</Text>
</AutoColumn>
<AutoColumn justify="center">
<TYPE.black>{price?.invert().toSignificant(6) ?? '0'}</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
{tokens[Field.TOKEN_A]?.symbol} per {tokens[Field.TOKEN_B]?.symbol}
</Text>
</AutoColumn>
<AutoColumn justify="center">
<TYPE.black>
{noLiquidity && price
? '100'
: (poolTokenPercentage?.lessThan(ONE_BIPS) ? '<0.01' : poolTokenPercentage?.toFixed(2)) ?? '0'}
%
</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
Share of Pool
</Text>
</AutoColumn>
</AutoRow>
</AutoColumn>
) )
} }
...@@ -305,6 +267,35 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps< ...@@ -305,6 +267,35 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
tokens[Field.TOKEN_A]?.symbol tokens[Field.TOKEN_A]?.symbol
} and ${parsedAmounts[Field.TOKEN_B]?.toSignificant(6)} ${tokens[Field.TOKEN_B]?.symbol}` } and ${parsedAmounts[Field.TOKEN_B]?.toSignificant(6)} ${tokens[Field.TOKEN_B]?.symbol}`
const handleTokenASelect = useCallback(
(tokenAddress: string) => {
const [tokenAId, tokenBId] = [
currencyId(chainId, tokenAddress),
tokenB ? currencyId(chainId, tokenB.address) : undefined
]
if (tokenAId === tokenBId) {
history.push(`/add/${tokenAId}/${tokenA ? currencyId(chainId, tokenA.address) : ''}`)
} else {
history.push(`/add/${tokenAId}/${tokenBId}`)
}
},
[chainId, tokenB, history, tokenA]
)
const handleTokenBSelect = useCallback(
(tokenAddress: string) => {
const [tokenAId, tokenBId] = [
tokenA ? currencyId(chainId, tokenA.address) : undefined,
currencyId(chainId, tokenAddress)
]
if (tokenAId === tokenBId) {
history.push(`/add/${tokenB ? currencyId(chainId, tokenB.address) : ''}/${tokenAId}`)
} else {
history.push(`/add/${currencyIdA ? currencyIdA : 'ETH'}/${currencyId(chainId, tokenAddress)}`)
}
},
[tokenA, chainId, history, tokenB, currencyIdA]
)
return ( return (
<> <>
<AppBody> <AppBody>
...@@ -346,34 +337,35 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps< ...@@ -346,34 +337,35 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
</ColumnCenter> </ColumnCenter>
)} )}
<CurrencyInputPanel <CurrencyInputPanel
disableTokenSelect={true}
field={Field.TOKEN_A} field={Field.TOKEN_A}
value={formattedAmounts[Field.TOKEN_A]} value={formattedAmounts[Field.TOKEN_A]}
onUserInput={onUserInput} onUserInput={handleTokenAInput}
onMax={() => { onMax={() => {
maxAmounts[Field.TOKEN_A] && onUserInput(Field.TOKEN_A, maxAmounts[Field.TOKEN_A].toExact()) onUserInput(Field.TOKEN_A, maxAmounts[Field.TOKEN_A]?.toExact() ?? '')
}} }}
onTokenSelection={handleTokenASelect}
showMaxButton={!atMaxAmounts[Field.TOKEN_A]} showMaxButton={!atMaxAmounts[Field.TOKEN_A]}
token={tokens[Field.TOKEN_A]} token={tokens[Field.TOKEN_A]}
pair={pair} pair={pair}
label="Input"
id="add-liquidity-input-tokena" id="add-liquidity-input-tokena"
showCommonBases
/> />
<ColumnCenter> <ColumnCenter>
<Plus size="16" color={theme.text2} /> <Plus size="16" color={theme.text2} />
</ColumnCenter> </ColumnCenter>
<CurrencyInputPanel <CurrencyInputPanel
disableTokenSelect={true}
field={Field.TOKEN_B} field={Field.TOKEN_B}
value={formattedAmounts[Field.TOKEN_B]} value={formattedAmounts[Field.TOKEN_B]}
onUserInput={onUserInput} onUserInput={handleTokenBInput}
onTokenSelection={handleTokenBSelect}
onMax={() => { onMax={() => {
maxAmounts[Field.TOKEN_B] && onUserInput(Field.TOKEN_B, maxAmounts[Field.TOKEN_B].toExact()) onUserInput(Field.TOKEN_B, maxAmounts[Field.TOKEN_B]?.toExact() ?? '')
}} }}
showMaxButton={!atMaxAmounts[Field.TOKEN_B]} showMaxButton={!atMaxAmounts[Field.TOKEN_B]}
token={tokens[Field.TOKEN_B]} token={tokens[Field.TOKEN_B]}
pair={pair} pair={pair}
id="add-liquidity-input-tokenb" id="add-liquidity-input-tokenb"
showCommonBases
/> />
{tokens[Field.TOKEN_A] && tokens[Field.TOKEN_B] && ( {tokens[Field.TOKEN_A] && tokens[Field.TOKEN_B] && (
<> <>
...@@ -384,7 +376,12 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps< ...@@ -384,7 +376,12 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
</TYPE.subHeader> </TYPE.subHeader>
</RowBetween>{' '} </RowBetween>{' '}
<LightCard padding="1rem" borderRadius={'20px'}> <LightCard padding="1rem" borderRadius={'20px'}>
<PriceBar /> <PoolPriceBar
tokens={tokens}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</LightCard> </LightCard>
</GreyCard> </GreyCard>
</> </>
...@@ -447,7 +444,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps< ...@@ -447,7 +444,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
{pair && !noLiquidity ? ( {pair && !noLiquidity ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}> <AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<PositionCard pair={pair} minimal={true} /> <MinimalPositionCard pair={pair} />
</AutoColumn> </AutoColumn>
) : null} ) : null}
</> </>
......
import { WETH } from '@uniswap/sdk'
import React from 'react'
import { Redirect, RouteComponentProps } from 'react-router-dom'
import AddLiquidity from './index'
export function RedirectToAddLiquidity() {
return <Redirect to="/add/" />
}
function convertToCurrencyIds(address: string): string {
if (Object.values(WETH).some(weth => weth.address === address)) {
return 'ETH'
}
return address
}
const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/
export function RedirectOldAddLiquidityPathStructure(props: RouteComponentProps<{ currencyIdA: string }>) {
const {
match: {
params: { currencyIdA }
}
} = props
const match = currencyIdA.match(OLD_PATH_STRUCTURE)
if (match?.length) {
return <Redirect to={`/add/${convertToCurrencyIds(match[1])}/${convertToCurrencyIds(match[2])}`} />
}
return <AddLiquidity {...props} />
}
export function RedirectDuplicateTokenIds(props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const {
match: {
params: { currencyIdA, currencyIdB }
}
} = props
if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
return <Redirect to={`/add/${currencyIdA}`} />
}
return <AddLiquidity {...props} />
}
{
"extends": "../../../tsconfig.strict.json",
"include": [
"**/*",
"../../../node_modules/eslint-plugin-react/lib/types.d.ts"
]
}
\ No newline at end of file
...@@ -7,7 +7,11 @@ import Popups from '../components/Popups' ...@@ -7,7 +7,11 @@ import Popups from '../components/Popups'
import Web3ReactManager from '../components/Web3ReactManager' import Web3ReactManager from '../components/Web3ReactManager'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader' import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import AddLiquidity from './AddLiquidity' import AddLiquidity from './AddLiquidity'
import CreatePool from './CreatePool' import {
RedirectDuplicateTokenIds,
RedirectOldAddLiquidityPathStructure,
RedirectToAddLiquidity
} from './AddLiquidity/redirects'
import MigrateV1 from './MigrateV1' import MigrateV1 from './MigrateV1'
import MigrateV1Exchange from './MigrateV1/MigrateV1Exchange' import MigrateV1Exchange from './MigrateV1/MigrateV1Exchange'
import RemoveV1Exchange from './MigrateV1/RemoveV1Exchange' import RemoveV1Exchange from './MigrateV1/RemoveV1Exchange'
...@@ -71,8 +75,10 @@ export default function App() { ...@@ -71,8 +75,10 @@ export default function App() {
<Route exact strict path="/send" component={RedirectPathToSwapOnly} /> <Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/find" component={PoolFinder} /> <Route exact strict path="/find" component={PoolFinder} />
<Route exact strict path="/pool" component={Pool} /> <Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/create" component={CreatePool} /> <Route exact strict path="/create" component={RedirectToAddLiquidity} />
<Route exact strict path="/add/:tokens" component={AddLiquidity} /> <Route exact path="/add" component={AddLiquidity} />
<Route exact path="/add/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} />
<Route exact path="/add/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} />
<Route exact strict path="/remove/:tokens" component={RemoveLiquidity} /> <Route exact strict path="/remove/:tokens" component={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} />
......
import React, { useState, useEffect } from 'react'
import { RouteComponentProps, Redirect } from 'react-router-dom'
import { Token, WETH } from '@uniswap/sdk'
import { CreatePoolTabs } from '../../components/NavigationTabs'
import AppBody from '../AppBody'
import Row, { AutoRow } from '../../components/Row'
import TokenLogo from '../../components/TokenLogo'
import TokenSearchModal from '../../components/SearchModal/TokenSearchModal'
import { Text } from 'rebass'
import { Plus } from 'react-feather'
import { TYPE, StyledInternalLink } from '../../theme'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonPrimary, ButtonDropdown, ButtonDropdownLight } from '../../components/Button'
import { useToken } from '../../hooks/Tokens'
import { useActiveWeb3React } from '../../hooks'
import { usePair } from '../../data/Reserves'
enum Fields {
TOKEN0 = 0,
TOKEN1 = 1
}
enum STEP {
SELECT_TOKENS = 'SELECT_TOKENS', // choose input and output tokens
READY_TO_CREATE = 'READY_TO_CREATE', // enable 'create' button
SHOW_CREATE_PAGE = 'SHOW_CREATE_PAGE' // show create page
}
export default function CreatePool({ location }: RouteComponentProps) {
const { chainId } = useActiveWeb3React()
const [showSearch, setShowSearch] = useState<boolean>(false)
const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
const [token0Address, setToken0Address] = useState<string>(WETH[chainId].address)
const [token1Address, setToken1Address] = useState<string>()
const token0: Token = useToken(token0Address)
const token1: Token = useToken(token1Address)
const [step, setStep] = useState<string>(STEP.SELECT_TOKENS)
const pair = usePair(token0, token1)
// if both tokens selected but pair doesnt exist, enable button to create pair
useEffect(() => {
if (token0Address && token1Address && pair === null) {
setStep(STEP.READY_TO_CREATE)
}
}, [pair, token0Address, token1Address])
// if theyve clicked create, show add liquidity page
if (step === STEP.SHOW_CREATE_PAGE) {
return <Redirect to={{ ...location, pathname: `/add/${token0Address}-${token1Address}` }} push={true} />
}
return (
<AppBody>
<CreatePoolTabs />
<AutoColumn gap="20px">
<AutoColumn gap="24px">
{!token0Address ? (
<ButtonDropdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Text fontSize={20}>Select first token</Text>
</ButtonDropdown>
) : (
<ButtonDropdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Row align="flex-end">
<TokenLogo address={token0Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token0?.symbol}{' '}
</Text>
<TYPE.darkGray fontWeight={500} fontSize={16} marginLeft={'8px'}>
{token0?.address === WETH[chainId]?.address && '(default)'}
</TYPE.darkGray>
</Row>
</ButtonDropdownLight>
)}
<ColumnCenter>
<Plus size="16" color="#888D9B" />
</ColumnCenter>
{!token1Address ? (
<ButtonDropdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
disabled={step !== STEP.SELECT_TOKENS}
>
<Text fontSize={20}>Select second token</Text>
</ButtonDropdown>
) : (
<ButtonDropdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
>
<Row>
<TokenLogo address={token1Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token1?.symbol}
</Text>
</Row>
</ButtonDropdownLight>
)}
{pair ? ( // pair already exists - prompt to add liquidity to existing pool
<AutoRow padding="10px" justify="center">
<TYPE.body textAlign="center">
Pool already exists!{' '}
<StyledInternalLink to={`/add/${token0Address}-${token1Address}`}>Join the pool.</StyledInternalLink>
</TYPE.body>
</AutoRow>
) : (
<ButtonPrimary disabled={step !== STEP.READY_TO_CREATE} onClick={() => setStep(STEP.SHOW_CREATE_PAGE)}>
<Text fontWeight={500} fontSize={20}>
Create Pool
</Text>
</ButtonPrimary>
)}
</AutoColumn>
<TokenSearchModal
isOpen={showSearch}
onTokenSelect={address => {
activeField === Fields.TOKEN0 ? setToken0Address(address) : setToken1Address(address)
}}
onDismiss={() => {
setShowSearch(false)
}}
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address}
showCommonBases={activeField === Fields.TOKEN0}
/>
</AutoColumn>
</AppBody>
)
}
import { TransactionResponse } from '@ethersproject/abstract-provider' import { TransactionResponse } from '@ethersproject/abstract-provider'
import { ChainId, Fraction, JSBI, Percent, Token, TokenAmount, WETH } from '@uniswap/sdk' import { ChainId, Fraction, JSBI, Percent, Token, TokenAmount, WETH } from '@uniswap/sdk'
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useMemo, useState } from 'react'
import { ArrowLeft } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { Redirect, RouteComponentProps } from 'react-router' import { Redirect, RouteComponentProps } from 'react-router'
import { ButtonConfirmed } from '../../components/Button' import { ButtonConfirmed } from '../../components/Button'
...@@ -21,7 +20,7 @@ import { useV1ExchangeContract, useV2MigratorContract } from '../../hooks/useCon ...@@ -21,7 +20,7 @@ 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 } from '../../theme' import { TYPE, ExternalLink, BackArrow } from '../../theme'
import { isAddress, getEtherscanLink } from '../../utils' import { isAddress, getEtherscanLink } from '../../utils'
import { BodyWrapper } from '../AppBody' import { BodyWrapper } from '../AppBody'
import { EmptyState } from './EmptyState' import { EmptyState } from './EmptyState'
...@@ -344,10 +343,6 @@ export default function MigrateV1Exchange({ ...@@ -344,10 +343,6 @@ export default function MigrateV1Exchange({
) )
const userLiquidityBalance = useTokenBalance(account, liquidityToken) const userLiquidityBalance = useTokenBalance(account, liquidityToken)
const handleBack = useCallback(() => {
history.push('/migrate/v1')
}, [history])
// redirect for invalid url params // redirect for invalid url params
if (!validatedAddress || tokenAddress === AddressZero) { if (!validatedAddress || tokenAddress === AddressZero) {
console.error('Invalid address in path', address) console.error('Invalid address in path', address)
...@@ -358,9 +353,7 @@ export default function MigrateV1Exchange({ ...@@ -358,9 +353,7 @@ export default function MigrateV1Exchange({
<BodyWrapper style={{ padding: 24 }}> <BodyWrapper style={{ padding: 24 }}>
<AutoColumn gap="16px"> <AutoColumn gap="16px">
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px"> <AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<div style={{ cursor: 'pointer' }}> <BackArrow to="/migrate/v1" />
<ArrowLeft onClick={handleBack} />
</div>
<TYPE.mediumHeader>Migrate V1 Liquidity</TYPE.mediumHeader> <TYPE.mediumHeader>Migrate V1 Liquidity</TYPE.mediumHeader>
<div> <div>
<QuestionHelper text="Migrate your liquidity tokens from Uniswap V1 to Uniswap V2." /> <QuestionHelper text="Migrate your liquidity tokens from Uniswap V1 to Uniswap V2." />
......
import { TransactionResponse } from '@ethersproject/abstract-provider' import { TransactionResponse } from '@ethersproject/abstract-provider'
import { JSBI, Token, TokenAmount, WETH, Fraction, Percent } from '@uniswap/sdk' import { JSBI, Token, TokenAmount, WETH, Fraction, Percent } from '@uniswap/sdk'
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useMemo, useState } from 'react'
import { ArrowLeft } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { Redirect, RouteComponentProps } from 'react-router' import { Redirect, RouteComponentProps } from 'react-router'
import { ButtonConfirmed } from '../../components/Button' import { ButtonConfirmed } from '../../components/Button'
...@@ -16,7 +15,7 @@ import { useV1ExchangeContract } from '../../hooks/useContract' ...@@ -16,7 +15,7 @@ import { useV1ExchangeContract } from '../../hooks/useContract'
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 { useTokenBalance, useETHBalances } from '../../state/wallet/hooks' import { useTokenBalance, useETHBalances } from '../../state/wallet/hooks'
import { TYPE } from '../../theme' import { BackArrow, TYPE } from '../../theme'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import { BodyWrapper } from '../AppBody' import { BodyWrapper } from '../AppBody'
import { EmptyState } from './EmptyState' import { EmptyState } from './EmptyState'
...@@ -128,7 +127,6 @@ function V1PairRemoval({ ...@@ -128,7 +127,6 @@ function V1PairRemoval({
} }
export default function RemoveV1Exchange({ export default function RemoveV1Exchange({
history,
match: { match: {
params: { address } params: { address }
} }
...@@ -149,10 +147,6 @@ export default function RemoveV1Exchange({ ...@@ -149,10 +147,6 @@ export default function RemoveV1Exchange({
) )
const userLiquidityBalance = useTokenBalance(account, liquidityToken) const userLiquidityBalance = useTokenBalance(account, liquidityToken)
const handleBack = useCallback(() => {
history.push('/migrate/v1')
}, [history])
// redirect for invalid url params // redirect for invalid url params
if (!validatedAddress || tokenAddress === AddressZero) { if (!validatedAddress || tokenAddress === AddressZero) {
console.error('Invalid address in path', address) console.error('Invalid address in path', address)
...@@ -163,9 +157,7 @@ export default function RemoveV1Exchange({ ...@@ -163,9 +157,7 @@ export default function RemoveV1Exchange({
<BodyWrapper style={{ padding: 24 }}> <BodyWrapper style={{ padding: 24 }}>
<AutoColumn gap="16px"> <AutoColumn gap="16px">
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px"> <AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<div style={{ cursor: 'pointer' }}> <BackArrow to="/migrate/v1" />
<ArrowLeft onClick={handleBack} />
</div>
<TYPE.mediumHeader>Remove V1 Liquidity</TYPE.mediumHeader> <TYPE.mediumHeader>Remove V1 Liquidity</TYPE.mediumHeader>
<div> <div>
<QuestionHelper text="Remove your Uniswap V1 liquidity tokens." /> <QuestionHelper text="Remove your Uniswap V1 liquidity tokens." />
......
import { JSBI, Token } from '@uniswap/sdk' import { JSBI, Token } from '@uniswap/sdk'
import React, { useCallback, useContext, useMemo, useState, useEffect } from 'react' import React, { useCallback, useContext, useMemo, useState, useEffect } from 'react'
import { ArrowLeft } from 'react-feather'
import { RouteComponentProps } from 'react-router'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { AutoColumn } from '../../components/Column' import { AutoColumn } from '../../components/Column'
import { AutoRow } from '../../components/Row' import { AutoRow } from '../../components/Row'
...@@ -10,7 +8,7 @@ import { useAllTokenV1Exchanges } from '../../data/V1' ...@@ -10,7 +8,7 @@ import { useAllTokenV1Exchanges } from '../../data/V1'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useToken, useAllTokens } from '../../hooks/Tokens' import { useToken, useAllTokens } from '../../hooks/Tokens'
import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks' import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks'
import { TYPE } from '../../theme' import { BackArrow, TYPE } from '../../theme'
import { LightCard } from '../../components/Card' import { LightCard } from '../../components/Card'
import { BodyWrapper } from '../AppBody' import { BodyWrapper } from '../AppBody'
import { EmptyState } from './EmptyState' import { EmptyState } from './EmptyState'
...@@ -20,7 +18,7 @@ import { Dots } from '../../components/swap/styleds' ...@@ -20,7 +18,7 @@ import { Dots } from '../../components/swap/styleds'
import { useAddUserToken } from '../../state/user/hooks' import { useAddUserToken } from '../../state/user/hooks'
import { isDefaultToken, isCustomAddedToken } from '../../utils' import { isDefaultToken, isCustomAddedToken } from '../../utils'
export default function MigrateV1({ history }: RouteComponentProps) { export default function MigrateV1() {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
...@@ -68,17 +66,11 @@ export default function MigrateV1({ history }: RouteComponentProps) { ...@@ -68,17 +66,11 @@ export default function MigrateV1({ history }: RouteComponentProps) {
// should never always be false, because a V1 exhchange exists for WETH on all testnets // should never always be false, because a V1 exhchange exists for WETH on all testnets
const isLoading = Object.keys(V1Exchanges)?.length === 0 || V1LiquidityBalancesLoading const isLoading = Object.keys(V1Exchanges)?.length === 0 || V1LiquidityBalancesLoading
const handleBackClick = useCallback(() => {
history.push('/pool')
}, [history])
return ( return (
<BodyWrapper style={{ padding: 24 }}> <BodyWrapper style={{ padding: 24 }}>
<AutoColumn gap="16px"> <AutoColumn gap="16px">
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px"> <AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<div style={{ cursor: 'pointer' }}> <BackArrow to="/pool" />
<ArrowLeft onClick={handleBackClick} />
</div>
<TYPE.mediumHeader>Migrate V1 Liquidity</TYPE.mediumHeader> <TYPE.mediumHeader>Migrate V1 Liquidity</TYPE.mediumHeader>
<div> <div>
<QuestionHelper text="Migrate your liquidity tokens from Uniswap V1 to Uniswap V2." /> <QuestionHelper text="Migrate your liquidity tokens from Uniswap V1 to Uniswap V2." />
......
import React, { useState, useContext, useCallback } from 'react' import React, { useContext } from 'react'
import styled, { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { JSBI } from '@uniswap/sdk' import { Pair } from '@uniswap/sdk'
import { RouteComponentProps } from 'react-router-dom' import { Link } from 'react-router-dom'
import { SwapPoolTabs } from '../../components/NavigationTabs' import { SwapPoolTabs } from '../../components/NavigationTabs'
import Question from '../../components/QuestionHelper' import Question from '../../components/QuestionHelper'
import PairSearchModal from '../../components/SearchModal/PairSearchModal' import FullPositionCard from '../../components/PositionCard'
import PositionCard from '../../components/PositionCard'
import { useUserHasLiquidityInAllTokens } from '../../data/V1' import { useUserHasLiquidityInAllTokens } from '../../data/V1'
import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks' import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks'
import { StyledInternalLink, TYPE } from '../../theme' import { StyledInternalLink, TYPE } from '../../theme'
...@@ -14,7 +13,7 @@ import { Text } from 'rebass' ...@@ -14,7 +13,7 @@ import { Text } from 'rebass'
import { LightCard } from '../../components/Card' import { LightCard } from '../../components/Card'
import { RowBetween } from '../../components/Row' import { RowBetween } from '../../components/Row'
import { ButtonPrimary, ButtonSecondary } from '../../components/Button' import { ButtonPrimary, ButtonSecondary } from '../../components/Button'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn } from '../../components/Column'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { usePairs } from '../../data/Reserves' import { usePairs } from '../../data/Reserves'
...@@ -22,71 +21,47 @@ import { useAllDummyPairs } from '../../state/user/hooks' ...@@ -22,71 +21,47 @@ import { useAllDummyPairs } from '../../state/user/hooks'
import AppBody from '../AppBody' import AppBody from '../AppBody'
import { Dots } from '../../components/swap/styleds' import { Dots } from '../../components/swap/styleds'
const Positions = styled.div` export default function Pool() {
position: relative;
width: 100%;
`
const FixedBottom = styled.div`
position: absolute;
bottom: -80px;
width: 100%;
`
export default function Pool({ history }: RouteComponentProps) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const [showPoolSearch, setShowPoolSearch] = useState(false)
// 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 v2DummyPairs = useAllDummyPairs()
const [V2PairsBalances, fetchingV2PairBalances] = useTokenBalancesWithLoadingIndicator( const [v2PairsBalances, fetchingV2PairBalances] = useTokenBalancesWithLoadingIndicator(
account, account ?? undefined,
V2DummyPairs?.map(p => p.liquidityToken) v2DummyPairs?.map(p => p.liquidityToken)
) )
// 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( const v2DummyPairsWithABalance = v2DummyPairs.filter(dummyPair =>
V2DummyPair => v2PairsBalances[dummyPair.liquidityToken.address]?.greaterThan('0')
V2PairsBalances[V2DummyPair.liquidityToken.address] &&
JSBI.greaterThan(V2PairsBalances[V2DummyPair.liquidityToken.address].raw, JSBI.BigInt(0))
) )
const V2Pairs = usePairs( const v2Pairs = usePairs(
V2DummyPairsWithABalance.map(V2DummyPairWithABalance => [ v2DummyPairsWithABalance.map(V2DummyPairWithABalance => [
V2DummyPairWithABalance.token0, V2DummyPairWithABalance.token0,
V2DummyPairWithABalance.token1 V2DummyPairWithABalance.token1
]) ])
) )
const V2IsLoading = const v2IsLoading =
fetchingV2PairBalances || V2Pairs?.length < V2DummyPairsWithABalance.length || V2Pairs?.some(V2Pair => !!!V2Pair) fetchingV2PairBalances || v2Pairs?.length < v2DummyPairsWithABalance.length || v2Pairs?.some(V2Pair => !V2Pair)
const allV2PairsWithLiquidity = V2Pairs.filter(V2Pair => !!V2Pair).map(V2Pair => ( const allV2PairsWithLiquidity = v2Pairs
<PositionCard key={V2Pair.liquidityToken.address} pair={V2Pair} /> .filter((v2Pair): v2Pair is Pair => Boolean(v2Pair))
)) .map(V2Pair => <FullPositionCard key={V2Pair.liquidityToken.address} pair={V2Pair} />)
const hasV1Liquidity = useUserHasLiquidityInAllTokens() const hasV1Liquidity = useUserHasLiquidityInAllTokens()
const handleSearchDismiss = useCallback(() => {
setShowPoolSearch(false)
}, [setShowPoolSearch])
return ( return (
<>
<AppBody> <AppBody>
<SwapPoolTabs active={'pool'} /> <SwapPoolTabs active={'pool'} />
<AutoColumn gap="lg" justify="center"> <AutoColumn gap="lg" justify="center">
<ButtonPrimary <ButtonPrimary id="join-pool-button" as={Link} style={{ padding: 16 }} to="/add/ETH">
id="join-pool-button"
padding="16px"
onClick={() => {
setShowPoolSearch(true)
}}
>
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
Join {allV2PairsWithLiquidity?.length > 0 ? 'another' : 'a'} pool Add Liquidity
</Text> </Text>
</ButtonPrimary> </ButtonPrimary>
<Positions> <AutoColumn gap="12px" style={{ width: '100%' }}>
<AutoColumn gap="12px">
<RowBetween padding={'0 8px'}> <RowBetween padding={'0 8px'}>
<Text color={theme.text1} fontWeight={500}> <Text color={theme.text1} fontWeight={500}>
Your Liquidity Your Liquidity
...@@ -100,7 +75,7 @@ export default function Pool({ history }: RouteComponentProps) { ...@@ -100,7 +75,7 @@ export default function Pool({ history }: RouteComponentProps) {
Connect to a wallet to view your liquidity. Connect to a wallet to view your liquidity.
</TYPE.body> </TYPE.body>
</LightCard> </LightCard>
) : V2IsLoading ? ( ) : v2IsLoading ? (
<LightCard padding="40px"> <LightCard padding="40px">
<TYPE.body color={theme.text3} textAlign="center"> <TYPE.body color={theme.text3} textAlign="center">
<Dots>Loading</Dots> <Dots>Loading</Dots>
...@@ -125,16 +100,14 @@ export default function Pool({ history }: RouteComponentProps) { ...@@ -125,16 +100,14 @@ export default function Pool({ history }: RouteComponentProps) {
</Text> </Text>
</div> </div>
</AutoColumn> </AutoColumn>
<FixedBottom>
<ColumnCenter>
<ButtonSecondary width="136px" padding="8px" borderRadius="10px" onClick={() => history.push('/create')}>
+ Create Pool
</ButtonSecondary>
</ColumnCenter>
</FixedBottom>
</Positions>
<PairSearchModal isOpen={showPoolSearch} onDismiss={handleSearchDismiss} />
</AutoColumn> </AutoColumn>
</AppBody> </AppBody>
<div style={{ display: 'flex', alignItems: 'center', marginTop: '1.5rem' }}>
<ButtonSecondary as={Link} style={{ width: 'initial' }} padding="8px" borderRadius="10px" to="/migrate/v1">
Migrate V1 Liquidity
</ButtonSecondary>
</div>
</>
) )
} }
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}
\ No newline at end of file
...@@ -6,7 +6,7 @@ import { ButtonDropdownLight } from '../../components/Button' ...@@ -6,7 +6,7 @@ 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 { FindPoolTabs } from '../../components/NavigationTabs' import { FindPoolTabs } from '../../components/NavigationTabs'
import PositionCard 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 TokenSearchModal from '../../components/SearchModal/TokenSearchModal'
import TokenLogo from '../../components/TokenLogo' import TokenLogo from '../../components/TokenLogo'
...@@ -29,12 +29,12 @@ export default function PoolFinder() { ...@@ -29,12 +29,12 @@ export default function PoolFinder() {
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>(WETH[chainId].address) const [token0Address, setToken0Address] = useState<string>(chainId ? WETH[chainId].address : '')
const [token1Address, setToken1Address] = useState<string>() const [token1Address, setToken1Address] = useState<string>()
const token0: Token = useToken(token0Address) const token0: Token | null | undefined = useToken(token0Address)
const token1: Token = useToken(token1Address) const token1: Token | null | undefined = useToken(token1Address)
const pair: Pair = usePair(token0, token1) const pair: Pair | null | undefined = usePair(token0 ?? undefined, token1 ?? undefined)
const addPair = usePairAdder() const addPair = usePairAdder()
useEffect(() => { useEffect(() => {
if (pair) { if (pair) {
...@@ -46,7 +46,7 @@ export default function PoolFinder() { ...@@ -46,7 +46,7 @@ export default function PoolFinder() {
pair === null || pair === null ||
(!!pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0))) (!!pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0)))
const position: TokenAmount = useTokenBalanceTreatingWETHasETH(account, pair?.liquidityToken) const position: TokenAmount | undefined = useTokenBalanceTreatingWETHasETH(account ?? undefined, pair?.liquidityToken)
const poolImported: boolean = !!position && JSBI.greaterThan(position.raw, JSBI.BigInt(0)) const poolImported: boolean = !!position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
const handleTokenSelect = useCallback( const handleTokenSelect = useCallback(
...@@ -120,13 +120,13 @@ export default function PoolFinder() { ...@@ -120,13 +120,13 @@ export default function PoolFinder() {
{position ? ( {position ? (
poolImported ? ( poolImported ? (
<PositionCard pair={pair} minimal={true} border="1px solid #CED0D9" /> <MinimalPositionCard pair={pair} border="1px solid #CED0D9" />
) : ( ) : (
<LightCard padding="45px 10px"> <LightCard padding="45px 10px">
<AutoColumn gap="sm" justify="center"> <AutoColumn gap="sm" justify="center">
<Text textAlign="center">You don’t have liquidity in this pool yet.</Text> <Text textAlign="center">You don’t have liquidity in this pool yet.</Text>
<StyledInternalLink to={`/add/${token0.address}-${token1.address}`}> <StyledInternalLink to={`/add/${token0?.address}/${token1?.address}`}>
<Text textAlign="center">Add liquidity?</Text> <Text textAlign="center">Add liquidity.</Text>
</StyledInternalLink> </StyledInternalLink>
</AutoColumn> </AutoColumn>
</LightCard> </LightCard>
...@@ -135,7 +135,7 @@ export default function PoolFinder() { ...@@ -135,7 +135,7 @@ export default function PoolFinder() {
<LightCard padding="45px 10px"> <LightCard padding="45px 10px">
<AutoColumn gap="sm" justify="center"> <AutoColumn gap="sm" justify="center">
<Text textAlign="center">No pool found.</Text> <Text textAlign="center">No pool found.</Text>
<StyledInternalLink to={`/add/${token0Address}-${token1Address}`}>Create pool?</StyledInternalLink> <StyledInternalLink to={`/add/${token0Address}/${token1Address}`}>Create pool?</StyledInternalLink>
</AutoColumn> </AutoColumn>
</LightCard> </LightCard>
) : ( ) : (
...@@ -151,6 +151,7 @@ export default function PoolFinder() { ...@@ -151,6 +151,7 @@ export default function PoolFinder() {
isOpen={showSearch} isOpen={showSearch}
onTokenSelect={handleTokenSelect} onTokenSelect={handleTokenSelect}
onDismiss={handleSearchDismiss} onDismiss={handleSearchDismiss}
showCommonBases
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address} hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address}
/> />
</AppBody> </AppBody>
......
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}
\ No newline at end of file
...@@ -14,7 +14,7 @@ import ConfirmationModal from '../../components/ConfirmationModal' ...@@ -14,7 +14,7 @@ import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import DoubleLogo from '../../components/DoubleLogo' import DoubleLogo from '../../components/DoubleLogo'
import { AddRemoveTabs } from '../../components/NavigationTabs' import { AddRemoveTabs } from '../../components/NavigationTabs'
import PositionCard from '../../components/PositionCard' import { MinimalPositionCard } from '../../components/PositionCard'
import Row, { RowBetween, RowFixed } from '../../components/Row' import Row, { RowBetween, RowFixed } from '../../components/Row'
import Slider from '../../components/Slider' import Slider from '../../components/Slider'
...@@ -595,7 +595,7 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro ...@@ -595,7 +595,7 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
{pair ? ( {pair ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}> <AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<PositionCard pair={pair} minimal={true} /> <MinimalPositionCard pair={pair} />
</AutoColumn> </AutoColumn>
) : null} ) : null}
</> </>
......
import { JSBI, TokenAmount, WETH } from '@uniswap/sdk' import { JSBI, TokenAmount } from '@uniswap/sdk'
import React, { useContext, useState, useEffect, useCallback } from 'react' import React, { useContext, useState, useEffect, useCallback } from 'react'
import { ArrowDown } from 'react-feather' import { ArrowDown } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
...@@ -27,7 +27,7 @@ import { useSwapCallback } from '../../hooks/useSwapCallback' ...@@ -27,7 +27,7 @@ import { useSwapCallback } from '../../hooks/useSwapCallback'
import { useWalletModalToggle, useToggleSettingsMenu } from '../../state/application/hooks' import { useWalletModalToggle, useToggleSettingsMenu } from '../../state/application/hooks'
import { useExpertModeManager, useUserSlippageTolerance, useUserDeadline } from '../../state/user/hooks' import { useExpertModeManager, useUserSlippageTolerance, useUserDeadline } from '../../state/user/hooks'
import { INITIAL_ALLOWED_SLIPPAGE, MIN_ETH, BETTER_TRADE_LINK_THRESHOLD } from '../../constants' import { INITIAL_ALLOWED_SLIPPAGE, BETTER_TRADE_LINK_THRESHOLD } from '../../constants'
import { getTradeVersion, isTradeBetter } from '../../data/V1' import { getTradeVersion, isTradeBetter } from '../../data/V1'
import useToggledVersion, { Version } from '../../hooks/useToggledVersion' import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
import { Field } from '../../state/swap/actions' import { Field } from '../../state/swap/actions'
...@@ -38,6 +38,7 @@ import { ...@@ -38,6 +38,7 @@ import {
useSwapState useSwapState
} from '../../state/swap/hooks' } from '../../state/swap/hooks'
import { CursorPointer, LinkStyledButton, TYPE } from '../../theme' import { CursorPointer, LinkStyledButton, TYPE } from '../../theme'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
import AppBody from '../AppBody' import AppBody from '../AppBody'
import { ClickableText } from '../Pool/styleds' import { ClickableText } from '../Pool/styleds'
...@@ -45,7 +46,7 @@ import { ClickableText } from '../Pool/styleds' ...@@ -45,7 +46,7 @@ import { ClickableText } from '../Pool/styleds'
export default function Swap() { export default function Swap() {
useDefaultsFromURLSearch() useDefaultsFromURLSearch()
const { chainId, account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
// toggle wallet when disconnected // toggle wallet when disconnected
...@@ -128,21 +129,7 @@ export default function Swap() { ...@@ -128,21 +129,7 @@ export default function Swap() {
} }
}, [approval, approvalSubmitted]) }, [approval, approvalSubmitted])
let maxAmountInput: TokenAmount | undefined const maxAmountInput: TokenAmount | undefined = maxAmountSpend(tokenBalances[Field.INPUT])
{
const inputToken = tokens[Field.INPUT]
maxAmountInput =
inputToken &&
chainId &&
WETH[chainId] &&
tokenBalances[Field.INPUT]?.greaterThan(
new TokenAmount(inputToken, inputToken.equals(WETH[chainId]) ? MIN_ETH : '0')
)
? inputToken.equals(WETH[chainId])
? tokenBalances[Field.INPUT]?.subtract(new TokenAmount(WETH[chainId], MIN_ETH))
: tokenBalances[Field.INPUT]
: undefined
}
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput)) const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage) const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage)
......
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',
...@@ -8,3 +9,7 @@ export enum Field { ...@@ -8,3 +9,7 @@ export enum Field {
} }
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')
...@@ -3,8 +3,7 @@ import { useDispatch, useSelector } from 'react-redux' ...@@ -3,8 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { AppDispatch, AppState } from '../index' import { AppDispatch, AppState } from '../index'
import { Field, typeInput } from './actions' import { Field, setBurnDefaultsFromURLMatchParams, typeInput } from './actions'
import { setDefaultsFromURLMatchParams } from '../mint/actions'
import { useToken } from '../../hooks/Tokens' import { useToken } from '../../hooks/Tokens'
import { Token, Pair, TokenAmount, Percent, JSBI, Route } from '@uniswap/sdk' import { Token, Pair, TokenAmount, Percent, JSBI, Route } from '@uniswap/sdk'
import { usePair } from '../../data/Reserves' import { usePair } from '../../data/Reserves'
...@@ -177,11 +176,11 @@ export function useBurnActionHandlers(): { ...@@ -177,11 +176,11 @@ export function useBurnActionHandlers(): {
} }
// updates the burn state to use the appropriate tokens, given the route // updates the burn state to use the appropriate tokens, given the route
export function useDefaultsFromURLMatchParams(params: { [k: string]: string }) { export function useDefaultsFromURLMatchParams(params: { tokens: string }) {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const dispatch = useDispatch<AppDispatch>() const dispatch = useDispatch<AppDispatch>()
useEffect(() => { useEffect(() => {
if (!chainId) return if (!chainId) return
dispatch(setDefaultsFromURLMatchParams({ chainId, params })) dispatch(setBurnDefaultsFromURLMatchParams({ chainId, params }))
}, [dispatch, chainId, params]) }, [dispatch, chainId, params])
} }
import { createReducer } from '@reduxjs/toolkit' import { createReducer } from '@reduxjs/toolkit'
import { ChainId, WETH } from '@uniswap/sdk'
import { isAddress } from '../../utils'
import { Field, typeInput } from './actions' import { Field, setBurnDefaultsFromURLMatchParams, typeInput } from './actions'
import { setDefaultsFromURLMatchParams } from '../mint/actions'
import { parseTokens } from '../mint/reducer'
export interface MintState { export interface BurnState {
readonly independentField: Field readonly independentField: Field
readonly typedValue: string readonly typedValue: string
readonly [Field.TOKEN_A]: { readonly [Field.TOKEN_A]: {
...@@ -15,7 +15,7 @@ export interface MintState { ...@@ -15,7 +15,7 @@ export interface MintState {
} }
} }
const initialState: MintState = { const initialState: BurnState = {
independentField: Field.LIQUIDITY_PERCENT, independentField: Field.LIQUIDITY_PERCENT,
typedValue: '0', typedValue: '0',
[Field.TOKEN_A]: { [Field.TOKEN_A]: {
...@@ -26,9 +26,27 @@ const initialState: MintState = { ...@@ -26,9 +26,27 @@ const initialState: MintState = {
} }
} }
export default createReducer<MintState>(initialState, builder => 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 =>
builder builder
.addCase(setDefaultsFromURLMatchParams, (state, { payload: { chainId, params } }) => { .addCase(setBurnDefaultsFromURLMatchParams, (state, { payload: { chainId, params } }) => {
const tokens = parseTokens(chainId, params?.tokens ?? '') const tokens = parseTokens(chainId, params?.tokens ?? '')
return { return {
independentField: Field.LIQUIDITY_PERCENT, independentField: Field.LIQUIDITY_PERCENT,
......
import { createAction } from '@reduxjs/toolkit' import { createAction } from '@reduxjs/toolkit'
import { RouteComponentProps } from 'react-router-dom'
export enum Field { export enum Field {
TOKEN_A = 'TOKEN_A', TOKEN_A = 'TOKEN_A',
TOKEN_B = 'TOKEN_B' TOKEN_B = 'TOKEN_B'
} }
export const setDefaultsFromURLMatchParams = createAction<{
chainId: number
params: RouteComponentProps<{ [k: string]: string }>['match']['params']
}>('setDefaultsFromMatch')
export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('typeInputMint') export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('typeInputMint')
export const resetMintState = createAction<void>('resetMintState')
import { useEffect, useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { Token, TokenAmount, Route, JSBI, Price, Percent, Pair } from '@uniswap/sdk' import { Token, TokenAmount, Route, JSBI, Price, Percent, Pair } from '@uniswap/sdk'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { AppDispatch, AppState } from '../index' import { AppDispatch, AppState } from '../index'
import { setDefaultsFromURLMatchParams, Field, typeInput } from './actions' import { Field, typeInput } from './actions'
import { useToken } from '../../hooks/Tokens'
import { useTokenBalancesTreatWETHAsETH } from '../wallet/hooks' import { useTokenBalancesTreatWETHAsETH } from '../wallet/hooks'
import { usePair } from '../../data/Reserves' import { usePair } from '../../data/Reserves'
import { useTotalSupply } from '../../data/TotalSupply' import { useTotalSupply } from '../../data/TotalSupply'
...@@ -17,7 +16,10 @@ export function useMintState(): AppState['mint'] { ...@@ -17,7 +16,10 @@ export function useMintState(): AppState['mint'] {
return useSelector<AppState, AppState['mint']>(state => state.mint) return useSelector<AppState, AppState['mint']>(state => state.mint)
} }
export function useDerivedMintInfo(): { export function useDerivedMintInfo(
tokenA: Token | undefined,
tokenB: Token | undefined
): {
dependentField: Field dependentField: Field
tokens: { [field in Field]?: Token } tokens: { [field in Field]?: Token }
pair?: Pair | null pair?: Pair | null
...@@ -31,19 +33,11 @@ export function useDerivedMintInfo(): { ...@@ -31,19 +33,11 @@ export function useDerivedMintInfo(): {
} { } {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const { const { independentField, typedValue, otherTypedValue } = useMintState()
independentField,
typedValue,
otherTypedValue,
[Field.TOKEN_A]: { address: tokenAAddress },
[Field.TOKEN_B]: { address: tokenBAddress }
} = useMintState()
const dependentField = independentField === Field.TOKEN_A ? Field.TOKEN_B : Field.TOKEN_A const dependentField = independentField === Field.TOKEN_A ? Field.TOKEN_B : Field.TOKEN_A
// tokens // tokens
const tokenA = useToken(tokenAAddress)
const tokenB = useToken(tokenBAddress)
const tokens: { [field in Field]?: Token } = useMemo( const tokens: { [field in Field]?: Token } = useMemo(
() => ({ () => ({
[Field.TOKEN_A]: tokenA, [Field.TOKEN_A]: tokenA,
...@@ -172,16 +166,16 @@ export function useDerivedMintInfo(): { ...@@ -172,16 +166,16 @@ export function useDerivedMintInfo(): {
} }
} }
export function useMintActionHandlers(): { export function useMintActionHandlers(
noLiquidity: boolean | undefined
): {
onUserInput: (field: Field, typedValue: string) => void onUserInput: (field: Field, typedValue: string) => void
} { } {
const dispatch = useDispatch<AppDispatch>() const dispatch = useDispatch<AppDispatch>()
const { noLiquidity } = useDerivedMintInfo()
const onUserInput = useCallback( const onUserInput = useCallback(
(field: Field, typedValue: string) => { (field: Field, typedValue: string) => {
dispatch(typeInput({ field, typedValue, noLiquidity: noLiquidity === true ? true : false })) dispatch(typeInput({ field, typedValue, noLiquidity: noLiquidity === true }))
}, },
[dispatch, noLiquidity] [dispatch, noLiquidity]
) )
...@@ -190,13 +184,3 @@ export function useMintActionHandlers(): { ...@@ -190,13 +184,3 @@ export function useMintActionHandlers(): {
onUserInput onUserInput
} }
} }
// updates the mint state to use the appropriate tokens, given the route
export function useDefaultsFromURLMatchParams(params: { [k: string]: string }) {
const { chainId } = useActiveWeb3React()
const dispatch = useDispatch<AppDispatch>()
useEffect(() => {
if (!chainId) return
dispatch(setDefaultsFromURLMatchParams({ chainId, params }))
}, [dispatch, chainId, params])
}
import { ChainId, WETH } from '@uniswap/sdk'
import { createStore, Store } from 'redux' import { createStore, Store } from 'redux'
import { Field, setDefaultsFromURLMatchParams } from './actions' import { Field, typeInput } from './actions'
import reducer, { MintState } from './reducer' import reducer, { MintState } from './reducer'
describe('mint reducer', () => { describe('mint reducer', () => {
...@@ -11,30 +10,19 @@ describe('mint reducer', () => { ...@@ -11,30 +10,19 @@ describe('mint reducer', () => {
store = createStore(reducer, { store = createStore(reducer, {
independentField: Field.TOKEN_A, independentField: Field.TOKEN_A,
typedValue: '', typedValue: '',
otherTypedValue: '', otherTypedValue: ''
[Field.TOKEN_A]: { address: '' },
[Field.TOKEN_B]: { address: '' }
}) })
}) })
describe('setDefaultsFromURLMatchParams', () => { describe('typeInput', () => {
test('ETH to DAI', () => { it('sets typed value', () => {
store.dispatch( store.dispatch(typeInput({ field: Field.TOKEN_A, typedValue: '1.0', noLiquidity: false }))
setDefaultsFromURLMatchParams({ expect(store.getState()).toEqual({ independentField: Field.TOKEN_A, typedValue: '1.0', otherTypedValue: '' })
chainId: ChainId.MAINNET,
params: {
tokens: 'ETH-0x6b175474e89094c44da98b954eedeac495271d0f'
}
})
)
expect(store.getState()).toEqual({
independentField: Field.TOKEN_A,
typedValue: '',
otherTypedValue: '',
[Field.TOKEN_A]: { address: WETH[ChainId.MAINNET].address },
[Field.TOKEN_B]: { address: '0x6b175474e89094c44da98b954eedeac495271d0f' }
}) })
it('clears other value', () => {
store.dispatch(typeInput({ field: Field.TOKEN_A, typedValue: '1.0', noLiquidity: false }))
store.dispatch(typeInput({ field: Field.TOKEN_B, typedValue: '1.0', noLiquidity: false }))
expect(store.getState()).toEqual({ independentField: Field.TOKEN_B, typedValue: '1.0', otherTypedValue: '' })
}) })
}) })
}) })
import { createReducer } from '@reduxjs/toolkit' import { createReducer } from '@reduxjs/toolkit'
import { ChainId, WETH } from '@uniswap/sdk' import { Field, resetMintState, typeInput } from './actions'
import { isAddress } from '../../utils'
import { Field, setDefaultsFromURLMatchParams, typeInput } from './actions'
export interface MintState { export interface MintState {
readonly independentField: Field readonly independentField: Field
readonly typedValue: string readonly typedValue: string
readonly otherTypedValue: string // for the case when there's no liquidity readonly otherTypedValue: string // for the case when there's no liquidity
readonly [Field.TOKEN_A]: {
readonly address: string
}
readonly [Field.TOKEN_B]: {
readonly address: string
}
} }
const initialState: MintState = { const initialState: MintState = {
independentField: Field.TOKEN_A, independentField: Field.TOKEN_A,
typedValue: '', typedValue: '',
otherTypedValue: '', otherTypedValue: ''
[Field.TOKEN_A]: {
address: ''
},
[Field.TOKEN_B]: {
address: ''
}
}
export function parseTokens(chainId: number, tokens: string): string[] {
return (
tokens
// split by '-'
.split('-')
// map to addresses
.map((token): string =>
isAddress(token)
? token
: token.toLowerCase() === 'ETH'.toLowerCase()
? WETH[chainId as 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<MintState>(initialState, builder => export default createReducer<MintState>(initialState, builder =>
builder builder
.addCase(setDefaultsFromURLMatchParams, (state, { payload: { chainId, params } }) => { .addCase(resetMintState, () => initialState)
const tokens = parseTokens(chainId, params?.tokens ?? '')
return {
independentField: Field.TOKEN_A,
typedValue: '',
otherTypedValue: '',
[Field.TOKEN_A]: {
address: tokens[0]
},
[Field.TOKEN_B]: {
address: tokens[1]
}
}
})
.addCase(typeInput, (state, { payload: { field, typedValue, noLiquidity } }) => { .addCase(typeInput, (state, { payload: { field, typedValue, noLiquidity } }) => {
if (noLiquidity) { if (noLiquidity) {
// they're typing into the field they've last typed in // they're typing into the field they've last typed in
......
...@@ -192,6 +192,54 @@ describe('multicall reducer', () => { ...@@ -192,6 +192,54 @@ describe('multicall reducer', () => {
} }
}) })
}) })
it('updates state to fetching even if already fetching older block', () => {
store.dispatch(
fetchingMulticallResults({
chainId: 1,
fetchingBlockNumber: 2,
calls: [{ address: DAI_ADDRESS, callData: '0x0' }]
})
)
store.dispatch(
fetchingMulticallResults({
chainId: 1,
fetchingBlockNumber: 3,
calls: [{ address: DAI_ADDRESS, callData: '0x0' }]
})
)
expect(store.getState()).toEqual({
callResults: {
[1]: {
[`${DAI_ADDRESS}-0x0`]: { fetchingBlockNumber: 3 }
}
}
})
})
it('does not do update if fetching newer block', () => {
store.dispatch(
fetchingMulticallResults({
chainId: 1,
fetchingBlockNumber: 2,
calls: [{ address: DAI_ADDRESS, callData: '0x0' }]
})
)
store.dispatch(
fetchingMulticallResults({
chainId: 1,
fetchingBlockNumber: 1,
calls: [{ address: DAI_ADDRESS, callData: '0x0' }]
})
)
expect(store.getState()).toEqual({
callResults: {
[1]: {
[`${DAI_ADDRESS}-0x0`]: { fetchingBlockNumber: 2 }
}
}
})
})
}) })
describe('errorFetchingMulticallResults', () => { describe('errorFetchingMulticallResults', () => {
......
...@@ -79,7 +79,7 @@ export default createReducer(initialState, builder => ...@@ -79,7 +79,7 @@ export default createReducer(initialState, builder =>
fetchingBlockNumber fetchingBlockNumber
} }
} else { } else {
if (current.fetchingBlockNumber ?? 0 >= fetchingBlockNumber) return if ((current.fetchingBlockNumber ?? 0) >= fetchingBlockNumber) return
state.callResults[chainId][callKey].fetchingBlockNumber = fetchingBlockNumber state.callResults[chainId][callKey].fetchingBlockNumber = fetchingBlockNumber
} }
}) })
......
...@@ -77,8 +77,8 @@ export function outdatedListeningKeys( ...@@ -77,8 +77,8 @@ export function outdatedListeningKeys(
// already fetching it for a recent enough block, don't refetch it // already fetching it for a recent enough block, don't refetch it
if (data.fetchingBlockNumber && data.fetchingBlockNumber >= minDataBlockNumber) return false if (data.fetchingBlockNumber && data.fetchingBlockNumber >= minDataBlockNumber) return false
// if data is newer than minDataBlockNumber, don't fetch it // if data is older than minDataBlockNumber, fetch it
return !(data.blockNumber && data.blockNumber >= minDataBlockNumber) return !data.blockNumber || data.blockNumber < minDataBlockNumber
}) })
} }
......
...@@ -3,7 +3,7 @@ import ReactGA from 'react-ga' ...@@ -3,7 +3,7 @@ import ReactGA from 'react-ga'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import styled, { keyframes } from 'styled-components' import styled, { keyframes } from 'styled-components'
import { darken } from 'polished' import { darken } from 'polished'
import { X } from 'react-feather' import { ArrowLeft, X } from 'react-feather'
export const Button = styled.button.attrs<{ warning: boolean }, { backgroundColor: string }>(({ warning, theme }) => ({ export const Button = styled.button.attrs<{ warning: boolean }, { backgroundColor: string }>(({ warning, theme }) => ({
backgroundColor: warning ? theme.red1 : theme.primary1 backgroundColor: warning ? theme.red1 : theme.primary1
...@@ -153,3 +153,14 @@ export const CursorPointer = styled.div` ...@@ -153,3 +153,14 @@ export const CursorPointer = styled.div`
cursor: pointer; cursor: pointer;
} }
` `
const BackArrowLink = styled(StyledInternalLink)`
color: ${({ theme }) => theme.text1};
`
export function BackArrow({ to }: { to: string }) {
return (
<BackArrowLink to={to}>
<ArrowLeft />
</BackArrowLink>
)
}
import { JSBI, TokenAmount, WETH } from '@uniswap/sdk'
import { MIN_ETH } from '../constants'
/**
* Given some token amount, return the max that can be spent of it
* @param tokenAmount to return max of
*/
export function maxAmountSpend(tokenAmount?: TokenAmount): TokenAmount | undefined {
if (!tokenAmount) return
if (tokenAmount.token.equals(WETH[tokenAmount.token.chainId])) {
if (JSBI.greaterThan(tokenAmount.raw, MIN_ETH)) {
return new TokenAmount(tokenAmount.token, JSBI.subtract(tokenAmount.raw, MIN_ETH))
} else {
return new TokenAmount(tokenAmount.token, JSBI.BigInt(0))
}
}
return tokenAmount
}
...@@ -3358,11 +3358,16 @@ aproba@^1.1.1: ...@@ -3358,11 +3358,16 @@ aproba@^1.1.1:
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
arch@2.1.1, arch@^2.1.0: arch@2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e"
integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==
arch@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.2.tgz#0c52bbe7344bb4fa260c443d2cbad9c00ff2f0bf"
integrity sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ==
arg@2.0.0: arg@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/arg/-/arg-2.0.0.tgz#c06e7ff69ab05b3a4a03ebe0407fac4cba657545" resolved "https://registry.yarnpkg.com/arg/-/arg-2.0.0.tgz#c06e7ff69ab05b3a4a03ebe0407fac4cba657545"
...@@ -13158,11 +13163,16 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: ...@@ -13158,11 +13163,16 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-event-emitter@^1.0.1: safe-event-emitter@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/safe-event-emitter/-/safe-event-emitter-1.0.1.tgz#5b692ef22329ed8f69fdce607e50ca734f6f20af" resolved "https://registry.yarnpkg.com/safe-event-emitter/-/safe-event-emitter-1.0.1.tgz#5b692ef22329ed8f69fdce607e50ca734f6f20af"
...@@ -13330,10 +13340,10 @@ serialize-javascript@^2.1.2: ...@@ -13330,10 +13340,10 @@ serialize-javascript@^2.1.2:
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
serve-handler@6.1.2: serve-handler@6.1.3:
version "6.1.2" version "6.1.3"
resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.2.tgz#f05b0421a313fff2d257838cba00cbcc512cd2b6" resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8"
integrity sha512-RFh49wX7zJmmOVDcIjiDSJnMH+ItQEvyuYLYuDBVoA/xmQSCuj+uRmk1cmBB5QQlI3qOiWKp6p4DUGY+Z5AB2A== integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==
dependencies: dependencies:
bytes "3.0.0" bytes "3.0.0"
content-disposition "0.5.2" content-disposition "0.5.2"
...@@ -13368,9 +13378,9 @@ serve-static@1.14.1: ...@@ -13368,9 +13378,9 @@ serve-static@1.14.1:
send "0.17.1" send "0.17.1"
serve@^11.3.0: serve@^11.3.0:
version "11.3.0" version "11.3.2"
resolved "https://registry.yarnpkg.com/serve/-/serve-11.3.0.tgz#1d342e13e310501ecf17b6602f1f35da640d6448" resolved "https://registry.yarnpkg.com/serve/-/serve-11.3.2.tgz#b905e980616feecd170e51c8f979a7b2374098f5"
integrity sha512-AU0g50Q1y5EVFX56bl0YX5OtVjUX1N737/Htj93dQGKuHiuLvVB45PD8Muar70W6Kpdlz8aNJfoUqTyAq9EE/A== integrity sha512-yKWQfI3xbj/f7X1lTBg91fXBP0FqjJ4TEi+ilES5yzH0iKJpN5LjNb1YzIfQg9Rqn4ECUS2SOf2+Kmepogoa5w==
dependencies: dependencies:
"@zeit/schemas" "2.6.0" "@zeit/schemas" "2.6.0"
ajv "6.5.3" ajv "6.5.3"
...@@ -13379,7 +13389,7 @@ serve@^11.3.0: ...@@ -13379,7 +13389,7 @@ serve@^11.3.0:
chalk "2.4.1" chalk "2.4.1"
clipboardy "1.2.3" clipboardy "1.2.3"
compression "1.7.3" compression "1.7.3"
serve-handler "6.1.2" serve-handler "6.1.3"
update-check "1.5.2" update-check "1.5.2"
set-blocking@^2.0.0: set-blocking@^2.0.0:
......
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