Skip to content

Commit c1d35cc

Browse files
moodysalemcallil
andauthored
feat(migrate): adds the migrate flow to the uniswap exchange site
* links and page * print all the details of the liquidity * show working approve/migrate buttons * testnet v1 factory addresses * split code up into two pages * getting closer to styled * compute min amount out of eth and token * compute min amount out of eth and token * add a back button to the list page * Improve empty states * Improve the state management * change the display of the migrate page to be more similar to original * style fix, pending transaction hook fix * add forwarding to netlify.toml * handle case where v2 spot price does not exist because pair does not exist * make ternaries more accurate * handle first liquidity provider situation * Style tweaks for migrate * merge * Address feedback - show pool token amount - show success when migration complete - show price warning only if price difference is large Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
1 parent f279b2b commit c1d35cc

File tree

15 files changed

+551
-63
lines changed

15 files changed

+551
-63
lines changed

netlify.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
conditions = {Country=["BY","CU","IR","IQ","CI","LR","KP","SD","SY","ZW"]}
88
headers = {Link="<https://uniswap.exchange>"}
99

10+
# forward migrate
11+
[[redirects]]
12+
from = "https://migrate.uniswap.exchange/*"
13+
to = "https://uniswap.exchange/migrate/v1"
14+
status = 301
15+
force = true
16+
1017
# forward v2 subdomain to apex
1118
[[redirects]]
1219
from = "https://v2.uniswap.exchange/*"

src/components/Header/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Row from '../Row'
88
import Menu from '../Menu'
99
import Web3Status from '../Web3Status'
1010

11-
import { ExternalLink } from '../../theme'
11+
import { ExternalLink, StyledInternalLink } from '../../theme'
1212
import { Text } from 'rebass'
1313
import { WETH, ChainId } from '@uniswap/sdk'
1414
import { isMobile } from 'react-device-detect'
@@ -163,9 +163,9 @@ export default function Header() {
163163
<b>blog post ↗</b>
164164
</ExternalLink>
165165
&nbsp;or&nbsp;
166-
<ExternalLink href="https://migrate.uniswap.exchange/">
166+
<StyledInternalLink to="/migrate/v1">
167167
<b>migrate your liquidity ↗</b>
168-
</ExternalLink>
168+
</StyledInternalLink>
169169
.
170170
</MigrateBanner>
171171
<RowBetween padding="1rem">

src/constants/abis/migrator.json

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
[
2+
{
3+
"inputs": [
4+
{
5+
"internalType": "address",
6+
"name": "_factoryV1",
7+
"type": "address"
8+
},
9+
{
10+
"internalType": "address",
11+
"name": "_router",
12+
"type": "address"
13+
}
14+
],
15+
"stateMutability": "nonpayable",
16+
"type": "constructor"
17+
},
18+
{
19+
"inputs": [
20+
{
21+
"internalType": "address",
22+
"name": "token",
23+
"type": "address"
24+
},
25+
{
26+
"internalType": "uint256",
27+
"name": "amountTokenMin",
28+
"type": "uint256"
29+
},
30+
{
31+
"internalType": "uint256",
32+
"name": "amountETHMin",
33+
"type": "uint256"
34+
},
35+
{
36+
"internalType": "address",
37+
"name": "to",
38+
"type": "address"
39+
},
40+
{
41+
"internalType": "uint256",
42+
"name": "deadline",
43+
"type": "uint256"
44+
}
45+
],
46+
"name": "migrate",
47+
"outputs": [],
48+
"stateMutability": "nonpayable",
49+
"type": "function"
50+
},
51+
{
52+
"stateMutability": "payable",
53+
"type": "receive"
54+
}
55+
]

src/constants/abis/migrator.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import MIGRATOR_ABI from './migrator.json'
2+
3+
const MIGRATOR_ADDRESS = '0x16D4F26C15f3658ec65B1126ff27DD3dF2a2996b'
4+
5+
export { MIGRATOR_ADDRESS, MIGRATOR_ABI }

src/constants/v1/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import { Interface } from '@ethersproject/abi'
2+
import { ChainId } from '@uniswap/sdk'
23
import V1_EXCHANGE_ABI from './v1_exchange.json'
34
import V1_FACTORY_ABI from './v1_factory.json'
45

5-
const V1_FACTORY_ADDRESS = '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
6+
const V1_FACTORY_ADDRESSES: { [chainId in ChainId]: string } = {
7+
[ChainId.MAINNET]: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
8+
[ChainId.ROPSTEN]: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351',
9+
[ChainId.RINKEBY]: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
10+
[ChainId.GÖRLI]: '0x6Ce570d02D73d4c384b46135E87f8C592A8c86dA',
11+
[ChainId.KOVAN]: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30'
12+
}
13+
614
const V1_FACTORY_INTERFACE = new Interface(V1_FACTORY_ABI)
715
const V1_EXCHANGE_INTERFACE = new Interface(V1_EXCHANGE_ABI)
816

9-
export { V1_FACTORY_ADDRESS, V1_FACTORY_INTERFACE, V1_FACTORY_ABI, V1_EXCHANGE_INTERFACE, V1_EXCHANGE_ABI }
17+
export { V1_FACTORY_ADDRESSES, V1_FACTORY_INTERFACE, V1_FACTORY_ABI, V1_EXCHANGE_INTERFACE, V1_EXCHANGE_ABI }

src/data/V1.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,42 +30,39 @@ function useMockV1Pair(token?: Token): MockV1Pair | undefined {
3030
: undefined
3131
}
3232

33-
// returns ALL v1 exchange addresses
34-
export function useAllV1ExchangeAddresses(): string[] {
35-
const factory = useV1FactoryContract()
36-
const exchangeCount = useSingleCallResult(factory, 'tokenCount')?.result
37-
38-
const parsedCount = parseInt(exchangeCount?.toString() ?? '0')
39-
40-
const indices = useMemo(() => [...Array(parsedCount).keys()].map(ix => [ix]), [parsedCount])
41-
const data = useSingleContractMultipleData(factory, 'getTokenWithId', indices, NEVER_RELOAD)
42-
43-
return useMemo(() => data?.map(({ result }) => result?.[0])?.filter(x => x) ?? [], [data])
44-
}
45-
4633
// returns all v1 exchange addresses in the user's token list
47-
export function useAllTokenV1ExchangeAddresses(): string[] {
34+
export function useAllTokenV1Exchanges(): { [exchangeAddress: string]: Token } {
4835
const allTokens = useAllTokens()
4936
const factory = useV1FactoryContract()
5037
const args = useMemo(() => Object.keys(allTokens).map(tokenAddress => [tokenAddress]), [allTokens])
5138

5239
const data = useSingleContractMultipleData(factory, 'getExchange', args, NEVER_RELOAD)
5340

54-
return useMemo(() => data?.map(({ result }) => result?.[0])?.filter(x => x) ?? [], [data])
41+
return useMemo(
42+
() =>
43+
data?.reduce<{ [exchangeAddress: string]: Token }>((memo, { result }, ix) => {
44+
const token = allTokens[args[ix][0]]
45+
if (result?.[0]) {
46+
memo[result?.[0]] = token
47+
}
48+
return memo
49+
}, {}) ?? {},
50+
[allTokens, args, data]
51+
)
5552
}
5653

5754
// returns whether any of the tokens in the user's token list have liquidity on v1
58-
export function useUserProbablyHasV1Liquidity(): boolean | undefined {
59-
const exchangeAddresses = useAllTokenV1ExchangeAddresses()
55+
export function useUserHasLiquidityInAllTokens(): boolean | undefined {
56+
const exchanges = useAllTokenV1Exchanges()
6057

6158
const { account, chainId } = useActiveWeb3React()
6259

63-
const fakeTokens = useMemo(
64-
() => (chainId ? exchangeAddresses.map(address => new Token(chainId, address, 18, 'UNI-V1')) : []),
65-
[chainId, exchangeAddresses]
60+
const fakeLiquidityTokens = useMemo(
61+
() => (chainId ? Object.keys(exchanges).map(address => new Token(chainId, address, 18, 'UNI-V1')) : []),
62+
[chainId, exchanges]
6663
)
6764

68-
const balances = useTokenBalances(account ?? undefined, fakeTokens)
65+
const balances = useTokenBalances(account ?? undefined, fakeLiquidityTokens)
6966

7067
return useMemo(
7168
() =>

src/hooks/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { Web3Provider } from '@ethersproject/providers'
2+
import { ChainId } from '@uniswap/sdk'
23
import { useWeb3React as useWeb3ReactCore } from '@web3-react/core'
4+
import { Web3ReactContextInterface } from '@web3-react/core/dist/types'
35
import { useEffect, useState } from 'react'
46
import { isMobile } from 'react-device-detect'
57
import { injected } from '../connectors'
68
import { NetworkContextName } from '../constants'
79

8-
export function useActiveWeb3React() {
10+
export function useActiveWeb3React(): Web3ReactContextInterface<Web3Provider> & { chainId?: ChainId } {
911
const context = useWeb3ReactCore<Web3Provider>()
1012
const contextNetwork = useWeb3ReactCore<Web3Provider>(NetworkContextName)
1113
return context.active ? context : contextNetwork

src/hooks/useContract.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { ChainId } from '@uniswap/sdk'
33
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
44
import { useMemo } from 'react'
55
import ERC20_ABI from '../constants/abis/erc20.json'
6-
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESS } from '../constants/v1'
6+
import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator'
7+
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from '../constants/v1'
78
import { MULTICALL_ABI, MULTICALL_NETWORKS } from '../constants/multicall'
89
import { getContract } from '../utils'
910
import { useActiveWeb3React } from './index'
@@ -25,13 +26,17 @@ function useContract(address?: string, ABI?: any, withSignerIfPossible = true):
2526

2627
export function useV1FactoryContract(): Contract | null {
2728
const { chainId } = useActiveWeb3React()
28-
return useContract(chainId === 1 ? V1_FACTORY_ADDRESS : undefined, V1_FACTORY_ABI, false)
29+
return useContract(V1_FACTORY_ADDRESSES[chainId as ChainId], V1_FACTORY_ABI, false)
2930
}
3031

3132
export function useV1ExchangeContract(address: string): Contract | null {
3233
return useContract(address, V1_EXCHANGE_ABI, false)
3334
}
3435

36+
export function useV2MigratorContract(): Contract | null {
37+
return useContract(MIGRATOR_ADDRESS, MIGRATOR_ABI, true)
38+
}
39+
3540
export function useTokenContract(tokenAddress?: string, withSignerIfPossible = true): Contract | null {
3641
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible)
3742
}

src/pages/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import Web3ReactManager from '../components/Web3ReactManager'
99
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
1010
import AddLiquidity from './AddLiquidity'
1111
import CreatePool from './CreatePool'
12+
import MigrateV1 from './MigrateV1'
13+
import MigrateV1Exchange from './MigrateV1/MigrateV1Exchange'
1214
import Pool from './Pool'
1315
import PoolFinder from './PoolFinder'
1416
import RemoveLiquidity from './RemoveLiquidity'
@@ -99,6 +101,8 @@ export default function App() {
99101
<Route exact strict path="/create" component={CreatePool} />
100102
<Route exact strict path="/add/:tokens" component={AddLiquidity} />
101103
<Route exact strict path="/remove/:tokens" component={RemoveLiquidity} />
104+
<Route exact strict path="/migrate/v1" component={MigrateV1} />
105+
<Route exact strict path="/migrate/v1/:address" component={MigrateV1Exchange} />
102106
<Route component={RedirectPathToSwapOnly} />
103107
</Switch>
104108
</Web3ReactManager>

src/pages/AppBody.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react'
22
import styled from 'styled-components'
33
import NavigationTabs from '../components/NavigationTabs'
44

5-
const Body = styled.div`
5+
export const BodyWrapper = styled.div`
66
position: relative;
77
max-width: 420px;
88
width: 100%;
@@ -18,9 +18,9 @@ const Body = styled.div`
1818
*/
1919
export default function AppBody({ children }: { children: React.ReactNode }) {
2020
return (
21-
<Body>
21+
<BodyWrapper>
2222
<NavigationTabs />
2323
<>{children}</>
24-
</Body>
24+
</BodyWrapper>
2525
)
2626
}

0 commit comments

Comments
 (0)