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:
Jordan Frankfurt 2023-03-13 12:36:45 -05:00 committed by GitHub
parent b5f665bc6e
commit 10eda002f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 63 deletions

@ -1,8 +1,6 @@
import { Trans } from '@lingui/macro'
import { sendEvent } from 'components/analytics'
import { ButtonOutlined } from 'components/Button'
import { AutoRow } from 'components/Row'
import React from 'react'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
@ -14,18 +12,14 @@ const Button = styled(ButtonOutlined).attrs(() => ({
flex: 1;
`
export default function PresetsButtons({ setFullRange }: { setFullRange: () => void }) {
interface PresetsButtonsProps {
onSetFullRange: () => void
}
export default function PresetsButtons({ onSetFullRange }: PresetsButtonsProps) {
return (
<AutoRow gap="4px" width="auto">
<Button
onClick={() => {
setFullRange()
sendEvent({
category: 'Liquidity',
action: 'Full Range Clicked',
})
}}
>
<Button onClick={onSetFullRange}>
<ThemedText.DeprecatedBody fontSize={12}>
<Trans>Full Range</Trans>
</ThemedText.DeprecatedBody>

@ -8,10 +8,10 @@ import { FeeAmount, NonfungiblePositionManager } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core'
import { sendEvent } from 'components/analytics'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import useParsedQueryString from 'hooks/useParsedQueryString'
import usePrevious from 'hooks/usePrevious'
import { useCallback, useEffect, useState } from 'react'
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 {
useRangeHopCallbacks,
@ -92,7 +92,6 @@ export default function AddLiquidity() {
const expertMode = useIsExpertMode()
const addTransaction = useTransactionAdder()
const positionManager = useV3NFTPositionManagerContract()
const parsedQs = useParsedQueryString()
// check for existing position if tokenId in url
const { position: existingPositionDetails, loading: positionLoading } = useV3PositionFromTokenId(
@ -114,8 +113,7 @@ export default function AddLiquidity() {
baseCurrency && currencyB && baseCurrency.wrapped.equals(currencyB.wrapped) ? undefined : currencyB
// mint state
const { independentField, typedValue, startPriceTypedValue, rightRangeTypedValue, leftRangeTypedValue } =
useV3MintState()
const { independentField, typedValue, startPriceTypedValue } = useV3MintState()
const {
pool,
@ -123,6 +121,7 @@ export default function AddLiquidity() {
dependentField,
price,
pricesAtTicks,
pricesAtLimit,
parsedAmounts,
currencyBalances,
position,
@ -153,26 +152,6 @@ export default function AddLiquidity() {
const [showConfirm, setShowConfirm] = useState<boolean>(false)
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
const deadline = useTransactionDeadline() // custom from users settings
@ -427,6 +406,58 @@ export default function AddLiquidity() {
!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 = () =>
addIsUnsupported ? (
<ButtonPrimary disabled={true} $borderRadius="12px" padding="12px">
@ -825,13 +856,7 @@ export default function AddLiquidity() {
feeAmount={feeAmount}
ticksAtLimit={ticksAtLimit}
/>
{!noLiquidity && (
<PresetsButtons
setFullRange={() => {
getSetFullRange()
}}
/>
)}
{!noLiquidity && <PresetsButtons onSetFullRange={handleSetFullRange} />}
</AutoColumn>
</StackedItem>
</StackedContainer>

@ -16,10 +16,9 @@ import { usePool } from 'hooks/usePools'
import JSBI from 'jsbi'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
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 { getTickToPrice } from 'utils/getTickToPrice'
import { replaceURLParam } from 'utils/routes'
import { BIG_INT_ZERO } from '../../../constants/misc'
import { PoolState } from '../../../hooks/usePools'
@ -48,7 +47,6 @@ export function useV3MintActionHandlers(noLiquidity: boolean | undefined): {
onStartPriceInput: (typedValue: string) => void
} {
const dispatch = useAppDispatch()
const navigate = useNavigate()
const onFieldAInput = useCallback(
(typedValue: string) => {
@ -64,22 +62,30 @@ export function useV3MintActionHandlers(noLiquidity: boolean | undefined): {
[dispatch, noLiquidity]
)
const { search } = useLocation()
const [searchParams, setSearchParams] = useSearchParams()
const onLeftRangeInput = useCallback(
(typedValue: string) => {
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(
(typedValue: string) => {
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(
@ -113,6 +119,9 @@ export function useV3DerivedMintInfo(
pricesAtTicks: {
[bound in Bound]?: Price<Token, Token> | undefined
}
pricesAtLimit: {
[bound in Bound]?: Price<Token, Token> | undefined
}
currencies: { [field in Field]?: Currency }
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> }
dependentField: Field
@ -226,9 +235,7 @@ export function useV3DerivedMintInfo(
const poolForPosition: Pool | undefined = pool ?? mockPool
// lower and upper limits in the tick space for `feeAmoun<Trans>
const tickSpaceLimits: {
[bound in Bound]: number | undefined
} = useMemo(
const tickSpaceLimits = useMemo(
() => ({
[Bound.LOWER]: feeAmount ? nearestUsableTick(TickMath.MIN_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
// lower should always be a smaller tick
const ticks: {
[key: string]: number | undefined
} = useMemo(() => {
const ticks = useMemo(() => {
return {
[Bound.LOWER]:
typeof existingPosition?.tickLower === 'number'
@ -286,6 +291,13 @@ export function useV3DerivedMintInfo(
// mark invalid range
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
const pricesAtTicks = useMemo(() => {
return {
@ -472,6 +484,7 @@ export function useV3DerivedMintInfo(
ticks,
price,
pricesAtTicks,
pricesAtLimit,
position,
noLiquidity,
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()
}