Commit df4c18c8 authored by Moody Salem's avatar Moody Salem Committed by GitHub

remove v1 swaps and migrate (#46)

* remove v1 swaps and migrate

* simplify

* more delete

* revert route changes

* fix v2 add

* fix integration tests

* try to fix https://github.com/Uniswap/v3-interface/issues/41
parent 62d3aa4b
describe('Migrate V1 Liquidity', () => {
describe('Remove V1 liquidity', () => {
it('renders the correct page', () => {
cy.visit('/remove/v1/0x93bB63aFe1E0180d0eF100D774B473034fd60C36')
cy.get('#remove-v1-exchange').should('contain', 'MKR/ETH')
})
})
})
...@@ -12,19 +12,19 @@ describe('Remove Liquidity', () => { ...@@ -12,19 +12,19 @@ describe('Remove Liquidity', () => {
}) })
it('loads the two correct tokens', () => { it('loads the two correct tokens', () => {
cy.visit('/remove/v2/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85') cy.visit('/remove/v2/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH') cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
}) })
it('does not crash if ETH is duplicated', () => { it('does not crash if ETH is duplicated', () => {
cy.visit('/remove/v2/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab') cy.visit('/remove/v2/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH') cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
}) })
it('token not in storage is loaded', () => { it('token not in storage is loaded', () => {
cy.visit('/remove/v2/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85') cy.visit('/remove/v2/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'SKL') cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'SKL')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
}) })
......
...@@ -116,23 +116,23 @@ export default function Menu() { ...@@ -116,23 +116,23 @@ export default function Menu() {
{open && ( {open && (
<MenuFlyout> <MenuFlyout>
<MenuItem id="link" href="https://uniswap.org/"> <MenuItem href="https://uniswap.org/">
<Info size={14} /> <Info size={14} />
About About
</MenuItem> </MenuItem>
<MenuItem id="link" href="https://uniswap.org/docs/v2"> <MenuItem href="https://uniswap.org/docs/v2">
<BookOpen size={14} /> <BookOpen size={14} />
Docs Docs
</MenuItem> </MenuItem>
<MenuItem id="link" href={CODE_LINK}> <MenuItem href={CODE_LINK}>
<Code size={14} /> <Code size={14} />
Code Code
</MenuItem> </MenuItem>
<MenuItem id="link" href="https://discord.gg/FCfyBSbCU5"> <MenuItem href="https://discord.gg/FCfyBSbCU5">
<MessageCircle size={14} /> <MessageCircle size={14} />
Discord Discord
</MenuItem> </MenuItem>
<MenuItem id="link" href="https://uniswap.info/"> <MenuItem href="https://uniswap.info/">
<PieChart size={14} /> <PieChart size={14} />
Analytics Analytics
</MenuItem> </MenuItem>
...@@ -175,7 +175,7 @@ export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, men ...@@ -175,7 +175,7 @@ export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, men
{open && ( {open && (
<NewMenuFlyout flyoutAlignment={flyoutAlignment}> <NewMenuFlyout flyoutAlignment={flyoutAlignment}>
{menuItems.map(({ content, link }, i) => ( {menuItems.map(({ content, link }, i) => (
<NewMenuItem id="link" href={link} key={link + i}> <NewMenuItem href={link} key={i}>
{content} {content}
</NewMenuItem> </NewMenuItem>
))} ))}
......
import React, { useContext } from 'react'
import { Link, RouteComponentProps, withRouter } from 'react-router-dom'
import { Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
import { useActiveWeb3React } from '../../hooks'
import { ButtonSecondary } from '../Button'
import { AutoColumn } from '../Column'
import DoubleCurrencyLogo from '../DoubleLogo'
import { RowBetween, RowFixed } from '../Row'
import { FixedHeightRow, HoverCard } from './index'
interface PositionCardProps extends RouteComponentProps<Record<string, any>> {
token: Token
V1LiquidityBalance: TokenAmount
}
function V1PositionCard({ token, V1LiquidityBalance }: PositionCardProps) {
const theme = useContext(ThemeContext)
const { chainId } = useActiveWeb3React()
return (
<HoverCard>
<AutoColumn gap="12px">
<FixedHeightRow>
<RowFixed>
<DoubleCurrencyLogo currency0={token} margin={true} size={20} />
<Text fontWeight={500} fontSize={20} style={{ marginLeft: '' }}>
{`${chainId && token.equals(WETH9[chainId]) ? 'WETH' : token.symbol}/ETH`}
</Text>
<Text
fontSize={12}
fontWeight={500}
ml="0.5rem"
px="0.75rem"
py="0.25rem"
style={{ borderRadius: '1rem' }}
backgroundColor={theme.yellow1}
color={'black'}
>
V1
</Text>
</RowFixed>
</FixedHeightRow>
<AutoColumn gap="8px">
<RowBetween marginTop="10px">
<ButtonSecondary width="68%" as={Link} to={`/migrate/v1/${V1LiquidityBalance.token.address}`}>
Migrate
</ButtonSecondary>
<ButtonSecondary
style={{ backgroundColor: 'transparent' }}
width="28%"
as={Link}
to={`/remove/v1/${V1LiquidityBalance.token.address}`}
>
Remove
</ButtonSecondary>
</RowBetween>
</AutoColumn>
</AutoColumn>
</HoverCard>
)
}
export default withRouter(V1PositionCard)
import { Interface } from '@ethersproject/abi'
import { ChainId } from '@uniswap/sdk-core'
import V1_EXCHANGE_ABI from './v1_exchange.json'
import V1_FACTORY_ABI from './v1_factory.json'
const V1_FACTORY_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
[ChainId.ROPSTEN]: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351',
[ChainId.RINKEBY]: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
[ChainId.GÖRLI]: '0x6Ce570d02D73d4c384b46135E87f8C592A8c86dA',
[ChainId.KOVAN]: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30',
}
const V1_FACTORY_INTERFACE = new Interface(V1_FACTORY_ABI)
const V1_EXCHANGE_INTERFACE = new Interface(V1_EXCHANGE_ABI)
export { V1_FACTORY_ADDRESSES, V1_FACTORY_INTERFACE, V1_FACTORY_ABI, V1_EXCHANGE_INTERFACE, V1_EXCHANGE_ABI }
This diff is collapsed.
[
{
"name": "NewExchange",
"inputs": [
{ "type": "address", "name": "token", "indexed": true },
{ "type": "address", "name": "exchange", "indexed": true }
],
"anonymous": false,
"type": "event"
},
{
"name": "initializeFactory",
"outputs": [],
"inputs": [{ "type": "address", "name": "template" }],
"constant": false,
"payable": false,
"type": "function"
},
{
"name": "createExchange",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [{ "type": "address", "name": "token" }],
"constant": false,
"payable": false,
"type": "function"
},
{
"name": "getExchange",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [{ "type": "address", "name": "token" }],
"constant": true,
"payable": false,
"type": "function"
},
{
"name": "getToken",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [{ "type": "address", "name": "exchange" }],
"constant": true,
"payable": false,
"type": "function"
},
{
"name": "getTokenWithId",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [{ "type": "uint256", "name": "token_id" }],
"constant": true,
"payable": false,
"type": "function"
},
{
"name": "exchangeTemplate",
"outputs": [{ "type": "address", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false,
"type": "function"
},
{
"name": "tokenCount",
"outputs": [{ "type": "uint256", "name": "out" }],
"inputs": [],
"constant": true,
"payable": false,
"type": "function"
}
]
import { ChainId } from '@uniswap/sdk-core' import { ChainId } from '@uniswap/sdk-core'
export const FACTORY_ADDRESSES: { [chainId in ChainId]: string } = { export const V3_CORE_FACTORY_ADDRESSES: { [chainId in ChainId]?: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '0xFeabCc62240297F1e4b238937D68e7516f0918D7', [ChainId.RINKEBY]: '0xFeabCc62240297F1e4b238937D68e7516f0918D7',
[ChainId.GÖRLI]: '0xA31B47971cdC5376E41CfA2D4378912156ab1F10', [ChainId.GÖRLI]: '0xA31B47971cdC5376E41CfA2D4378912156ab1F10',
[ChainId.KOVAN]: '0x58f6b77148BE49BF7898472268ae8f26377d0AA6', [ChainId.KOVAN]: '0x58f6b77148BE49BF7898472268ae8f26377d0AA6',
} }
export const TICK_LENS_ADDRESSES: { [chainId in ChainId]: string } = { export const TICK_LENS_ADDRESSES: { [chainId in ChainId]?: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '0x3d137e860008BaF6d1c063158e5ec0baBbcFefF8', [ChainId.RINKEBY]: '0x3d137e860008BaF6d1c063158e5ec0baBbcFefF8',
[ChainId.GÖRLI]: '0x80AacDBEe92DC1c2Fbaa261Fb369696AF1AD9f98', [ChainId.GÖRLI]: '0x80AacDBEe92DC1c2Fbaa261Fb369696AF1AD9f98',
[ChainId.KOVAN]: '0xB79bDE60fc227217f4EE2102dC93fa1264E33DaB', [ChainId.KOVAN]: '0xB79bDE60fc227217f4EE2102dC93fa1264E33DaB',
} }
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: { [chainId in ChainId]: string } = { export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: { [chainId in ChainId]?: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '0x2F9e608FD881861B8916257B76613Cb22EE0652c', [ChainId.RINKEBY]: '0x2F9e608FD881861B8916257B76613Cb22EE0652c',
[ChainId.GÖRLI]: '0xd6852c52B9c97cBfb7e79B6ab4407AA20Ba31439', [ChainId.GÖRLI]: '0xd6852c52B9c97cBfb7e79B6ab4407AA20Ba31439',
[ChainId.KOVAN]: '0xA31B47971cdC5376E41CfA2D4378912156ab1F10', [ChainId.KOVAN]: '0xA31B47971cdC5376E41CfA2D4378912156ab1F10',
} }
export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]: string } = { export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]?: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '0x273Edaa13C845F605b5886Dd66C89AB497A6B17b', [ChainId.RINKEBY]: '0x273Edaa13C845F605b5886Dd66C89AB497A6B17b',
[ChainId.GÖRLI]: '0x91a64CCaead471caFF912314E466D9CF7C55E0E8', [ChainId.GÖRLI]: '0x91a64CCaead471caFF912314E466D9CF7C55E0E8',
[ChainId.KOVAN]: '0x1988F2e49A72C4D73961C7f4Bb896819d3d2F6a3', [ChainId.KOVAN]: '0x1988F2e49A72C4D73961C7f4Bb896819d3d2F6a3',
} }
export const V2_MIGRATOR_ADDRESSES: { [chainId in ChainId]: string } = { export const V2_MIGRATOR_ADDRESSES: { [chainId in ChainId]?: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '0x03782388516e94FcD4c18666303601A12Aa729Ea', [ChainId.RINKEBY]: '0x03782388516e94FcD4c18666303601A12Aa729Ea',
[ChainId.GÖRLI]: '0x2F9e608FD881861B8916257B76613Cb22EE0652c', [ChainId.GÖRLI]: '0x2F9e608FD881861B8916257B76613Cb22EE0652c',
[ChainId.KOVAN]: '0xFeabCc62240297F1e4b238937D68e7516f0918D7', [ChainId.KOVAN]: '0xFeabCc62240297F1e4b238937D68e7516f0918D7',
......
...@@ -7,7 +7,7 @@ import { useSingleCallResult } from '../state/multicall/hooks' ...@@ -7,7 +7,7 @@ import { useSingleCallResult } from '../state/multicall/hooks'
import { wrappedCurrency } from '../utils/wrappedCurrency' import { wrappedCurrency } from '../utils/wrappedCurrency'
import { Pool, FeeAmount, computePoolAddress } from '@uniswap/v3-sdk' import { Pool, FeeAmount, computePoolAddress } from '@uniswap/v3-sdk'
import { useV3Factory, useV3Pool } from 'hooks/useContract' import { useV3Factory, useV3Pool } from 'hooks/useContract'
import { FACTORY_ADDRESSES } from 'constants/v3' import { V3_CORE_FACTORY_ADDRESSES } from 'constants/v3'
import { useAllV3Ticks } from 'hooks/useAllV3Ticks' import { useAllV3Ticks } from 'hooks/useAllV3Ticks'
export enum PoolState { export enum PoolState {
...@@ -38,9 +38,10 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?: ...@@ -38,9 +38,10 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?:
// fetch all generated addresses for pools // fetch all generated addresses for pools
const poolAddress = useMemo(() => { const poolAddress = useMemo(() => {
try { try {
return chainId && tokenA && tokenB && feeAmount && !tokenA.equals(tokenB) const addr = chainId && V3_CORE_FACTORY_ADDRESSES[chainId]
return addr && tokenA && tokenB && feeAmount && !tokenA.equals(tokenB)
? computePoolAddress({ ? computePoolAddress({
factoryAddress: FACTORY_ADDRESSES[chainId], factoryAddress: addr,
tokenA, tokenA,
tokenB, tokenB,
fee: feeAmount, fee: feeAmount,
...@@ -71,10 +72,12 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?: ...@@ -71,10 +72,12 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?:
const poolAddressFromFactory = addressesResult?.[0] const poolAddressFromFactory = addressesResult?.[0]
// fetch tick data for pool // fetch tick data for pool
const { tickData, loading: tickLoading } = useAllV3Ticks(token0, token1, feeAmount) const { tickData, loading: tickLoading, syncing: tickSyncing } = useAllV3Ticks(token0, token1, feeAmount)
return useMemo(() => {
// still loading data // still loading data
if (slot0Loading || addressesLoading || liquidityLoading || tickLoading) return [PoolState.LOADING, null] if (slot0Loading || addressesLoading || liquidityLoading || tickLoading || tickSyncing)
return [PoolState.LOADING, null]
// invalid pool setup // invalid pool setup
if (!tokenA || !tokenB || !feeAmount || tokenA.equals(tokenB)) return [PoolState.INVALID, null] if (!tokenA || !tokenB || !feeAmount || tokenA.equals(tokenB)) return [PoolState.INVALID, null]
...@@ -95,4 +98,18 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?: ...@@ -95,4 +98,18 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?:
.sort((tickA, tickB) => (tickA.index > tickB.index ? 1 : -1)) .sort((tickA, tickB) => (tickA.index > tickB.index ? 1 : -1))
return [PoolState.EXISTS, new Pool(tokenA, tokenB, feeAmount, slot0.sqrtPriceX96, liquidity, slot0.tick, tickList)] return [PoolState.EXISTS, new Pool(tokenA, tokenB, feeAmount, slot0.sqrtPriceX96, liquidity, slot0.tick, tickList)]
}, [
addressesLoading,
feeAmount,
liquidity,
liquidityLoading,
poolAddressFromFactory,
slot0,
slot0Loading,
tickData,
tickLoading,
tickSyncing,
tokenA,
tokenB,
])
} }
import { AddressZero } from '@ethersproject/constants'
import {
BigintIsh,
Currency,
CurrencyAmount,
ETHER,
Token,
TokenAmount,
TradeType,
WETH9,
ChainId,
} from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { Pair as V2Pair, Route as V2Route, Trade as V2Trade } from '@uniswap/v2-sdk'
import { useMemo } from 'react'
import { useActiveWeb3React } from '../hooks'
import { useAllTokens } from '../hooks/Tokens'
import { useV1FactoryContract } from '../hooks/useContract'
import { Version } from '../hooks/useToggledVersion'
import { NEVER_RELOAD, useSingleCallResult, useSingleContractMultipleData } from '../state/multicall/hooks'
import { useETHBalances, useTokenBalance, useTokenBalances } from '../state/wallet/hooks'
import { supportedChainId } from 'utils'
export function useV1ExchangeAddress(tokenAddress?: string): string | undefined {
const contract = useV1FactoryContract()
const inputs = useMemo(() => [tokenAddress], [tokenAddress])
return useSingleCallResult(contract, 'getExchange', inputs)?.result?.[0]
}
export class MockV1Pair extends V2Pair {
constructor(etherAmount: BigintIsh, tokenAmount: TokenAmount) {
super(tokenAmount, new TokenAmount(WETH9[tokenAmount.token.chainId as ChainId], etherAmount))
}
}
function useMockV1Pair(inputCurrency?: Currency): MockV1Pair | undefined {
const token = inputCurrency instanceof Token ? inputCurrency : undefined
const chainId: ChainId | undefined = token && supportedChainId(token.chainId)
const isWETH = Boolean(token && chainId && token.equals(WETH9[chainId]))
const v1PairAddress = useV1ExchangeAddress(isWETH ? undefined : token?.address)
const tokenBalance = useTokenBalance(v1PairAddress, token)
const ETHBalance = useETHBalances([v1PairAddress])[v1PairAddress ?? '']
return useMemo(
() =>
token && tokenBalance && ETHBalance && inputCurrency ? new MockV1Pair(ETHBalance.raw, tokenBalance) : undefined,
[ETHBalance, inputCurrency, token, tokenBalance]
)
}
// returns all v1 exchange addresses in the user's token list
export function useAllTokenV1Exchanges(): { [exchangeAddress: string]: Token } {
const allTokens = useAllTokens()
const factory = useV1FactoryContract()
const args = useMemo(() => Object.keys(allTokens).map((tokenAddress) => [tokenAddress]), [allTokens])
const data = useSingleContractMultipleData(factory, 'getExchange', args, NEVER_RELOAD)
return useMemo(
() =>
data?.reduce<{ [exchangeAddress: string]: Token }>((memo, { result }, ix) => {
if (result?.[0] && result[0] !== AddressZero) {
memo[result[0]] = allTokens[args[ix][0]]
}
return memo
}, {}) ?? {},
[allTokens, args, data]
)
}
// returns whether any of the tokens in the user's token list have liquidity on v1
export function useUserHasLiquidityInAllTokens(): boolean | undefined {
const { account, chainId } = useActiveWeb3React()
const exchanges = useAllTokenV1Exchanges()
const v1ExchangeLiquidityTokens = useMemo(
() =>
chainId ? Object.keys(exchanges).map((address) => new Token(chainId, address, 18, 'UNI-V1', 'Uniswap V1')) : [],
[chainId, exchanges]
)
const balances = useTokenBalances(account ?? undefined, v1ExchangeLiquidityTokens)
return useMemo(
() =>
Object.keys(balances).some((tokenAddress) => {
const b = balances[tokenAddress]?.raw
return b && JSBI.greaterThan(b, JSBI.BigInt(0))
}),
[balances]
)
}
/**
* Returns the trade to execute on V1 to go between input and output token
*/
export function useV1Trade(
isExactIn?: boolean,
inputCurrency?: Currency,
outputCurrency?: Currency,
exactAmount?: CurrencyAmount
): V2Trade | undefined {
// get the mock v1 pairs
const inputPair = useMockV1Pair(inputCurrency)
const outputPair = useMockV1Pair(outputCurrency)
const inputIsETH = inputCurrency === ETHER
const outputIsETH = outputCurrency === ETHER
// construct a direct or through ETH v1 route
let pairs: V2Pair[] = []
if (inputIsETH && outputPair) {
pairs = [outputPair]
} else if (outputIsETH && inputPair) {
pairs = [inputPair]
}
// if neither are ETH, it's token-to-token (if they both exist)
else if (inputPair && outputPair) {
pairs = [inputPair, outputPair]
}
const route = inputCurrency && pairs && pairs.length > 0 && new V2Route(pairs, inputCurrency, outputCurrency)
let v1Trade: V2Trade | undefined
try {
v1Trade =
route && exactAmount
? new V2Trade(route, exactAmount, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT)
: undefined
} catch (error) {
console.debug('Failed to create V1 trade', error)
}
return v1Trade
}
export function getTradeVersion(trade?: V2Trade): Version | undefined {
const isV1 = trade?.route?.pairs?.some((pair) => pair instanceof MockV1Pair)
if (isV1) return Version.v1
if (isV1 === false) return Version.v2
return undefined
}
// returns the v1 exchange against which a trade should be executed
export function useV1TradeExchangeAddress(trade: V2Trade | undefined): string | undefined {
const tokenAddress: string | undefined = useMemo(() => {
if (!trade) return undefined
const isV1 = getTradeVersion(trade) === Version.v1
if (!isV1) return undefined
return trade.inputAmount instanceof TokenAmount
? trade.inputAmount.token.address
: trade.outputAmount instanceof TokenAmount
? trade.outputAmount.token.address
: undefined
}, [trade])
return useV1ExchangeAddress(tokenAddress)
}
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Version } from '../hooks/useToggledVersion'
export function getTradeVersion(trade?: V2Trade): Version | undefined {
if (!trade) return undefined
return Version.v2
}
import { Token } from '@uniswap/sdk-core' import { Token } from '@uniswap/sdk-core'
import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk' import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk'
import { nearestUsableTick, TickMath } from '@uniswap/v3-sdk/dist/'
import { ZERO_ADDRESS } from '../constants' import { ZERO_ADDRESS } from '../constants'
import { useMemo } from 'react' import { useEffect, useMemo, useState } from 'react'
import { Result, useSingleCallResult, useSingleContractMultipleData } from 'state/multicall/hooks' import { Result, useSingleCallResult, useSingleContractMultipleData } from 'state/multicall/hooks'
import { useTickLens, useV3Factory } from './useContract' import { useTickLens, useV3Factory } from './useContract'
// the following should probably all be from the sdk, just mocking it for now
function MIN_TICK(tickSpacing: number) {
return Math.ceil(-887272 / tickSpacing) * tickSpacing
}
function MAX_TICK(tickSpacing: number) {
return Math.floor(887272 / tickSpacing) * tickSpacing
}
function bitmapIndex(tick: number, tickSpacing: number) { function bitmapIndex(tick: number, tickSpacing: number) {
const compressed = tick / tickSpacing return Math.floor(tick / tickSpacing / 256)
return compressed >> 8
} }
const REFRESH_FREQUENCY = { blocksPerFetch: 2 } const REFRESH_FREQUENCY = { blocksPerFetch: 2 }
...@@ -38,19 +29,22 @@ export function useAllV3Ticks( ...@@ -38,19 +29,22 @@ export function useAllV3Ticks(
valid: boolean valid: boolean
tickData: TickData[] tickData: TickData[]
} { } {
const tickSpacing = useMemo(() => (feeAmount ? TICK_SPACINGS[feeAmount] : undefined), [feeAmount]) const tickSpacing = feeAmount && TICK_SPACINGS[feeAmount]
const minIndex = useMemo(() => (tickSpacing ? bitmapIndex(MIN_TICK(tickSpacing), tickSpacing) : undefined), [ const minIndex = useMemo(
tickSpacing, () => (tickSpacing ? bitmapIndex(nearestUsableTick(TickMath.MIN_TICK, tickSpacing), tickSpacing) : undefined),
]) [tickSpacing]
const maxIndex = useMemo(() => (tickSpacing ? bitmapIndex(MAX_TICK(tickSpacing), tickSpacing) : undefined), [ )
tickSpacing, const maxIndex = useMemo(
]) () => (tickSpacing ? bitmapIndex(nearestUsableTick(TickMath.MAX_TICK, tickSpacing), tickSpacing) : undefined),
[tickSpacing]
)
const [tickDataLatestSynced, setTickDataLatestSynced] = useState<TickData[]>([])
// fetch the pool address // fetch the pool address
const factoryContract = useV3Factory() const factoryContract = useV3Factory()
const addressParams = token0 && token1 && feeAmount ? [token0.address, token1.address, feeAmount] : undefined const poolAddress = useSingleCallResult(factoryContract, 'getPool', [token0?.address, token1?.address, feeAmount])
const poolAddress = useSingleCallResult(addressParams ? factoryContract : undefined, 'getPool', addressParams)
.result?.[0] .result?.[0]
const tickLensArgs: [string, number][] = useMemo( const tickLensArgs: [string, number][] = useMemo(
...@@ -98,14 +92,21 @@ export function useAllV3Ticks( ...@@ -98,14 +92,21 @@ export function useAllV3Ticks(
[callStates] [callStates]
) )
// return the latest synced tickdata even if we are still loading the newest data
useEffect(() => {
if (!syncing && !loading && !error && valid) {
setTickDataLatestSynced(tickData)
}
}, [error, loading, syncing, tickData, valid])
return useMemo( return useMemo(
() => ({ () => ({
loading, loading,
syncing, syncing,
error, error,
valid, valid,
tickData, tickData: tickDataLatestSynced,
}), }),
[loading, syncing, error, valid, tickData] [loading, syncing, error, valid, tickDataLatestSynced]
) )
} }
...@@ -5,14 +5,12 @@ import { Trade } from '@uniswap/v2-sdk' ...@@ -5,14 +5,12 @@ import { Trade } from '@uniswap/v2-sdk'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { ROUTER_ADDRESS } from '../constants' import { ROUTER_ADDRESS } from '../constants'
import { useTokenAllowance } from '../data/Allowances' import { useTokenAllowance } from '../data/Allowances'
import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1'
import { Field } from '../state/swap/actions' import { Field } from '../state/swap/actions'
import { useTransactionAdder, useHasPendingApproval } from '../state/transactions/hooks' import { useTransactionAdder, useHasPendingApproval } from '../state/transactions/hooks'
import { computeSlippageAdjustedAmounts } from '../utils/prices' import { computeSlippageAdjustedAmounts } from '../utils/prices'
import { calculateGasMargin } from '../utils' import { calculateGasMargin } from '../utils'
import { useTokenContract } from './useContract' import { useTokenContract } from './useContract'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
import { Version } from './useToggledVersion'
export enum ApprovalState { export enum ApprovalState {
UNKNOWN, UNKNOWN,
...@@ -106,7 +104,5 @@ export function useApproveCallbackFromTrade(trade?: Trade, allowedSlippage = 0) ...@@ -106,7 +104,5 @@ export function useApproveCallbackFromTrade(trade?: Trade, allowedSlippage = 0)
() => (trade ? computeSlippageAdjustedAmounts(trade, allowedSlippage)[Field.INPUT] : undefined), () => (trade ? computeSlippageAdjustedAmounts(trade, allowedSlippage)[Field.INPUT] : undefined),
[trade, allowedSlippage] [trade, allowedSlippage]
) )
const tradeIsV1 = getTradeVersion(trade) === Version.v1 return useApproveCallback(amountToApprove, ROUTER_ADDRESS)
const v1ExchangeAddress = useV1TradeExchangeAddress(trade)
return useApproveCallback(amountToApprove, tradeIsV1 ? v1ExchangeAddress : ROUTER_ADDRESS)
} }
...@@ -29,10 +29,9 @@ import { ...@@ -29,10 +29,9 @@ import {
MULTICALL_ADDRESSES, MULTICALL_ADDRESSES,
} from 'constants/index' } from 'constants/index'
import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json' import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from 'constants/v1'
import { import {
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
FACTORY_ADDRESSES, V3_CORE_FACTORY_ADDRESSES,
TICK_LENS_ADDRESSES, TICK_LENS_ADDRESSES,
V2_MIGRATOR_ADDRESSES, V2_MIGRATOR_ADDRESSES,
} from 'constants/v3' } from 'constants/v3'
...@@ -58,11 +57,6 @@ export function useContract(address: string | undefined, ABI: any, withSignerIfP ...@@ -58,11 +57,6 @@ export function useContract(address: string | undefined, ABI: any, withSignerIfP
}, [address, ABI, library, withSignerIfPossible, account]) }, [address, ABI, library, withSignerIfPossible, account])
} }
export function useV1FactoryContract(): Contract | null {
const { chainId } = useActiveWeb3React()
return useContract(chainId && V1_FACTORY_ADDRESSES[chainId], V1_FACTORY_ABI, false)
}
export function useV1MigratorContract(): Contract | null { export function useV1MigratorContract(): Contract | null {
return useContract(V1_MIGRATOR_ADDRESS, MIGRATOR_ABI, true) return useContract(V1_MIGRATOR_ADDRESS, MIGRATOR_ABI, true)
} }
...@@ -72,13 +66,10 @@ export function useV2MigratorContract(): V3Migrator | null { ...@@ -72,13 +66,10 @@ export function useV2MigratorContract(): V3Migrator | null {
return useContract(chainId && V2_MIGRATOR_ADDRESSES[chainId], V2MigratorABI, true) as V3Migrator | null return useContract(chainId && V2_MIGRATOR_ADDRESSES[chainId], V2MigratorABI, true) as V3Migrator | null
} }
export function useV1ExchangeContract(address?: string, withSignerIfPossible?: boolean): Contract | null {
return useContract(address, V1_EXCHANGE_ABI, withSignerIfPossible)
}
export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null { export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null {
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible) return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible)
} }
export function useWETHContract(withSignerIfPossible?: boolean): Contract | null { export function useWETHContract(withSignerIfPossible?: boolean): Contract | null {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const address = chainId && chainId in WETH9 ? WETH9[chainId].address : undefined const address = chainId && chainId in WETH9 ? WETH9[chainId].address : undefined
...@@ -162,7 +153,7 @@ export function useV3NFTPositionManagerContract(): NonfungiblePositionManager | ...@@ -162,7 +153,7 @@ export function useV3NFTPositionManagerContract(): NonfungiblePositionManager |
export function useV3Factory(): UniswapV3Factory | null { export function useV3Factory(): UniswapV3Factory | null {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const address = chainId ? FACTORY_ADDRESSES[chainId] : undefined const address = chainId ? V3_CORE_FACTORY_ADDRESSES[chainId] : undefined
return useContract(address, V3FactoryABI) as UniswapV3Factory | null return useContract(address, V3FactoryABI) as UniswapV3Factory | null
} }
......
...@@ -4,13 +4,11 @@ import { JSBI, Router, SwapParameters, Trade } from '@uniswap/v2-sdk' ...@@ -4,13 +4,11 @@ import { JSBI, Router, SwapParameters, Trade } from '@uniswap/v2-sdk'
import { Percent, TradeType } from '@uniswap/sdk-core' import { Percent, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react' import { useMemo } from 'react'
import { BIPS_BASE, INITIAL_ALLOWED_SLIPPAGE } from '../constants' import { BIPS_BASE, INITIAL_ALLOWED_SLIPPAGE } from '../constants'
import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1' import { getTradeVersion } from '../data/getTradeVersion'
import { useTransactionAdder } from '../state/transactions/hooks' import { useTransactionAdder } from '../state/transactions/hooks'
import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils' import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils'
import isZero from '../utils/isZero' import isZero from '../utils/isZero'
import v1SwapArguments from '../utils/v1SwapArguments'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
import { useV1ExchangeContract } from './useContract'
import useTransactionDeadline from './useTransactionDeadline' import useTransactionDeadline from './useTransactionDeadline'
import useENS from './useENS' import useENS from './useENS'
import { Version } from './useToggledVersion' import { Version } from './useToggledVersion'
...@@ -55,22 +53,16 @@ function useSwapCallArguments( ...@@ -55,22 +53,16 @@ function useSwapCallArguments(
const recipient = recipientAddressOrName === null ? account : recipientAddress const recipient = recipientAddressOrName === null ? account : recipientAddress
const deadline = useTransactionDeadline() const deadline = useTransactionDeadline()
const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true)
return useMemo(() => { return useMemo(() => {
const tradeVersion = getTradeVersion(trade) if (!trade || !recipient || !library || !account || !chainId || !deadline) return []
if (!trade || !recipient || !library || !account || !tradeVersion || !chainId || !deadline) return []
const contract: Contract | null = const contract: Contract | null = getRouterContract(chainId, library, account)
tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange
if (!contract) { if (!contract) {
return [] return []
} }
const swapMethods = [] const swapMethods = []
switch (tradeVersion) {
case Version.v2:
swapMethods.push( swapMethods.push(
Router.swapCallParameters(trade, { Router.swapCallParameters(trade, {
feeOnTransfer: false, feeOnTransfer: false,
...@@ -90,19 +82,8 @@ function useSwapCallArguments( ...@@ -90,19 +82,8 @@ function useSwapCallArguments(
}) })
) )
} }
break
case Version.v1:
swapMethods.push(
v1SwapArguments(trade, {
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
recipient,
deadline: deadline.toNumber(),
})
)
break
}
return swapMethods.map((parameters) => ({ parameters, contract })) return swapMethods.map((parameters) => ({ parameters, contract }))
}, [account, allowedSlippage, chainId, deadline, library, recipient, trade, v1Exchange]) }, [account, allowedSlippage, chainId, deadline, library, recipient, trade])
} }
// returns a function that will execute a swap, if the parameters are all valid // returns a function that will execute a swap, if the parameters are all valid
......
import useParsedQueryString from './useParsedQueryString' import useParsedQueryString from './useParsedQueryString'
export enum Version { export enum Version {
v1 = 'v1',
v2 = 'v2', v2 = 'v2',
} }
...@@ -9,7 +8,8 @@ export const DEFAULT_VERSION: Version = Version.v2 ...@@ -9,7 +8,8 @@ export const DEFAULT_VERSION: Version = Version.v2
export default function useToggledVersion(): Version { export default function useToggledVersion(): Version {
const { use } = useParsedQueryString() const { use } = useParsedQueryString()
if (!use || typeof use !== 'string') return Version.v2 if (typeof use !== 'string') {
if (use.toLowerCase() === 'v1') return Version.v1 return DEFAULT_VERSION
}
return DEFAULT_VERSION return DEFAULT_VERSION
} }
...@@ -9,8 +9,9 @@ export function RedirectDuplicateTokenIdsV2(props: RouteComponentProps<{ currenc ...@@ -9,8 +9,9 @@ export function RedirectDuplicateTokenIdsV2(props: RouteComponentProps<{ currenc
}, },
} = props } = props
if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) { if (currencyIdA && currencyIdB && currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
return <Redirect to={`/add/v2/${currencyIdA}`} /> return <Redirect to={`/add/v2/${currencyIdA}`} />
} }
return <AddLiquidityV2 {...props} /> return <AddLiquidityV2 {...props} />
} }
...@@ -14,9 +14,6 @@ import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader' ...@@ -14,9 +14,6 @@ import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects' import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
import Earn from './Earn' import Earn from './Earn'
import Manage from './Earn/Manage' import Manage from './Earn/Manage'
import MigrateV1 from './MigrateV1'
import MigrateV1Exchange from './MigrateV1/MigrateV1Exchange'
import RemoveV1Exchange from './MigrateV1/RemoveV1Exchange'
import MigrateV2 from './MigrateV2' import MigrateV2 from './MigrateV2'
import MigrateV2Pair from './MigrateV2/MigrateV2Pair' import MigrateV2Pair from './MigrateV2/MigrateV2Pair'
import Pool from './Pool' import Pool from './Pool'
...@@ -114,12 +111,9 @@ export default function App() { ...@@ -114,12 +111,9 @@ export default function App() {
component={RedirectDuplicateTokenIds} component={RedirectDuplicateTokenIds}
/> />
<Route exact strict path="/remove/v1/:address" component={RemoveV1Exchange} />
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} /> <Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} /> <Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
<Route exact strict path="/migrate/v1" component={MigrateV1} />
<Route exact strict path="/migrate/v1/:address" component={MigrateV1Exchange} />
<Route exact strict path="/migrate/v2" component={MigrateV2} /> <Route exact strict path="/migrate/v2" component={MigrateV2} />
<Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} /> <Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} />
......
import React from 'react'
import { AutoColumn } from '../../components/Column'
import { TYPE } from '../../theme'
export function EmptyState({ message }: { message: string }) {
return (
<AutoColumn style={{ minHeight: 200, justifyContent: 'center', alignItems: 'center' }}>
<TYPE.body>{message}</TYPE.body>
</AutoColumn>
)
}
This diff is collapsed.
import { TransactionResponse } from '@ethersproject/abstract-provider'
import { Token, TokenAmount, WETH9, Fraction, Percent, CurrencyAmount } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import React, { useCallback, useMemo, useState } from 'react'
import ReactGA from 'react-ga'
import { Redirect, RouteComponentProps } from 'react-router'
import { ButtonConfirmed } from '../../components/Button'
import { LightCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column'
import QuestionHelper from '../../components/QuestionHelper'
import { AutoRow } from '../../components/Row'
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants'
import { useActiveWeb3React } from '../../hooks'
import { useToken } from '../../hooks/Tokens'
import { useV1ExchangeContract } from '../../hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks'
import { useIsTransactionPending, useTransactionAdder } from '../../state/transactions/hooks'
import { useTokenBalance, useETHBalances } from '../../state/wallet/hooks'
import { BackArrow, TYPE } from '../../theme'
import { isAddress } from '../../utils'
import { BodyWrapper } from '../AppBody'
import { EmptyState } from './EmptyState'
import { V1LiquidityInfo } from './MigrateV1Exchange'
import { AddressZero } from '@ethersproject/constants'
import { Dots } from '../../components/swap/styleds'
import { Contract } from '@ethersproject/contracts'
import { useTotalSupply } from '../../data/TotalSupply'
const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
const ZERO = JSBI.BigInt(0)
const ONE = JSBI.BigInt(1)
const ZERO_FRACTION = new Fraction(ZERO, ONE)
function V1PairRemoval({
exchangeContract,
liquidityTokenAmount,
token,
}: {
exchangeContract: Contract
liquidityTokenAmount: TokenAmount
token: Token
}) {
const { chainId } = useActiveWeb3React()
const totalSupply = useTotalSupply(liquidityTokenAmount.token)
const exchangeETHBalance = useETHBalances([liquidityTokenAmount.token.address])?.[liquidityTokenAmount.token.address]
const exchangeTokenBalance = useTokenBalance(liquidityTokenAmount.token.address, token)
const [confirmingRemoval, setConfirmingRemoval] = useState<boolean>(false)
const [pendingRemovalHash, setPendingRemovalHash] = useState<string | null>(null)
const shareFraction: Fraction = totalSupply ? new Percent(liquidityTokenAmount.raw, totalSupply.raw) : ZERO_FRACTION
const ethWorth: CurrencyAmount = exchangeETHBalance
? CurrencyAmount.ether(exchangeETHBalance.multiply(shareFraction).multiply(WEI_DENOM).quotient)
: CurrencyAmount.ether(ZERO)
const tokenWorth: TokenAmount = exchangeTokenBalance
? new TokenAmount(token, shareFraction.multiply(exchangeTokenBalance.raw).quotient)
: new TokenAmount(token, ZERO)
const addTransaction = useTransactionAdder()
const isRemovalPending = useIsTransactionPending(pendingRemovalHash ?? undefined)
const remove = useCallback(() => {
if (!liquidityTokenAmount) return
setConfirmingRemoval(true)
exchangeContract
.removeLiquidity(
liquidityTokenAmount.raw.toString(),
1, // min_eth, this is safe because we're removing liquidity
1, // min_tokens, this is safe because we're removing liquidity
Math.floor(new Date().getTime() / 1000) + DEFAULT_DEADLINE_FROM_NOW
)
.then((response: TransactionResponse) => {
ReactGA.event({
category: 'Remove',
action: 'V1',
label: token?.symbol,
})
addTransaction(response, {
summary: `Remove ${chainId && token.equals(WETH9[chainId]) ? 'WETH' : token.symbol}/ETH V1 liquidity`,
})
setPendingRemovalHash(response.hash)
})
.catch((error: Error) => {
console.error(error)
setConfirmingRemoval(false)
})
}, [exchangeContract, liquidityTokenAmount, token, chainId, addTransaction])
const noLiquidityTokens = !!liquidityTokenAmount && liquidityTokenAmount.equalTo(ZERO)
const isSuccessfullyRemoved = !!pendingRemovalHash && noLiquidityTokens
return (
<AutoColumn gap="20px">
<TYPE.body my={9} style={{ fontWeight: 400 }}>
This tool will remove your V1 liquidity and send the underlying assets to your wallet.
</TYPE.body>
<LightCard>
<V1LiquidityInfo
token={token}
liquidityTokenAmount={liquidityTokenAmount}
tokenWorth={tokenWorth}
ethWorth={ethWorth}
/>
<div style={{ display: 'flex', marginTop: '1rem' }}>
<ButtonConfirmed
confirmed={isSuccessfullyRemoved}
disabled={isSuccessfullyRemoved || noLiquidityTokens || isRemovalPending || confirmingRemoval}
onClick={remove}
>
{isSuccessfullyRemoved ? 'Success' : isRemovalPending ? <Dots>Removing</Dots> : 'Remove'}
</ButtonConfirmed>
</div>
</LightCard>
<TYPE.darkGray style={{ textAlign: 'center' }}>
{`Your Uniswap V1 ${
chainId && token.equals(WETH9[chainId]) ? 'WETH' : token.symbol
}/ETH liquidity will be redeemed for underlying assets.`}
</TYPE.darkGray>
</AutoColumn>
)
}
export default function RemoveV1Exchange({
match: {
params: { address },
},
}: RouteComponentProps<{ address: string }>) {
const validatedAddress = isAddress(address)
const { chainId, account } = useActiveWeb3React()
const exchangeContract = useV1ExchangeContract(validatedAddress ? validatedAddress : undefined, true)
const tokenAddress = useSingleCallResult(exchangeContract, 'tokenAddress', undefined, NEVER_RELOAD)?.result?.[0]
const token = useToken(tokenAddress)
const liquidityToken: Token | undefined = useMemo(
() =>
validatedAddress && chainId && token
? new Token(chainId, validatedAddress, 18, `UNI-V1-${token.symbol}`, 'Uniswap V1')
: undefined,
[chainId, validatedAddress, token]
)
const userLiquidityBalance = useTokenBalance(account ?? undefined, liquidityToken)
// redirect for invalid url params
if (!validatedAddress || tokenAddress === AddressZero) {
console.error('Invalid address in path', address)
return <Redirect to="/migrate/v1" />
}
return (
<BodyWrapper style={{ padding: 24 }} id="remove-v1-exchange">
<AutoColumn gap="16px">
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<BackArrow to="/migrate/v1" />
<TYPE.mediumHeader>Remove V1 Liquidity</TYPE.mediumHeader>
<div>
<QuestionHelper text="Remove your Uniswap V1 liquidity tokens." />
</div>
</AutoRow>
{!account ? (
<TYPE.largeHeader>You must connect an account.</TYPE.largeHeader>
) : userLiquidityBalance && token && exchangeContract ? (
<V1PairRemoval
exchangeContract={exchangeContract}
liquidityTokenAmount={userLiquidityBalance}
token={token}
/>
) : (
<EmptyState message="Loading..." />
)}
</AutoColumn>
</BodyWrapper>
)
}
import { Token } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import React, { useCallback, useContext, useMemo, useState, useEffect } from 'react'
import { ThemeContext } from 'styled-components'
import { AutoColumn } from '../../components/Column'
import { AutoRow } from '../../components/Row'
import { SearchInput } from '../../components/SearchModal/styleds'
import { useAllTokenV1Exchanges } from '../../data/V1'
import { useActiveWeb3React } from '../../hooks'
import { useAllTokens, useToken } from '../../hooks/Tokens'
import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks'
import { BackArrow, TYPE } from '../../theme'
import { LightCard } from '../../components/Card'
import { BodyWrapper } from '../AppBody'
import { EmptyState } from './EmptyState'
import V1PositionCard from '../../components/PositionCard/V1'
import QuestionHelper from '../../components/QuestionHelper'
import { Dots } from '../../components/swap/styleds'
import { useAddUserToken } from '../../state/user/hooks'
import { isTokenOnList } from '../../utils'
import { useCombinedActiveList } from '../../state/lists/hooks'
export default function MigrateV1() {
const theme = useContext(ThemeContext)
const { account, chainId } = useActiveWeb3React()
const [tokenSearch, setTokenSearch] = useState<string>('')
const handleTokenSearchChange = useCallback((e) => setTokenSearch(e.target.value), [setTokenSearch])
// automatically add the search token
const token = useToken(tokenSearch)
const selectedTokenListTokens = useCombinedActiveList()
const isOnSelectedList = isTokenOnList(selectedTokenListTokens, token ?? undefined)
const allTokens = useAllTokens()
const addToken = useAddUserToken()
useEffect(() => {
if (token && !isOnSelectedList && !allTokens[token.address]) {
addToken(token)
}
}, [token, isOnSelectedList, addToken, allTokens])
// get V1 LP balances
const V1Exchanges = useAllTokenV1Exchanges()
const V1LiquidityTokens: Token[] = useMemo(() => {
return chainId
? Object.keys(V1Exchanges).map(
(exchangeAddress) => new Token(chainId, exchangeAddress, 18, 'UNI-V1', 'Uniswap V1')
)
: []
}, [chainId, V1Exchanges])
const [V1LiquidityBalances, V1LiquidityBalancesLoading] = useTokenBalancesWithLoadingIndicator(
account ?? undefined,
V1LiquidityTokens
)
const allV1PairsWithLiquidity = V1LiquidityTokens.filter((V1LiquidityToken) => {
const balance = V1LiquidityBalances?.[V1LiquidityToken.address]
return balance && JSBI.greaterThan(balance.raw, JSBI.BigInt(0))
}).map((V1LiquidityToken) => {
const balance = V1LiquidityBalances[V1LiquidityToken.address]
return balance ? (
<V1PositionCard
key={V1LiquidityToken.address}
token={V1Exchanges[V1LiquidityToken.address]}
V1LiquidityBalance={balance}
/>
) : null
})
// should never always be false, because a V1 exhchange exists for WETH on all testnets
const isLoading = Object.keys(V1Exchanges)?.length === 0 || V1LiquidityBalancesLoading
return (
<BodyWrapper style={{ padding: 24 }}>
<AutoColumn gap="16px">
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<BackArrow to="/pool" />
<TYPE.mediumHeader>Migrate V1 Liquidity</TYPE.mediumHeader>
<div>
<QuestionHelper text="Migrate your liquidity tokens from Uniswap V1 to Uniswap V2." />
</div>
</AutoRow>
<TYPE.body style={{ marginBottom: 8, fontWeight: 400 }}>
For each pool shown below, click migrate to remove your liquidity from Uniswap V1 and deposit it into Uniswap
V2.
</TYPE.body>
{!account ? (
<LightCard padding="40px">
<TYPE.body color={theme.text3} textAlign="center">
Connect to a wallet to view your V1 liquidity.
</TYPE.body>
</LightCard>
) : isLoading ? (
<LightCard padding="40px">
<TYPE.body color={theme.text3} textAlign="center">
<Dots>Loading</Dots>
</TYPE.body>
</LightCard>
) : (
<>
<AutoRow>
<SearchInput
value={tokenSearch}
onChange={handleTokenSearchChange}
placeholder="Enter a token address to find liquidity"
/>
</AutoRow>
{allV1PairsWithLiquidity?.length > 0 ? (
<>{allV1PairsWithLiquidity}</>
) : (
<EmptyState message="No V1 Liquidity found." />
)}
</>
)}
</AutoColumn>
</BodyWrapper>
)
}
...@@ -17,7 +17,6 @@ import { useTokenBalance } from '../../state/wallet/hooks' ...@@ -17,7 +17,6 @@ import { useTokenBalance } from '../../state/wallet/hooks'
import { BackArrow, ExternalLink, TYPE } from '../../theme' import { BackArrow, ExternalLink, TYPE } from '../../theme'
import { getEtherscanLink, isAddress } from '../../utils' import { getEtherscanLink, isAddress } from '../../utils'
import { BodyWrapper } from '../AppBody' import { BodyWrapper } from '../AppBody'
import { EmptyState } from '../MigrateV1/EmptyState'
import { V2_MIGRATOR_ADDRESSES } from 'constants/v3' import { V2_MIGRATOR_ADDRESSES } from 'constants/v3'
import { PoolState, usePool } from 'data/Pools' import { PoolState, usePool } from 'data/Pools'
import { FeeAmount, Pool, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk' import { FeeAmount, Pool, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk'
...@@ -43,6 +42,14 @@ import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' ...@@ -43,6 +42,14 @@ import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
const ZERO = JSBI.BigInt(0) const ZERO = JSBI.BigInt(0)
function EmptyState({ message }: { message: string }) {
return (
<AutoColumn style={{ minHeight: 200, justifyContent: 'center', alignItems: 'center' }}>
<TYPE.body>{message}</TYPE.body>
</AutoColumn>
)
}
function LiquidityInfo({ token0Amount, token1Amount }: { token0Amount: TokenAmount; token1Amount: TokenAmount }) { function LiquidityInfo({ token0Amount, token1Amount }: { token0Amount: TokenAmount; token1Amount: TokenAmount }) {
return ( return (
<> <>
...@@ -198,10 +205,9 @@ function V2PairMigration({ ...@@ -198,10 +205,9 @@ function V2PairMigration({
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: BigNumber } | null>( const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: BigNumber } | null>(
null null
) )
const [approval, approveManually] = useApproveCallback(
pairBalance, const migratorAddress = chainId && V2_MIGRATOR_ADDRESSES[chainId]
chainId ? V2_MIGRATOR_ADDRESSES[chainId] : undefined const [approval, approveManually] = useApproveCallback(pairBalance, migratorAddress)
)
const isArgentWallet = useIsArgentWallet() const isArgentWallet = useIsArgentWallet()
const approve = useCallback(async () => { const approve = useCallback(async () => {
...@@ -389,8 +395,8 @@ function V2PairMigration({ ...@@ -389,8 +395,8 @@ function V2PairMigration({
<AutoColumn gap="20px"> <AutoColumn gap="20px">
<TYPE.body my={9} style={{ fontWeight: 400 }}> <TYPE.body my={9} style={{ fontWeight: 400 }}>
This tool will safely migrate your V2 liquidity to V3. The process is completely trustless thanks to the{' '} This tool will safely migrate your V2 liquidity to V3. The process is completely trustless thanks to the{' '}
{chainId && ( {chainId && migratorAddress && (
<ExternalLink href={getEtherscanLink(chainId, V2_MIGRATOR_ADDRESSES[chainId], 'address')}> <ExternalLink href={getEtherscanLink(chainId, migratorAddress, 'address')}>
<TYPE.blue display="inline">Uniswap migration contract↗</TYPE.blue> <TYPE.blue display="inline">Uniswap migration contract↗</TYPE.blue>
</ExternalLink> </ExternalLink>
)} )}
......
...@@ -9,7 +9,6 @@ import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks' ...@@ -9,7 +9,6 @@ import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks'
import { BackArrow, StyledInternalLink, TYPE } from '../../theme' import { BackArrow, StyledInternalLink, TYPE } from '../../theme'
import { LightCard } from '../../components/Card' import { LightCard } from '../../components/Card'
import { BodyWrapper } from '../AppBody' import { BodyWrapper } from '../AppBody'
import { EmptyState } from '../MigrateV1/EmptyState'
import QuestionHelper from '../../components/QuestionHelper' import QuestionHelper from '../../components/QuestionHelper'
import { Dots } from '../../components/swap/styleds' import { Dots } from '../../components/swap/styleds'
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks' import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
...@@ -18,6 +17,13 @@ import { usePairs } from 'data/V2' ...@@ -18,6 +17,13 @@ import { usePairs } from 'data/V2'
// TODO there's a bug in loading where "No V2 Liquidity found" flashes // TODO there's a bug in loading where "No V2 Liquidity found" flashes
// TODO add support for more pairs // TODO add support for more pairs
function EmptyState({ message }: { message: string }) {
return (
<AutoColumn style={{ minHeight: 200, justifyContent: 'center', alignItems: 'center' }}>
<TYPE.body>{message}</TYPE.body>
</AutoColumn>
)
}
export default function MigrateV2() { export default function MigrateV2() {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
......
...@@ -3,11 +3,9 @@ import styled, { ThemeContext } from 'styled-components' ...@@ -3,11 +3,9 @@ import styled, { ThemeContext } from 'styled-components'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { SwapPoolTabs } from '../../components/NavigationTabs' import { SwapPoolTabs } from '../../components/NavigationTabs'
import FullPositionCard from '../../components/PositionCard' import FullPositionCard from '../../components/PositionCard'
import { useUserHasLiquidityInAllTokens } from '../../data/V1'
import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks' import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks'
import { StyledInternalLink, ExternalLink, TYPE, HideSmall } from '../../theme' import { ExternalLink, TYPE, HideSmall } from '../../theme'
import { Text } from 'rebass' import { Text } from 'rebass'
import Card from '../../components/Card' import Card from '../../components/Card'
import { RowBetween, RowFixed } from '../../components/Row' import { RowBetween, RowFixed } from '../../components/Row'
...@@ -108,8 +106,6 @@ export default function Pool() { ...@@ -108,8 +106,6 @@ export default function Pool() {
const allV2PairsWithLiquidity = v2Pairs.map(([, pair]) => pair).filter((v2Pair): v2Pair is Pair => Boolean(v2Pair)) const allV2PairsWithLiquidity = v2Pairs.map(([, pair]) => pair).filter((v2Pair): v2Pair is Pair => Boolean(v2Pair))
const hasV1Liquidity = useUserHasLiquidityInAllTokens()
// show liquidity even if its deposited in rewards contract // show liquidity even if its deposited in rewards contract
const stakingInfo = useStakingInfo() const stakingInfo = useStakingInfo()
const stakingInfosWithBalance = stakingInfo?.filter((pool) => JSBI.greaterThan(pool.stakedAmount.raw, BIG_INT_ZERO)) const stakingInfosWithBalance = stakingInfo?.filter((pool) => JSBI.greaterThan(pool.stakedAmount.raw, BIG_INT_ZERO))
...@@ -223,15 +219,6 @@ export default function Pool() { ...@@ -223,15 +219,6 @@ export default function Pool() {
</TYPE.body> </TYPE.body>
</EmptyProposals> </EmptyProposals>
)} )}
<AutoColumn justify={'center'} gap="md">
<Text textAlign="center" fontSize={14} style={{ padding: '.5rem 0 .5rem 0' }}>
{hasV1Liquidity ? 'Uniswap V1 liquidity found!' : "Don't see a pool you joined?"}{' '}
<StyledInternalLink id="import-pool-link" to={hasV1Liquidity ? '/migrate/v1' : '/find'}>
{hasV1Liquidity ? 'Migrate now.' : 'Import it.'}
</StyledInternalLink>
</Text>
</AutoColumn>
</AutoColumn> </AutoColumn>
</AutoColumn> </AutoColumn>
</PageWrapper> </PageWrapper>
......
...@@ -14,7 +14,6 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel' ...@@ -14,7 +14,6 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { SwapPoolTabs } from '../../components/NavigationTabs' import { SwapPoolTabs } from '../../components/NavigationTabs'
import { AutoRow, RowBetween } from '../../components/Row' import { AutoRow, RowBetween } from '../../components/Row'
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown' import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
import BetterTradeLink, { DefaultVersionLink } from '../../components/swap/BetterTradeLink'
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee' import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
import { ArrowWrapper, BottomGrouping, SwapCallbackError, Wrapper } from '../../components/swap/styleds' import { ArrowWrapper, BottomGrouping, SwapCallbackError, Wrapper } from '../../components/swap/styleds'
import TradePrice from '../../components/swap/TradePrice' import TradePrice from '../../components/swap/TradePrice'
...@@ -23,13 +22,13 @@ import ProgressSteps from '../../components/ProgressSteps' ...@@ -23,13 +22,13 @@ import ProgressSteps from '../../components/ProgressSteps'
import SwapHeader from '../../components/swap/SwapHeader' import SwapHeader from '../../components/swap/SwapHeader'
import { INITIAL_ALLOWED_SLIPPAGE } from '../../constants' import { INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
import { getTradeVersion } from '../../data/V1' import { getTradeVersion } from '../../data/getTradeVersion'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useCurrency, useAllTokens } from '../../hooks/Tokens' import { useCurrency, useAllTokens } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback' import { ApprovalState, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback'
import useENSAddress from '../../hooks/useENSAddress' import useENSAddress from '../../hooks/useENSAddress'
import { useSwapCallback } from '../../hooks/useSwapCallback' import { useSwapCallback } from '../../hooks/useSwapCallback'
import useToggledVersion, { DEFAULT_VERSION, Version } from '../../hooks/useToggledVersion' import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
import useWrapCallback, { WrapType } from '../../hooks/useWrapCallback' import useWrapCallback, { WrapType } from '../../hooks/useWrapCallback'
import { useToggleSettingsMenu, useWalletModalToggle } from '../../state/application/hooks' import { useToggleSettingsMenu, useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/swap/actions' import { Field } from '../../state/swap/actions'
...@@ -48,7 +47,6 @@ import { ClickableText } from '../Pool/styleds' ...@@ -48,7 +47,6 @@ import { ClickableText } from '../Pool/styleds'
import Loader from '../../components/Loader' import Loader from '../../components/Loader'
import { useIsTransactionUnsupported } from 'hooks/Trades' import { useIsTransactionUnsupported } from 'hooks/Trades'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { isTradeBetter } from 'utils/trades'
import { RouteComponentProps } from 'react-router-dom' import { RouteComponentProps } from 'react-router-dom'
export default function Swap({ history }: RouteComponentProps) { export default function Swap({ history }: RouteComponentProps) {
...@@ -91,14 +89,7 @@ export default function Swap({ history }: RouteComponentProps) { ...@@ -91,14 +89,7 @@ export default function Swap({ history }: RouteComponentProps) {
// swap state // swap state
const { independentField, typedValue, recipient } = useSwapState() const { independentField, typedValue, recipient } = useSwapState()
const { const { v2Trade, currencyBalances, parsedAmount, currencies, inputError: swapInputError } = useDerivedSwapInfo()
v1Trade,
v2Trade,
currencyBalances,
parsedAmount,
currencies,
inputError: swapInputError,
} = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback( const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT], currencies[Field.INPUT],
...@@ -109,14 +100,9 @@ export default function Swap({ history }: RouteComponentProps) { ...@@ -109,14 +100,9 @@ export default function Swap({ history }: RouteComponentProps) {
const { address: recipientAddress } = useENSAddress(recipient) const { address: recipientAddress } = useENSAddress(recipient)
const toggledVersion = useToggledVersion() const toggledVersion = useToggledVersion()
const tradesByVersion = { const tradesByVersion = {
[Version.v1]: v1Trade,
[Version.v2]: v2Trade, [Version.v2]: v2Trade,
} }
const trade = showWrap ? undefined : tradesByVersion[toggledVersion] const trade = showWrap ? undefined : tradesByVersion[toggledVersion]
const defaultTrade = showWrap ? undefined : tradesByVersion[DEFAULT_VERSION]
const betterTradeLinkV2: Version | undefined =
toggledVersion === Version.v1 && isTradeBetter(v1Trade, v2Trade) ? Version.v2 : undefined
const parsedAmounts = showWrap const parsedAmounts = showWrap
? { ? {
...@@ -509,11 +495,6 @@ export default function Swap({ history }: RouteComponentProps) { ...@@ -509,11 +495,6 @@ export default function Swap({ history }: RouteComponentProps) {
</Column> </Column>
)} )}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null} {isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkV2 && !swapIsUnsupported && toggledVersion === Version.v1 ? (
<BetterTradeLink version={betterTradeLinkV2} />
) : toggledVersion !== DEFAULT_VERSION && defaultTrade ? (
<DefaultVersionLink />
) : null}
</BottomGrouping> </BottomGrouping>
</Wrapper> </Wrapper>
</AppBody> </AppBody>
......
import useENS from '../../hooks/useENS' import useENS from '../../hooks/useENS'
import { Version } from '../../hooks/useToggledVersion'
import { parseUnits } from '@ethersproject/units' import { parseUnits } from '@ethersproject/units'
import { Currency, CurrencyAmount, ETHER, Token, TokenAmount } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, ETHER, Token, TokenAmount } from '@uniswap/sdk-core'
import { JSBI, Trade } from '@uniswap/v2-sdk' import { JSBI, Trade } from '@uniswap/v2-sdk'
import { ParsedQs } from 'qs' import { ParsedQs } from 'qs'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { useV1Trade } from '../../data/V1'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useCurrency } from '../../hooks/Tokens' import { useCurrency } from '../../hooks/Tokens'
import { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades' import { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades'
...@@ -16,7 +14,6 @@ import { AppDispatch, AppState } from '../index' ...@@ -16,7 +14,6 @@ import { AppDispatch, AppState } from '../index'
import { useCurrencyBalances } from '../wallet/hooks' import { useCurrencyBalances } from '../wallet/hooks'
import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions' import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions'
import { SwapState } from './reducer' import { SwapState } from './reducer'
import useToggledVersion from '../../hooks/useToggledVersion'
import { useUserSlippageTolerance } from '../user/hooks' import { useUserSlippageTolerance } from '../user/hooks'
import { computeSlippageAdjustedAmounts } from '../../utils/prices' import { computeSlippageAdjustedAmounts } from '../../utils/prices'
...@@ -89,11 +86,11 @@ export function tryParseAmount(value?: string, currency?: Currency): CurrencyAmo ...@@ -89,11 +86,11 @@ export function tryParseAmount(value?: string, currency?: Currency): CurrencyAmo
return undefined return undefined
} }
const BAD_RECIPIENT_ADDRESSES: string[] = [ const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = {
'0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f', // v2 factory '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f': true, // v2 factory
'0xf164fC0Ec4E93095b804a4795bBe1e041497b92a', // v2 router 01 '0xf164fC0Ec4E93095b804a4795bBe1e041497b92a': true, // v2 router 01
'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', // v2 router 02 '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D': true, // v2 router 02
] }
/** /**
* Returns true if any of the pairs or tokens in a trade have the given checksummed address * Returns true if any of the pairs or tokens in a trade have the given checksummed address
...@@ -114,12 +111,9 @@ export function useDerivedSwapInfo(): { ...@@ -114,12 +111,9 @@ export function useDerivedSwapInfo(): {
parsedAmount: CurrencyAmount | undefined parsedAmount: CurrencyAmount | undefined
v2Trade: Trade | undefined v2Trade: Trade | undefined
inputError?: string inputError?: string
v1Trade: Trade | undefined
} { } {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const toggledVersion = useToggledVersion()
const { const {
independentField, independentField,
typedValue, typedValue,
...@@ -156,9 +150,6 @@ export function useDerivedSwapInfo(): { ...@@ -156,9 +150,6 @@ export function useDerivedSwapInfo(): {
[Field.OUTPUT]: outputCurrency ?? undefined, [Field.OUTPUT]: outputCurrency ?? undefined,
} }
// get link to trade on v1, if a better rate exists
const v1Trade = useV1Trade(isExactIn, currencies[Field.INPUT], currencies[Field.OUTPUT], parsedAmount)
let inputError: string | undefined let inputError: string | undefined
if (!account) { if (!account) {
inputError = 'Connect Wallet' inputError = 'Connect Wallet'
...@@ -177,7 +168,7 @@ export function useDerivedSwapInfo(): { ...@@ -177,7 +168,7 @@ export function useDerivedSwapInfo(): {
inputError = inputError ?? 'Enter a recipient' inputError = inputError ?? 'Enter a recipient'
} else { } else {
if ( if (
BAD_RECIPIENT_ADDRESSES.indexOf(formattedTo) !== -1 || BAD_RECIPIENT_ADDRESSES[formattedTo] ||
(bestTradeExactIn && involvesAddress(bestTradeExactIn, formattedTo)) || (bestTradeExactIn && involvesAddress(bestTradeExactIn, formattedTo)) ||
(bestTradeExactOut && involvesAddress(bestTradeExactOut, formattedTo)) (bestTradeExactOut && involvesAddress(bestTradeExactOut, formattedTo))
) { ) {
...@@ -189,19 +180,10 @@ export function useDerivedSwapInfo(): { ...@@ -189,19 +180,10 @@ export function useDerivedSwapInfo(): {
const slippageAdjustedAmounts = v2Trade && allowedSlippage && computeSlippageAdjustedAmounts(v2Trade, allowedSlippage) const slippageAdjustedAmounts = v2Trade && allowedSlippage && computeSlippageAdjustedAmounts(v2Trade, allowedSlippage)
const slippageAdjustedAmountsV1 =
v1Trade && allowedSlippage && computeSlippageAdjustedAmounts(v1Trade, allowedSlippage)
// compare input balance to max input based on version // compare input balance to max input based on version
const [balanceIn, amountIn] = [ const [balanceIn, amountIn] = [
currencyBalances[Field.INPUT], currencyBalances[Field.INPUT],
toggledVersion === Version.v1 slippageAdjustedAmounts ? slippageAdjustedAmounts[Field.INPUT] : null,
? slippageAdjustedAmountsV1
? slippageAdjustedAmountsV1[Field.INPUT]
: null
: slippageAdjustedAmounts
? slippageAdjustedAmounts[Field.INPUT]
: null,
] ]
if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) { if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
...@@ -214,7 +196,6 @@ export function useDerivedSwapInfo(): { ...@@ -214,7 +196,6 @@ export function useDerivedSwapInfo(): {
parsedAmount, parsedAmount,
v2Trade: v2Trade ?? undefined, v2Trade: v2Trade ?? undefined,
inputError, inputError,
v1Trade,
} }
} }
......
import { CurrencyAmount, ETHER, Percent, TokenAmount } from '@uniswap/sdk-core'
import { Route, Trade } from '@uniswap/v2-sdk'
import { DAI, USDC } from '../constants'
import { MockV1Pair } from '../data/V1'
import v1SwapArguments from './v1SwapArguments'
describe('v1SwapArguments', () => {
const USDC_WETH = new MockV1Pair('1000000', new TokenAmount(USDC, '1000000'))
const DAI_WETH = new MockV1Pair('1000000', new TokenAmount(DAI, '1000000'))
// just some random address
const TEST_RECIPIENT_ADDRESS = USDC_WETH.liquidityToken.address
it('exact eth to token', () => {
const trade = Trade.exactIn(new Route([USDC_WETH], ETHER), CurrencyAmount.ether('100'))
const result = v1SwapArguments(trade, {
recipient: TEST_RECIPIENT_ADDRESS,
allowedSlippage: new Percent('1', '100'),
deadline: 20 * 60,
})
expect(result.methodName).toEqual('ethToTokenTransferInput')
expect(result.args).toEqual(['0x62', '0x4b0', TEST_RECIPIENT_ADDRESS])
expect(result.value).toEqual('0x64')
})
it('exact token to eth', () => {
const trade = Trade.exactIn(new Route([USDC_WETH], USDC, ETHER), new TokenAmount(USDC, '100'))
const result = v1SwapArguments(trade, {
recipient: TEST_RECIPIENT_ADDRESS,
allowedSlippage: new Percent('1', '100'),
deadline: 40 * 60,
})
expect(result.methodName).toEqual('tokenToEthTransferInput')
expect(result.args[0]).toEqual('0x64')
expect(result.args[1]).toEqual('0x62')
expect(result.args[2]).toEqual('0x960')
expect(result.args[3]).toEqual(TEST_RECIPIENT_ADDRESS)
expect(result.value).toEqual('0x0')
})
it('exact token to token', () => {
const trade = Trade.exactIn(new Route([USDC_WETH, DAI_WETH], USDC), new TokenAmount(USDC, '100'))
const result = v1SwapArguments(trade, {
recipient: TEST_RECIPIENT_ADDRESS,
allowedSlippage: new Percent('1', '100'),
deadline: 20 * 60,
})
expect(result.methodName).toEqual('tokenToTokenTransferInput')
expect(result.args[0]).toEqual('0x64')
expect(result.args[1]).toEqual('0x61')
expect(result.args[2]).toEqual('0x1')
expect(result.args[3]).toEqual('0x4b0')
expect(result.args[4]).toEqual(TEST_RECIPIENT_ADDRESS)
expect(result.args[5]).toEqual(DAI.address)
expect(result.value).toEqual('0x0')
})
it('eth to exact token', () => {
const trade = Trade.exactOut(new Route([USDC_WETH], ETHER), new TokenAmount(USDC, '100'))
const result = v1SwapArguments(trade, {
recipient: TEST_RECIPIENT_ADDRESS,
allowedSlippage: new Percent('1', '100'),
deadline: 20 * 60,
})
expect(result.methodName).toEqual('ethToTokenTransferOutput')
expect(result.args[0]).toEqual('0x64')
expect(result.args[1]).toEqual('0x4b0')
expect(result.args[2]).toEqual(TEST_RECIPIENT_ADDRESS)
expect(result.value).toEqual('0x66')
})
it('token to exact eth', () => {
const trade = Trade.exactOut(new Route([USDC_WETH], USDC, ETHER), CurrencyAmount.ether('100'))
const result = v1SwapArguments(trade, {
recipient: TEST_RECIPIENT_ADDRESS,
allowedSlippage: new Percent('1', '100'),
deadline: 20 * 60,
})
expect(result.methodName).toEqual('tokenToEthTransferOutput')
expect(result.args[0]).toEqual('0x64')
expect(result.args[1]).toEqual('0x66')
expect(result.args[2]).toEqual('0x4b0')
expect(result.args[3]).toEqual(TEST_RECIPIENT_ADDRESS)
expect(result.value).toEqual('0x0')
})
it('token to exact token', () => {
const trade = Trade.exactOut(new Route([USDC_WETH, DAI_WETH], USDC), new TokenAmount(DAI, '100'))
const result = v1SwapArguments(trade, {
recipient: TEST_RECIPIENT_ADDRESS,
allowedSlippage: new Percent('1', '100'),
deadline: 20 * 60,
})
expect(result.methodName).toEqual('tokenToTokenTransferOutput')
expect(result.args[0]).toEqual('0x64')
expect(result.args[1]).toEqual('0x67')
expect(result.args[2]).toEqual(`0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`)
expect(result.args[3]).toEqual('0x4b0')
expect(result.args[4]).toEqual(TEST_RECIPIENT_ADDRESS)
expect(result.args[5]).toEqual(DAI.address)
expect(result.value).toEqual('0x0')
})
})
import { MaxUint256 } from '@ethersproject/constants'
import { CurrencyAmount, ETHER, Token, TradeType } from '@uniswap/sdk-core'
import { Trade, TradeOptionsDeadline, SwapParameters } from '@uniswap/v2-sdk'
import { getTradeVersion } from '../data/V1'
import { Version } from '../hooks/useToggledVersion'
function toHex(currencyAmount: CurrencyAmount): string {
return `0x${currencyAmount.raw.toString(16)}`
}
/**
* Get the arguments to make for a swap
* @param trade trade to get v1 arguments for swapping
* @param options options for swapping
*/
export default function v1SwapArguments(
trade: Trade,
options: Omit<TradeOptionsDeadline, 'feeOnTransfer'>
): SwapParameters {
if (getTradeVersion(trade) !== Version.v1) {
throw new Error('invalid trade version')
}
if (trade.route.pairs.length > 2) {
throw new Error('too many pairs')
}
const isExactIn = trade.tradeType === TradeType.EXACT_INPUT
const inputETH = trade.inputAmount.currency === ETHER
const outputETH = trade.outputAmount.currency === ETHER
if (inputETH && outputETH) throw new Error('ETHER to ETHER')
const minimumAmountOut = toHex(trade.minimumAmountOut(options.allowedSlippage))
const maximumAmountIn = toHex(trade.maximumAmountIn(options.allowedSlippage))
const deadline = `0x${options.deadline.toString(16)}`
if (isExactIn) {
if (inputETH) {
return {
methodName: 'ethToTokenTransferInput',
args: [minimumAmountOut, deadline, options.recipient],
value: maximumAmountIn,
}
} else if (outputETH) {
return {
methodName: 'tokenToEthTransferInput',
args: [maximumAmountIn, minimumAmountOut, deadline, options.recipient],
value: '0x0',
}
} else {
const outputToken = trade.outputAmount.currency
// should never happen, needed for type check
if (!(outputToken instanceof Token)) {
throw new Error('token to token')
}
return {
methodName: 'tokenToTokenTransferInput',
args: [maximumAmountIn, minimumAmountOut, '0x1', deadline, options.recipient, outputToken.address],
value: '0x0',
}
}
} else {
if (inputETH) {
return {
methodName: 'ethToTokenTransferOutput',
args: [minimumAmountOut, deadline, options.recipient],
value: maximumAmountIn,
}
} else if (outputETH) {
return {
methodName: 'tokenToEthTransferOutput',
args: [minimumAmountOut, maximumAmountIn, deadline, options.recipient],
value: '0x0',
}
} else {
const output = trade.outputAmount.currency
if (!(output instanceof Token)) {
throw new Error('invalid output amount currency')
}
return {
methodName: 'tokenToTokenTransferOutput',
args: [
minimumAmountOut,
maximumAmountIn,
MaxUint256.toHexString(),
deadline,
options.recipient,
output.address,
],
value: '0x0',
}
}
}
}
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