diff --git a/cypress/integration/migrate-v1.test.ts b/cypress/integration/migrate-v1.test.ts deleted file mode 100644 index 362504828a..0000000000 --- a/cypress/integration/migrate-v1.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -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') - }) - }) -}) diff --git a/cypress/integration/remove-liquidity.test.ts b/cypress/integration/remove-liquidity.test.ts index 8bcd2ebabf..faea17132a 100644 --- a/cypress/integration/remove-liquidity.test.ts +++ b/cypress/integration/remove-liquidity.test.ts @@ -12,19 +12,19 @@ describe('Remove Liquidity', () => { }) 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-tokenb-symbol').should('contain.text', 'MKR') }) 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-tokenb-symbol').should('contain.text', 'WETH') }) 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-tokenb-symbol').should('contain.text', 'MKR') }) diff --git a/package.json b/package.json index 90460181eb..6019ca0c91 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@uniswap/v2-sdk": "^1.0.7", "@uniswap/v3-core": "^1.0.0-rc.2", "@uniswap/v3-periphery": "^1.0.0-beta.20", - "@uniswap/v3-sdk": "^1.0.0-alpha.13", + "@uniswap/v3-sdk": "^1.0.0-alpha.17", "@web3-react/core": "^6.0.9", "@web3-react/fortmatic-connector": "^6.0.9", "@web3-react/injected-connector": "^6.0.7", diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index dcbe4c225b..d8179311c7 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -116,23 +116,23 @@ export default function Menu() { {open && ( - + About - + Docs - + Code - + Discord - + Analytics @@ -175,7 +175,7 @@ export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, men {open && ( {menuItems.map(({ content, link }, i) => ( - + {content} ))} diff --git a/src/components/PositionCard/V1.tsx b/src/components/PositionCard/V1.tsx deleted file mode 100644 index 3fd9f26842..0000000000 --- a/src/components/PositionCard/V1.tsx +++ /dev/null @@ -1,68 +0,0 @@ -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> { - token: Token - V1LiquidityBalance: TokenAmount -} - -function V1PositionCard({ token, V1LiquidityBalance }: PositionCardProps) { - const theme = useContext(ThemeContext) - - const { chainId } = useActiveWeb3React() - - return ( - - - - - - - {`${chainId && token.equals(WETH9[chainId]) ? 'WETH' : token.symbol}/ETH`} - - - V1 - - - - - - - - Migrate - - - - Remove - - - - - - ) -} - -export default withRouter(V1PositionCard) diff --git a/src/constants/v1/index.ts b/src/constants/v1/index.ts deleted file mode 100644 index 8bef6ef79c..0000000000 --- a/src/constants/v1/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -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 } diff --git a/src/constants/v1/v1_exchange.json b/src/constants/v1/v1_exchange.json deleted file mode 100644 index 94483263f3..0000000000 --- a/src/constants/v1/v1_exchange.json +++ /dev/null @@ -1,971 +0,0 @@ -[ - { - "name": "TokenPurchase", - "inputs": [ - { - "type": "address", - "name": "buyer", - "indexed": true - }, - { - "type": "uint256", - "name": "eth_sold", - "indexed": true - }, - { - "type": "uint256", - "name": "tokens_bought", - "indexed": true - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "EthPurchase", - "inputs": [ - { - "type": "address", - "name": "buyer", - "indexed": true - }, - { - "type": "uint256", - "name": "tokens_sold", - "indexed": true - }, - { - "type": "uint256", - "name": "eth_bought", - "indexed": true - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "AddLiquidity", - "inputs": [ - { - "type": "address", - "name": "provider", - "indexed": true - }, - { - "type": "uint256", - "name": "eth_amount", - "indexed": true - }, - { - "type": "uint256", - "name": "token_amount", - "indexed": true - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "RemoveLiquidity", - "inputs": [ - { - "type": "address", - "name": "provider", - "indexed": true - }, - { - "type": "uint256", - "name": "eth_amount", - "indexed": true - }, - { - "type": "uint256", - "name": "token_amount", - "indexed": true - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "Transfer", - "inputs": [ - { - "type": "address", - "name": "_from", - "indexed": true - }, - { - "type": "address", - "name": "_to", - "indexed": true - }, - { - "type": "uint256", - "name": "_value", - "indexed": false - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "Approval", - "inputs": [ - { - "type": "address", - "name": "_owner", - "indexed": true - }, - { - "type": "address", - "name": "_spender", - "indexed": true - }, - { - "type": "uint256", - "name": "_value", - "indexed": false - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "setup", - "outputs": [], - "inputs": [ - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "addLiquidity", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "min_liquidity" - }, - { - "type": "uint256", - "name": "max_tokens" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": true, - "type": "function" - }, - { - "name": "removeLiquidity", - "outputs": [ - { - "type": "uint256", - "name": "outA" - }, - { - "type": "uint256", - "name": "outB" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "amount" - }, - { - "type": "uint256", - "name": "min_eth" - }, - { - "type": "uint256", - "name": "min_tokens" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "__default__", - "outputs": [], - "inputs": [], - "constant": false, - "payable": true, - "type": "function" - }, - { - "name": "ethToTokenSwapInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "min_tokens" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": true, - "type": "function" - }, - { - "name": "ethToTokenTransferInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "min_tokens" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - } - ], - "constant": false, - "payable": true, - "type": "function" - }, - { - "name": "ethToTokenSwapOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": true, - "type": "function" - }, - { - "name": "ethToTokenTransferOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - } - ], - "constant": false, - "payable": true, - "type": "function" - }, - { - "name": "tokenToEthSwapInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_eth" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToEthTransferInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_eth" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToEthSwapOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "eth_bought" - }, - { - "type": "uint256", - "name": "max_tokens" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToEthTransferOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "eth_bought" - }, - { - "type": "uint256", - "name": "max_tokens" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToTokenSwapInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_tokens_bought" - }, - { - "type": "uint256", - "name": "min_eth_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToTokenTransferInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_tokens_bought" - }, - { - "type": "uint256", - "name": "min_eth_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - }, - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToTokenSwapOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "max_tokens_sold" - }, - { - "type": "uint256", - "name": "max_eth_sold" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToTokenTransferOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "max_tokens_sold" - }, - { - "type": "uint256", - "name": "max_eth_sold" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - }, - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToExchangeSwapInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_tokens_bought" - }, - { - "type": "uint256", - "name": "min_eth_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "exchange_addr" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToExchangeTransferInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_tokens_bought" - }, - { - "type": "uint256", - "name": "min_eth_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - }, - { - "type": "address", - "name": "exchange_addr" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToExchangeSwapOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "max_tokens_sold" - }, - { - "type": "uint256", - "name": "max_eth_sold" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "exchange_addr" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "tokenToExchangeTransferOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "max_tokens_sold" - }, - { - "type": "uint256", - "name": "max_eth_sold" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - }, - { - "type": "address", - "name": "exchange_addr" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "getEthToTokenInputPrice", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "eth_sold" - } - ], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "getEthToTokenOutputPrice", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - } - ], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "getTokenToEthInputPrice", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - } - ], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "getTokenToEthOutputPrice", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "eth_bought" - } - ], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "tokenAddress", - "outputs": [ - { - "type": "address", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "factoryAddress", - "outputs": [ - { - "type": "address", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "balanceOf", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_owner" - } - ], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "transfer", - "outputs": [ - { - "type": "bool", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_to" - }, - { - "type": "uint256", - "name": "_value" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "transferFrom", - "outputs": [ - { - "type": "bool", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_from" - }, - { - "type": "address", - "name": "_to" - }, - { - "type": "uint256", - "name": "_value" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "approve", - "outputs": [ - { - "type": "bool", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_spender" - }, - { - "type": "uint256", - "name": "_value" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "allowance", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_owner" - }, - { - "type": "address", - "name": "_spender" - } - ], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "name", - "outputs": [ - { - "type": "bytes32", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "symbol", - "outputs": [ - { - "type": "bytes32", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "decimals", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "totalSupply", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function" - } -] diff --git a/src/constants/v1/v1_factory.json b/src/constants/v1/v1_factory.json deleted file mode 100644 index cbdf5fa5cb..0000000000 --- a/src/constants/v1/v1_factory.json +++ /dev/null @@ -1,67 +0,0 @@ -[ - { - "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" - } -] diff --git a/src/constants/v3/index.ts b/src/constants/v3/index.ts index 3d112f99a2..6b2b84200b 100644 --- a/src/constants/v3/index.ts +++ b/src/constants/v3/index.ts @@ -1,49 +1,31 @@ import { ChainId } from '@uniswap/sdk-core' -export const FACTORY_ADDRESSES: { [chainId in ChainId]: string } = { - [ChainId.MAINNET]: '', - [ChainId.ROPSTEN]: '0x864e344eCd7f3a9A4368dEC11Be8104db5770364', - [ChainId.RINKEBY]: '0xAE28628c0fdFb5e54d60FEDC6C9085199aec14dF', - [ChainId.GÖRLI]: '0x864e344eCd7f3a9A4368dEC11Be8104db5770364', - [ChainId.KOVAN]: '0xd4013a706fa79487989b595Df35eF8AD1ffBb422', +export const V3_CORE_FACTORY_ADDRESSES: { [chainId in ChainId]?: string } = { + [ChainId.RINKEBY]: '0xFeabCc62240297F1e4b238937D68e7516f0918D7', + [ChainId.GÖRLI]: '0xA31B47971cdC5376E41CfA2D4378912156ab1F10', + [ChainId.KOVAN]: '0x58f6b77148BE49BF7898472268ae8f26377d0AA6', } -export const TICK_LENS_ADDRESSES: { [chainId in ChainId]: string } = { - [ChainId.MAINNET]: '', - [ChainId.ROPSTEN]: '0xc97c7D6C2F1EE518bE4D4B8566bcEb917dED4F39', - [ChainId.RINKEBY]: '0x2d31366B7D446d629ac36933F12bdbca96860f84', - [ChainId.GÖRLI]: '0xc97c7D6C2F1EE518bE4D4B8566bcEb917dED4F39', - [ChainId.KOVAN]: '0xD2AAa0217a203d9FaB6e5272b211Be2Aba52f385', +export const TICK_LENS_ADDRESSES: { [chainId in ChainId]?: string } = { + [ChainId.RINKEBY]: '0x3d137e860008BaF6d1c063158e5ec0baBbcFefF8', + [ChainId.GÖRLI]: '0x80AacDBEe92DC1c2Fbaa261Fb369696AF1AD9f98', + [ChainId.KOVAN]: '0xB79bDE60fc227217f4EE2102dC93fa1264E33DaB', } -export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: { [chainId in ChainId]: string } = { - [ChainId.MAINNET]: '', - [ChainId.ROPSTEN]: '0xc97c7D6C2F1EE518bE4D4B8566bcEb917dED4F39', - [ChainId.RINKEBY]: '0xbBca0fFBFE60F60071630A8c80bb6253dC9D6023', - [ChainId.GÖRLI]: '0x539BF58f052dE91ae369dAd59f1ac6887dF39Bc5', - [ChainId.KOVAN]: '0xAE28628c0fdFb5e54d60FEDC6C9085199aec14dF', +export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: { [chainId in ChainId]?: string } = { + [ChainId.RINKEBY]: '0x2F9e608FD881861B8916257B76613Cb22EE0652c', + [ChainId.GÖRLI]: '0xd6852c52B9c97cBfb7e79B6ab4407AA20Ba31439', + [ChainId.KOVAN]: '0xA31B47971cdC5376E41CfA2D4378912156ab1F10', } -export const NONFUNGIBLE_TOKEN_POSITION_DESCRIPTOR_ADDRESSES: { [chainId in ChainId]: string } = { - [ChainId.MAINNET]: '', - [ChainId.ROPSTEN]: '0x8dF824f7885611c587AA45924BF23153EC832b89', - [ChainId.RINKEBY]: '0x3b1aC1c352F3A18A58471908982b8b870c836EC0', - [ChainId.GÖRLI]: '0x8dF824f7885611c587AA45924BF23153EC832b89', - [ChainId.KOVAN]: '0x30Ba713F78Ad3c175a25aD767e3f423549Ac2D65', +export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]?: string } = { + [ChainId.RINKEBY]: '0x273Edaa13C845F605b5886Dd66C89AB497A6B17b', + [ChainId.GÖRLI]: '0x91a64CCaead471caFF912314E466D9CF7C55E0E8', + [ChainId.KOVAN]: '0x1988F2e49A72C4D73961C7f4Bb896819d3d2F6a3', } -export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]: string } = { - [ChainId.MAINNET]: '', - [ChainId.ROPSTEN]: '0x7046f9311663DB8B7cf218BC7B6F3f17B0Ea1047', - [ChainId.RINKEBY]: '0x8dF824f7885611c587AA45924BF23153EC832b89', - [ChainId.GÖRLI]: '0x7046f9311663DB8B7cf218BC7B6F3f17B0Ea1047', - [ChainId.KOVAN]: '0x921647f0c094e2e59CDE6DEfafD77743012f52bd', -} - -export const V2_MIGRATOR_ADDRESSES: { [chainId in ChainId]: string } = { - [ChainId.MAINNET]: '', - [ChainId.ROPSTEN]: '0xbBca0fFBFE60F60071630A8c80bb6253dC9D6023', - [ChainId.RINKEBY]: '0xc4b81504F9a2bd6a6f2617091FB01Efb38D119c8', - [ChainId.GÖRLI]: '0xbBca0fFBFE60F60071630A8c80bb6253dC9D6023', - [ChainId.KOVAN]: '0xc97c7D6C2F1EE518bE4D4B8566bcEb917dED4F39', +export const V2_MIGRATOR_ADDRESSES: { [chainId in ChainId]?: string } = { + [ChainId.RINKEBY]: '0x03782388516e94FcD4c18666303601A12Aa729Ea', + [ChainId.GÖRLI]: '0x2F9e608FD881861B8916257B76613Cb22EE0652c', + [ChainId.KOVAN]: '0xFeabCc62240297F1e4b238937D68e7516f0918D7', } diff --git a/src/data/Pools.ts b/src/data/Pools.ts index 5e3c013673..a641ff3094 100644 --- a/src/data/Pools.ts +++ b/src/data/Pools.ts @@ -7,7 +7,7 @@ import { useSingleCallResult } from '../state/multicall/hooks' import { wrappedCurrency } from '../utils/wrappedCurrency' import { Pool, FeeAmount, computePoolAddress } from '@uniswap/v3-sdk' 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' export enum PoolState { @@ -38,9 +38,10 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?: // fetch all generated addresses for pools const poolAddress = useMemo(() => { 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({ - factoryAddress: FACTORY_ADDRESSES[chainId], + factoryAddress: addr, tokenA, tokenB, fee: feeAmount, @@ -71,28 +72,44 @@ export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?: const poolAddressFromFactory = addressesResult?.[0] // fetch tick data for pool - const { tickData, loading: tickLoading } = useAllV3Ticks(token0, token1, feeAmount) + const { tickData, loading: tickLoading, syncing: tickSyncing } = useAllV3Ticks(token0, token1, feeAmount) - // still loading data - if (slot0Loading || addressesLoading || liquidityLoading || tickLoading) return [PoolState.LOADING, null] + return useMemo(() => { + // still loading data + if (slot0Loading || addressesLoading || liquidityLoading || tickLoading || tickSyncing) + return [PoolState.LOADING, null] - // invalid pool setup - if (!tokenA || !tokenB || !feeAmount || tokenA.equals(tokenB)) return [PoolState.INVALID, null] + // invalid pool setup + if (!tokenA || !tokenB || !feeAmount || tokenA.equals(tokenB)) return [PoolState.INVALID, null] - // pool has not been created or not initialized yet - if (poolAddressFromFactory === ZERO_ADDRESS || !slot0 || !liquidity || slot0.sqrtPriceX96 === 0) { - return [PoolState.NOT_EXISTS, null] - } + // pool has not been created or not initialized yet + if (poolAddressFromFactory === ZERO_ADDRESS || !slot0 || !liquidity || slot0.sqrtPriceX96 === 0) { + return [PoolState.NOT_EXISTS, null] + } - const tickList: Tick[] = tickData - .map((tick) => { - return new Tick({ - index: tick.tick, - liquidityGross: tick.liquidityGross, - liquidityNet: tick.liquidityNet, + const tickList: Tick[] = tickData + .map((tick) => { + return new Tick({ + index: tick.tick, + liquidityGross: tick.liquidityGross, + liquidityNet: tick.liquidityNet, + }) }) - }) - .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, + ]) } diff --git a/src/data/V1.ts b/src/data/V1.ts deleted file mode 100644 index a0fd5e1f57..0000000000 --- a/src/data/V1.ts +++ /dev/null @@ -1,157 +0,0 @@ -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) -} diff --git a/src/data/getTradeVersion.ts b/src/data/getTradeVersion.ts new file mode 100644 index 0000000000..ed76591d72 --- /dev/null +++ b/src/data/getTradeVersion.ts @@ -0,0 +1,7 @@ +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 +} diff --git a/src/hooks/useAllV3Ticks.ts b/src/hooks/useAllV3Ticks.ts index 45ca8268b4..01914eef14 100644 --- a/src/hooks/useAllV3Ticks.ts +++ b/src/hooks/useAllV3Ticks.ts @@ -1,22 +1,13 @@ import { Token } from '@uniswap/sdk-core' import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk' +import { nearestUsableTick, TickMath } from '@uniswap/v3-sdk/dist/' import { ZERO_ADDRESS } from '../constants' -import { useMemo } from 'react' +import { useEffect, useMemo, useState } from 'react' import { Result, useSingleCallResult, useSingleContractMultipleData } from 'state/multicall/hooks' 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) { - const compressed = tick / tickSpacing - return compressed >> 8 + return Math.floor(tick / tickSpacing / 256) } const REFRESH_FREQUENCY = { blocksPerFetch: 2 } @@ -38,19 +29,22 @@ export function useAllV3Ticks( valid: boolean 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), [ - tickSpacing, - ]) - const maxIndex = useMemo(() => (tickSpacing ? bitmapIndex(MAX_TICK(tickSpacing), tickSpacing) : undefined), [ - tickSpacing, - ]) + const minIndex = useMemo( + () => (tickSpacing ? bitmapIndex(nearestUsableTick(TickMath.MIN_TICK, tickSpacing), tickSpacing) : undefined), + [tickSpacing] + ) + const maxIndex = useMemo( + () => (tickSpacing ? bitmapIndex(nearestUsableTick(TickMath.MAX_TICK, tickSpacing), tickSpacing) : undefined), + [tickSpacing] + ) + + const [tickDataLatestSynced, setTickDataLatestSynced] = useState([]) // fetch the pool address const factoryContract = useV3Factory() - const addressParams = token0 && token1 && feeAmount ? [token0.address, token1.address, feeAmount] : undefined - const poolAddress = useSingleCallResult(addressParams ? factoryContract : undefined, 'getPool', addressParams) + const poolAddress = useSingleCallResult(factoryContract, 'getPool', [token0?.address, token1?.address, feeAmount]) .result?.[0] const tickLensArgs: [string, number][] = useMemo( @@ -98,14 +92,21 @@ export function useAllV3Ticks( [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( () => ({ loading, syncing, error, valid, - tickData, + tickData: tickDataLatestSynced, }), - [loading, syncing, error, valid, tickData] + [loading, syncing, error, valid, tickDataLatestSynced] ) } diff --git a/src/hooks/useApproveCallback.ts b/src/hooks/useApproveCallback.ts index fe82cb86a4..ccdf431dfe 100644 --- a/src/hooks/useApproveCallback.ts +++ b/src/hooks/useApproveCallback.ts @@ -5,14 +5,12 @@ import { Trade } from '@uniswap/v2-sdk' import { useCallback, useMemo } from 'react' import { ROUTER_ADDRESS } from '../constants' import { useTokenAllowance } from '../data/Allowances' -import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1' import { Field } from '../state/swap/actions' import { useTransactionAdder, useHasPendingApproval } from '../state/transactions/hooks' import { computeSlippageAdjustedAmounts } from '../utils/prices' import { calculateGasMargin } from '../utils' import { useTokenContract } from './useContract' import { useActiveWeb3React } from './index' -import { Version } from './useToggledVersion' export enum ApprovalState { UNKNOWN, @@ -106,7 +104,5 @@ export function useApproveCallbackFromTrade(trade?: Trade, allowedSlippage = 0) () => (trade ? computeSlippageAdjustedAmounts(trade, allowedSlippage)[Field.INPUT] : undefined), [trade, allowedSlippage] ) - const tradeIsV1 = getTradeVersion(trade) === Version.v1 - const v1ExchangeAddress = useV1TradeExchangeAddress(trade) - return useApproveCallback(amountToApprove, tradeIsV1 ? v1ExchangeAddress : ROUTER_ADDRESS) + return useApproveCallback(amountToApprove, ROUTER_ADDRESS) } diff --git a/src/hooks/useContract.ts b/src/hooks/useContract.ts index ccf758b1eb..699360b694 100644 --- a/src/hooks/useContract.ts +++ b/src/hooks/useContract.ts @@ -29,10 +29,9 @@ import { MULTICALL_ADDRESSES, } from 'constants/index' 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 { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, - FACTORY_ADDRESSES, + V3_CORE_FACTORY_ADDRESSES, TICK_LENS_ADDRESSES, V2_MIGRATOR_ADDRESSES, } from 'constants/v3' @@ -58,11 +57,6 @@ export function useContract(address: string | undefined, ABI: any, withSignerIfP }, [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 { return useContract(V1_MIGRATOR_ADDRESS, MIGRATOR_ABI, true) } @@ -72,13 +66,10 @@ export function useV2MigratorContract(): 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 { return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible) } + export function useWETHContract(withSignerIfPossible?: boolean): Contract | null { const { chainId } = useActiveWeb3React() const address = chainId && chainId in WETH9 ? WETH9[chainId].address : undefined @@ -162,7 +153,7 @@ export function useV3NFTPositionManagerContract(): NonfungiblePositionManager | export function useV3Factory(): UniswapV3Factory | null { 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 } diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index 716595c39c..e40d2e86f1 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -4,13 +4,11 @@ import { JSBI, Router, SwapParameters, Trade } from '@uniswap/v2-sdk' import { Percent, TradeType } from '@uniswap/sdk-core' import { useMemo } from 'react' 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 { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils' import isZero from '../utils/isZero' -import v1SwapArguments from '../utils/v1SwapArguments' import { useActiveWeb3React } from './index' -import { useV1ExchangeContract } from './useContract' import useTransactionDeadline from './useTransactionDeadline' import useENS from './useENS' import { Version } from './useToggledVersion' @@ -55,54 +53,37 @@ function useSwapCallArguments( const recipient = recipientAddressOrName === null ? account : recipientAddress const deadline = useTransactionDeadline() - const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true) - return useMemo(() => { - const tradeVersion = getTradeVersion(trade) - if (!trade || !recipient || !library || !account || !tradeVersion || !chainId || !deadline) return [] + if (!trade || !recipient || !library || !account || !chainId || !deadline) return [] - const contract: Contract | null = - tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange + const contract: Contract | null = getRouterContract(chainId, library, account) if (!contract) { return [] } const swapMethods = [] - switch (tradeVersion) { - case Version.v2: - swapMethods.push( - Router.swapCallParameters(trade, { - feeOnTransfer: false, - allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), - recipient, - deadline: deadline.toNumber(), - }) - ) + swapMethods.push( + Router.swapCallParameters(trade, { + feeOnTransfer: false, + allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), + recipient, + deadline: deadline.toNumber(), + }) + ) - if (trade.tradeType === TradeType.EXACT_INPUT) { - swapMethods.push( - Router.swapCallParameters(trade, { - feeOnTransfer: true, - allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), - recipient, - deadline: deadline.toNumber(), - }) - ) - } - break - case Version.v1: - swapMethods.push( - v1SwapArguments(trade, { - allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), - recipient, - deadline: deadline.toNumber(), - }) - ) - break + if (trade.tradeType === TradeType.EXACT_INPUT) { + swapMethods.push( + Router.swapCallParameters(trade, { + feeOnTransfer: true, + allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), + recipient, + deadline: deadline.toNumber(), + }) + ) } 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 diff --git a/src/hooks/useToggledVersion.ts b/src/hooks/useToggledVersion.ts index 3d8d71f5f3..6b62003d0b 100644 --- a/src/hooks/useToggledVersion.ts +++ b/src/hooks/useToggledVersion.ts @@ -1,7 +1,6 @@ import useParsedQueryString from './useParsedQueryString' export enum Version { - v1 = 'v1', v2 = 'v2', } @@ -9,7 +8,8 @@ export const DEFAULT_VERSION: Version = Version.v2 export default function useToggledVersion(): Version { const { use } = useParsedQueryString() - if (!use || typeof use !== 'string') return Version.v2 - if (use.toLowerCase() === 'v1') return Version.v1 + if (typeof use !== 'string') { + return DEFAULT_VERSION + } return DEFAULT_VERSION } diff --git a/src/pages/AddLiquidityV2/redirects.tsx b/src/pages/AddLiquidityV2/redirects.tsx index 41d7ad5646..3f29356cbe 100644 --- a/src/pages/AddLiquidityV2/redirects.tsx +++ b/src/pages/AddLiquidityV2/redirects.tsx @@ -9,8 +9,9 @@ export function RedirectDuplicateTokenIdsV2(props: RouteComponentProps<{ currenc }, } = props - if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) { + if (currencyIdA && currencyIdB && currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) { return } + return } diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 29253c5438..1605f22777 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -14,9 +14,6 @@ import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader' import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects' import Earn from './Earn' import Manage from './Earn/Manage' -import MigrateV1 from './MigrateV1' -import MigrateV1Exchange from './MigrateV1/MigrateV1Exchange' -import RemoveV1Exchange from './MigrateV1/RemoveV1Exchange' import MigrateV2 from './MigrateV2' import MigrateV2Pair from './MigrateV2/MigrateV2Pair' import Pool from './Pool' @@ -114,12 +111,9 @@ export default function App() { component={RedirectDuplicateTokenIds} /> - - - diff --git a/src/pages/MigrateV1/EmptyState.tsx b/src/pages/MigrateV1/EmptyState.tsx deleted file mode 100644 index 9f3bf82cfd..0000000000 --- a/src/pages/MigrateV1/EmptyState.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import { AutoColumn } from '../../components/Column' -import { TYPE } from '../../theme' - -export function EmptyState({ message }: { message: string }) { - return ( - - {message} - - ) -} diff --git a/src/pages/MigrateV1/MigrateV1Exchange.tsx b/src/pages/MigrateV1/MigrateV1Exchange.tsx deleted file mode 100644 index 8aeb775a39..0000000000 --- a/src/pages/MigrateV1/MigrateV1Exchange.tsx +++ /dev/null @@ -1,370 +0,0 @@ -import { TransactionResponse } from '@ethersproject/abstract-provider' -import { AddressZero } from '@ethersproject/constants' -import { Currency, CurrencyAmount, Fraction, Percent, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core' -import { JSBI } from '@uniswap/v2-sdk' -import React, { useCallback, useMemo, useState } from 'react' -import ReactGA from 'react-ga' -import { Redirect, RouteComponentProps } from 'react-router' -import { Text } from 'rebass' -import { ButtonConfirmed } from '../../components/Button' -import { LightCard, PinkCard, YellowCard } from '../../components/Card' -import { AutoColumn } from '../../components/Column' -import CurrencyLogo from '../../components/CurrencyLogo' -import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount' -import QuestionHelper from '../../components/QuestionHelper' -import { AutoRow, RowBetween, RowFixed } from '../../components/Row' -import { Dots } from '../../components/swap/styleds' -import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, V1_MIGRATOR_ADDRESS } from '../../constants' -import { PairState, usePair } from '../../data/V2' -import { useTotalSupply } from '../../data/TotalSupply' -import { useActiveWeb3React } from '../../hooks' -import { useToken } from '../../hooks/Tokens' -import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' -import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks' -import { useIsTransactionPending, useTransactionAdder } from '../../state/transactions/hooks' -import { useETHBalances, useTokenBalance } from '../../state/wallet/hooks' -import { BackArrow, ExternalLink, TYPE } from '../../theme' -import { getEtherscanLink, isAddress } from '../../utils' -import { BodyWrapper } from '../AppBody' -import { EmptyState } from './EmptyState' -import { useV1ExchangeContract, useV1MigratorContract } from 'hooks/useContract' - -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) -const ALLOWED_OUTPUT_MIN_PERCENT = new Percent(JSBI.BigInt(10000 - INITIAL_ALLOWED_SLIPPAGE), JSBI.BigInt(10000)) - -export function V1LiquidityInfo({ - token, - liquidityTokenAmount, - tokenWorth, - ethWorth, -}: { - token: Token - liquidityTokenAmount: TokenAmount - tokenWorth: TokenAmount - ethWorth: CurrencyAmount -}) { - const { chainId } = useActiveWeb3React() - - return ( - <> - - -
- - {}{' '} - {chainId && token.equals(WETH9[chainId]) ? 'WETH' : token.symbol}/ETH - -
-
- - - - Pooled {chainId && token.equals(WETH9[chainId]) ? 'WETH' : token.symbol}: - - - - {tokenWorth.toSignificant(4)} - - - - - - - Pooled ETH: - - - - - - - - - - ) -} - -function V1PairMigration({ liquidityTokenAmount, token }: { liquidityTokenAmount: TokenAmount; token: Token }) { - const { account, chainId } = useActiveWeb3React() - const totalSupply = useTotalSupply(liquidityTokenAmount.token) - const exchangeETHBalance = useETHBalances([liquidityTokenAmount.token.address])?.[liquidityTokenAmount.token.address] - const exchangeTokenBalance = useTokenBalance(liquidityTokenAmount.token.address, token) - - const [v2PairState, v2Pair] = usePair(chainId ? WETH9[chainId] : undefined, token) - const isFirstLiquidityProvider: boolean = v2PairState === PairState.NOT_EXISTS - - const v2SpotPrice = chainId && v2Pair ? v2Pair.reserveOf(token).divide(v2Pair.reserveOf(WETH9[chainId])) : undefined - - const [confirmingMigration, setConfirmingMigration] = useState(false) - const [pendingMigrationHash, setPendingMigrationHash] = useState(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 [approval, approve] = useApproveCallback(liquidityTokenAmount, V1_MIGRATOR_ADDRESS) - - const v1SpotPrice = - exchangeTokenBalance && exchangeETHBalance - ? exchangeTokenBalance.divide(new Fraction(exchangeETHBalance.raw, WEI_DENOM)) - : null - - const priceDifferenceFraction: Fraction | undefined = - v1SpotPrice && v2SpotPrice ? v1SpotPrice.divide(v2SpotPrice).multiply('100').subtract('100') : undefined - - const priceDifferenceAbs: Fraction | undefined = priceDifferenceFraction?.lessThan(ZERO) - ? priceDifferenceFraction?.multiply('-1') - : priceDifferenceFraction - - const minAmountETH: JSBI | undefined = - v2SpotPrice && tokenWorth - ? tokenWorth.divide(v2SpotPrice).multiply(WEI_DENOM).multiply(ALLOWED_OUTPUT_MIN_PERCENT).quotient - : ethWorth?.numerator - - const minAmountToken: JSBI | undefined = - v2SpotPrice && ethWorth - ? ethWorth - .multiply(v2SpotPrice) - .multiply(JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(token.decimals))) - .multiply(ALLOWED_OUTPUT_MIN_PERCENT).quotient - : tokenWorth?.numerator - - const addTransaction = useTransactionAdder() - const isMigrationPending = useIsTransactionPending(pendingMigrationHash ?? undefined) - - const migrator = useV1MigratorContract() - const migrate = useCallback(() => { - if (!minAmountToken || !minAmountETH || !migrator) return - - setConfirmingMigration(true) - migrator - .migrate( - token.address, - minAmountToken.toString(), - minAmountETH.toString(), - account, - Math.floor(new Date().getTime() / 1000) + DEFAULT_DEADLINE_FROM_NOW - ) - .then((response: TransactionResponse) => { - ReactGA.event({ - category: 'Migrate', - action: 'V1->V2', - label: token?.symbol, - }) - - addTransaction(response, { - summary: `Migrate ${token.symbol} liquidity to V2`, - }) - setPendingMigrationHash(response.hash) - }) - .catch(() => { - setConfirmingMigration(false) - }) - }, [minAmountToken, minAmountETH, migrator, token, account, addTransaction]) - - const noLiquidityTokens = !!liquidityTokenAmount && liquidityTokenAmount.equalTo(ZERO) - - const largePriceDifference = !!priceDifferenceAbs && !priceDifferenceAbs.lessThan(JSBI.BigInt(5)) - - const isSuccessfullyMigrated = !!pendingMigrationHash && noLiquidityTokens - - return ( - - - This tool will safely migrate your V1 liquidity to V2 with minimal price risk. The process is completely - trustless thanks to the{' '} - {chainId && ( - - Uniswap migration contract↗ - - )} - . - - - {!isFirstLiquidityProvider && largePriceDifference ? ( - - - It{"'"}s best to deposit liquidity into Uniswap V2 at a price you believe is correct. If the V2 price seems - incorrect, you can either make a swap to move the price or wait for someone else to do so. - - - - V1 Price: - - {v1SpotPrice?.toSignificant(6)} {token.symbol}/ETH - - - -
- - {v1SpotPrice?.invert()?.toSignificant(6)} ETH/{token.symbol} - - - - - V2 Price: - - {v2SpotPrice?.toSignificant(6)} {token.symbol}/ETH - - - -
- - {v2SpotPrice?.invert()?.toSignificant(6)} ETH/{token.symbol} - - - - - Price Difference: - {priceDifferenceAbs?.toSignificant(4)}% - - - - ) : null} - - {isFirstLiquidityProvider && ( - - - You are the first liquidity provider for this pair on Uniswap V2. Your liquidity will be migrated at the - current V1 price. Your transaction cost also includes the gas to create the pool. - - - - - V1 Price: - - {v1SpotPrice?.toSignificant(6)} {token.symbol}/ETH - - - -
- - {v1SpotPrice?.invert()?.toSignificant(6)} ETH/{token.symbol} - - - - - )} - - - - -
- - - {approval === ApprovalState.PENDING ? ( - Approving - ) : approval === ApprovalState.APPROVED ? ( - 'Approved' - ) : ( - 'Approve' - )} - - - - - {isSuccessfullyMigrated ? 'Success' : isMigrationPending ? Migrating : 'Migrate'} - - -
-
- - {`Your Uniswap V1 ${token.symbol}/ETH liquidity will become Uniswap V2 ${token.symbol}/ETH liquidity.`} - - - ) -} - -export default function MigrateV1Exchange({ - history, - match: { - params: { address }, - }, -}: RouteComponentProps<{ address: string }>) { - const validatedAddress = isAddress(address) - const { chainId, account } = useActiveWeb3React() - - const exchangeContract = useV1ExchangeContract(validatedAddress ? validatedAddress : undefined) - 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 - } - - return ( - - - - - Migrate V1 Liquidity -
- -
-
- - {!account ? ( - You must connect an account. - ) : validatedAddress && chainId && token?.equals(WETH9[chainId]) ? ( - <> - - Because Uniswap V2 uses WETH under the hood, your Uniswap V1 WETH/ETH liquidity cannot be migrated. You - may want to remove your liquidity instead. - - - { - history.push(`/remove/v1/${validatedAddress}`) - }} - > - Remove - - - ) : userLiquidityBalance && token ? ( - - ) : ( - - )} -
-
- ) -} diff --git a/src/pages/MigrateV1/RemoveV1Exchange.tsx b/src/pages/MigrateV1/RemoveV1Exchange.tsx deleted file mode 100644 index 5862106795..0000000000 --- a/src/pages/MigrateV1/RemoveV1Exchange.tsx +++ /dev/null @@ -1,182 +0,0 @@ -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(false) - const [pendingRemovalHash, setPendingRemovalHash] = useState(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 ( - - - This tool will remove your V1 liquidity and send the underlying assets to your wallet. - - - - - -
- - {isSuccessfullyRemoved ? 'Success' : isRemovalPending ? Removing : 'Remove'} - -
-
- - {`Your Uniswap V1 ${ - chainId && token.equals(WETH9[chainId]) ? 'WETH' : token.symbol - }/ETH liquidity will be redeemed for underlying assets.`} - -
- ) -} - -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 - } - - return ( - - - - - Remove V1 Liquidity -
- -
-
- - {!account ? ( - You must connect an account. - ) : userLiquidityBalance && token && exchangeContract ? ( - - ) : ( - - )} -
-
- ) -} diff --git a/src/pages/MigrateV1/index.tsx b/src/pages/MigrateV1/index.tsx deleted file mode 100644 index dcb53d553e..0000000000 --- a/src/pages/MigrateV1/index.tsx +++ /dev/null @@ -1,119 +0,0 @@ -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('') - 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 ? ( - - ) : 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 ( - - - - - Migrate V1 Liquidity -
- -
-
- - - For each pool shown below, click migrate to remove your liquidity from Uniswap V1 and deposit it into Uniswap - V2. - - - {!account ? ( - - - Connect to a wallet to view your V1 liquidity. - - - ) : isLoading ? ( - - - Loading - - - ) : ( - <> - - - - {allV1PairsWithLiquidity?.length > 0 ? ( - <>{allV1PairsWithLiquidity} - ) : ( - - )} - - )} -
-
- ) -} diff --git a/src/pages/MigrateV2/MigrateV2Pair.tsx b/src/pages/MigrateV2/MigrateV2Pair.tsx index 4474f26bd9..e688caa581 100644 --- a/src/pages/MigrateV2/MigrateV2Pair.tsx +++ b/src/pages/MigrateV2/MigrateV2Pair.tsx @@ -17,7 +17,6 @@ import { useTokenBalance } from '../../state/wallet/hooks' import { BackArrow, ExternalLink, TYPE } from '../../theme' import { getEtherscanLink, isAddress } from '../../utils' import { BodyWrapper } from '../AppBody' -import { EmptyState } from '../MigrateV1/EmptyState' import { V2_MIGRATOR_ADDRESSES } from 'constants/v3' import { PoolState, usePool } from 'data/Pools' import { FeeAmount, Pool, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk' @@ -43,6 +42,14 @@ import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' const ZERO = JSBI.BigInt(0) +function EmptyState({ message }: { message: string }) { + return ( + + {message} + + ) +} + function LiquidityInfo({ token0Amount, token1Amount }: { token0Amount: TokenAmount; token1Amount: TokenAmount }) { return ( <> @@ -198,10 +205,9 @@ function V2PairMigration({ const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: BigNumber } | null>( null ) - const [approval, approveManually] = useApproveCallback( - pairBalance, - chainId ? V2_MIGRATOR_ADDRESSES[chainId] : undefined - ) + + const migratorAddress = chainId && V2_MIGRATOR_ADDRESSES[chainId] + const [approval, approveManually] = useApproveCallback(pairBalance, migratorAddress) const isArgentWallet = useIsArgentWallet() const approve = useCallback(async () => { @@ -389,8 +395,8 @@ function V2PairMigration({ This tool will safely migrate your V2 liquidity to V3. The process is completely trustless thanks to the{' '} - {chainId && ( - + {chainId && migratorAddress && ( + Uniswap migration contract↗ )} diff --git a/src/pages/MigrateV2/index.tsx b/src/pages/MigrateV2/index.tsx index 0f522072fb..4ebf021c0c 100644 --- a/src/pages/MigrateV2/index.tsx +++ b/src/pages/MigrateV2/index.tsx @@ -9,7 +9,6 @@ import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks' import { BackArrow, StyledInternalLink, TYPE } from '../../theme' import { LightCard } from '../../components/Card' import { BodyWrapper } from '../AppBody' -import { EmptyState } from '../MigrateV1/EmptyState' import QuestionHelper from '../../components/QuestionHelper' import { Dots } from '../../components/swap/styleds' import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks' @@ -18,6 +17,13 @@ import { usePairs } from 'data/V2' // TODO there's a bug in loading where "No V2 Liquidity found" flashes // TODO add support for more pairs +function EmptyState({ message }: { message: string }) { + return ( + + {message} + + ) +} export default function MigrateV2() { const theme = useContext(ThemeContext) diff --git a/src/pages/Pool/v2.tsx b/src/pages/Pool/v2.tsx index 2812e6e208..6b5d5783d8 100644 --- a/src/pages/Pool/v2.tsx +++ b/src/pages/Pool/v2.tsx @@ -3,11 +3,9 @@ import styled, { ThemeContext } from 'styled-components' import JSBI from 'jsbi' import { Link } from 'react-router-dom' import { SwapPoolTabs } from '../../components/NavigationTabs' - import FullPositionCard from '../../components/PositionCard' -import { useUserHasLiquidityInAllTokens } from '../../data/V1' import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks' -import { StyledInternalLink, ExternalLink, TYPE, HideSmall } from '../../theme' +import { ExternalLink, TYPE, HideSmall } from '../../theme' import { Text } from 'rebass' import Card from '../../components/Card' import { RowBetween, RowFixed } from '../../components/Row' @@ -108,8 +106,6 @@ export default function Pool() { 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 const stakingInfo = useStakingInfo() const stakingInfosWithBalance = stakingInfo?.filter((pool) => JSBI.greaterThan(pool.stakedAmount.raw, BIG_INT_ZERO)) @@ -223,15 +219,6 @@ export default function Pool() { )} - - - - {hasV1Liquidity ? 'Uniswap V1 liquidity found!' : "Don't see a pool you joined?"}{' '} - - {hasV1Liquidity ? 'Migrate now.' : 'Import it.'} - - - diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index f18e27486f..fce4cd27b7 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -14,7 +14,6 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel' import { SwapPoolTabs } from '../../components/NavigationTabs' import { AutoRow, RowBetween } from '../../components/Row' import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown' -import BetterTradeLink, { DefaultVersionLink } from '../../components/swap/BetterTradeLink' import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee' import { ArrowWrapper, BottomGrouping, SwapCallbackError, Wrapper } from '../../components/swap/styleds' import TradePrice from '../../components/swap/TradePrice' @@ -23,13 +22,13 @@ import ProgressSteps from '../../components/ProgressSteps' import SwapHeader from '../../components/swap/SwapHeader' import { INITIAL_ALLOWED_SLIPPAGE } from '../../constants' -import { getTradeVersion } from '../../data/V1' +import { getTradeVersion } from '../../data/getTradeVersion' import { useActiveWeb3React } from '../../hooks' import { useCurrency, useAllTokens } from '../../hooks/Tokens' import { ApprovalState, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback' import useENSAddress from '../../hooks/useENSAddress' 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 { useToggleSettingsMenu, useWalletModalToggle } from '../../state/application/hooks' import { Field } from '../../state/swap/actions' @@ -48,7 +47,6 @@ import { ClickableText } from '../Pool/styleds' import Loader from '../../components/Loader' import { useIsTransactionUnsupported } from 'hooks/Trades' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' -import { isTradeBetter } from 'utils/trades' import { RouteComponentProps } from 'react-router-dom' export default function Swap({ history }: RouteComponentProps) { @@ -91,14 +89,7 @@ export default function Swap({ history }: RouteComponentProps) { // swap state const { independentField, typedValue, recipient } = useSwapState() - const { - v1Trade, - v2Trade, - currencyBalances, - parsedAmount, - currencies, - inputError: swapInputError, - } = useDerivedSwapInfo() + const { v2Trade, currencyBalances, parsedAmount, currencies, inputError: swapInputError } = useDerivedSwapInfo() const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback( currencies[Field.INPUT], @@ -109,14 +100,9 @@ export default function Swap({ history }: RouteComponentProps) { const { address: recipientAddress } = useENSAddress(recipient) const toggledVersion = useToggledVersion() const tradesByVersion = { - [Version.v1]: v1Trade, [Version.v2]: v2Trade, } 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 ? { @@ -509,11 +495,6 @@ export default function Swap({ history }: RouteComponentProps) { )} {isExpertMode && swapErrorMessage ? : null} - {betterTradeLinkV2 && !swapIsUnsupported && toggledVersion === Version.v1 ? ( - - ) : toggledVersion !== DEFAULT_VERSION && defaultTrade ? ( - - ) : null} diff --git a/src/state/swap/hooks.ts b/src/state/swap/hooks.ts index 9730c34134..dc12843f2a 100644 --- a/src/state/swap/hooks.ts +++ b/src/state/swap/hooks.ts @@ -1,12 +1,10 @@ import useENS from '../../hooks/useENS' -import { Version } from '../../hooks/useToggledVersion' import { parseUnits } from '@ethersproject/units' import { Currency, CurrencyAmount, ETHER, Token, TokenAmount } from '@uniswap/sdk-core' import { JSBI, Trade } from '@uniswap/v2-sdk' import { ParsedQs } from 'qs' import { useCallback, useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { useV1Trade } from '../../data/V1' import { useActiveWeb3React } from '../../hooks' import { useCurrency } from '../../hooks/Tokens' import { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades' @@ -16,7 +14,6 @@ import { AppDispatch, AppState } from '../index' import { useCurrencyBalances } from '../wallet/hooks' import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions' import { SwapState } from './reducer' -import useToggledVersion from '../../hooks/useToggledVersion' import { useUserSlippageTolerance } from '../user/hooks' import { computeSlippageAdjustedAmounts } from '../../utils/prices' @@ -89,11 +86,11 @@ export function tryParseAmount(value?: string, currency?: Currency): CurrencyAmo return undefined } -const BAD_RECIPIENT_ADDRESSES: string[] = [ - '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f', // v2 factory - '0xf164fC0Ec4E93095b804a4795bBe1e041497b92a', // v2 router 01 - '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', // v2 router 02 -] +const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = { + '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f': true, // v2 factory + '0xf164fC0Ec4E93095b804a4795bBe1e041497b92a': true, // v2 router 01 + '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D': true, // v2 router 02 +} /** * Returns true if any of the pairs or tokens in a trade have the given checksummed address @@ -114,12 +111,9 @@ export function useDerivedSwapInfo(): { parsedAmount: CurrencyAmount | undefined v2Trade: Trade | undefined inputError?: string - v1Trade: Trade | undefined } { const { account } = useActiveWeb3React() - const toggledVersion = useToggledVersion() - const { independentField, typedValue, @@ -156,9 +150,6 @@ export function useDerivedSwapInfo(): { [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 if (!account) { inputError = 'Connect Wallet' @@ -177,7 +168,7 @@ export function useDerivedSwapInfo(): { inputError = inputError ?? 'Enter a recipient' } else { if ( - BAD_RECIPIENT_ADDRESSES.indexOf(formattedTo) !== -1 || + BAD_RECIPIENT_ADDRESSES[formattedTo] || (bestTradeExactIn && involvesAddress(bestTradeExactIn, formattedTo)) || (bestTradeExactOut && involvesAddress(bestTradeExactOut, formattedTo)) ) { @@ -189,19 +180,10 @@ export function useDerivedSwapInfo(): { const slippageAdjustedAmounts = v2Trade && allowedSlippage && computeSlippageAdjustedAmounts(v2Trade, allowedSlippage) - const slippageAdjustedAmountsV1 = - v1Trade && allowedSlippage && computeSlippageAdjustedAmounts(v1Trade, allowedSlippage) - // compare input balance to max input based on version const [balanceIn, amountIn] = [ currencyBalances[Field.INPUT], - toggledVersion === Version.v1 - ? slippageAdjustedAmountsV1 - ? slippageAdjustedAmountsV1[Field.INPUT] - : null - : slippageAdjustedAmounts - ? slippageAdjustedAmounts[Field.INPUT] - : null, + slippageAdjustedAmounts ? slippageAdjustedAmounts[Field.INPUT] : null, ] if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) { @@ -214,7 +196,6 @@ export function useDerivedSwapInfo(): { parsedAmount, v2Trade: v2Trade ?? undefined, inputError, - v1Trade, } } diff --git a/src/utils/v1SwapArgument.test.ts b/src/utils/v1SwapArgument.test.ts deleted file mode 100644 index d59be1ae7d..0000000000 --- a/src/utils/v1SwapArgument.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -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') - }) -}) diff --git a/src/utils/v1SwapArguments.ts b/src/utils/v1SwapArguments.ts deleted file mode 100644 index d7c9390ed0..0000000000 --- a/src/utils/v1SwapArguments.ts +++ /dev/null @@ -1,91 +0,0 @@ -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 -): 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', - } - } - } -} diff --git a/yarn.lock b/yarn.lock index 03e0a6a254..760c6f59f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4149,25 +4149,25 @@ "@uniswap/v2-core" "1.0.1" "@uniswap/v3-core" "1.0.0-rc.2" -"@uniswap/v3-periphery@^1.0.0-beta.21": - version "1.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.0.0-beta.21.tgz#0b8510bebca4b74aabdca72c5545bd5bec5128cd" - integrity sha512-o4U+lyH6qtlG2RTy3H/mtUGJuflkmVJ0pnXyrThZKC1KV/avlVgf4hmlG2PvOCV0yfwGMjQARKQ4jv6OpLFVqA== +"@uniswap/v3-periphery@^1.0.0-beta.22": + version "1.0.0-beta.22" + resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.0.0-beta.22.tgz#b084926f290494ba3e4fcfa2b6a4ca11cd6631be" + integrity sha512-kDi5d1/7j++JDdfr0eemZo/2ZYtMmxRaq7O6cfaOW5Pv5j2wCYsauwC3SKzjpXsFD6Z0SdPPS3pVAu7UygnJYg== dependencies: "@openzeppelin/contracts" "3.4.1-solc-0.7-2" "@uniswap/lib" "^4.0.1-alpha" "@uniswap/v2-core" "1.0.1" "@uniswap/v3-core" "1.0.0-rc.2" -"@uniswap/v3-sdk@^1.0.0-alpha.13": - version "1.0.0-alpha.13" - resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-1.0.0-alpha.13.tgz#5634bb361e6bcb8e1a05d79a2aa61ffe13412f0c" - integrity sha512-OEEMhMoJlRQyqcd6u4r37cyifVy5jKJf/afW1jD8iqRqg/qAiYG0aR9Y/s7HiDA9WVHyaL/0YsWG8f0hv2UK/w== +"@uniswap/v3-sdk@^1.0.0-alpha.17": + version "1.0.0-alpha.17" + resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-1.0.0-alpha.17.tgz#5d21d2fc9f7d265af241933353661824e54b3fe4" + integrity sha512-fdLMwC+YrXV1KMf2nCzPqbl6309hn0Z74GpWGa3L2ow4Ntp6e4VHQu6uWffCRIwoRbFbhIQaoWlYasCuOU+o2Q== dependencies: "@ethersproject/abi" "^5.0.12" "@ethersproject/solidity" "^5.0.9" "@uniswap/sdk-core" "^1.0.9" - "@uniswap/v3-periphery" "^1.0.0-beta.21" + "@uniswap/v3-periphery" "^1.0.0-beta.22" tiny-invariant "^1.1.0" tiny-warning "^1.0.3"