Compare commits

...

16 Commits

Author SHA1 Message Date
Moody Salem
5bc5d6504e improve the text slightly 2021-05-14 08:45:17 -05:00
Moody Salem
7a0b85bf41 improvement: estimated amounts instead of minimum/maximums (#1570)
* estimated amounts instead of minimum/maximums

* missed a spot

* fix linting error
2021-05-14 07:15:53 -05:00
OMGspidertanx
534afb3278 fix: tweak element sizing/wraping in approve button (#1569) 2021-05-14 07:15:41 -05:00
Moody Salem
7d71af353e perf: debounce localStorage save 2021-05-14 07:10:25 -05:00
Moody Salem
af6098bfe5 feat: allow walletconnect on testnets (#1389)
* try walletconnect multinetwork

* clean up environment variables for multinetwork

* remove irrelevant config

* move some stuff around

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-13 17:46:30 -05:00
Moody Salem
fce29bb36f fix: division by 0 when computing the USDC value of tokens that do not have a v2 pair (fixes https://github.com/Uniswap/uniswap-interface/issues/1566) 2021-05-13 17:27:34 -05:00
Noah Zinsmeister
4517af39ba don't jump on mouse exit (#1565) 2021-05-13 17:06:25 -04:00
Noah Zinsmeister
b40163ce05 allow fee collection/liquidity removal in weth (#1553)
* add dummy flags for burn/collect as weth

* add toggles

* clean up toggle position

* only show weth toggle if collection is possible
2021-05-13 14:49:54 -04:00
Moody Salem
809902efec fixes https://github.com/Uniswap/uniswap-interface/issues/1386 2021-05-13 12:17:34 -05:00
Noah Zinsmeister
70be9894fa bump walletlink connector (#1563) 2021-05-13 12:35:17 -04:00
Moody Salem
698ad5bac9 bump v3 sdk 2021-05-13 11:21:07 -05:00
Moody Salem
cd37b7533d put the error reason in the message so we can differentiate between unrecognized errors 2021-05-13 11:16:37 -05:00
Justin Domingue
c0cd6a1c8d handle ape= query parameter (#1555)
* handle expert_mode= and ape= query parameters

* removed console log

* use useParsedQueryString

* only handle setting ape mode
2021-05-13 12:01:58 -04:00
Moody Salem
f6245d1093 retry more frequently, couple more error nits 2021-05-13 10:51:06 -05:00
Moody Salem
83c784f7c0 improve the error coverage 2021-05-13 10:51:06 -05:00
Moody Salem
3d95b1a33b fix access of undefined property 2021-05-13 09:37:04 -05:00
24 changed files with 357 additions and 291 deletions

3
.env
View File

@@ -1,5 +1,4 @@
REACT_APP_CHAIN_ID="1"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_WALLETCONNECT_BRIDGE_URL="https://uniswap.bridge.walletconnect.org"
# Because we use storybook which has its own babel-loader dependency @ 8.2.2, where react-scripts uses 8.1.0
SKIP_PREFLIGHT_CHECK=true

View File

@@ -1,5 +1,4 @@
REACT_APP_CHAIN_ID="1"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_PORTIS_ID="c0e2bf01-4b08-4fd5-ac7b-8e26b58cd236"
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-4"

View File

@@ -94,19 +94,13 @@ jobs:
The latest release is always accessible via our alias to the Cloudflare IPFS gateway at [app.uniswap.org](https://app.uniswap.org).
You can also access the Uniswap Interface directly from an IPFS gateway.
The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to store your settings.
**Beware** that other sites you access via the _same_ IPFS gateway can read and modify your settings on the Uniswap interface without your permission.
You can avoid this issue by using a subdomain IPFS gateway, or our alias to the latest release at [app.uniswap.org](https://app.uniswap.org).
The preferred URLs below are safe to use to access this specific release.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our alias to the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
Preferred URLs:
IPFS gateways:
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.dweb.link/
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.upload.outputs.hash }}/)
Other IPFS gateways:
- https://cloudflare-ipfs.com/ipfs/${{ steps.upload.outputs.hash }}/
- https://ipfs.infura.io/ipfs/${{ steps.upload.outputs.hash }}/
- https://ipfs.io/ipfs/${{ steps.upload.outputs.hash }}/
${{ needs.bump_version.outputs.changelog }}

View File

@@ -27,7 +27,7 @@ or visit [app.uniswap.org](https://app.uniswap.org).
### Install Dependencies
```bash
yarn
yarn install
```
### Run
@@ -36,19 +36,6 @@ yarn
yarn start
```
### Configuring the environment (optional)
To have the interface default to a different network when a wallet is not connected:
1. Make a copy of `.env` named `.env.local`
2. Change `REACT_APP_NETWORK_ID` to `"{YOUR_NETWORK_ID}"`
3. Change `REACT_APP_NETWORK_URL` to e.g. `"https://{YOUR_NETWORK_ID}.infura.io/v3/{YOUR_INFURA_KEY}"`
Note that the interface only works on testnets where both
[Uniswap V2](https://uniswap.org/docs/v2/smart-contracts/factory/) and
[multicall](https://github.com/makerdao/multicall) are deployed.
The interface will not work on other networks.
## Contributions
**Please open all pull requests against the `main` branch.**

View File

@@ -51,13 +51,13 @@
"@uniswap/v2-sdk": "^3.0.0-alpha.0",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "1.0.0",
"@uniswap/v3-sdk": "^3.0.0-alpha.1",
"@uniswap/v3-sdk": "^3.0.0-alpha.4",
"@web3-react/core": "^6.0.9",
"@web3-react/fortmatic-connector": "^6.0.9",
"@web3-react/injected-connector": "^6.0.7",
"@web3-react/portis-connector": "^6.0.9",
"@web3-react/walletconnect-connector": "^6.1.1",
"@web3-react/walletlink-connector": "^6.0.9",
"@web3-react/walletconnect-connector": "^6.2.0",
"@web3-react/walletlink-connector": "^6.2.0",
"ajv": "^6.12.3",
"cids": "^1.0.0",
"copy-to-clipboard": "^3.2.0",

View File

@@ -1,11 +1,10 @@
import { Percent, Currency, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import React, { useContext } from 'react'
import React, { useContext, useMemo } from 'react'
import { ThemeContext } from 'styled-components'
import { TYPE } from '../../theme'
import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage'
import { computeRealizedLPFeeAmount } from '../../utils/prices'
import { computeRealizedLPFeePercent } from '../../utils/prices'
import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
import FormattedPriceImpact from './FormattedPriceImpact'
@@ -19,7 +18,14 @@ export interface AdvancedSwapDetailsProps {
export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDetailsProps) {
const theme = useContext(ThemeContext)
const realizedLPFee = computeRealizedLPFeeAmount(trade)
const { realizedLPFee, priceImpact } = useMemo(() => {
if (!trade) return { realizedLPFee: undefined, priceImpact: undefined }
const realizedLpFeePercent = computeRealizedLPFeePercent(trade)
const realizedLPFee = trade.inputAmount.multiply(realizedLpFeePercent)
const priceImpact = trade.priceImpact.subtract(realizedLpFeePercent)
return { priceImpact, realizedLPFee }
}, [trade])
return !trade ? null : (
<AutoColumn gap="8px">
@@ -30,7 +36,7 @@ export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDeta
</TYPE.black>
</RowFixed>
<TYPE.black textAlign="right" fontSize={12} color={theme.text1}>
{realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.currency.symbol}` : '-'}
{realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${realizedLPFee.currency.symbol}` : '-'}
</TYPE.black>
</RowBetween>
@@ -48,11 +54,24 @@ export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDeta
<RowBetween>
<RowFixed>
<TYPE.black fontSize={12} fontWeight={400} color={theme.text2}>
Execution price vs. spot price
Price Impact
</TYPE.black>
</RowFixed>
<TYPE.black textAlign="right" fontSize={12} color={theme.text1}>
<FormattedPriceImpact priceImpact={computePriceImpactWithMaximumSlippage(trade, allowedSlippage)} />
<FormattedPriceImpact priceImpact={priceImpact} />
</TYPE.black>
</RowBetween>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={12} fontWeight={400} color={theme.text2}>
{trade.tradeType === TradeType.EXACT_INPUT ? 'Minimum received' : 'Maximum sent'}
</TYPE.black>
</RowFixed>
<TYPE.black textAlign="right" fontSize={12} color={theme.text1}>
{trade.tradeType === TradeType.EXACT_INPUT
? `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${trade.outputAmount.currency.symbol}`
: `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`}
</TYPE.black>
</RowBetween>

View File

@@ -90,9 +90,9 @@ export default function ConfirmSwapModal({
}, [onConfirm, showAcceptChanges, swapErrorMessage, trade])
// text to show while loading
const pendingText = `Swapping ${trade?.maximumAmountIn(allowedSlippage)?.toSignificant(6)} ${
const pendingText = `Swapping ${trade?.inputAmount?.toSignificant(6)} ${
trade?.inputAmount?.currency?.symbol
} for ${trade?.minimumAmountOut(allowedSlippage)?.toSignificant(6)} ${trade?.outputAmount?.currency?.symbol}`
} for ${trade?.outputAmount?.toSignificant(6)} ${trade?.outputAmount?.currency?.symbol}`
const confirmationContent = useCallback(
() =>

View File

@@ -31,12 +31,10 @@ export const ArrowWrapper = styled.div`
margin-top: -18px;
margin-bottom: -18px;
left: calc(50% - 16px);
/* transform: rotate(90deg); */
display: flex;
justify-content: center;
align-items: center;
background-color: ${({ theme }) => theme.bg1};
/* border: 4px solid ${({ theme }) => theme.bg0}; */
z-index: 2;
`
@@ -53,15 +51,12 @@ export default function SwapModalHeader({
showAcceptChanges: boolean
onAcceptChanges: () => void
}) {
const maximumAmountIn = trade.maximumAmountIn(allowedSlippage)
const minimumAmountOut = trade.minimumAmountOut(allowedSlippage)
const theme = useContext(ThemeContext)
const [showInverted, setShowInverted] = useState<boolean>(false)
const fiatValueInput = useUSDCValue(maximumAmountIn)
const fiatValueOutput = useUSDCValue(minimumAmountOut)
const fiatValueInput = useUSDCValue(trade.inputAmount)
const fiatValueOutput = useUSDCValue(trade.outputAmount)
return (
<AutoColumn gap={'4px'} style={{ marginTop: '1rem' }}>
@@ -86,7 +81,7 @@ export default function SwapModalHeader({
fontWeight={500}
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.primary1 : ''}
>
{maximumAmountIn.toSignificant(6)}
{trade.inputAmount.toSignificant(6)}
</TruncatedText>
</RowFixed>
</RowBetween>
@@ -117,7 +112,7 @@ export default function SwapModalHeader({
</RowFixed>
<RowFixed gap={'0px'}>
<TruncatedText fontSize={24} fontWeight={500}>
{minimumAmountOut.toSignificant(6)}
{trade.outputAmount.toSignificant(6)}
</TruncatedText>
</RowFixed>
</RowBetween>
@@ -127,11 +122,7 @@ export default function SwapModalHeader({
<TYPE.body color={theme.text2} fontWeight={500} fontSize={14}>
{'Price:'}
</TYPE.body>
<TradePrice
price={trade.worstExecutionPrice(allowedSlippage)}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
<TradePrice price={trade.executionPrice} showInverted={showInverted} setShowInverted={setShowInverted} />
</RowBetween>
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
@@ -155,12 +146,12 @@ export default function SwapModalHeader({
</SwapShowAcceptChanges>
) : null}
{/* <AutoColumn justify="flex-start" gap="sm" style={{ padding: '.75rem 1rem' }}>
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '.75rem 1rem' }}>
{trade.tradeType === TradeType.EXACT_INPUT ? (
<TYPE.italic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
{`Output is estimated. You will receive at least `}
<b>
{minimumAmountOut.toSignificant(6)} {trade.outputAmount.currency.symbol}
{trade.minimumAmountOut(allowedSlippage).toSignificant(6)} {trade.outputAmount.currency.symbol}
</b>
{' or the transaction will revert.'}
</TYPE.italic>
@@ -168,12 +159,12 @@ export default function SwapModalHeader({
<TYPE.italic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
{`Input is estimated. You will sell at most `}
<b>
{maximumAmountIn.toSignificant(6)} {trade.inputAmount.currency.symbol}
{trade.maximumAmountIn(allowedSlippage).toSignificant(6)} {trade.inputAmount.currency.symbol}
</b>
{' or the transaction will revert.'}
</TYPE.italic>
)}
</AutoColumn> */}
</AutoColumn>
{recipient !== null ? (
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
<TYPE.main>

View File

@@ -1,40 +1,53 @@
import { Web3Provider } from '@ethersproject/providers'
import { ChainId } from '@uniswap/sdk-core'
import { InjectedConnector } from '@web3-react/injected-connector'
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
import { WalletLinkConnector } from '@web3-react/walletlink-connector'
import { PortisConnector } from '@web3-react/portis-connector'
import getLibrary from '../utils/getLibrary'
import { FortmaticConnector } from './Fortmatic'
import { NetworkConnector } from './NetworkConnector'
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
const NETWORK_URL = process.env.REACT_APP_NETWORK_URL
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
const PORTIS_ID = process.env.REACT_APP_PORTIS_ID
const WALLETCONNECT_BRIDGE_URL = process.env.REACT_APP_WALLETCONNECT_BRIDGE_URL
export const NETWORK_CHAIN_ID: number = parseInt(process.env.REACT_APP_CHAIN_ID ?? '1')
if (typeof NETWORK_URL === 'undefined') {
throw new Error(`REACT_APP_NETWORK_URL must be a defined environment variable`)
if (typeof INFURA_KEY === 'undefined') {
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
}
const NETWORK_URLS: {
[chainId in ChainId]: string
} = {
[ChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
[ChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`,
[ChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
[ChainId.GÖRLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`,
[ChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`,
}
const SUPPORTED_CHAIN_IDS = [ChainId.MAINNET, ChainId.RINKEBY, ChainId.ROPSTEN, ChainId.KOVAN, ChainId.GÖRLI]
export const network = new NetworkConnector({
urls: { [NETWORK_CHAIN_ID]: NETWORK_URL },
urls: NETWORK_URLS,
defaultChainId: ChainId.MAINNET,
})
let networkLibrary: Web3Provider | undefined
export function getNetworkLibrary(): Web3Provider {
return (networkLibrary = networkLibrary ?? new Web3Provider(network.provider as any))
return (networkLibrary = networkLibrary ?? getLibrary(network.provider))
}
export const injected = new InjectedConnector({
supportedChainIds: [1, 3, 4, 5, 42],
supportedChainIds: SUPPORTED_CHAIN_IDS,
})
// mainnet only
export const walletconnect = new WalletConnectConnector({
rpc: { 1: NETWORK_URL },
supportedChainIds: SUPPORTED_CHAIN_IDS,
infuraId: INFURA_KEY, // obviously a hack
bridge: WALLETCONNECT_BRIDGE_URL,
qrcode: true,
pollingInterval: 15000,
@@ -54,7 +67,7 @@ export const portis = new PortisConnector({
// mainnet only
export const walletlink = new WalletLinkConnector({
url: NETWORK_URL,
url: NETWORK_URLS[ChainId.MAINNET],
appName: 'Uniswap',
appLogoUrl: UNISWAP_LOGO_URL,
})

View File

@@ -0,0 +1,22 @@
import { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { AppDispatch } from 'state'
import { updateUserExpertMode } from '../state/user/actions'
import useParsedQueryString from './useParsedQueryString'
export default function ApeModeQueryParamReader(): null {
useApeModeQueryParamReader()
return null
}
function useApeModeQueryParamReader() {
const dispatch = useDispatch<AppDispatch>()
const { ape } = useParsedQueryString()
useEffect(() => {
if (typeof ape !== 'string') return
if (ape === '' || ape.toLowerCase() === 'true') {
dispatch(updateUserExpertMode({ userExpertMode: true }))
}
})
}

View File

@@ -3,7 +3,7 @@ import { ChainId } from '@uniswap/sdk-core'
import { TokenList } from '@uniswap/token-lists'
import { useCallback } from 'react'
import { useDispatch } from 'react-redux'
import { getNetworkLibrary, NETWORK_CHAIN_ID } from '../connectors'
import { getNetworkLibrary } from '../connectors'
import { AppDispatch } from '../state'
import { fetchTokenList } from '../state/lists/actions'
import getTokenList from '../utils/getTokenList'
@@ -15,13 +15,12 @@ export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean
const dispatch = useDispatch<AppDispatch>()
const ensResolver = useCallback(
(ensName: string) => {
async (ensName: string) => {
if (!library || chainId !== ChainId.MAINNET) {
if (NETWORK_CHAIN_ID === ChainId.MAINNET) {
const networkLibrary = getNetworkLibrary()
if (networkLibrary) {
return resolveENSContentHash(ensName, networkLibrary)
}
const networkLibrary = getNetworkLibrary()
const network = await networkLibrary.getNetwork()
if (networkLibrary && network.chainId === ChainId.MAINNET) {
return resolveENSContentHash(ensName, networkLibrary)
}
throw new Error('Could not construct mainnet ENS resolver')
}

View File

@@ -142,7 +142,7 @@ export function swapErrorToUserReadableMessage(error: any): string {
let reason: string | undefined
while (Boolean(error)) {
reason = error.reason ?? error.message ?? reason
error = error.error ?? error.data.originalError
error = error.error ?? error.data?.originalError
}
if (reason?.indexOf('execution reverted: ') === 0) reason = reason.substr('execution reverted: '.length)
@@ -153,16 +153,20 @@ export function swapErrorToUserReadableMessage(error: any): string {
case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT':
case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT':
return 'This transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.'
case 'TransferHelper: TRANSFER_FROM_FAILED':
return 'The input token cannot be transferred. There may be an issue with the input token.'
case 'UniswapV2: TRANSFER_FAILED':
return 'The token could not be transferred. There may be an issue with the token.'
return 'The output token cannot be transferred. There may be an issue with the output token.'
case 'UniswapV2: K':
return 'The Uniswap invariant x*y=k was not satisfied by the swap. This usually means one of the tokens you are swapping incorporates custom behavior on transfer.'
case 'Too little received':
case 'Too much requested':
case 'STF':
return 'This transaction will not succeed due to price movement. Try increasing your slippage tolerance.'
return 'This transaction will not succeed due to price movement. Try increasing your slippage tolerance. Note fee on transfer and rebase tokens are incompatible with Uniswap V3.'
case 'TF':
return 'The output token cannot be transferred. There may be an issue with the output token. Note fee on transfer and rebase tokens are incompatible with Uniswap V3.'
default:
return 'Unknown error. Please join the Discord to get help.'
return `Unknown error${reason ? `: "${reason}"` : ''}. Please join the Discord to get help.`
}
}
@@ -271,8 +275,8 @@ export function useSwapCallback(
.then((response) => {
const inputSymbol = trade.inputAmount.currency.symbol
const outputSymbol = trade.outputAmount.currency.symbol
const inputAmount = trade.maximumAmountIn(allowedSlippage).toSignificant(4)
const outputAmount = trade.minimumAmountOut(allowedSlippage).toSignificant(4)
const inputAmount = trade.inputAmount.toSignificant(4)
const outputAmount = trade.outputAmount.toSignificant(4)
const base = `Swap ${inputAmount} ${inputSymbol} for ${outputAmount} ${outputSymbol}`
const withRecipient =
@@ -302,13 +306,11 @@ export function useSwapCallback(
// otherwise, the error was unexpected and we need to convey that
console.error(`Swap failed`, error, address, calldata, value)
throw new Error(
`Swap failed: ${'reason' in error ? swapErrorToUserReadableMessage(error) : error.message}`
)
throw new Error(`Swap failed: ${swapErrorToUserReadableMessage(error)}`)
}
})
},
error: null,
}
}, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, allowedSlippage, addTransaction])
}, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, addTransaction])
}

View File

@@ -56,7 +56,9 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token
const ethPairETHAmount = ethPair?.reserveOf(weth)
const ethPairETHUSDCValue: JSBI =
ethPairETHAmount && usdcEthPair ? usdcEthPair.priceOf(weth).quote(ethPairETHAmount).quotient : JSBI.BigInt(0)
ethPairETHAmount?.greaterThan(0) && usdcEthPair?.reserveOf(weth)?.greaterThan(0)
? usdcEthPair.priceOf(weth).quote(ethPairETHAmount).quotient
: JSBI.BigInt(0)
// all other tokens
// first try the usdc pair
@@ -81,6 +83,10 @@ export function useUSDCValue(currencyAmount: CurrencyAmount<Currency> | undefine
return useMemo(() => {
if (!price || !currencyAmount) return null
return price.quote(currencyAmount)
try {
return price.quote(currencyAmount)
} catch (error) {
return null
}
}, [currencyAmount, price])
}

View File

@@ -3,16 +3,18 @@ import { useEffect, useState } from 'react'
import { useV3NFTPositionManagerContract } from './useContract'
import { BigNumber } from '@ethersproject/bignumber'
import { Pool } from '@uniswap/v3-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { CurrencyAmount, Token, currencyEquals, ETHER, Ether } from '@uniswap/sdk-core'
import { useBlockNumber } from 'state/application/hooks'
import { unwrappedToken } from 'utils/wrappedCurrency'
const MAX_UINT128 = BigNumber.from(2).pow(128).sub(1)
// compute current + counterfactual fees for a v3 position
export function useV3PositionFees(
pool?: Pool,
tokenId?: BigNumber
): [CurrencyAmount<Token>, CurrencyAmount<Token>] | [undefined, undefined] {
tokenId?: BigNumber,
asWETH = false
): [CurrencyAmount<Token | Ether>, CurrencyAmount<Token | Ether>] | [undefined, undefined] {
const positionManager = useV3NFTPositionManagerContract(false)
const owner = useSingleCallResult(tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
@@ -43,8 +45,12 @@ export function useV3PositionFees(
if (pool && amounts) {
return [
CurrencyAmount.fromRawAmount(pool.token0, amounts[0].toString()),
CurrencyAmount.fromRawAmount(pool.token1, amounts[1].toString()),
!asWETH && currencyEquals(unwrappedToken(pool.token0), ETHER)
? CurrencyAmount.ether(amounts[0].toString())
: CurrencyAmount.fromRawAmount(pool.token0, amounts[0].toString()),
!asWETH && currencyEquals(unwrappedToken(pool.token1), ETHER)
? CurrencyAmount.ether(amounts[1].toString())
: CurrencyAmount.fromRawAmount(pool.token1, amounts[1].toString()),
]
} else {
return [undefined, undefined]

View File

@@ -29,6 +29,7 @@ import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects'
import { PositionPage } from './Pool/PositionPage'
import AddLiquidity from './AddLiquidity'
import { ThemedBackground } from '../theme'
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
const AppWrapper = styled.div`
display: flex;
@@ -76,6 +77,7 @@ export default function App() {
<Suspense fallback={null}>
<Route component={GoogleAnalyticsReporter} />
<Route component={DarkModeQueryParamReader} />
<Route component={ApeModeQueryParamReader} />
<AppWrapper>
<HeaderWrapper>
<Header />

View File

@@ -1,11 +1,11 @@
import React, { SyntheticEvent, useCallback, useMemo, useRef, useState } from 'react'
import React, { useCallback, useMemo, useRef, useState } from 'react'
import { NonfungiblePositionManager, Pool, Position } from '@uniswap/v3-sdk'
import { PoolState, usePool } from 'hooks/usePools'
import { useToken } from 'hooks/Tokens'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import { Link, RouteComponentProps } from 'react-router-dom'
import { unwrappedToken } from 'utils/wrappedCurrency'
import { unwrappedToken, wrappedCurrencyAmount } from 'utils/wrappedCurrency'
import { usePositionTokenURI } from '../../hooks/usePositionTokenURI'
import { LoadingRows } from './styleds'
import styled from 'styled-components/macro'
@@ -23,7 +23,7 @@ import { currencyId } from 'utils/currencyId'
import { formatTokenAmount } from 'utils/formatTokenAmount'
import { useV3PositionFees } from 'hooks/useV3PositionFees'
import { BigNumber } from '@ethersproject/bignumber'
import { Token, WETH9, Currency, CurrencyAmount, Percent, Fraction, Price, currencyEquals } from '@uniswap/sdk-core'
import { Token, Currency, CurrencyAmount, Percent, Fraction, Price, Ether } from '@uniswap/sdk-core'
import { useActiveWeb3React } from 'hooks'
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
@@ -38,6 +38,7 @@ import { useSingleCallResult } from 'state/multicall/hooks'
import RangeBadge from '../../components/Badge/RangeBadge'
import useUSDCPrice from 'hooks/useUSDCPrice'
import Loader from 'components/Loader'
import Toggle from 'components/Toggle'
const PageWrapper = styled.div`
min-width: 800px;
@@ -200,20 +201,11 @@ function getRatio(
}
}
function NFT({ image, height: targetHeight }: { image: string; height: number }) {
const [animate, setAnimate] = useState(false)
const canvasRef = useRef<HTMLCanvasElement>()
const imageRef = useRef<HTMLImageElement>()
const getSnapshot = (src: HTMLImageElement) => {
if (!canvasRef.current) return
const { current: canvas } = canvasRef
const context = canvas.getContext('2d')
if (!context) return
// snapshots a src img into a canvas
function getSnapshot(src: HTMLImageElement, canvas: HTMLCanvasElement, targetHeight: number) {
const context = canvas.getContext('2d')
if (context) {
let { width, height } = src
// src may be hidden and not have the target dimensions
@@ -231,15 +223,39 @@ function NFT({ image, height: targetHeight }: { image: string; height: number })
context.clearRect(0, 0, width, height)
context.drawImage(src, 0, 0, width, height)
}
}
const onLoad = (e: SyntheticEvent<HTMLImageElement>) => {
getSnapshot(e.target as HTMLImageElement)
}
function NFT({ image, height: targetHeight }: { image: string; height: number }) {
const [animate, setAnimate] = useState(false)
const canvasRef = useRef<HTMLCanvasElement>(null)
const imageRef = useRef<HTMLImageElement>(null)
return (
<NFTGrid onMouseEnter={() => setAnimate(true)} onMouseLeave={() => setAnimate(false)}>
<NFTCanvas ref={canvasRef as any} />
<NFTImage src={image} hidden={!animate} onLoad={onLoad} ref={imageRef as any} />
<NFTGrid
onMouseEnter={() => {
setAnimate(true)
}}
onMouseLeave={() => {
// snapshot the current frame so the transition to the canvas is smooth
if (imageRef.current && canvasRef.current) {
getSnapshot(imageRef.current, canvasRef.current, targetHeight)
}
setAnimate(false)
}}
>
<NFTCanvas ref={canvasRef} />
<NFTImage
ref={imageRef}
src={image}
hidden={!animate}
onLoad={() => {
// snapshot for the canvas
if (imageRef.current && canvasRef.current) {
getSnapshot(imageRef.current, canvasRef.current, targetHeight)
}
}}
/>
</NFTGrid>
)
}
@@ -269,8 +285,11 @@ export function PositionPage({
const currency0 = token0 ? unwrappedToken(token0) : undefined
const currency1 = token1 ? unwrappedToken(token1) : undefined
// flag for receiving WETH
const [receiveWETH, setReceiveWETH] = useState(false)
// construct Position from details returned
const [poolState, pool] = usePool(currency0 ?? undefined, currency1 ?? undefined, feeAmount)
const [poolState, pool] = usePool(token0 ?? undefined, token1 ?? undefined, feeAmount)
const position = useMemo(() => {
if (pool && liquidity && typeof tickLower === 'number' && typeof tickUpper === 'number') {
return new Position({ pool, liquidity: liquidity.toString(), tickLower, tickUpper })
@@ -304,7 +323,7 @@ export function PositionPage({
}, [inverted, pool, priceLower, priceUpper])
// fees
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails?.tokenId)
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails?.tokenId, receiveWETH)
const [collecting, setCollecting] = useState<boolean>(false)
const [collectMigrationHash, setCollectMigrationHash] = useState<string | null>(null)
@@ -320,12 +339,8 @@ export function PositionPage({
const { calldata, value } = NonfungiblePositionManager.collectCallParameters({
tokenId: tokenId.toString(),
expectedCurrencyOwed0: currencyEquals(feeValue0.currency, WETH9[chainId])
? CurrencyAmount.ether(feeValue0.quotient)
: feeValue0,
expectedCurrencyOwed1: currencyEquals(feeValue1.currency, WETH9[chainId])
? CurrencyAmount.ether(feeValue1.quotient)
: feeValue1,
expectedCurrencyOwed0: feeValue0,
expectedCurrencyOwed1: feeValue1,
recipient: account,
})
@@ -371,15 +386,23 @@ export function PositionPage({
const owner = useSingleCallResult(!!tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
const ownsNFT = owner === account || positionDetails?.operator === account
// usdc prices always in terms of tokens
const price0 = useUSDCPrice(token0 ?? undefined)
const price1 = useUSDCPrice(token1 ?? undefined)
const fiatValueOfFees: CurrencyAmount<Token> | null = useMemo(() => {
const fiatValueOfFees: CurrencyAmount<Token | Ether> | null = useMemo(() => {
if (!price0 || !price1 || !feeValue0 || !feeValue1) return null
const amount0 = price0.quote(feeValue0)
const amount1 = price1.quote(feeValue1)
// we wrap because it doesn't matter, the quote returns a USDC amount
const feeValue0Wrapped = wrappedCurrencyAmount(feeValue0, chainId)
const feeValue1Wrapped = wrappedCurrencyAmount(feeValue1, chainId)
if (!feeValue0Wrapped || !feeValue1Wrapped) return null
const amount0 = price0.quote(feeValue0Wrapped)
const amount1 = price1.quote(feeValue1Wrapped)
return amount0.add(amount1)
}, [price0, price1, feeValue0, feeValue1])
}, [price0, price1, feeValue0, feeValue1, chainId])
const fiatValueOfLiquidity: CurrencyAmount<Token> | null = useMemo(() => {
if (!price0 || !price1 || !position) return null
@@ -388,6 +411,9 @@ export function PositionPage({
return amount0.add(amount1)
}, [price0, price1, position])
const feeValueUpper = inverted ? feeValue0 : feeValue1
const feeValueLower = inverted ? feeValue1 : feeValue0
function modalHeader() {
return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
@@ -395,33 +421,17 @@ export function PositionPage({
<AutoColumn gap="md">
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyQuote} size={'20px'} style={{ marginRight: '0.5rem' }} />
<TYPE.main>
{inverted
? feeValue0
? formatTokenAmount(feeValue0, 4)
: '-'
: feeValue1
? formatTokenAmount(feeValue1, 4)
: '-'}
</TYPE.main>
<CurrencyLogo currency={feeValueUpper?.currency} size={'20px'} style={{ marginRight: '0.5rem' }} />
<TYPE.main>{feeValueUpper ? formatTokenAmount(feeValueUpper, 4) : '-'}</TYPE.main>
</RowFixed>
<TYPE.main>{currencyQuote?.symbol}</TYPE.main>
<TYPE.main>{feeValueUpper?.currency?.symbol}</TYPE.main>
</RowBetween>
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyBase} size={'20px'} style={{ marginRight: '0.5rem' }} />
<TYPE.main>
{inverted
? feeValue0
? formatTokenAmount(feeValue1, 4)
: '-'
: feeValue1
? formatTokenAmount(feeValue0, 4)
: '-'}
</TYPE.main>
<CurrencyLogo currency={feeValueLower?.currency} size={'20px'} style={{ marginRight: '0.5rem' }} />
<TYPE.main>{feeValueLower ? formatTokenAmount(feeValueLower, 4) : '-'}</TYPE.main>
</RowFixed>
<TYPE.main>{currencyBase?.symbol}</TYPE.main>
<TYPE.main>{feeValueLower?.currency?.symbol}</TYPE.main>
</RowBetween>
</AutoColumn>
</LightCard>
@@ -640,40 +650,44 @@ export function PositionPage({
<AutoColumn gap="md">
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyQuote} size={'20px'} style={{ marginRight: '0.5rem' }} />
<TYPE.main>{currencyQuote?.symbol}</TYPE.main>
<CurrencyLogo
currency={feeValueUpper?.currency}
size={'20px'}
style={{ marginRight: '0.5rem' }}
/>
<TYPE.main>{feeValueUpper?.currency?.symbol}</TYPE.main>
</RowFixed>
<RowFixed>
<TYPE.main>
{inverted
? feeValue0
? formatTokenAmount(feeValue0, 4)
: '-'
: feeValue1
? formatTokenAmount(feeValue1, 4)
: '-'}
</TYPE.main>
<TYPE.main>{feeValueUpper ? formatTokenAmount(feeValueUpper, 4) : '-'}</TYPE.main>
</RowFixed>
</RowBetween>
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyBase} size={'20px'} style={{ marginRight: '0.5rem' }} />
<TYPE.main>{currencyBase?.symbol}</TYPE.main>
<CurrencyLogo
currency={feeValueLower?.currency}
size={'20px'}
style={{ marginRight: '0.5rem' }}
/>
<TYPE.main>{feeValueLower?.currency?.symbol}</TYPE.main>
</RowFixed>
<RowFixed>
<TYPE.main>
{inverted
? feeValue0
? formatTokenAmount(feeValue1, 4)
: '-'
: feeValue1
? formatTokenAmount(feeValue0, 4)
: '-'}
</TYPE.main>
<TYPE.main>{feeValueLower ? formatTokenAmount(feeValueLower, 4) : '-'}</TYPE.main>
</RowFixed>
</RowBetween>
</AutoColumn>
</LightCard>
{ownsNFT && (feeValue0?.greaterThan(0) || feeValue1?.greaterThan(0)) && !collectMigrationHash ? (
<AutoColumn gap="md">
<RowBetween>
<TYPE.main>Collect as WETH</TYPE.main>
<Toggle
id="receive-as-weth"
isActive={receiveWETH}
toggle={() => setReceiveWETH((receiveWETH) => !receiveWETH)}
/>
</RowBetween>
</AutoColumn>
) : null}
</AutoColumn>
</DarkCard>
</AutoColumn>

View File

@@ -21,12 +21,10 @@ import ReactGA from 'react-ga'
import { useActiveWeb3React } from 'hooks'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from 'state/transactions/hooks'
import { WETH9, CurrencyAmount, currencyEquals, Percent } from '@uniswap/sdk-core'
import { Percent } from '@uniswap/sdk-core'
import { TYPE } from 'theme'
import { Wrapper, SmallMaxButton, ResponsiveHeaderText } from './styled'
import Loader from 'components/Loader'
import { useToken } from 'hooks/Tokens'
import { unwrappedToken } from 'utils/wrappedCurrency'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import { Break } from 'components/earn/styled'
import { NonfungiblePositionManager } from '@uniswap/v3-sdk'
@@ -34,6 +32,7 @@ import { calculateGasMargin } from 'utils'
import useTheme from 'hooks/useTheme'
import { AddRemoveTabs } from 'components/NavigationTabs'
import RangeBadge from 'components/Badge/RangeBadge'
import Toggle from 'components/Toggle'
export const UINT128MAX = BigNumber.from(2).pow(128).sub(1)
@@ -65,11 +64,8 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
const theme = useTheme()
const { account, chainId, library } = useActiveWeb3React()
// currencies from position
const token0 = useToken(position?.token0)
const token1 = useToken(position?.token1)
const currency0 = token0 ? unwrappedToken(token0) : undefined
const currency1 = token1 ? unwrappedToken(token1) : undefined
// flag for receiving WETH
const [receiveWETH, setReceiveWETH] = useState(false)
// burn state
const { percent } = useBurnV3State()
@@ -82,7 +78,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
feeValue1,
outOfRange,
error,
} = useDerivedV3BurnInfo(position)
} = useDerivedV3BurnInfo(position, receiveWETH)
const { onPercentSelect } = useBurnV3ActionHandlers()
const removed = position?.liquidity?.eq(0)
@@ -122,12 +118,8 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
slippageTolerance: allowedSlippage,
deadline: deadline.toString(),
collectOptions: {
expectedCurrencyOwed0: currencyEquals(liquidityValue0.currency, WETH9[chainId])
? CurrencyAmount.ether(feeValue0.quotient)
: feeValue0,
expectedCurrencyOwed1: currencyEquals(liquidityValue1.currency, WETH9[chainId])
? CurrencyAmount.ether(feeValue1.quotient)
: feeValue1,
expectedCurrencyOwed0: feeValue0,
expectedCurrencyOwed1: feeValue1,
recipient: account,
},
})
@@ -195,32 +187,32 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
}, [onPercentSelectForSlider, txnHash])
const pendingText = `Removing ${liquidityValue0?.toSignificant(6)} ${
currency0?.symbol
} and ${liquidityValue1?.toSignificant(6)} ${currency1?.symbol}`
liquidityValue0?.currency?.symbol
} and ${liquidityValue1?.toSignificant(6)} ${liquidityValue1?.currency?.symbol}`
function modalHeader() {
return (
<AutoColumn gap={'sm'} style={{ padding: '16px' }}>
<RowBetween align="flex-end">
<Text fontSize={16} fontWeight={500}>
{currency0?.symbol}:
Pooled {liquidityValue0?.currency?.symbol}:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{liquidityValue0 && <FormattedCurrencyAmount currencyAmount={liquidityValue0} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={liquidityValue0?.currency} />
</RowFixed>
</RowBetween>
<RowBetween align="flex-end">
<Text fontSize={16} fontWeight={500}>
{currency1?.symbol}:
Pooled {liquidityValue1?.currency?.symbol}:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{liquidityValue1 && <FormattedCurrencyAmount currencyAmount={liquidityValue1} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={liquidityValue1?.currency} />
</RowFixed>
</RowBetween>
{feeValue0?.greaterThan(0) || feeValue1?.greaterThan(0) ? (
@@ -230,24 +222,24 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
</TYPE.italic>
<RowBetween>
<Text fontSize={16} fontWeight={500}>
{currency0?.symbol} from fees:
{feeValue0?.currency?.symbol} Fees Earned:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{feeValue0 && <FormattedCurrencyAmount currencyAmount={feeValue0} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={feeValue0?.currency} />
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize={16} fontWeight={500}>
{currency1?.symbol} from fees:
{feeValue1?.currency?.symbol} Fees Earned:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{feeValue1 && <FormattedCurrencyAmount currencyAmount={feeValue1} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={feeValue1?.currency} />
</RowFixed>
</RowBetween>
</>
@@ -287,8 +279,16 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
<AutoColumn gap="lg">
<RowBetween>
<RowFixed>
<DoubleCurrencyLogo currency0={currency1} currency1={currency0} size={20} margin={true} />
<TYPE.label ml="10px" fontSize="20px">{`${currency0?.symbol}/${currency1?.symbol}`}</TYPE.label>
<DoubleCurrencyLogo
currency0={feeValue0?.currency}
currency1={feeValue1?.currency}
size={20}
margin={true}
/>
<TYPE.label
ml="10px"
fontSize="20px"
>{`${feeValue0?.currency?.symbol}/${feeValue1?.currency?.symbol}`}</TYPE.label>
</RowFixed>
<RangeBadge removed={removed} inRange={!outOfRange} />
</RowBetween>
@@ -319,24 +319,24 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
<AutoColumn gap="md">
<RowBetween>
<Text fontSize={16} fontWeight={500}>
Pooled {currency0?.symbol}:
Pooled {liquidityValue0?.currency?.symbol}:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{liquidityValue0 && <FormattedCurrencyAmount currencyAmount={liquidityValue0} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={liquidityValue0?.currency} />
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize={16} fontWeight={500}>
Pooled {currency1?.symbol}:
Pooled {liquidityValue1?.currency?.symbol}:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{liquidityValue1 && <FormattedCurrencyAmount currencyAmount={liquidityValue1} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={liquidityValue1?.currency} />
</RowFixed>
</RowBetween>
{feeValue0?.greaterThan(0) || feeValue1?.greaterThan(0) ? (
@@ -344,30 +344,40 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
<Break />
<RowBetween>
<Text fontSize={16} fontWeight={500}>
{currency0?.symbol} Fees Earned:
{feeValue0?.currency?.symbol} Fees Earned:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{feeValue0 && <FormattedCurrencyAmount currencyAmount={feeValue0} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={feeValue0?.currency} />
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize={16} fontWeight={500}>
{currency1?.symbol} Fees Earned:
{feeValue1?.currency?.symbol} Fees Earned:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{feeValue1 && <FormattedCurrencyAmount currencyAmount={feeValue1} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={feeValue1?.currency} />
</RowFixed>
</RowBetween>
</>
) : null}
</AutoColumn>
</LightCard>
<RowBetween>
<TYPE.main>Collect as WETH</TYPE.main>
<Toggle
id="receive-as-weth"
isActive={receiveWETH}
toggle={() => setReceiveWETH((receiveWETH) => !receiveWETH)}
/>
</RowBetween>
<div style={{ display: 'flex' }}>
<AutoColumn gap="12px" style={{ flex: '1' }}>
<ButtonConfirmed

View File

@@ -49,7 +49,6 @@ import {
import { useExpertModeManager, useUserSingleHopOnly } from '../../state/user/hooks'
import { HideSmall, LinkStyledButton, TYPE } from '../../theme'
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage'
import { getTradeVersion } from '../../utils/getTradeVersion'
import { isTradeBetter } from '../../utils/isTradeBetter'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
@@ -132,10 +131,10 @@ export default function Swap({ history }: RouteComponentProps) {
[Field.OUTPUT]: parsedAmount,
}
: {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.maximumAmountIn(allowedSlippage),
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.minimumAmountOut(allowedSlippage),
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount,
},
[allowedSlippage, independentField, parsedAmount, showWrap, trade]
[independentField, parsedAmount, showWrap, trade]
)
const fiatValueInput = useUSDCValue(parsedAmounts[Field.INPUT])
@@ -292,7 +291,7 @@ export default function Swap({ history }: RouteComponentProps) {
// warnings on the greater of fiat value price impact and execution price impact
const priceImpactSeverity = useMemo(() => {
const executionPriceImpact = trade ? computePriceImpactWithMaximumSlippage(trade, allowedSlippage) : undefined
const executionPriceImpact = trade?.priceImpact
return warningSeverity(
executionPriceImpact && priceImpact
? executionPriceImpact.greaterThan(priceImpact)
@@ -300,7 +299,7 @@ export default function Swap({ history }: RouteComponentProps) {
: priceImpact
: executionPriceImpact ?? priceImpact
)
}, [allowedSlippage, priceImpact, trade])
}, [priceImpact, trade])
// show approve flow when: no error on inputs, not approved or pending, or approved in current session
// never show if price impact is above threshold in non expert mode
@@ -475,7 +474,7 @@ export default function Swap({ history }: RouteComponentProps) {
{trade ? (
<RowFixed>
<TradePrice
price={trade.worstExecutionPrice(allowedSlippage)}
price={trade.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
@@ -526,12 +525,12 @@ export default function Swap({ history }: RouteComponentProps) {
approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED
}
>
<AutoRow justify="space-between">
<AutoRow justify="space-between" style={{ flexWrap: 'nowrap' }}>
<span style={{ display: 'flex', alignItems: 'center' }}>
<CurrencyLogo
currency={currencies[Field.INPUT]}
size={'20px'}
style={{ marginRight: '8px' }}
style={{ marginRight: '8px', flexShrink: 0 }}
/>
{/* we need to shorten this string on mobile */}
{approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED
@@ -551,7 +550,7 @@ export default function Swap({ history }: RouteComponentProps) {
'. You only have to do this once per token.'
}
>
<HelpCircle size="20" color={'white'} />
<HelpCircle size="20" color={'white'} style={{ marginLeft: '8px' }} />
</MouseoverTooltip>
)}
</AutoRow>

View File

@@ -1,4 +1,4 @@
import { Token, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { Token, CurrencyAmount, Percent, Ether, currencyEquals, ETHER } from '@uniswap/sdk-core'
import { Position } from '@uniswap/v3-sdk'
import { usePool } from 'hooks/usePools'
import { useActiveWeb3React } from 'hooks'
@@ -10,20 +10,22 @@ import { PositionDetails } from 'types/position'
import { AppDispatch, AppState } from '../../index'
import { selectPercent } from './actions'
import { unwrappedToken } from 'utils/wrappedCurrency'
export function useBurnV3State(): AppState['burnV3'] {
return useSelector<AppState, AppState['burnV3']>((state) => state.burnV3)
}
export function useDerivedV3BurnInfo(
position?: PositionDetails
position?: PositionDetails,
asWETH = false
): {
position?: Position
liquidityPercentage?: Percent
liquidityValue0?: CurrencyAmount<Token>
liquidityValue1?: CurrencyAmount<Token>
feeValue0?: CurrencyAmount<Token>
feeValue1?: CurrencyAmount<Token>
liquidityValue0?: CurrencyAmount<Token | Ether>
liquidityValue1?: CurrencyAmount<Token | Ether>
feeValue0?: CurrencyAmount<Token | Ether>
feeValue1?: CurrencyAmount<Token | Ether>
outOfRange: boolean
error?: string
} {
@@ -50,20 +52,27 @@ export function useDerivedV3BurnInfo(
const liquidityPercentage = new Percent(percent, 100)
const liquidityValue0 =
positionSDK &&
CurrencyAmount.fromRawAmount(
positionSDK.amount0.currency,
liquidityPercentage.multiply(positionSDK.amount0.quotient).quotient
)
const liquidityValue1 =
positionSDK &&
CurrencyAmount.fromRawAmount(
positionSDK.amount1.currency,
liquidityPercentage.multiply(positionSDK.amount1.quotient).quotient
)
const discountedAmount0 = positionSDK
? liquidityPercentage.multiply(positionSDK.amount0.quotient).quotient
: undefined
const discountedAmount1 = positionSDK
? liquidityPercentage.multiply(positionSDK.amount1.quotient).quotient
: undefined
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, position?.tokenId)
const liquidityValue0 =
token0 && discountedAmount0
? currencyEquals(unwrappedToken(token0), ETHER) && !asWETH
? CurrencyAmount.ether(discountedAmount0)
: CurrencyAmount.fromRawAmount(token0, discountedAmount0)
: undefined
const liquidityValue1 =
token1 && discountedAmount1
? currencyEquals(unwrappedToken(token1), ETHER) && !asWETH
? CurrencyAmount.ether(discountedAmount1)
: CurrencyAmount.fromRawAmount(token1, discountedAmount1)
: undefined
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, position?.tokenId, asWETH)
const outOfRange =
pool && position ? pool.tickCurrent < position.tickLower || pool.tickCurrent > position.tickUpper : false

View File

@@ -28,7 +28,7 @@ const store = configureStore({
multicall,
lists,
},
middleware: [...getDefaultMiddleware({ thunk: false }), save({ states: PERSISTED_KEYS })],
middleware: [...getDefaultMiddleware({ thunk: false }), save({ states: PERSISTED_KEYS, debounce: 1000 })],
preloadedState: load({ states: PERSISTED_KEYS }),
})

View File

@@ -164,8 +164,8 @@ export default function Updater(): null {
cancellations: chunkedCalls.map((chunk, index) => {
const { cancel, promise } = retry(() => fetchChunk(multicall2Contract, chunk, latestBlockNumber), {
n: Infinity,
minWait: 2500,
maxWait: 3500,
minWait: 1000,
maxWait: 2500,
})
promise
.then(({ results: returnData, blockNumber: fetchBlockNumber }) => {

View File

@@ -1,14 +0,0 @@
import { computePriceImpact, Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
export function computePriceImpactWithMaximumSlippage(
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType>,
allowedSlippage: Percent
): Percent {
return computePriceImpact(
trade.route.midPrice,
trade.maximumAmountIn(allowedSlippage),
trade.minimumAmountOut(allowedSlippage)
)
}

View File

@@ -22,15 +22,15 @@ export function computeRealizedLPFeePercent(
// for each hop in our trade, take away the x*y=k price impact from 0.3% fees
// e.g. for 3 tokens/2 hops: 1 - ((1 - .03) * (1-.03))
percent = ONE_HUNDRED_PERCENT.subtract(
trade.route.pairs.reduce<Fraction>(
(currentFee: Fraction): Fraction => currentFee.multiply(INPUT_FRACTION_AFTER_FEE),
trade.route.pairs.reduce<Percent>(
(currentFee: Percent): Percent => currentFee.multiply(INPUT_FRACTION_AFTER_FEE),
ONE_HUNDRED_PERCENT
)
)
} else {
percent = ONE_HUNDRED_PERCENT.subtract(
trade.route.pools.reduce<Fraction>(
(currentFee: Fraction, pool): Fraction =>
trade.route.pools.reduce<Percent>(
(currentFee: Percent, pool): Percent =>
currentFee.multiply(ONE_HUNDRED_PERCENT.subtract(new Fraction(pool.fee, 1_000_000))),
ONE_HUNDRED_PERCENT
)
@@ -44,14 +44,11 @@ export function computeRealizedLPFeePercent(
export function computeRealizedLPFeeAmount(
trade?: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | null
): CurrencyAmount<Currency> | undefined {
if (trade instanceof V2Trade || trade instanceof V3Trade) {
if (trade) {
const realizedLPFee = computeRealizedLPFeePercent(trade)
// the amount of the input that accrues to LPs
return CurrencyAmount.fromRawAmount(
trade.inputAmount.currency,
trade.inputAmount.asFraction.multiply(realizedLPFee).quotient
)
return CurrencyAmount.fromRawAmount(trade.inputAmount.currency, trade.inputAmount.multiply(realizedLPFee).quotient)
}
return undefined

View File

@@ -2452,7 +2452,7 @@
resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b"
integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==
"@metamask/safe-event-emitter@^2.0.0":
"@metamask/safe-event-emitter@2.0.0", "@metamask/safe-event-emitter@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c"
integrity sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==
@@ -4478,10 +4478,10 @@
"@uniswap/v3-core" "1.0.0"
base64-sol "1.0.1"
"@uniswap/v3-sdk@^3.0.0-alpha.1":
version "3.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.0.0-alpha.1.tgz#33fcd4b1587d323c6afde3bb546ad0bb1c77e492"
integrity sha512-3+rVWwGlryEX/Nu7qevBrScTjZ4791fSfmLDz+U5ofWQL/edhZkjgTY1I/fkndUSI8FKWRppRAdzqcnmbpqqlQ==
"@uniswap/v3-sdk@^3.0.0-alpha.4":
version "3.0.0-alpha.4"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.0.0-alpha.4.tgz#e8bf26291fd74e36a5a3d9b88f1809a7aceb7d3a"
integrity sha512-BcEH8eHt+b6eaaiLDlzbFox2NquP1H7KgrgmNjAlU/et+vC4azdfNN6SsRlTFzhioPOwHlAKAAZJLq+yzboDOQ==
dependencies:
"@ethersproject/abi" "^5.0.12"
"@ethersproject/solidity" "^5.0.9"
@@ -4582,7 +4582,7 @@
js-sha3 "0.8.0"
query-string "6.13.5"
"@walletconnect/web3-provider@^1.3.6":
"@walletconnect/web3-provider@^1.4.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@walletconnect/web3-provider/-/web3-provider-1.4.1.tgz#34f6319ab2473ab9ff0fcf1e8bc280c697fa01ff"
integrity sha512-gUoBGM5hdtcXSoLXDTG1/WTamnUNpEWfaYMIVkfVnvVFd4whIjb0iOW5ywvDOf/49wq0C2+QThZL2Wc+r+jKLA==
@@ -4646,24 +4646,24 @@
resolved "https://registry.yarnpkg.com/@web3-react/types/-/types-6.0.7.tgz#34a6204224467eedc6123abaf55fbb6baeb2809f"
integrity sha512-ofGmfDhxmNT1/P/MgVa8IKSkCStFiyvXe+U5tyZurKdrtTDFU+wJ/LxClPDtFerWpczNFPUSrKcuhfPX1sI6+A==
"@web3-react/walletconnect-connector@^6.1.1":
version "6.1.9"
resolved "https://registry.yarnpkg.com/@web3-react/walletconnect-connector/-/walletconnect-connector-6.1.9.tgz#3459ccf2a2ac7ae8f155645d29a517712afeb731"
integrity sha512-gPtcFFRAnRgqhmBjhH+dtuG3cx23X+JOX+mRO1D7dN+8yxLZhLhjOZlJFECH5hkC20KMM/sk+rq2yy6Vqp76PQ==
"@web3-react/walletconnect-connector@^6.2.0":
version "6.2.0"
resolved "https://registry.yarnpkg.com/@web3-react/walletconnect-connector/-/walletconnect-connector-6.2.0.tgz#5451f332a25b94cf7e615a20cc7d22a27532629d"
integrity sha512-F6xYwI3MKiSdKa0248y2wBW0kTDddc2/IGn4CjMSYe0DJFggtxFsAAGHQTRmvwDcLlgQwtemJJ0cTA82MOVfEg==
dependencies:
"@walletconnect/web3-provider" "^1.3.6"
"@walletconnect/web3-provider" "^1.4.1"
"@web3-react/abstract-connector" "^6.0.7"
"@web3-react/types" "^6.0.7"
tiny-invariant "^1.0.6"
"@web3-react/walletlink-connector@^6.0.9":
version "6.1.9"
resolved "https://registry.yarnpkg.com/@web3-react/walletlink-connector/-/walletlink-connector-6.1.9.tgz#3b27b79057ea3865b447a2ecdd3807d094f34061"
integrity sha512-0kgb28CsUp5k0MVdl3bk5FUSvgDl5psXyRLrXi2WFeHToOJTQnmMstVo9/1Mj36zaYHuSUcdcyLKnLGoabSivQ==
"@web3-react/walletlink-connector@^6.2.0":
version "6.2.0"
resolved "https://registry.yarnpkg.com/@web3-react/walletlink-connector/-/walletlink-connector-6.2.0.tgz#7619bdca5a030b475b3a205f92f949d61e3b9b25"
integrity sha512-Vz0QOLHQnZD/xDJfZ+TJ19NLYqy0Ii62RKwaF0xiKnAIcwjrq8MVEPtnY7kB9G1g8EoCD2ghqnWb4NyZTltQHw==
dependencies:
"@web3-react/abstract-connector" "^6.0.7"
"@web3-react/types" "^6.0.7"
walletlink "^2.0.2"
walletlink "^2.1.0"
"@webassemblyjs/ast@1.9.0":
version "1.9.0"
@@ -9114,7 +9114,7 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
eth-block-tracker@^4.2.0, eth-block-tracker@^4.4.2:
eth-block-tracker@4.4.3, eth-block-tracker@^4.2.0, eth-block-tracker@^4.4.2:
version "4.4.3"
resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-4.4.3.tgz#766a0a0eb4a52c867a28328e9ae21353812cf626"
integrity sha512-A8tG4Z4iNg4mw5tP1Vung9N9IjgMNqpiMoJ/FouSFwNCGHv2X0mmOYwtQOJzki6XN7r7Tyo01S29p7b224I4jw==
@@ -9126,7 +9126,7 @@ eth-block-tracker@^4.2.0, eth-block-tracker@^4.4.2:
pify "^3.0.0"
safe-event-emitter "^1.0.1"
eth-json-rpc-filters@^4.0.2, eth-json-rpc-filters@^4.2.1:
eth-json-rpc-filters@4.2.2, eth-json-rpc-filters@^4.0.2, eth-json-rpc-filters@^4.2.1:
version "4.2.2"
resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-4.2.2.tgz#eb35e1dfe9357ace8a8908e7daee80b2cd60a10d"
integrity sha512-DGtqpLU7bBg63wPMWg1sCpkKCf57dJ+hj/k3zF26anXMzkmtSBDExL8IhUu7LUd34f0Zsce3PYNO2vV2GaTzaw==
@@ -9222,6 +9222,13 @@ eth-query@^2.0.2, eth-query@^2.1.0, eth-query@^2.1.2:
json-rpc-random-id "^1.0.0"
xtend "^4.0.1"
eth-rpc-errors@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.2.tgz#11bc164e25237a679061ac05b7da7537b673d3b7"
integrity sha512-n+Re6Gu8XGyfFy1it0AwbD1x0MUzspQs0D5UiPs1fFPCr6WAwZM+vbIhXheBFrpgosqN9bs5PqlB4Q61U/QytQ==
dependencies:
fast-safe-stringify "^2.0.6"
eth-rpc-errors@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-3.0.0.tgz#d7b22653c70dbf9defd4ef490fd08fe70608ca10"
@@ -12503,6 +12510,14 @@ json-parse-even-better-errors@^2.3.0:
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
json-rpc-engine@6.1.0, json-rpc-engine@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz#bf5ff7d029e1c1bf20cb6c0e9f348dcd8be5a393"
integrity sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==
dependencies:
"@metamask/safe-event-emitter" "^2.0.0"
eth-rpc-errors "^4.0.2"
json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-3.8.0.tgz#9d4ff447241792e1d0a232f6ef927302bb0c62a9"
@@ -12523,14 +12538,6 @@ json-rpc-engine@^5.3.0:
eth-rpc-errors "^3.0.0"
safe-event-emitter "^1.0.1"
json-rpc-engine@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz#bf5ff7d029e1c1bf20cb6c0e9f348dcd8be5a393"
integrity sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==
dependencies:
"@metamask/safe-event-emitter" "^2.0.0"
eth-rpc-errors "^4.0.2"
json-rpc-error@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02"
@@ -19214,14 +19221,19 @@ walker@^1.0.7, walker@~1.0.5:
dependencies:
makeerror "1.0.x"
walletlink@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/walletlink/-/walletlink-2.0.3.tgz#8905deed6ba9a07d5dd49d709db359df571b0cc4"
integrity sha512-fl8LmelFpgVITdGxGkoGhCn9coIcV/8ubg2kT96DaqGi2N4BNvUjlmgOsXMuHvVUMg4kGVZeq2XKaChXBC9ySA==
walletlink@^2.1.0:
version "2.1.3"
resolved "https://registry.yarnpkg.com/walletlink/-/walletlink-2.1.3.tgz#dd7ae5c5464ab3608c0faa5435ba736b945abcd7"
integrity sha512-W8qgXiJn5BoecV8gneo7hMCGue7H5UsXjLfhaph6Z6BT7cC4+3ItWWGY5PoSbXRtR7LGe3h0kPZqCggiPrSQzQ==
dependencies:
"@metamask/safe-event-emitter" "2.0.0"
bind-decorator "^1.0.11"
bn.js "^5.1.1"
clsx "^1.1.0"
eth-block-tracker "4.4.3"
eth-json-rpc-filters "4.2.2"
eth-rpc-errors "4.0.2"
json-rpc-engine "6.1.0"
preact "^10.5.9"
rxjs "^6.6.3"