Commit 925668c7 authored by Noah Zinsmeister's avatar Noah Zinsmeister

get skeleton remove working

parent d1fad3c4
......@@ -147,7 +147,7 @@ export default function PositionListItem({ positionDetails, positionIndex }: Pos
const [poolState, pool] = usePool(currency0 ?? undefined, currency1 ?? undefined, feeAmount)
const position = useMemo(() => {
if (pool) {
return new Position({ pool, liquidity, tickLower, tickUpper })
return new Position({ pool, liquidity: liquidity.toString(), tickLower, tickUpper })
}
return undefined
}, [liquidity, pool, tickLower, tickUpper])
......
......@@ -11,10 +11,10 @@ import { FACTORY_ADDRESSES } from 'constants/v3'
import { useAllV3Ticks } from 'hooks/useAllV3Ticks'
export enum PoolState {
LOADING,
NOT_EXISTS,
EXISTS,
INVALID,
LOADING = 'LOADING',
NOT_EXISTS = 'NOT_EXISTS',
EXISTS = 'EXISTS',
INVALID = 'INVALID',
}
export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?: FeeAmount): [PoolState, Pool | null] {
......
......@@ -70,7 +70,7 @@ export function useAllV3Ticks(
'getPopulatedTicksInWord',
tickLensArgs,
REFRESH_FREQUENCY,
1_500_000
3_000_000
)
const error = useMemo(() => callStates.some(({ error }) => error), [callStates])
......
......@@ -2,17 +2,17 @@ import { useSingleCallResult, useSingleContractMultipleData, Result } from 'stat
import { useMemo } from 'react'
import { PositionDetails } from 'types/position'
import { useV3NFTPositionManagerContract } from './useContract'
import { BigNumber, BigNumberish } from '@ethersproject/bignumber'
import { BigNumber } from '@ethersproject/bignumber'
interface UseV3PositionsResults {
loading: boolean
error: boolean
positions: PositionDetails[] | undefined
positions: (PositionDetails & { tokenId: BigNumber })[] | undefined
}
function useV3PositionsFromTokenIds(tokenIds: BigNumberish[]): UseV3PositionsResults {
function useV3PositionsFromTokenIds(tokenIds: BigNumber[]): UseV3PositionsResults {
const positionManager = useV3NFTPositionManagerContract()
const inputs = useMemo(() => tokenIds.map((tokenId) => [BigNumber.from(tokenId)]), [tokenIds])
const inputs = useMemo(() => tokenIds.map((tokenId) => [tokenId]), [tokenIds])
const results = useSingleContractMultipleData(positionManager ?? undefined, 'positions', inputs)
const loading = useMemo(() => results.some(({ loading }) => loading), [results])
......@@ -44,17 +44,17 @@ function useV3PositionsFromTokenIds(tokenIds: BigNumberish[]): UseV3PositionsRes
return {
loading,
error,
positions,
positions: positions?.map((position, i) => ({ ...position, tokenId: inputs[i][0] })),
}
}
interface UseV3PositionResults {
loading: boolean
error: boolean
position: PositionDetails | undefined
position: (PositionDetails & { tokenId: BigNumber }) | undefined
}
export function useV3PositionFromTokenId(tokenId: BigNumberish): UseV3PositionResults {
export function useV3PositionFromTokenId(tokenId: BigNumber): UseV3PositionResults {
const position = useV3PositionsFromTokenIds([tokenId])
return {
loading: position.loading,
......
......@@ -47,7 +47,7 @@ function LiquidityInfo({ token0Amount, token1Amount }: { token0Amount: TokenAmou
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token0Amount.toSignificant(4)}
<FormattedCurrencyAmount currencyAmount={token0Amount} />
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={token0Amount.token} />
</RowFixed>
......
......@@ -4,7 +4,7 @@ import { PoolState, usePool } from 'data/Pools'
import { useActiveWeb3React } from 'hooks'
import { useToken } from 'hooks/Tokens'
import { useV3Positions } from 'hooks/useV3Positions'
import { RouteComponentProps } from 'react-router-dom'
import { RouteComponentProps, Link } from 'react-router-dom'
import { unwrappedToken } from 'utils/wrappedCurrency'
import { LoadingRows } from './styleds'
import styled from 'styled-components'
......@@ -99,6 +99,7 @@ export function PositionPage({
liquidity,
tickLower,
tickUpper,
tokenId,
// feeGrowthInside0LastX128,
// feeGrowthInside1LastX128,
} = positionDetails || {}
......@@ -112,8 +113,8 @@ export function PositionPage({
// construct Position from details returned
const [poolState, pool] = usePool(currency0 ?? undefined, currency1 ?? undefined, feeAmount)
const position = useMemo(() => {
if (pool && tickLower && tickUpper) {
return new Position({ pool, liquidity, tickLower, tickUpper })
if (pool && liquidity && tickLower && tickUpper) {
return new Position({ pool, liquidity: liquidity.toString(), tickLower, tickUpper })
}
return undefined
}, [liquidity, pool, tickLower, tickUpper])
......@@ -156,9 +157,11 @@ export function PositionPage({
<BadgeText>{basisPointsToPercent(feeAmount / 100).toSignificant()}%</BadgeText>
</Badge>
</RowFixed>
<ButtonPrimary width="200px" padding="8px" borderRadius="12px">
Remove liquidity
</ButtonPrimary>
{tokenId && (
<ButtonPrimary width="200px" padding="8px" borderRadius="12px" as={Link} to={`/remove/${tokenId}`}>
Remove liquidity
</ButtonPrimary>
)}
</RowBetween>
<RowBetween>
<BadgeWrapper>
......
import React, { useCallback, useMemo } from 'react'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import React from 'react'
import { RouteComponentProps } from 'react-router'
import { Redirect } from 'react-router-dom'
import AppBody from '../AppBody'
import { BigNumber } from '@ethersproject/bignumber'
import useDebouncedChangeHandler from 'hooks/useDebouncedChangeHandler'
import { useBurnV3ActionHandlers, useBurnV3State, useDerivedV3BurnInfo } from 'state/burn/v3/hooks'
import Slider from 'components/Slider'
import { RowBetween, RowFixed } from 'components/Row'
import { MaxButton } from 'pages/Pool/styleds'
import { AutoColumn } from 'components/Column'
import { ButtonConfirmed } from 'components/Button'
import { LightCard } from 'components/Card'
import { Text } from 'rebass'
import CurrencyLogo from 'components/CurrencyLogo'
import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount'
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import { useUserSlippageTolerance } from 'state/user/hooks'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import JSBI from 'jsbi'
import ReactGA from 'react-ga'
import { useActiveWeb3React } from 'hooks'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from 'state/transactions/hooks'
// TODO
// TODO still a lot of polishing left here
const UINT128MAX = BigNumber.from(2).pow(128).sub(1)
// redirect invalid tokenIds
export default function RemoveLiquidityV3({
location,
match: {
params: { tokenId },
},
}: RouteComponentProps<{ tokenId: string }>) {
const parsedTokenId = useMemo(() => {
try {
return BigNumber.from(tokenId)
} catch {
return null
}
}, [tokenId])
if (parsedTokenId === null || parsedTokenId.eq(0)) {
return <Redirect to={{ ...location, pathname: '/pool' }} />
}
return <Remove tokenId={parsedTokenId} />
}
function Remove({ tokenId }: { tokenId: BigNumber }) {
const position = useV3PositionFromTokenId(tokenId)
// check that account actually owns the position
console.log(position)
const { account } = useActiveWeb3React()
// burn state
const { percent } = useBurnV3State()
const { liquidity, liquidityValue0, liquidityValue1, feeValue0, feeValue1, error } = useDerivedV3BurnInfo(
position?.position
)
const { onPercentSelect } = useBurnV3ActionHandlers()
// boilerplate for the slider
const [percentForSlider, onPercentSelectForSlider] = useDebouncedChangeHandler(percent, onPercentSelect)
const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const addTransaction = useTransactionAdder()
const positionManager = useV3NFTPositionManagerContract()
const burn = useCallback(() => {
if (!liquidity || !positionManager || !liquidityValue0 || !liquidityValue1 || !deadline || !account) return
const data = []
// decreaseLiquidity if necessary
if (liquidity.gt(0)) {
data.push(
positionManager.interface.encodeFunctionData('decreaseLiquidity', [
{
tokenId,
liquidity,
amount0Min: `0x${JSBI.divide(
JSBI.multiply(liquidityValue0.raw, JSBI.BigInt(10000 - allowedSlippage)),
JSBI.BigInt(10000)
).toString(16)}`,
amount1Min: `0x${JSBI.divide(
JSBI.multiply(liquidityValue1.raw, JSBI.BigInt(10000 - allowedSlippage)),
JSBI.BigInt(10000)
).toString(16)}`,
deadline,
},
])
)
}
data.push(
positionManager.interface.encodeFunctionData('collect', [
{
tokenId,
recipient: account,
amount0Max: UINT128MAX,
amount1Max: UINT128MAX,
},
])
)
positionManager
.multicall(data)
.then((response: TransactionResponse) => {
ReactGA.event({
category: 'Liquidity',
action: 'RemoveV3',
label: [liquidityValue0.token.symbol, liquidityValue1.token.symbol].join('/'),
})
addTransaction(response, {
summary: `Remove ${liquidityValue0.token.symbol}/${liquidityValue1.token.symbol} V3 liquidity`,
})
})
.catch((error) => {
console.error(error)
})
}, [
tokenId,
liquidity,
liquidityValue0,
liquidityValue1,
deadline,
allowedSlippage,
account,
addTransaction,
positionManager,
])
return (
<AppBody>
<>
<Slider value={percentForSlider} onChange={onPercentSelectForSlider} />
<RowBetween>
<MaxButton onClick={() => onPercentSelect(25)} width="20%">
25%
</MaxButton>
<MaxButton onClick={() => onPercentSelect(50)} width="20%">
50%
</MaxButton>
<MaxButton onClick={() => onPercentSelect(75)} width="20%">
75%
</MaxButton>
<MaxButton onClick={() => onPercentSelect(100)} width="20%">
Max
</MaxButton>
</RowBetween>
<RowBetween my="1rem">
<Text fontSize={16} fontWeight={500}>
Pooled {liquidityValue0?.token?.symbol}:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{liquidityValue0 && <FormattedCurrencyAmount currencyAmount={liquidityValue0} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={liquidityValue0?.token} />
</RowFixed>
</RowBetween>
<RowBetween mb="1rem">
<Text fontSize={16} fontWeight={500}>
Pooled {liquidityValue1?.token?.symbol}:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{liquidityValue1 && <FormattedCurrencyAmount currencyAmount={liquidityValue1} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={liquidityValue1?.token} />
</RowFixed>
</RowBetween>
<RowBetween my="1rem">
<Text fontSize={16} fontWeight={500}>
{feeValue0?.token?.symbol} Fees:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{feeValue0 && <FormattedCurrencyAmount currencyAmount={feeValue0} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={feeValue0?.token} />
</RowFixed>
</RowBetween>
<RowBetween mb="1rem">
<Text fontSize={16} fontWeight={500}>
{feeValue1?.token?.symbol} Fees:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{feeValue1 && <FormattedCurrencyAmount currencyAmount={feeValue1} />}
</Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={feeValue1?.token} />
</RowFixed>
</RowBetween>
return <AppBody>TODO</AppBody>
<LightCard>
<div style={{ display: 'flex', marginTop: '1rem' }}>
<AutoColumn gap="12px" style={{ flex: '1' }}>
<ButtonConfirmed confirmed={false} disabled={!liquidity} onClick={burn}>
{error ?? liquidity?.eq(0) ? 'Collect' : 'Burn'}
</ButtonConfirmed>
</AutoColumn>
</div>
</LightCard>
</>
</AppBody>
)
}
import { createAction } from '@reduxjs/toolkit'
export const selectPercent = createAction<{ percent: number }>('burnV3/selectBurnPercent')
import { BigNumber } from '@ethersproject/bignumber'
import { TokenAmount } from '@uniswap/sdk-core'
import { Position } from '@uniswap/v3-sdk'
import { usePool } from 'data/Pools'
import { useActiveWeb3React } from 'hooks'
import { useToken } from 'hooks/Tokens'
import { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { PositionDetails } from 'types/position'
import { AppDispatch, AppState } from '../../index'
import { selectPercent } from './actions'
export function useBurnV3State(): AppState['burnV3'] {
return useSelector<AppState, AppState['burnV3']>((state) => state.burnV3)
}
export function useDerivedV3BurnInfo(
position?: PositionDetails & { tokenId: BigNumber }
): {
liquidity?: BigNumber
liquidityValue0?: TokenAmount
liquidityValue1?: TokenAmount
feeValue0?: TokenAmount
feeValue1?: TokenAmount
error?: string
} {
const { account } = useActiveWeb3React()
const { percent } = useBurnV3State()
const token0 = useToken(position?.token0)
const token1 = useToken(position?.token1)
const [, pool] = usePool(token0 ?? undefined, token1 ?? undefined, position?.fee)
const liquidity = position?.liquidity ? position.liquidity.mul(percent).div(100) : undefined
const positionSDK = useMemo(
() =>
pool && liquidity && position?.tickLower && position?.tickLower
? new Position({
pool,
liquidity: liquidity.toString(),
tickLower: position?.tickLower,
tickUpper: position?.tickUpper,
})
: undefined,
[pool, liquidity, position]
)
const liquidityValue0 = positionSDK?.amount0
const liquidityValue1 = positionSDK?.amount1
// TODO include counterfactual fees calculate from fee growth snapshots here
const feeValue0 =
token0 && position?.tokensOwed0 ? new TokenAmount(token0, position.tokensOwed0.toString()) : undefined
const feeValue1 =
token1 && position?.tokensOwed1 ? new TokenAmount(token1, position.tokensOwed1.toString()) : undefined
let error: string | undefined
if (!account) {
error = 'Connect Wallet'
}
if (percent === 0) {
error = error ?? 'Enter an percent'
}
return { liquidity, liquidityValue0, liquidityValue1, feeValue0, feeValue1, error }
}
export function useBurnV3ActionHandlers(): {
onPercentSelect: (percent: number) => void
} {
const dispatch = useDispatch<AppDispatch>()
const onPercentSelect = useCallback(
(percent: number) => {
dispatch(selectPercent({ percent }))
},
[dispatch]
)
return {
onPercentSelect,
}
}
import { createReducer } from '@reduxjs/toolkit'
import { selectPercent } from './actions'
export interface BurnV3State {
readonly percent: number
}
const initialState: BurnV3State = {
percent: 0,
}
export default createReducer<BurnV3State>(initialState, (builder) =>
builder.addCase(selectPercent, (state, { payload: { percent } }) => {
return {
...state,
percent,
}
})
)
......@@ -9,6 +9,7 @@ import swap from './swap/reducer'
import mint from './mint/reducer'
import lists from './lists/reducer'
import burn from './burn/reducer'
import burnV3 from './burn/v3/reducer'
import multicall from './multicall/reducer'
const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists']
......@@ -21,6 +22,7 @@ const store = configureStore({
swap,
mint,
burn,
burnV3,
multicall,
lists,
},
......
import { BigNumber } from '@ethersproject/bignumber'
export interface PositionDetails {
nonce: BigNumber
operator: string
......
......@@ -4134,45 +4134,40 @@
tiny-invariant "^1.1.0"
tiny-warning "^1.0.3"
"@uniswap/v3-core@1.0.0-rc.1":
version "1.0.0-rc.1"
resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0-rc.1.tgz#f2bbc483451364a951fba06eb2d978c6e8bdd58f"
integrity sha512-4ET2H0a8p7nVBGFWfio9SHc+RA6UIXEvlTRIJNsDwjQvfs8Jq9lfJ+eSOUTGmiB8Vp8V5dWarLDBU/rDE159pQ==
"@uniswap/v3-core@1.0.0-rc.2", "@uniswap/v3-core@^1.0.0-rc.2":
version "1.0.0-rc.2"
resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0-rc.2.tgz#a1afb3253a7295bec6165ad1d960121e6851a576"
integrity sha512-vsqkqAHPCKsVi0nWwWeX+mHnSTJ8ZUdu1zAVXB9Mj9A+aeBQGV9foRKs9ufDGJq7S1nqmz+7FdjSOcVoeiUqgQ==
"@uniswap/v3-periphery@^1.0.0-beta.17":
version "1.0.0-beta.19"
resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.0.0-beta.19.tgz#d9af90b12657049674cd2f26ae1c61b6cc393261"
integrity sha512-ZQX5KN/89OB7UjrmGOSB7QZIEbgW+R0uaVM5NdlK63Ji0rZjmddeoYS8oNk7i5BU3WR+xJY5DgfiDSmn1W6Sww==
"@uniswap/v3-periphery@^1.0.0-beta.20":
version "1.0.0-beta.20"
resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.0.0-beta.20.tgz#67a8086315e4820e3669b1f645916f9e246a6e56"
integrity sha512-sWh2W+CBjRVAwbPbKwgu8DWKYY7J3ucZf5mjRPBnmvsNLi6ILM5/NDAtefCvMnKEX7IQzO3gk7gDWZHPrkWXyw==
dependencies:
"@openzeppelin/contracts" "3.4.1-solc-0.7-2"
"@uniswap/lib" "^4.0.1-alpha"
"@uniswap/v2-core" "1.0.1"
"@uniswap/v3-core" "1.0.0-rc.1"
"@uniswap/v3-core" "1.0.0-rc.2"
"@uniswap/v3-periphery@^1.0.0-beta.20":
version "1.0.0-beta.20"
resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.0.0-beta.20.tgz#67a8086315e4820e3669b1f645916f9e246a6e56"
integrity sha512-sWh2W+CBjRVAwbPbKwgu8DWKYY7J3ucZf5mjRPBnmvsNLi6ILM5/NDAtefCvMnKEX7IQzO3gk7gDWZHPrkWXyw==
"@uniswap/v3-periphery@^1.0.0-beta.21":
version "1.0.0-beta.21"
resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.0.0-beta.21.tgz#0b8510bebca4b74aabdca72c5545bd5bec5128cd"
integrity sha512-o4U+lyH6qtlG2RTy3H/mtUGJuflkmVJ0pnXyrThZKC1KV/avlVgf4hmlG2PvOCV0yfwGMjQARKQ4jv6OpLFVqA==
dependencies:
"@openzeppelin/contracts" "3.4.1-solc-0.7-2"
"@uniswap/lib" "^4.0.1-alpha"
"@uniswap/v2-core" "1.0.1"
"@uniswap/v3-core" "1.0.0-rc.2"
"@uniswap/v3-sdk@^1.0.0-alpha.11":
version "1.0.0-alpha.11"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-1.0.0-alpha.11.tgz#184ed5ee8322b27f35aa830ad5e217b5dda6bd67"
integrity sha512-Tl4IzxlukT/vg+3hMLhwDqNDd0tg1rN5ODbrlTlOTJRg6+fyBvrM9y10nRftjS5e5nzyq+owDIQh8BBhb86CBw==
"@uniswap/v3-sdk@^1.0.0-alpha.13":
version "1.0.0-alpha.13"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-1.0.0-alpha.13.tgz#5634bb361e6bcb8e1a05d79a2aa61ffe13412f0c"
integrity sha512-OEEMhMoJlRQyqcd6u4r37cyifVy5jKJf/afW1jD8iqRqg/qAiYG0aR9Y/s7HiDA9WVHyaL/0YsWG8f0hv2UK/w==
dependencies:
"@ethersproject/abi" "^5.0.12"
"@ethersproject/solidity" "^5.0.9"
"@uniswap/sdk-core" "^1.0.9"
"@uniswap/v3-periphery" "^1.0.0-beta.17"
"@uniswap/v3-periphery" "^1.0.0-beta.21"
tiny-invariant "^1.1.0"
tiny-warning "^1.0.3"
......
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