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'
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]
......
......@@ -5,7 +5,7 @@ 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;
......@@ -269,8 +270,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 +308,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 +324,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 +371,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 +396,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 +406,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 +635,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>
......
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 { 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 discountedAmount0 = positionSDK
? liquidityPercentage.multiply(positionSDK.amount0.quotient).quotient
: undefined
const discountedAmount1 = positionSDK
? liquidityPercentage.multiply(positionSDK.amount1.quotient).quotient
: undefined
const liquidityValue0 =
positionSDK &&
CurrencyAmount.fromRawAmount(
positionSDK.amount0.currency,
liquidityPercentage.multiply(positionSDK.amount0.quotient).quotient
)
token0 && discountedAmount0
? currencyEquals(unwrappedToken(token0), ETHER) && !asWETH
? CurrencyAmount.ether(discountedAmount0)
: CurrencyAmount.fromRawAmount(token0, discountedAmount0)
: undefined
const liquidityValue1 =
positionSDK &&
CurrencyAmount.fromRawAmount(
positionSDK.amount1.currency,
liquidityPercentage.multiply(positionSDK.amount1.quotient).quotient
)
token1 && discountedAmount1
? currencyEquals(unwrappedToken(token1), ETHER) && !asWETH
? CurrencyAmount.ether(discountedAmount1)
: CurrencyAmount.fromRawAmount(token1, discountedAmount1)
: undefined
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, position?.tokenId)
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, position?.tokenId, asWETH)
const outOfRange =
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