Commit e311e2fc authored by Noah Zinsmeister's avatar Noah Zinsmeister Committed by GitHub

mainnet (#782)

* mainnet config

* fix token + pair sorting

* standardize wrapped useWeb3React imports

* add final return to pair sorting

* add connecting pairs

* break tokens out into separate files

revert isAddress change

* filter out duplicate pairs

* remove add liquidity prompts

* link to v1 trades that are invalid on v2

* forward v2 subdomain to apex

* update blog link

* get rid of smart quotes

* link to uniswap info in position card

* improve pair sorting/searching

break out token identification

fix crash on early pool position clicks

change pair token separator from : to /
parent 9c4f63f4
...@@ -7,6 +7,12 @@ ...@@ -7,6 +7,12 @@
conditions = {Country=["BY","CU","IR","IQ","CI","LR","KP","SD","SY","ZW"]} conditions = {Country=["BY","CU","IR","IQ","CI","LR","KP","SD","SY","ZW"]}
headers = {Link="<https://uniswap.exchange>"} headers = {Link="<https://uniswap.exchange>"}
# forward v2 subdomain to apex
[[redirects]]
from = "https://v2.uniswap.exchange/*"
to = "https://uniswap.exchange/:splat"
status = 301
# support SPA setup # support SPA setup
[[redirects]] [[redirects]]
from = "/*" from = "/*"
......
...@@ -158,15 +158,15 @@ export default function Header() { ...@@ -158,15 +158,15 @@ export default function Header() {
return ( return (
<HeaderFrame> <HeaderFrame>
<MigrateBanner> <MigrateBanner>
{/* <b>Uniswap V2 is live.&nbsp;</b> Move your liquidity now using the&nbsp; */} Uniswap V2 is live! Read the&nbsp;
<b>Testnet only.</b>&nbsp;Uniswap V2 has not been launched and is coming soon.&nbsp;Read the&nbsp; <Link href="https://uniswap.org/blog/launch-uniswap-v2/">
{/* <Link href="https://migrate.uniswap.exchange/">
<b>migration helper</b>
</Link>
&nbsp;or read the&nbsp; */}
<Link href="https://uniswap.org/blog/uniswap-v2/">
<b>blog post ↗</b> <b>blog post ↗</b>
</Link> </Link>
&nbsp;or&nbsp;
<Link href="https://migrate.uniswap.exchange/">
<b>migrate your liquidity ↗</b>
</Link>
.
</MigrateBanner> </MigrateBanner>
<RowBetween padding="1rem"> <RowBetween padding="1rem">
<HeaderElement> <HeaderElement>
......
...@@ -15,7 +15,7 @@ import { AutoColumn, ColumnCenter } from '../Column' ...@@ -15,7 +15,7 @@ import { AutoColumn, ColumnCenter } from '../Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button' import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
import { useToken } from '../../hooks/Tokens' import { useToken } from '../../hooks/Tokens'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '../../hooks'
import { usePairAdder } from '../../state/user/hooks' import { usePairAdder } from '../../state/user/hooks'
import { usePair } from '../../data/Reserves' import { usePair } from '../../data/Reserves'
......
...@@ -4,7 +4,7 @@ import { darken } from 'polished' ...@@ -4,7 +4,7 @@ import { darken } from 'polished'
import { RouteComponentProps, withRouter } from 'react-router-dom' import { RouteComponentProps, withRouter } from 'react-router-dom'
import { Percent, Pair, JSBI } from '@uniswap/sdk' import { Percent, Pair, JSBI } from '@uniswap/sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '../../hooks'
import { useTotalSupply } from '../../data/TotalSupply' import { useTotalSupply } from '../../data/TotalSupply'
import { useTokenBalance } from '../../state/wallet/hooks' import { useTokenBalance } from '../../state/wallet/hooks'
...@@ -80,7 +80,7 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr ...@@ -80,7 +80,7 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
<RowFixed> <RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} /> <DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} />
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
{token0?.symbol}:{token1?.symbol} {token0?.symbol}/{token1?.symbol}
</Text> </Text>
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
...@@ -134,7 +134,7 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr ...@@ -134,7 +134,7 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
<RowFixed> <RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} /> <DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} />
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
{token0?.symbol}:{token1?.symbol} {token0?.symbol}/{token1?.symbol}
</Text> </Text>
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
...@@ -204,7 +204,7 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr ...@@ -204,7 +204,7 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
)} )}
<AutoRow justify="center" marginTop={'10px'}> <AutoRow justify="center" marginTop={'10px'}>
<Link>View pool information ↗</Link> <Link href={`https://uniswap.info/pair/${pair?.liquidityToken.address}`}>View pool information ↗</Link>
</AutoRow> </AutoRow>
<RowBetween marginTop="10px"> <RowBetween marginTop="10px">
<ButtonSecondary <ButtonSecondary
......
...@@ -182,7 +182,7 @@ function SearchModal({ ...@@ -182,7 +182,7 @@ function SearchModal({
const allBalances = useAllTokenBalancesTreatingWETHasETH() const allBalances = useAllTokenBalancesTreatingWETHasETH()
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [sortDirection, setSortDirection] = useState(true) const [invertSearchOrder, setInvertSearchOrder] = useState(false)
const userAddedTokens = useUserAddedTokens() const userAddedTokens = useUserAddedTokens()
const fetchTokenByAddress = useFetchTokenByAddress() const fetchTokenByAddress = useFetchTokenByAddress()
...@@ -200,7 +200,7 @@ function SearchModal({ ...@@ -200,7 +200,7 @@ function SearchModal({
const [showTokenImport, setShowTokenImport] = useState(false) const [showTokenImport, setShowTokenImport] = useState(false)
// used to help scanning on results, put token found from input on left // used to help scanning on results, put token found from input on left
const [identifiedToken, setIdentifiedToken] = useState<Token | null>() const [identifiedToken, setIdentifiedToken] = useState<Token>()
useEffect(() => { useEffect(() => {
const address = isAddress(searchQuery) const address = isAddress(searchQuery)
...@@ -228,37 +228,37 @@ function SearchModal({ ...@@ -228,37 +228,37 @@ function SearchModal({
const tokenList = useMemo(() => { const tokenList = useMemo(() => {
return Object.keys(allTokens) return Object.keys(allTokens)
.sort((tokenAddressA, tokenAddressB): number => { .sort((tokenAddressA, tokenAddressB): number => {
if (tokenAddressA && allTokens[tokenAddressA]?.equals(WETH[chainId])) return -1 // -1 = a is first
if (tokenAddressB && allTokens[tokenAddressB]?.equals(WETH[chainId])) return 1 // 1 = b is first
if (allTokens[tokenAddressA].symbol && allTokens[tokenAddressB].symbol) { // sort ETH first
const aSymbol = allTokens[tokenAddressA].symbol.toLowerCase() const a = allTokens[tokenAddressA]
const bSymbol = allTokens[tokenAddressB].symbol.toLowerCase() const b = allTokens[tokenAddressB]
// sort by balance if (a.equals(WETH[chainId])) return -1
const balanceA = allBalances?.[account]?.[tokenAddressA] if (b.equals(WETH[chainId])) return 1
const balanceB = allBalances?.[account]?.[tokenAddressB]
// sort by balances
if (balanceA?.greaterThan('0') && !balanceB?.greaterThan('0')) { const balanceA = allBalances[account]?.[tokenAddressA]
return sortDirection ? -1 : 1 const balanceB = allBalances[account]?.[tokenAddressB]
} if (balanceA?.greaterThan('0') && !balanceB?.greaterThan('0')) return !invertSearchOrder ? -1 : 1
if (!balanceA?.greaterThan('0') && balanceB?.greaterThan('0')) { if (!balanceA?.greaterThan('0') && balanceB?.greaterThan('0')) return !invertSearchOrder ? 1 : -1
return sortDirection ? 1 : -1 if (balanceA?.greaterThan('0') && balanceB?.greaterThan('0')) {
} return balanceA.greaterThan(balanceB) ? (!invertSearchOrder ? -1 : 1) : !invertSearchOrder ? 1 : -1
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0 }
} else {
return 0 // sort by symbol
} return a.symbol.toLowerCase() < b.symbol.toLowerCase() ? -1 : 1
}) })
.filter(tokenAddress => isAddress(tokenAddress))
.map(tokenAddress => { .map(tokenAddress => {
const token = allTokens[tokenAddress]
return { return {
name: allTokens[tokenAddress].name, name: token.name,
symbol: allTokens[tokenAddress].symbol, symbol: token.symbol,
address: isAddress(tokenAddress) as string, // always a string after filtering address: isAddress(tokenAddress) as string,
balance: allBalances?.[account]?.[tokenAddress] balance: allBalances?.[account]?.[tokenAddress]
} }
}) })
}, [allTokens, allBalances, account, sortDirection, chainId]) }, [allTokens, chainId, allBalances, account, invertSearchOrder])
const filteredTokenList = useMemo(() => { const filteredTokenList = useMemo(() => {
return tokenList.filter(tokenEntry => { return tokenList.filter(tokenEntry => {
...@@ -307,57 +307,69 @@ function SearchModal({ ...@@ -307,57 +307,69 @@ function SearchModal({
onDismiss() onDismiss()
} }
// make an effort to identify the specific token a user is searching for
useEffect(() => {
const searchQueryIsAddress = !!isAddress(searchQuery)
// try to find an exact match by address
if (searchQueryIsAddress) {
const identifiedTokenByAddress = Object.values(allTokens).filter(token => {
if (searchQueryIsAddress && token.address === isAddress(searchQuery)) {
return true
}
return false
})
if (identifiedTokenByAddress.length > 0) setIdentifiedToken(identifiedTokenByAddress[0])
}
// try to find an exact match by symbol
else {
const identifiedTokenBySymbol = Object.values(allTokens).filter(token => {
if (token.symbol.slice(0, searchQuery.length).toLowerCase() === searchQuery.toLowerCase()) return true
return false
})
if (identifiedTokenBySymbol.length > 0) setIdentifiedToken(identifiedTokenBySymbol[0])
}
return () => {
setIdentifiedToken(undefined)
}
}, [allTokens, searchQuery])
const sortedPairList = useMemo(() => { const sortedPairList = useMemo(() => {
return allPairs.sort((a, b): number => { return allPairs.sort((a, b): number => {
// sort by balance // sort by balance
const balanceA = allBalances?.[account]?.[a.liquidityToken.address] const balanceA = allBalances[account]?.[a.liquidityToken.address]
const balanceB = allBalances?.[account]?.[b.liquidityToken.address] const balanceB = allBalances[account]?.[b.liquidityToken.address]
if (balanceA?.greaterThan('0') && !balanceB?.greaterThan('0')) return !invertSearchOrder ? -1 : 1
if (balanceA?.greaterThan('0') && !balanceB?.greaterThan('0')) { if (!balanceA?.greaterThan('0') && balanceB?.greaterThan('0')) return !invertSearchOrder ? 1 : -1
return sortDirection ? -1 : 1 if (balanceA?.greaterThan('0') && balanceB?.greaterThan('0')) {
return balanceA.greaterThan(balanceB) ? (!invertSearchOrder ? -1 : 1) : !invertSearchOrder ? 1 : -1
} }
if (!balanceA?.greaterThan('0') && balanceB?.greaterThan('0')) {
return sortDirection ? 1 : -1
} else {
return 0 return 0
}
}) })
}, [account, allBalances, allPairs, sortDirection]) }, [allPairs, allBalances, account, invertSearchOrder])
const filteredPairList = useMemo(() => { const filteredPairList = useMemo(() => {
const isAddress = searchQuery.slice(0, 2) === '0x' const searchQueryIsAddress = !!isAddress(searchQuery)
return sortedPairList.filter(pair => { return sortedPairList.filter(pair => {
if (searchQuery === '') { // if there's no search query, hide non-ETH pairs
return true if (searchQuery === '') return pair.token0.equals(WETH[chainId]) || pair.token1.equals(WETH[chainId])
}
const token0 = pair.token0 const token0 = pair.token0
const token1 = pair.token1 const token1 = pair.token1
if (!token0 || !token1) {
return false // no token fetched yet if (searchQueryIsAddress) {
if (token0.address === isAddress(searchQuery)) return true
if (token1.address === isAddress(searchQuery)) return true
} else { } else {
const regexMatches = Object.keys(token0).map(field => { const identifier0 = `${token0.symbol}/${token1.symbol}`
if ( const identifier1 = `${token1.symbol}/${token0.symbol}`
(field === 'address' && isAddress) || if (identifier0.slice(0, searchQuery.length).toLowerCase() === searchQuery.toLowerCase()) return true
(field === 'name' && !isAddress) || if (identifier1.slice(0, searchQuery.length).toLowerCase() === searchQuery.toLowerCase()) return true
(field === 'symbol' && !isAddress)
) {
if (token0[field].match(new RegExp(escapeRegExp(searchQuery), 'i'))) {
setIdentifiedToken(token0)
}
if (token1[field].match(new RegExp(escapeRegExp(searchQuery), 'i'))) {
setIdentifiedToken(token1)
}
return (
token0[field].match(new RegExp(escapeRegExp(searchQuery), 'i')) ||
token1[field].match(new RegExp(escapeRegExp(searchQuery), 'i'))
)
} }
return false return false
}) })
return regexMatches.some(m => m) }, [searchQuery, sortedPairList, chainId])
}
})
}, [searchQuery, sortedPairList])
function renderPairsList() { function renderPairsList() {
if (filteredPairList?.length === 0) { if (filteredPairList?.length === 0) {
...@@ -441,21 +453,7 @@ function SearchModal({ ...@@ -441,21 +453,7 @@ function SearchModal({
return <TokenModalInfo>{t('noToken')}</TokenModalInfo> return <TokenModalInfo>{t('noToken')}</TokenModalInfo>
} }
} else { } else {
return filteredTokenList return filteredTokenList.map(({ address, symbol, balance }) => {
.sort((a, b) => {
if (a.address === WETH[chainId].address) {
return -1
} else if (b.address === WETH[chainId].address) {
return 1
} else if (a.balance?.greaterThan('0') && !b.balance?.greaterThan('0')) {
return sortDirection ? -1 : 1
} else if (!a.balance?.greaterThan('0') && b.balance?.greaterThan('0')) {
return sortDirection ? 1 : -1
} else {
return sortDirection ? -1 : 1
}
})
.map(({ address, symbol, balance }) => {
const urlAdded = urlAddedTokens?.some(token => token.address === address) const urlAdded = urlAddedTokens?.some(token => token.address === address)
const customAdded = userAddedTokens?.some(token => token.address === address) && !urlAdded const customAdded = userAddedTokens?.some(token => token.address === address) && !urlAdded
...@@ -530,7 +528,7 @@ function SearchModal({ ...@@ -530,7 +528,7 @@ function SearchModal({
<FilterWrapper <FilterWrapper
onClick={() => { onClick={() => {
setActiveFilter(filter) setActiveFilter(filter)
setSortDirection(!sortDirection) setInvertSearchOrder(invertSearchOrder => !invertSearchOrder)
}} }}
selected={filter === activeFilter} selected={filter === activeFilter}
> >
...@@ -539,7 +537,7 @@ function SearchModal({ ...@@ -539,7 +537,7 @@ function SearchModal({
</Text> </Text>
{filter === activeFilter && filterType === 'tokens' && ( {filter === activeFilter && filterType === 'tokens' && (
<Text fontSize={14} fontWeight={500}> <Text fontSize={14} fontWeight={500}>
{sortDirection ? '' : ''} {!invertSearchOrder ? '' : ''}
</Text> </Text>
)} )}
</FilterWrapper> </FilterWrapper>
......
...@@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react' ...@@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import styled from 'styled-components' import styled from 'styled-components'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core' import { UnsupportedChainIdError } from '@web3-react/core'
import { useWeb3React } from '../../hooks'
import { URI_AVAILABLE } from '@web3-react/walletconnect-connector' import { URI_AVAILABLE } from '@web3-react/walletconnect-connector'
import { useWalletModalOpen, useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalOpen, useWalletModalToggle } from '../../state/application/hooks'
......
import { Trade } from '@uniswap/sdk'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { V1_TRADE_LINK_THRESHOLD } from '../../constants'
import { useV1TradeLinkIfBetter } from '../../data/V1'
import { Link } from '../../theme' import { Link } from '../../theme'
import { YellowCard } from '../Card' import { YellowCard } from '../Card'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
export default function V1TradeLink({ bestV2Trade }: { bestV2Trade?: Trade }) { export default function V1TradeLink({ v1TradeLinkIfBetter }: { v1TradeLinkIfBetter: string }) {
const v1TradeLinkIfBetter = useV1TradeLinkIfBetter(bestV2Trade, V1_TRADE_LINK_THRESHOLD)
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
return v1TradeLinkIfBetter ? ( return v1TradeLinkIfBetter ? (
<YellowCard style={{ marginTop: '12px', padding: '8px 4px' }}> <YellowCard style={{ marginTop: '12px', padding: '8px 4px' }}>
......
...@@ -15,7 +15,7 @@ export const network = new NetworkConnector({ ...@@ -15,7 +15,7 @@ export const network = new NetworkConnector({
}) })
export const injected = new InjectedConnector({ export const injected = new InjectedConnector({
supportedChainIds: [3, 4, 5, 42] supportedChainIds: [1, 3, 4, 5, 42]
}) })
// mainnet only // mainnet only
......
import { Token, ChainId } from '@uniswap/sdk'
export default [
new Token(ChainId.KOVAN, '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.KOVAN, '0xAaF64BFCC32d0F15873a02163e7E500671a4ffcD', 18, 'MKR', 'Maker')
]
This diff is collapsed.
import { Token, ChainId } from '@uniswap/sdk'
export default [
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.RINKEBY, '0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85', 18, 'MKR', 'Maker')
]
import { Token, ChainId } from '@uniswap/sdk'
export default [new Token(ChainId.ROPSTEN, '0xaD6D458402F60fD3Bd25163575031ACDce07538D', 18, 'DAI', 'Dai Stablecoin')]
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { Token, TokenAmount, Pair, Trade, ChainId, WETH, Route, TradeType, Percent } from '@uniswap/sdk' import { Token, TokenAmount, Pair, Trade, ChainId, WETH, Route, TradeType, Percent } from '@uniswap/sdk'
import useSWR from 'swr' import useSWR from 'swr'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '../hooks'
import IUniswapV1Factory from '../constants/abis/v1_factory.json' import IUniswapV1Factory from '../constants/abis/v1_factory.json'
import { V1_FACTORY_ADDRESS } from '../constants' import { V1_FACTORY_ADDRESS } from '../constants'
...@@ -41,49 +41,63 @@ function useMockV1Pair(token?: Token) { ...@@ -41,49 +41,63 @@ function useMockV1Pair(token?: Token) {
: undefined : undefined
} }
export function useV1TradeLinkIfBetter(trade: Trade, minimumDelta: Percent = new Percent('0')): string { export function useV1TradeLinkIfBetter(
const inputPair = useMockV1Pair(trade?.route?.input) isExactIn: boolean,
const outputPair = useMockV1Pair(trade?.route?.output) inputToken: Token,
outputToken: Token,
exactAmount: TokenAmount,
v2Trade: Trade,
minimumDelta: Percent = new Percent('0')
): string {
const { chainId } = useWeb3React()
const input = inputToken
const output = outputToken
const mainnet = chainId === ChainId.MAINNET
// get the mock v1 pairs
const inputPair = useMockV1Pair(input)
const outputPair = useMockV1Pair(output)
const mainnet = trade?.route?.input?.chainId === ChainId.MAINNET const inputIsWETH = mainnet && input?.equals(WETH[ChainId.MAINNET])
const inputIsWETH = mainnet && trade?.route?.input?.equals(WETH[ChainId.MAINNET]) const outputIsWETH = mainnet && output?.equals(WETH[ChainId.MAINNET])
const outputIsWETH = mainnet && trade?.route?.output?.equals(WETH[ChainId.MAINNET])
const neitherWETH = mainnet && !!trade && !inputIsWETH && !outputIsWETH
// construct a direct or through ETH v1 route
let pairs: Pair[] let pairs: Pair[]
if (inputIsWETH && outputPair) { if (inputIsWETH && outputPair) {
pairs = [outputPair] pairs = [outputPair]
} else if (outputIsWETH && inputPair) { } else if (outputIsWETH && inputPair) {
pairs = [inputPair] pairs = [inputPair]
} else if (neitherWETH && inputPair && outputPair) { }
// if neither are WETH, it's token-to-token (if they both exist)
else if (inputPair && outputPair) {
pairs = [inputPair, outputPair] pairs = [inputPair, outputPair]
} }
const route = pairs && new Route(pairs, trade.route.input) const route = pairs && new Route(pairs, input)
const v1Trade = const v1Trade =
route && route && exactAmount
new Trade( ? new Trade(route, exactAmount, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT)
route, : undefined
trade.tradeType === TradeType.EXACT_INPUT ? trade.inputAmount : trade.outputAmount,
trade.tradeType
)
let v1HasBetterTrade = false let v1HasBetterTrade = false
if (v1Trade) { if (v1Trade) {
if (trade.tradeType === TradeType.EXACT_INPUT) { if (isExactIn) {
// check if the output amount on v1, discounted by minimumDelta, is greater than on v2 // discount the v1 output amount by minimumDelta
const discountedV1Output = v1Trade.outputAmount.multiply(new Percent('1').subtract(minimumDelta)) const discountedV1Output = v1Trade?.outputAmount.multiply(new Percent('1').subtract(minimumDelta))
v1HasBetterTrade = discountedV1Output.greaterThan(trade.outputAmount) // check if the discounted v1 amount is still greater than v2, short-circuiting if no v2 trade exists
v1HasBetterTrade = !!!v2Trade || discountedV1Output.greaterThan(v2Trade.outputAmount)
} else { } else {
// check if the input amount on v1, inflated by minimumDelta, is less than on v2 // inflate the v1 amount by minimumDelta
const inflatedV1Input = v1Trade.inputAmount.multiply(new Percent('1').add(minimumDelta)) const inflatedV1Input = v1Trade?.inputAmount.multiply(new Percent('1').add(minimumDelta))
v1HasBetterTrade = inflatedV1Input.lessThan(trade.inputAmount) // check if the inflated v1 amount is still less than v2, short-circuiting if no v2 trade exists
v1HasBetterTrade = !!!v2Trade || inflatedV1Input.lessThan(v2Trade.inputAmount)
} }
} }
return v1HasBetterTrade return v1HasBetterTrade
? `https://v1.uniswap.exchange/swap?inputCurrency=${ ? `https://v1.uniswap.exchange/swap?inputCurrency=${inputIsWETH ? 'ETH' : input.address}&outputCurrency=${
inputIsWETH ? 'ETH' : trade.route.input.address outputIsWETH ? 'ETH' : output.address
}&outputCurrency=${outputIsWETH ? 'ETH' : trade.route.output.address}` }`
: undefined : undefined
} }
import { ChainId, Token, WETH } from '@uniswap/sdk' import { Token, WETH } from '@uniswap/sdk'
import { useEffect, useMemo } from 'react' import { useEffect, useMemo } from 'react'
import { useAddUserToken, useFetchTokenByAddress, useUserAddedTokens } from '../state/user/hooks' import { useAddUserToken, useFetchTokenByAddress, useUserAddedTokens } from '../state/user/hooks'
import { useWeb3React } from './index' import { useWeb3React } from './index'
import MAINNET_TOKENS from '../constants/tokens/mainnet'
import RINKEBY_TOKENS from '../constants/tokens/rinkeby'
import KOVAN_TOKENS from '../constants/tokens/kovan'
import ROPSTEN_TOKENS from '../constants/tokens/ropsten'
export const ALL_TOKENS = [ export const ALL_TOKENS = [
// WETH on all chains // WETH on all chains
...Object.values(WETH), ...Object.values(WETH),
// chain-specific tokens
// Mainnet Tokens ...MAINNET_TOKENS,
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'), ...RINKEBY_TOKENS,
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C'), ...KOVAN_TOKENS,
new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker'), ...ROPSTEN_TOKENS
// Rinkeby Tokens
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.RINKEBY, '0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85', 18, 'MKR', 'Maker'),
// Kovan Tokens
new Token(ChainId.KOVAN, '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.KOVAN, '0xAaF64BFCC32d0F15873a02163e7E500671a4ffcD', 18, 'MKR', 'Maker'),
// Ropsten Tokens
new Token(ChainId.ROPSTEN, '0xaD6D458402F60fD3Bd25163575031ACDce07538D', 18, 'DAI', 'Dai Stablecoin')
// Goerli Tokens
] ]
// remap WETH to ETH // remap WETH to ETH
.map(token => { .map(token => {
......
...@@ -23,15 +23,23 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] { ...@@ -23,15 +23,23 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
const aToUSDC = usePair(tokenA, chainId === ChainId.MAINNET ? USDC : null) const aToUSDC = usePair(tokenA, chainId === ChainId.MAINNET ? USDC : null)
const bToUSDC = usePair(tokenB, chainId === ChainId.MAINNET ? USDC : null) const bToUSDC = usePair(tokenB, chainId === ChainId.MAINNET ? USDC : null)
return useMemo(() => [pairBetween, aToETH, bToETH, aToDAI, bToDAI, aToUSDC, bToUSDC].filter(p => !!p), [ // get connecting pairs
pairBetween, const DAIToETH = usePair(chainId === ChainId.MAINNET ? DAI : null, WETH[chainId])
aToETH, const USDCToETH = usePair(chainId === ChainId.MAINNET ? USDC : null, WETH[chainId])
bToETH, const DAIToUSDC = usePair(chainId === ChainId.MAINNET ? DAI : null, chainId === ChainId.MAINNET ? USDC : null)
aToDAI,
bToDAI, // only pass along valid pairs, non-duplicated pairs
aToUSDC, return useMemo(
bToUSDC () =>
]) [pairBetween, aToETH, bToETH, aToDAI, bToDAI, aToUSDC, bToUSDC, DAIToETH, USDCToETH, DAIToUSDC]
// filter out invalid pairs
.filter(p => !!p)
// filter out duplicated pairs
.filter(
(p, i, pairs) => i === pairs.findIndex(pair => pair?.liquidityToken.address === p.liquidityToken.address)
),
[pairBetween, aToETH, bToETH, aToDAI, bToDAI, aToUSDC, bToUSDC, DAIToETH, USDCToETH, DAIToUSDC]
)
} }
/** /**
......
...@@ -14,7 +14,7 @@ import { RowBetween } from '../../components/Row' ...@@ -14,7 +14,7 @@ import { RowBetween } from '../../components/Row'
import { ButtonPrimary, ButtonSecondary } from '../../components/Button' import { ButtonPrimary, ButtonSecondary } from '../../components/Button'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '../../hooks'
import { usePair } from '../../data/Reserves' import { usePair } from '../../data/Reserves'
import { useAllDummyPairs } from '../../state/user/hooks' import { useAllDummyPairs } from '../../state/user/hooks'
......
...@@ -39,10 +39,9 @@ import { useDefaultsFromURL, useDerivedSwapInfo, useSwapActionHandlers, useSwapS ...@@ -39,10 +39,9 @@ import { useDefaultsFromURL, useDerivedSwapInfo, useSwapActionHandlers, useSwapS
import { useHasPendingApproval } from '../../state/transactions/hooks' import { useHasPendingApproval } from '../../state/transactions/hooks'
import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks' import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks'
import { CursorPointer, TYPE } from '../../theme' import { CursorPointer, TYPE } from '../../theme'
import { Link } from '../../theme/components'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningServerity } from '../../utils/prices' import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningServerity } from '../../utils/prices'
export default function Send({ history, location: { search } }: RouteComponentProps) { export default function Send({ location: { search } }: RouteComponentProps) {
useDefaultsFromURL(search) useDefaultsFromURL(search)
// text translation // text translation
...@@ -61,7 +60,14 @@ export default function Send({ history, location: { search } }: RouteComponentPr ...@@ -61,7 +60,14 @@ export default function Send({ history, location: { search } }: RouteComponentPr
// trade details, check query params for initial state // trade details, check query params for initial state
const { independentField, typedValue } = useSwapState() const { independentField, typedValue } = useSwapState()
const { parsedAmounts, bestTrade, tokenBalances, tokens, error: swapError } = useDerivedSwapInfo() const {
parsedAmounts,
bestTrade,
tokenBalances,
tokens,
error: swapError,
v1TradeLinkIfBetter
} = useDerivedSwapInfo()
const isSwapValid = !swapError && !recipientError && bestTrade const isSwapValid = !swapError && !recipientError && bestTrade
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
...@@ -474,13 +480,6 @@ export default function Send({ history, location: { search } }: RouteComponentPr ...@@ -474,13 +480,6 @@ export default function Send({ history, location: { search } }: RouteComponentPr
) : noRoute && userHasSpecifiedInputOutput ? ( ) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}> <GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main> <TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
<Link
onClick={() => {
history.push('/add/' + tokens[Field.INPUT]?.address + '-' + tokens[Field.OUTPUT]?.address)
}}
>
Add liquidity now.
</Link>
</GreyCard> </GreyCard>
) : mustApprove === true ? ( ) : mustApprove === true ? (
<ButtonLight onClick={approveCallback} disabled={pendingApprovalInput}> <ButtonLight onClick={approveCallback} disabled={pendingApprovalInput}>
...@@ -507,7 +506,7 @@ export default function Send({ history, location: { search } }: RouteComponentPr ...@@ -507,7 +506,7 @@ export default function Send({ history, location: { search } }: RouteComponentPr
</Text> </Text>
</ButtonError> </ButtonError>
)} )}
<V1TradeLink bestV2Trade={bestTrade} /> <V1TradeLink v1TradeLinkIfBetter={v1TradeLinkIfBetter} />
</BottomGrouping> </BottomGrouping>
{bestTrade && ( {bestTrade && (
<AdvancedSwapDetailsDropdown <AdvancedSwapDetailsDropdown
......
...@@ -25,11 +25,11 @@ import { useWalletModalToggle } from '../../state/application/hooks' ...@@ -25,11 +25,11 @@ import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/swap/actions' import { Field } from '../../state/swap/actions'
import { useDefaultsFromURL, useDerivedSwapInfo, useSwapActionHandlers, useSwapState } from '../../state/swap/hooks' import { useDefaultsFromURL, useDerivedSwapInfo, useSwapActionHandlers, useSwapState } from '../../state/swap/hooks'
import { useHasPendingApproval } from '../../state/transactions/hooks' import { useHasPendingApproval } from '../../state/transactions/hooks'
import { CursorPointer, Link, TYPE } from '../../theme' import { CursorPointer, TYPE } from '../../theme'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningServerity } from '../../utils/prices' import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningServerity } from '../../utils/prices'
import SwapModalHeader from '../../components/swap/SwapModalHeader' import SwapModalHeader from '../../components/swap/SwapModalHeader'
export default function Swap({ history, location: { search } }: RouteComponentProps) { export default function Swap({ location: { search } }: RouteComponentProps) {
useDefaultsFromURL(search) useDefaultsFromURL(search)
// text translation // text translation
// const { t } = useTranslation() // const { t } = useTranslation()
...@@ -40,7 +40,7 @@ export default function Swap({ history, location: { search } }: RouteComponentPr ...@@ -40,7 +40,7 @@ export default function Swap({ history, location: { search } }: RouteComponentPr
const toggleWalletModal = useWalletModalToggle() const toggleWalletModal = useWalletModalToggle()
const { independentField, typedValue } = useSwapState() const { independentField, typedValue } = useSwapState()
const { bestTrade, tokenBalances, parsedAmounts, tokens, error } = useDerivedSwapInfo() const { bestTrade, tokenBalances, parsedAmounts, tokens, error, v1TradeLinkIfBetter } = useDerivedSwapInfo()
const isValid = !error const isValid = !error
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
...@@ -277,14 +277,6 @@ export default function Swap({ history, location: { search } }: RouteComponentPr ...@@ -277,14 +277,6 @@ export default function Swap({ history, location: { search } }: RouteComponentPr
) : noRoute && userHasSpecifiedInputOutput ? ( ) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}> <GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main> <TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
<Link
onClick={() => {
history.push('/add/' + tokens[Field.INPUT]?.address + '-' + tokens[Field.OUTPUT]?.address)
}}
>
{' '}
Add liquidity now.
</Link>
</GreyCard> </GreyCard>
) : mustApprove === true ? ( ) : mustApprove === true ? (
<ButtonLight onClick={approveCallback} disabled={pendingApprovalInput}> <ButtonLight onClick={approveCallback} disabled={pendingApprovalInput}>
...@@ -308,7 +300,7 @@ export default function Swap({ history, location: { search } }: RouteComponentPr ...@@ -308,7 +300,7 @@ export default function Swap({ history, location: { search } }: RouteComponentPr
</Text> </Text>
</ButtonError> </ButtonError>
)} )}
<V1TradeLink bestV2Trade={bestTrade} /> <V1TradeLink v1TradeLinkIfBetter={v1TradeLinkIfBetter} />
</BottomGrouping> </BottomGrouping>
{bestTrade && ( {bestTrade && (
<AdvancedSwapDetailsDropdown <AdvancedSwapDetailsDropdown
......
...@@ -8,6 +8,8 @@ import { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades' ...@@ -8,6 +8,8 @@ import { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades'
import { AppDispatch, AppState } from '../index' import { AppDispatch, AppState } from '../index'
import { useTokenBalancesTreatWETHAsETH } from '../wallet/hooks' import { useTokenBalancesTreatWETHAsETH } from '../wallet/hooks'
import { Field, selectToken, setDefaultsFromURL, switchTokens, typeInput } from './actions' import { Field, selectToken, setDefaultsFromURL, switchTokens, typeInput } from './actions'
import { useV1TradeLinkIfBetter } from '../../data/V1'
import { V1_TRADE_LINK_THRESHOLD } from '../../constants'
export function useSwapState(): AppState['swap'] { export function useSwapState(): AppState['swap'] {
return useSelector<AppState, AppState['swap']>(state => state.swap) return useSelector<AppState, AppState['swap']>(state => state.swap)
...@@ -68,6 +70,7 @@ export function useDerivedSwapInfo(): { ...@@ -68,6 +70,7 @@ export function useDerivedSwapInfo(): {
parsedAmounts: { [field in Field]?: TokenAmount } parsedAmounts: { [field in Field]?: TokenAmount }
bestTrade?: Trade bestTrade?: Trade
error?: string error?: string
v1TradeLinkIfBetter?: string
} { } {
const { account } = useWeb3React() const { account } = useWeb3React()
...@@ -106,6 +109,16 @@ export function useDerivedSwapInfo(): { ...@@ -106,6 +109,16 @@ export function useDerivedSwapInfo(): {
[Field.OUTPUT]: tokenOut [Field.OUTPUT]: tokenOut
} }
// get link to trade on v1, if a better rate exists
const v1TradeLinkIfBetter = useV1TradeLinkIfBetter(
isExactIn,
tokens[Field.INPUT],
tokens[Field.OUTPUT],
isExactIn ? parsedAmounts[Field.INPUT] : parsedAmounts[Field.OUTPUT],
bestTrade,
V1_TRADE_LINK_THRESHOLD
)
let error: string | undefined let error: string | undefined
if (!account) { if (!account) {
error = 'Connect Wallet' error = 'Connect Wallet'
...@@ -132,7 +145,8 @@ export function useDerivedSwapInfo(): { ...@@ -132,7 +145,8 @@ export function useDerivedSwapInfo(): {
tokenBalances, tokenBalances,
parsedAmounts, parsedAmounts,
bestTrade, bestTrade,
error error,
v1TradeLinkIfBetter
} }
} }
......
import { ChainId, JSBI, Pair, Token, TokenAmount, WETH } from '@uniswap/sdk' import { ChainId, JSBI, Pair, Token, TokenAmount, WETH } from '@uniswap/sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '../../hooks'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { shallowEqual, useDispatch, useSelector } from 'react-redux' import { shallowEqual, useDispatch, useSelector } from 'react-redux'
import { useAllTokens } from '../../hooks/Tokens' import { useAllTokens } from '../../hooks/Tokens'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment