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

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
parent 809902ef
...@@ -3,16 +3,18 @@ import { useEffect, useState } from 'react' ...@@ -3,16 +3,18 @@ import { useEffect, useState } from 'react'
import { useV3NFTPositionManagerContract } from './useContract' import { useV3NFTPositionManagerContract } from './useContract'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { Pool } from '@uniswap/v3-sdk' 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 { useBlockNumber } from 'state/application/hooks'
import { unwrappedToken } from 'utils/wrappedCurrency'
const MAX_UINT128 = BigNumber.from(2).pow(128).sub(1) const MAX_UINT128 = BigNumber.from(2).pow(128).sub(1)
// compute current + counterfactual fees for a v3 position // compute current + counterfactual fees for a v3 position
export function useV3PositionFees( export function useV3PositionFees(
pool?: Pool, pool?: Pool,
tokenId?: BigNumber tokenId?: BigNumber,
): [CurrencyAmount<Token>, CurrencyAmount<Token>] | [undefined, undefined] { asWETH = false
): [CurrencyAmount<Token | Ether>, CurrencyAmount<Token | Ether>] | [undefined, undefined] {
const positionManager = useV3NFTPositionManagerContract(false) const positionManager = useV3NFTPositionManagerContract(false)
const owner = useSingleCallResult(tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0] const owner = useSingleCallResult(tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
...@@ -43,8 +45,12 @@ export function useV3PositionFees( ...@@ -43,8 +45,12 @@ export function useV3PositionFees(
if (pool && amounts) { if (pool && amounts) {
return [ return [
CurrencyAmount.fromRawAmount(pool.token0, amounts[0].toString()), !asWETH && currencyEquals(unwrappedToken(pool.token0), ETHER)
CurrencyAmount.fromRawAmount(pool.token1, amounts[1].toString()), ? 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 { } else {
return [undefined, undefined] return [undefined, undefined]
......
...@@ -5,7 +5,7 @@ import { PoolState, usePool } from 'hooks/usePools' ...@@ -5,7 +5,7 @@ import { PoolState, usePool } from 'hooks/usePools'
import { useToken } from 'hooks/Tokens' import { useToken } from 'hooks/Tokens'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions' import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import { Link, RouteComponentProps } from 'react-router-dom' import { Link, RouteComponentProps } from 'react-router-dom'
import { unwrappedToken } from 'utils/wrappedCurrency' import { unwrappedToken, wrappedCurrencyAmount } from 'utils/wrappedCurrency'
import { usePositionTokenURI } from '../../hooks/usePositionTokenURI' import { usePositionTokenURI } from '../../hooks/usePositionTokenURI'
import { LoadingRows } from './styleds' import { LoadingRows } from './styleds'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
...@@ -23,7 +23,7 @@ import { currencyId } from 'utils/currencyId' ...@@ -23,7 +23,7 @@ import { currencyId } from 'utils/currencyId'
import { formatTokenAmount } from 'utils/formatTokenAmount' import { formatTokenAmount } from 'utils/formatTokenAmount'
import { useV3PositionFees } from 'hooks/useV3PositionFees' import { useV3PositionFees } from 'hooks/useV3PositionFees'
import { BigNumber } from '@ethersproject/bignumber' 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 { useActiveWeb3React } from 'hooks'
import { useV3NFTPositionManagerContract } from 'hooks/useContract' import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks' import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
...@@ -38,6 +38,7 @@ import { useSingleCallResult } from 'state/multicall/hooks' ...@@ -38,6 +38,7 @@ import { useSingleCallResult } from 'state/multicall/hooks'
import RangeBadge from '../../components/Badge/RangeBadge' import RangeBadge from '../../components/Badge/RangeBadge'
import useUSDCPrice from 'hooks/useUSDCPrice' import useUSDCPrice from 'hooks/useUSDCPrice'
import Loader from 'components/Loader' import Loader from 'components/Loader'
import Toggle from 'components/Toggle'
const PageWrapper = styled.div` const PageWrapper = styled.div`
min-width: 800px; min-width: 800px;
...@@ -269,8 +270,11 @@ export function PositionPage({ ...@@ -269,8 +270,11 @@ export function PositionPage({
const currency0 = token0 ? unwrappedToken(token0) : undefined const currency0 = token0 ? unwrappedToken(token0) : undefined
const currency1 = token1 ? unwrappedToken(token1) : undefined const currency1 = token1 ? unwrappedToken(token1) : undefined
// flag for receiving WETH
const [receiveWETH, setReceiveWETH] = useState(false)
// construct Position from details returned // 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(() => { const position = useMemo(() => {
if (pool && liquidity && typeof tickLower === 'number' && typeof tickUpper === 'number') { if (pool && liquidity && typeof tickLower === 'number' && typeof tickUpper === 'number') {
return new Position({ pool, liquidity: liquidity.toString(), tickLower, tickUpper }) return new Position({ pool, liquidity: liquidity.toString(), tickLower, tickUpper })
...@@ -304,7 +308,7 @@ export function PositionPage({ ...@@ -304,7 +308,7 @@ export function PositionPage({
}, [inverted, pool, priceLower, priceUpper]) }, [inverted, pool, priceLower, priceUpper])
// fees // 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 [collecting, setCollecting] = useState<boolean>(false)
const [collectMigrationHash, setCollectMigrationHash] = useState<string | null>(null) const [collectMigrationHash, setCollectMigrationHash] = useState<string | null>(null)
...@@ -320,12 +324,8 @@ export function PositionPage({ ...@@ -320,12 +324,8 @@ export function PositionPage({
const { calldata, value } = NonfungiblePositionManager.collectCallParameters({ const { calldata, value } = NonfungiblePositionManager.collectCallParameters({
tokenId: tokenId.toString(), tokenId: tokenId.toString(),
expectedCurrencyOwed0: currencyEquals(feeValue0.currency, WETH9[chainId]) expectedCurrencyOwed0: feeValue0,
? CurrencyAmount.ether(feeValue0.quotient) expectedCurrencyOwed1: feeValue1,
: feeValue0,
expectedCurrencyOwed1: currencyEquals(feeValue1.currency, WETH9[chainId])
? CurrencyAmount.ether(feeValue1.quotient)
: feeValue1,
recipient: account, recipient: account,
}) })
...@@ -371,15 +371,23 @@ export function PositionPage({ ...@@ -371,15 +371,23 @@ export function PositionPage({
const owner = useSingleCallResult(!!tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0] const owner = useSingleCallResult(!!tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
const ownsNFT = owner === account || positionDetails?.operator === account const ownsNFT = owner === account || positionDetails?.operator === account
// usdc prices always in terms of tokens
const price0 = useUSDCPrice(token0 ?? undefined) const price0 = useUSDCPrice(token0 ?? undefined)
const price1 = useUSDCPrice(token1 ?? 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 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) return amount0.add(amount1)
}, [price0, price1, feeValue0, feeValue1]) }, [price0, price1, feeValue0, feeValue1, chainId])
const fiatValueOfLiquidity: CurrencyAmount<Token> | null = useMemo(() => { const fiatValueOfLiquidity: CurrencyAmount<Token> | null = useMemo(() => {
if (!price0 || !price1 || !position) return null if (!price0 || !price1 || !position) return null
...@@ -388,6 +396,9 @@ export function PositionPage({ ...@@ -388,6 +396,9 @@ export function PositionPage({
return amount0.add(amount1) return amount0.add(amount1)
}, [price0, price1, position]) }, [price0, price1, position])
const feeValueUpper = inverted ? feeValue0 : feeValue1
const feeValueLower = inverted ? feeValue1 : feeValue0
function modalHeader() { function modalHeader() {
return ( return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}> <AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
...@@ -395,33 +406,17 @@ export function PositionPage({ ...@@ -395,33 +406,17 @@ export function PositionPage({
<AutoColumn gap="md"> <AutoColumn gap="md">
<RowBetween> <RowBetween>
<RowFixed> <RowFixed>
<CurrencyLogo currency={currencyQuote} size={'20px'} style={{ marginRight: '0.5rem' }} /> <CurrencyLogo currency={feeValueUpper?.currency} size={'20px'} style={{ marginRight: '0.5rem' }} />
<TYPE.main> <TYPE.main>{feeValueUpper ? formatTokenAmount(feeValueUpper, 4) : '-'}</TYPE.main>
{inverted
? feeValue0
? formatTokenAmount(feeValue0, 4)
: '-'
: feeValue1
? formatTokenAmount(feeValue1, 4)
: '-'}
</TYPE.main>
</RowFixed> </RowFixed>
<TYPE.main>{currencyQuote?.symbol}</TYPE.main> <TYPE.main>{feeValueUpper?.currency?.symbol}</TYPE.main>
</RowBetween> </RowBetween>
<RowBetween> <RowBetween>
<RowFixed> <RowFixed>
<CurrencyLogo currency={currencyBase} size={'20px'} style={{ marginRight: '0.5rem' }} /> <CurrencyLogo currency={feeValueLower?.currency} size={'20px'} style={{ marginRight: '0.5rem' }} />
<TYPE.main> <TYPE.main>{feeValueLower ? formatTokenAmount(feeValueLower, 4) : '-'}</TYPE.main>
{inverted
? feeValue0
? formatTokenAmount(feeValue1, 4)
: '-'
: feeValue1
? formatTokenAmount(feeValue0, 4)
: '-'}
</TYPE.main>
</RowFixed> </RowFixed>
<TYPE.main>{currencyBase?.symbol}</TYPE.main> <TYPE.main>{feeValueLower?.currency?.symbol}</TYPE.main>
</RowBetween> </RowBetween>
</AutoColumn> </AutoColumn>
</LightCard> </LightCard>
...@@ -640,40 +635,44 @@ export function PositionPage({ ...@@ -640,40 +635,44 @@ export function PositionPage({
<AutoColumn gap="md"> <AutoColumn gap="md">
<RowBetween> <RowBetween>
<RowFixed> <RowFixed>
<CurrencyLogo currency={currencyQuote} size={'20px'} style={{ marginRight: '0.5rem' }} /> <CurrencyLogo
<TYPE.main>{currencyQuote?.symbol}</TYPE.main> currency={feeValueUpper?.currency}
size={'20px'}
style={{ marginRight: '0.5rem' }}
/>
<TYPE.main>{feeValueUpper?.currency?.symbol}</TYPE.main>
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
<TYPE.main> <TYPE.main>{feeValueUpper ? formatTokenAmount(feeValueUpper, 4) : '-'}</TYPE.main>
{inverted
? feeValue0
? formatTokenAmount(feeValue0, 4)
: '-'
: feeValue1
? formatTokenAmount(feeValue1, 4)
: '-'}
</TYPE.main>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
<RowBetween> <RowBetween>
<RowFixed> <RowFixed>
<CurrencyLogo currency={currencyBase} size={'20px'} style={{ marginRight: '0.5rem' }} /> <CurrencyLogo
<TYPE.main>{currencyBase?.symbol}</TYPE.main> currency={feeValueLower?.currency}
size={'20px'}
style={{ marginRight: '0.5rem' }}
/>
<TYPE.main>{feeValueLower?.currency?.symbol}</TYPE.main>
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
<TYPE.main> <TYPE.main>{feeValueLower ? formatTokenAmount(feeValueLower, 4) : '-'}</TYPE.main>
{inverted
? feeValue0
? formatTokenAmount(feeValue1, 4)
: '-'
: feeValue1
? formatTokenAmount(feeValue0, 4)
: '-'}
</TYPE.main>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
</AutoColumn> </AutoColumn>
</LightCard> </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> </AutoColumn>
</DarkCard> </DarkCard>
</AutoColumn> </AutoColumn>
......
This diff is collapsed.
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 { Position } from '@uniswap/v3-sdk'
import { usePool } from 'hooks/usePools' import { usePool } from 'hooks/usePools'
import { useActiveWeb3React } from 'hooks' import { useActiveWeb3React } from 'hooks'
...@@ -10,20 +10,22 @@ import { PositionDetails } from 'types/position' ...@@ -10,20 +10,22 @@ import { PositionDetails } from 'types/position'
import { AppDispatch, AppState } from '../../index' import { AppDispatch, AppState } from '../../index'
import { selectPercent } from './actions' import { selectPercent } from './actions'
import { unwrappedToken } from 'utils/wrappedCurrency'
export function useBurnV3State(): AppState['burnV3'] { export function useBurnV3State(): AppState['burnV3'] {
return useSelector<AppState, AppState['burnV3']>((state) => state.burnV3) return useSelector<AppState, AppState['burnV3']>((state) => state.burnV3)
} }
export function useDerivedV3BurnInfo( export function useDerivedV3BurnInfo(
position?: PositionDetails position?: PositionDetails,
asWETH = false
): { ): {
position?: Position position?: Position
liquidityPercentage?: Percent liquidityPercentage?: Percent
liquidityValue0?: CurrencyAmount<Token> liquidityValue0?: CurrencyAmount<Token | Ether>
liquidityValue1?: CurrencyAmount<Token> liquidityValue1?: CurrencyAmount<Token | Ether>
feeValue0?: CurrencyAmount<Token> feeValue0?: CurrencyAmount<Token | Ether>
feeValue1?: CurrencyAmount<Token> feeValue1?: CurrencyAmount<Token | Ether>
outOfRange: boolean outOfRange: boolean
error?: string error?: string
} { } {
...@@ -50,20 +52,27 @@ export function useDerivedV3BurnInfo( ...@@ -50,20 +52,27 @@ export function useDerivedV3BurnInfo(
const liquidityPercentage = new Percent(percent, 100) const liquidityPercentage = new Percent(percent, 100)
const discountedAmount0 = positionSDK
? liquidityPercentage.multiply(positionSDK.amount0.quotient).quotient
: undefined
const discountedAmount1 = positionSDK
? liquidityPercentage.multiply(positionSDK.amount1.quotient).quotient
: undefined
const liquidityValue0 = const liquidityValue0 =
positionSDK && token0 && discountedAmount0
CurrencyAmount.fromRawAmount( ? currencyEquals(unwrappedToken(token0), ETHER) && !asWETH
positionSDK.amount0.currency, ? CurrencyAmount.ether(discountedAmount0)
liquidityPercentage.multiply(positionSDK.amount0.quotient).quotient : CurrencyAmount.fromRawAmount(token0, discountedAmount0)
) : undefined
const liquidityValue1 = const liquidityValue1 =
positionSDK && token1 && discountedAmount1
CurrencyAmount.fromRawAmount( ? currencyEquals(unwrappedToken(token1), ETHER) && !asWETH
positionSDK.amount1.currency, ? CurrencyAmount.ether(discountedAmount1)
liquidityPercentage.multiply(positionSDK.amount1.quotient).quotient : CurrencyAmount.fromRawAmount(token1, discountedAmount1)
) : undefined
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, position?.tokenId) const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, position?.tokenId, asWETH)
const outOfRange = const outOfRange =
pool && position ? pool.tickCurrent < position.tickLower || pool.tickCurrent > position.tickUpper : false pool && position ? pool.tickCurrent < position.tickLower || pool.tickCurrent > position.tickUpper : false
......
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