fix: full range lp shortcut (#6136)
* fix: fix full range LP * make a dedicated hook for syncing query parameters * sync full range button to url * add comment explaining lint rule disable
This commit is contained in:
parent
b5f665bc6e
commit
10eda002f5
@ -1,8 +1,6 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { sendEvent } from 'components/analytics'
|
|
||||||
import { ButtonOutlined } from 'components/Button'
|
import { ButtonOutlined } from 'components/Button'
|
||||||
import { AutoRow } from 'components/Row'
|
import { AutoRow } from 'components/Row'
|
||||||
import React from 'react'
|
|
||||||
import styled from 'styled-components/macro'
|
import styled from 'styled-components/macro'
|
||||||
import { ThemedText } from 'theme'
|
import { ThemedText } from 'theme'
|
||||||
|
|
||||||
@ -14,18 +12,14 @@ const Button = styled(ButtonOutlined).attrs(() => ({
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default function PresetsButtons({ setFullRange }: { setFullRange: () => void }) {
|
interface PresetsButtonsProps {
|
||||||
|
onSetFullRange: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PresetsButtons({ onSetFullRange }: PresetsButtonsProps) {
|
||||||
return (
|
return (
|
||||||
<AutoRow gap="4px" width="auto">
|
<AutoRow gap="4px" width="auto">
|
||||||
<Button
|
<Button onClick={onSetFullRange}>
|
||||||
onClick={() => {
|
|
||||||
setFullRange()
|
|
||||||
sendEvent({
|
|
||||||
category: 'Liquidity',
|
|
||||||
action: 'Full Range Clicked',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ThemedText.DeprecatedBody fontSize={12}>
|
<ThemedText.DeprecatedBody fontSize={12}>
|
||||||
<Trans>Full Range</Trans>
|
<Trans>Full Range</Trans>
|
||||||
</ThemedText.DeprecatedBody>
|
</ThemedText.DeprecatedBody>
|
||||||
|
@ -8,10 +8,10 @@ import { FeeAmount, NonfungiblePositionManager } from '@uniswap/v3-sdk'
|
|||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { sendEvent } from 'components/analytics'
|
import { sendEvent } from 'components/analytics'
|
||||||
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
|
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
|
||||||
import useParsedQueryString from 'hooks/useParsedQueryString'
|
import usePrevious from 'hooks/usePrevious'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { AlertTriangle } from 'react-feather'
|
import { AlertTriangle } from 'react-feather'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||||
import { Text } from 'rebass'
|
import { Text } from 'rebass'
|
||||||
import {
|
import {
|
||||||
useRangeHopCallbacks,
|
useRangeHopCallbacks,
|
||||||
@ -92,7 +92,6 @@ export default function AddLiquidity() {
|
|||||||
const expertMode = useIsExpertMode()
|
const expertMode = useIsExpertMode()
|
||||||
const addTransaction = useTransactionAdder()
|
const addTransaction = useTransactionAdder()
|
||||||
const positionManager = useV3NFTPositionManagerContract()
|
const positionManager = useV3NFTPositionManagerContract()
|
||||||
const parsedQs = useParsedQueryString()
|
|
||||||
|
|
||||||
// check for existing position if tokenId in url
|
// check for existing position if tokenId in url
|
||||||
const { position: existingPositionDetails, loading: positionLoading } = useV3PositionFromTokenId(
|
const { position: existingPositionDetails, loading: positionLoading } = useV3PositionFromTokenId(
|
||||||
@ -114,8 +113,7 @@ export default function AddLiquidity() {
|
|||||||
baseCurrency && currencyB && baseCurrency.wrapped.equals(currencyB.wrapped) ? undefined : currencyB
|
baseCurrency && currencyB && baseCurrency.wrapped.equals(currencyB.wrapped) ? undefined : currencyB
|
||||||
|
|
||||||
// mint state
|
// mint state
|
||||||
const { independentField, typedValue, startPriceTypedValue, rightRangeTypedValue, leftRangeTypedValue } =
|
const { independentField, typedValue, startPriceTypedValue } = useV3MintState()
|
||||||
useV3MintState()
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
pool,
|
pool,
|
||||||
@ -123,6 +121,7 @@ export default function AddLiquidity() {
|
|||||||
dependentField,
|
dependentField,
|
||||||
price,
|
price,
|
||||||
pricesAtTicks,
|
pricesAtTicks,
|
||||||
|
pricesAtLimit,
|
||||||
parsedAmounts,
|
parsedAmounts,
|
||||||
currencyBalances,
|
currencyBalances,
|
||||||
position,
|
position,
|
||||||
@ -153,26 +152,6 @@ export default function AddLiquidity() {
|
|||||||
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
||||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
|
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
parsedQs.minPrice &&
|
|
||||||
typeof parsedQs.minPrice === 'string' &&
|
|
||||||
parsedQs.minPrice !== leftRangeTypedValue &&
|
|
||||||
!isNaN(parsedQs.minPrice as any)
|
|
||||||
) {
|
|
||||||
onLeftRangeInput(parsedQs.minPrice)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
parsedQs.maxPrice &&
|
|
||||||
typeof parsedQs.maxPrice === 'string' &&
|
|
||||||
parsedQs.maxPrice !== rightRangeTypedValue &&
|
|
||||||
!isNaN(parsedQs.maxPrice as any)
|
|
||||||
) {
|
|
||||||
onRightRangeInput(parsedQs.maxPrice)
|
|
||||||
}
|
|
||||||
}, [parsedQs, rightRangeTypedValue, leftRangeTypedValue, onRightRangeInput, onLeftRangeInput])
|
|
||||||
|
|
||||||
// txn values
|
// txn values
|
||||||
const deadline = useTransactionDeadline() // custom from users settings
|
const deadline = useTransactionDeadline() // custom from users settings
|
||||||
|
|
||||||
@ -427,6 +406,58 @@ export default function AddLiquidity() {
|
|||||||
!depositBDisabled ? currencies[Field.CURRENCY_B]?.symbol : ''
|
!depositBDisabled ? currencies[Field.CURRENCY_B]?.symbol : ''
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
|
|
||||||
|
const handleSetFullRange = useCallback(() => {
|
||||||
|
getSetFullRange()
|
||||||
|
|
||||||
|
const minPrice = pricesAtLimit[Bound.LOWER]
|
||||||
|
if (minPrice) searchParams.set('minPrice', minPrice.toSignificant(5))
|
||||||
|
const maxPrice = pricesAtLimit[Bound.UPPER]
|
||||||
|
if (maxPrice) searchParams.set('maxPrice', maxPrice.toSignificant(5))
|
||||||
|
setSearchParams(searchParams)
|
||||||
|
|
||||||
|
sendEvent({
|
||||||
|
category: 'Liquidity',
|
||||||
|
action: 'Full Range Clicked',
|
||||||
|
})
|
||||||
|
}, [getSetFullRange, pricesAtLimit, searchParams, setSearchParams])
|
||||||
|
|
||||||
|
// START: sync values with query string
|
||||||
|
const oldSearchParams = usePrevious(searchParams)
|
||||||
|
// use query string as an input to onInput handlers
|
||||||
|
useEffect(() => {
|
||||||
|
const minPrice = searchParams.get('minPrice')
|
||||||
|
const oldMinPrice = oldSearchParams?.get('minPrice')
|
||||||
|
if (
|
||||||
|
minPrice &&
|
||||||
|
typeof minPrice === 'string' &&
|
||||||
|
!isNaN(minPrice as any) &&
|
||||||
|
(!oldMinPrice || oldMinPrice !== minPrice)
|
||||||
|
) {
|
||||||
|
onLeftRangeInput(minPrice)
|
||||||
|
}
|
||||||
|
// disable eslint rule because this hook only cares about the url->input state data flow
|
||||||
|
// input state -> url updates are handled in the input handlers
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [searchParams])
|
||||||
|
useEffect(() => {
|
||||||
|
const maxPrice = searchParams.get('maxPrice')
|
||||||
|
const oldMaxPrice = oldSearchParams?.get('maxPrice')
|
||||||
|
if (
|
||||||
|
maxPrice &&
|
||||||
|
typeof maxPrice === 'string' &&
|
||||||
|
!isNaN(maxPrice as any) &&
|
||||||
|
(!oldMaxPrice || oldMaxPrice !== maxPrice)
|
||||||
|
) {
|
||||||
|
onRightRangeInput(maxPrice)
|
||||||
|
}
|
||||||
|
// disable eslint rule because this hook only cares about the url->input state data flow
|
||||||
|
// input state -> url updates are handled in the input handlers
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [searchParams])
|
||||||
|
// END: sync values with query string
|
||||||
|
|
||||||
const Buttons = () =>
|
const Buttons = () =>
|
||||||
addIsUnsupported ? (
|
addIsUnsupported ? (
|
||||||
<ButtonPrimary disabled={true} $borderRadius="12px" padding="12px">
|
<ButtonPrimary disabled={true} $borderRadius="12px" padding="12px">
|
||||||
@ -825,13 +856,7 @@ export default function AddLiquidity() {
|
|||||||
feeAmount={feeAmount}
|
feeAmount={feeAmount}
|
||||||
ticksAtLimit={ticksAtLimit}
|
ticksAtLimit={ticksAtLimit}
|
||||||
/>
|
/>
|
||||||
{!noLiquidity && (
|
{!noLiquidity && <PresetsButtons onSetFullRange={handleSetFullRange} />}
|
||||||
<PresetsButtons
|
|
||||||
setFullRange={() => {
|
|
||||||
getSetFullRange()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</StackedItem>
|
</StackedItem>
|
||||||
</StackedContainer>
|
</StackedContainer>
|
||||||
|
@ -16,10 +16,9 @@ import { usePool } from 'hooks/usePools'
|
|||||||
import JSBI from 'jsbi'
|
import JSBI from 'jsbi'
|
||||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||||
import { ReactNode, useCallback, useMemo } from 'react'
|
import { ReactNode, useCallback, useMemo } from 'react'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||||
import { getTickToPrice } from 'utils/getTickToPrice'
|
import { getTickToPrice } from 'utils/getTickToPrice'
|
||||||
import { replaceURLParam } from 'utils/routes'
|
|
||||||
|
|
||||||
import { BIG_INT_ZERO } from '../../../constants/misc'
|
import { BIG_INT_ZERO } from '../../../constants/misc'
|
||||||
import { PoolState } from '../../../hooks/usePools'
|
import { PoolState } from '../../../hooks/usePools'
|
||||||
@ -48,7 +47,6 @@ export function useV3MintActionHandlers(noLiquidity: boolean | undefined): {
|
|||||||
onStartPriceInput: (typedValue: string) => void
|
onStartPriceInput: (typedValue: string) => void
|
||||||
} {
|
} {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const onFieldAInput = useCallback(
|
const onFieldAInput = useCallback(
|
||||||
(typedValue: string) => {
|
(typedValue: string) => {
|
||||||
@ -64,22 +62,30 @@ export function useV3MintActionHandlers(noLiquidity: boolean | undefined): {
|
|||||||
[dispatch, noLiquidity]
|
[dispatch, noLiquidity]
|
||||||
)
|
)
|
||||||
|
|
||||||
const { search } = useLocation()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
|
|
||||||
const onLeftRangeInput = useCallback(
|
const onLeftRangeInput = useCallback(
|
||||||
(typedValue: string) => {
|
(typedValue: string) => {
|
||||||
dispatch(typeLeftRangeInput({ typedValue }))
|
dispatch(typeLeftRangeInput({ typedValue }))
|
||||||
navigate({ search: replaceURLParam(search, 'minPrice', typedValue) }, { replace: true })
|
const paramMinPrice = searchParams.get('minPrice')
|
||||||
|
if (!paramMinPrice || (paramMinPrice && paramMinPrice !== typedValue)) {
|
||||||
|
searchParams.set('minPrice', typedValue)
|
||||||
|
setSearchParams(searchParams)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[dispatch, navigate, search]
|
[dispatch, searchParams, setSearchParams]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onRightRangeInput = useCallback(
|
const onRightRangeInput = useCallback(
|
||||||
(typedValue: string) => {
|
(typedValue: string) => {
|
||||||
dispatch(typeRightRangeInput({ typedValue }))
|
dispatch(typeRightRangeInput({ typedValue }))
|
||||||
navigate({ search: replaceURLParam(search, 'maxPrice', typedValue) }, { replace: true })
|
const paramMaxPrice = searchParams.get('maxPrice')
|
||||||
|
if (!paramMaxPrice || (paramMaxPrice && paramMaxPrice !== typedValue)) {
|
||||||
|
searchParams.set('maxPrice', typedValue)
|
||||||
|
setSearchParams(searchParams)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[dispatch, navigate, search]
|
[dispatch, searchParams, setSearchParams]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onStartPriceInput = useCallback(
|
const onStartPriceInput = useCallback(
|
||||||
@ -113,6 +119,9 @@ export function useV3DerivedMintInfo(
|
|||||||
pricesAtTicks: {
|
pricesAtTicks: {
|
||||||
[bound in Bound]?: Price<Token, Token> | undefined
|
[bound in Bound]?: Price<Token, Token> | undefined
|
||||||
}
|
}
|
||||||
|
pricesAtLimit: {
|
||||||
|
[bound in Bound]?: Price<Token, Token> | undefined
|
||||||
|
}
|
||||||
currencies: { [field in Field]?: Currency }
|
currencies: { [field in Field]?: Currency }
|
||||||
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> }
|
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> }
|
||||||
dependentField: Field
|
dependentField: Field
|
||||||
@ -226,9 +235,7 @@ export function useV3DerivedMintInfo(
|
|||||||
const poolForPosition: Pool | undefined = pool ?? mockPool
|
const poolForPosition: Pool | undefined = pool ?? mockPool
|
||||||
|
|
||||||
// lower and upper limits in the tick space for `feeAmoun<Trans>
|
// lower and upper limits in the tick space for `feeAmoun<Trans>
|
||||||
const tickSpaceLimits: {
|
const tickSpaceLimits = useMemo(
|
||||||
[bound in Bound]: number | undefined
|
|
||||||
} = useMemo(
|
|
||||||
() => ({
|
() => ({
|
||||||
[Bound.LOWER]: feeAmount ? nearestUsableTick(TickMath.MIN_TICK, TICK_SPACINGS[feeAmount]) : undefined,
|
[Bound.LOWER]: feeAmount ? nearestUsableTick(TickMath.MIN_TICK, TICK_SPACINGS[feeAmount]) : undefined,
|
||||||
[Bound.UPPER]: feeAmount ? nearestUsableTick(TickMath.MAX_TICK, TICK_SPACINGS[feeAmount]) : undefined,
|
[Bound.UPPER]: feeAmount ? nearestUsableTick(TickMath.MAX_TICK, TICK_SPACINGS[feeAmount]) : undefined,
|
||||||
@ -238,9 +245,7 @@ export function useV3DerivedMintInfo(
|
|||||||
|
|
||||||
// parse typed range values and determine closest ticks
|
// parse typed range values and determine closest ticks
|
||||||
// lower should always be a smaller tick
|
// lower should always be a smaller tick
|
||||||
const ticks: {
|
const ticks = useMemo(() => {
|
||||||
[key: string]: number | undefined
|
|
||||||
} = useMemo(() => {
|
|
||||||
return {
|
return {
|
||||||
[Bound.LOWER]:
|
[Bound.LOWER]:
|
||||||
typeof existingPosition?.tickLower === 'number'
|
typeof existingPosition?.tickLower === 'number'
|
||||||
@ -286,6 +291,13 @@ export function useV3DerivedMintInfo(
|
|||||||
// mark invalid range
|
// mark invalid range
|
||||||
const invalidRange = Boolean(typeof tickLower === 'number' && typeof tickUpper === 'number' && tickLower >= tickUpper)
|
const invalidRange = Boolean(typeof tickLower === 'number' && typeof tickUpper === 'number' && tickLower >= tickUpper)
|
||||||
|
|
||||||
|
const pricesAtLimit = useMemo(() => {
|
||||||
|
return {
|
||||||
|
[Bound.LOWER]: getTickToPrice(token0, token1, tickSpaceLimits.LOWER),
|
||||||
|
[Bound.UPPER]: getTickToPrice(token0, token1, tickSpaceLimits.UPPER),
|
||||||
|
}
|
||||||
|
}, [token0, token1, tickSpaceLimits.LOWER, tickSpaceLimits.UPPER])
|
||||||
|
|
||||||
// always returns the price with 0 as base token
|
// always returns the price with 0 as base token
|
||||||
const pricesAtTicks = useMemo(() => {
|
const pricesAtTicks = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -472,6 +484,7 @@ export function useV3DerivedMintInfo(
|
|||||||
ticks,
|
ticks,
|
||||||
price,
|
price,
|
||||||
pricesAtTicks,
|
pricesAtTicks,
|
||||||
|
pricesAtLimit,
|
||||||
position,
|
position,
|
||||||
noLiquidity,
|
noLiquidity,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
export const replaceURLParam = (search: string, param: string, newValue: string) => {
|
|
||||||
const searchParams = new URLSearchParams(search)
|
|
||||||
searchParams.set(param, newValue)
|
|
||||||
return searchParams.toString()
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user