Commit da33ec9d authored by Ian Lapham's avatar Ian Lapham Committed by GitHub

use token0 as base in all calculations (#51)

* use token0 as base in all calculations

* refactor

* fix price order

* fix existing position ticks

* remove empty space
Co-authored-by: default avatarNoah Zinsmeister <noahwz@gmail.com>
parent 60536862
......@@ -2,7 +2,7 @@ import React, { useState, useCallback, useEffect } from 'react'
import { OutlineCard } from 'components/Card'
import { RowBetween } from 'components/Row'
import { Input as NumericalInput } from '../NumericalInput'
import styled, { keyframes, css } from 'styled-components'
import styled, { keyframes } from 'styled-components'
import { TYPE } from 'theme'
import { AutoColumn } from 'components/Column'
import { ButtonSecondary } from 'components/Button'
......@@ -34,11 +34,7 @@ const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boo
border-color: ${({ active, theme }) => active && theme.blue1};
padding: 12px;
${({ pulsing, theme }) =>
pulsing &&
css`
animation: ${pulse(theme.blue1)} 0.8s linear;
`}
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.blue1)} 0.8s linear;
`
const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
......@@ -54,8 +50,8 @@ const ContentWrapper = styled(RowBetween)`
interface StepCounterProps {
value: string
onUserInput: (value: string) => void
getDecrementValue?: () => string
getIncrementValue?: () => string
decrement: () => string
increment: () => string
feeAmount?: FeeAmount
label?: string
width?: string
......@@ -64,13 +60,13 @@ interface StepCounterProps {
const StepCounter = ({
value,
onUserInput,
getDecrementValue,
getIncrementValue,
decrement,
increment,
feeAmount,
label,
width,
locked,
onUserInput,
}: StepCounterProps) => {
// for focus state, styled components doesnt let you select input parent container
const [active, setActive] = useState(false)
......@@ -83,7 +79,7 @@ const StepCounter = ({
const [pulsing, setPulsing] = useState<boolean>(false)
// format fee amount
const feeAmountFormatted = feeAmount ? formattedFeeAmount(feeAmount) : ''
const feeAmountFormatted = feeAmount ? formattedFeeAmount(feeAmount * 2) : ''
const handleOnFocus = () => {
setUseLocalValue(true)
......@@ -92,18 +88,14 @@ const StepCounter = ({
// for button clicks
const handleDecrement = useCallback(() => {
if (getDecrementValue) {
setLocalValue(getDecrementValue())
onUserInput(getDecrementValue())
}
}, [getDecrementValue, onUserInput])
setLocalValue(decrement())
onUserInput(decrement())
}, [decrement, onUserInput])
const handleIncrement = useCallback(() => {
if (getIncrementValue) {
setLocalValue(getIncrementValue())
onUserInput(getIncrementValue())
}
}, [getIncrementValue, onUserInput])
setLocalValue(increment())
onUserInput(increment())
}, [increment, onUserInput])
const handleOnBlur = useCallback(() => {
setUseLocalValue(false)
......@@ -138,7 +130,7 @@ const StepCounter = ({
/>
</ContentWrapper>
{label && <TYPE.label fontSize="12px">{label}</TYPE.label>}
{getDecrementValue && getIncrementValue && !locked ? (
{!locked ? (
<RowBetween>
<SmallButton onClick={handleDecrement}>
<TYPE.main fontSize="12px">-{feeAmountFormatted}%</TYPE.main>
......
......@@ -159,10 +159,10 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
const formattedAmount1 = formatTokenAmount(amount1, 4)
// prices
const price0Lower = position ? position.token0PriceLower : undefined
const price0Upper = position ? position.token0PriceUpper : undefined
const price1Lower = price0Upper ? price0Upper.invert() : undefined
const price1Upper = price0Lower ? price0Lower.invert() : undefined
const price0Lower = position?.token0PriceLower
const price0Upper = position?.token0PriceUpper
const price1Lower = price0Lower?.invert()
const price1Upper = price0Upper?.invert()
// fees
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails)
......@@ -205,13 +205,13 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
<>
<DataLineItem>
{formatPrice(price0Lower, 4)} <DoubleArrow></DoubleArrow> {formatPrice(price0Upper, 4)}{' '}
{currency0?.symbol}&nbsp;/&nbsp;
{currency1?.symbol}
{currency1?.symbol}&nbsp;/&nbsp;
{currency0?.symbol}
</DataLineItem>
<DataLineItem>
{formatPrice(price1Lower, 4)} <DoubleArrow></DoubleArrow> {formatPrice(price1Upper, 4)}{' '}
{currency1?.symbol}&nbsp;/&nbsp;
{currency0?.symbol}
{currency0?.symbol}&nbsp;/&nbsp;
{currency1?.symbol}
</DataLineItem>
</>
) : (
......
import React from 'react'
import { Currency, Price } from '@uniswap/sdk-core'
import StepCounter from 'components/InputStepCounter/InputStepCounter'
import { RowBetween } from 'components/Row'
import React from 'react'
import { useActiveWeb3React } from 'hooks'
import { wrappedCurrency } from 'utils/wrappedCurrency'
// currencyA is the base token
export default function RangeSelector({
priceLower,
priceUpper,
onUpperRangeInput,
onLowerRangeInput,
getLowerDecrement,
getLowerIncrement,
getUpperDecrement,
getUpperIncrement,
onLeftRangeInput,
onRightRangeInput,
getDecrementLower,
getIncrementLower,
getDecrementUpper,
getIncrementUpper,
currencyA,
currencyB,
feeAmount,
fixedValueLower,
fixedValueUpper,
}: {
priceLower?: Price
priceUpper?: Price
getLowerIncrement?: () => string
getLowerDecrement?: () => string
getUpperIncrement?: () => string
getUpperDecrement?: () => string
onLowerRangeInput: (typedValue: string) => void
onUpperRangeInput: (typedValue: string) => void
getDecrementLower: () => string
getIncrementLower: () => string
getDecrementUpper: () => string
getIncrementUpper: () => string
onLeftRangeInput: (typedValue: string) => void
onRightRangeInput: (typedValue: string) => void
currencyA?: Currency | null
currencyB?: Currency | null
feeAmount?: number
fixedValueLower?: string
fixedValueUpper?: string
}) {
const { chainId } = useActiveWeb3React()
const tokenA = wrappedCurrency(currencyA ?? undefined, chainId)
const tokenB = wrappedCurrency(currencyB ?? undefined, chainId)
const isSorted = tokenA && tokenB && tokenA.sortsBefore(tokenB)
const leftPrice = isSorted ? priceLower : priceUpper?.invert()
const rightPrice = isSorted ? priceUpper : priceLower?.invert()
return (
<RowBetween>
<StepCounter
value={fixedValueLower ?? priceLower?.toSignificant(5) ?? ''}
onUserInput={onLowerRangeInput}
value={leftPrice?.toSignificant(5) ?? ''}
onUserInput={onLeftRangeInput}
width="48%"
getIncrementValue={getLowerIncrement}
getDecrementValue={getLowerDecrement}
decrement={isSorted ? getDecrementLower : getIncrementUpper}
increment={isSorted ? getIncrementLower : getDecrementUpper}
feeAmount={feeAmount}
label={
priceLower && currencyA && currencyB
? `${priceLower.toSignificant(4)} ${currencyB.symbol} / 1 ${currencyA.symbol}`
: '-'
}
locked={!!fixedValueLower}
label={leftPrice ? `${leftPrice.toSignificant(5)} ${currencyB?.symbol}/${currencyA?.symbol}` : '-'}
/>
<StepCounter
value={fixedValueUpper ?? priceUpper?.toSignificant(5) ?? ''}
onUserInput={onUpperRangeInput}
value={rightPrice?.toSignificant(5) ?? ''}
onUserInput={onRightRangeInput}
width="48%"
getDecrementValue={getUpperDecrement}
getIncrementValue={getUpperIncrement}
decrement={isSorted ? getDecrementUpper : getIncrementLower}
increment={isSorted ? getIncrementUpper : getDecrementLower}
feeAmount={feeAmount}
label={
priceUpper && currencyA && currencyB
? `${priceUpper.toSignificant(4)} ${currencyB?.symbol} / 1 ${currencyA?.symbol}`
: '-'
}
locked={!!fixedValueUpper}
label={rightPrice ? `${rightPrice.toSignificant(5)} ${currencyB?.symbol}/${currencyA?.symbol}` : '-'}
/>
</RowBetween>
)
......
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, TokenAmount, Percent, ETHER } from '@uniswap/sdk-core'
import React, { useCallback, useContext, useState } from 'react'
import React, { useCallback, useContext, useMemo, useState } from 'react'
import { Link2, AlertTriangle } from 'react-feather'
import ReactGA from 'react-ga'
import { useV3NFTPositionManagerContract } from '../../hooks/useContract'
......@@ -26,7 +26,6 @@ import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { wrappedCurrency } from '../../utils/wrappedCurrency'
import AppBody from '../AppBody'
import { Dots } from '../Pool/styleds'
import { currencyId } from '../../utils/currencyId'
......@@ -42,8 +41,8 @@ import {
ScrollablePage,
} from './styled'
import { useTranslation } from 'react-i18next'
import { useMintState, useMintActionHandlers, useDerivedMintInfo } from 'state/mint/hooks'
import { FeeAmount, NonfungiblePositionManager, tickToPrice, TICK_SPACINGS } from '@uniswap/v3-sdk'
import { useMintState, useMintActionHandlers, useDerivedMintInfo, useRangeHopCallbacks } from 'state/mint/hooks'
import { FeeAmount, NonfungiblePositionManager } from '@uniswap/v3-sdk'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from 'constants/v3'
import JSBI from 'jsbi'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
......@@ -76,22 +75,6 @@ export default function AddLiquidity({
)
const hasExistingPosition = !!existingPositionDetails && !positionLoading
const { position: existingPosition } = useDerivedPositionInfo(existingPositionDetails)
const fixedValueLower =
hasExistingPosition && existingPosition
? tickToPrice(
existingPosition.pool.token0,
existingPosition.pool.token1,
existingPosition.tickLower
).toSignificant(4)
: undefined
const fixedValueUpper =
hasExistingPosition && existingPosition
? tickToPrice(
existingPosition.pool.token0,
existingPosition.pool.token1,
existingPosition.tickUpper
).toSignificant(4)
: undefined
// fee selection from url
const feeAmount: FeeAmount | undefined =
......@@ -102,6 +85,14 @@ export default function AddLiquidity({
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
// keep track for UI display purposes of user selected base currency
const [baseCurrency, setBaseCurrency] = useState(currencyA)
const quoteCurrency = useMemo(() => (baseCurrency === currencyA ? currencyB : currencyA), [
baseCurrency,
currencyA,
currencyB,
])
// mint state
const { independentField, typedValue, startPriceTypedValue } = useMintState()
......@@ -121,17 +112,24 @@ export default function AddLiquidity({
outOfRange,
depositADisabled,
depositBDisabled,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined, feeAmount, existingPosition)
invertPrice,
} = useDerivedMintInfo(
currencyA ?? undefined,
currencyB ?? undefined,
feeAmount,
baseCurrency ?? undefined,
existingPosition
)
const {
onFieldAInput,
onFieldBInput,
onLowerRangeInput,
onUpperRangeInput,
onLeftRangeInput,
onRightRangeInput,
onStartPriceInput,
} = useMintActionHandlers(noLiquidity)
const isValid = !errorMessage
const isValid = !errorMessage && !invalidRange
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
......@@ -322,90 +320,22 @@ export default function AddLiquidity({
const clearAll = useCallback(() => {
onFieldAInput('')
onFieldBInput('')
onLowerRangeInput('')
onUpperRangeInput('')
onLeftRangeInput('')
onRightRangeInput('')
history.push(`/add/`)
}, [history, onFieldAInput, onFieldBInput, onLowerRangeInput, onUpperRangeInput])
}, [history, onFieldAInput, onFieldBInput, onLeftRangeInput, onRightRangeInput])
// get value and prices at ticks
const { [Bound.LOWER]: tickLower, [Bound.UPPER]: tickUpper } = ticks
const { [Bound.LOWER]: priceLower, [Bound.UPPER]: priceUpper } = pricesAtTicks
const getLowerDecrement = () => {
if (tickLower && feeAmount && currencyA && currencyB && chainId) {
const tokenA = wrappedCurrency(currencyA, chainId)
const tokenB = wrappedCurrency(currencyB, chainId)
const tickAmount = TICK_SPACINGS[feeAmount]
const newTick = tickLower - tickAmount
const newPrice = tokenA && tokenB ? tickToPrice(tokenA, tokenB, newTick) : undefined
return newPrice ? newPrice.toFixed(10) : ''
} else {
return ''
}
}
const getLowerIncrement = () => {
if (tickLower && feeAmount && currencyA && currencyB && chainId) {
const tokenA = wrappedCurrency(currencyA, chainId)
const tokenB = wrappedCurrency(currencyB, chainId)
const tickAmount = TICK_SPACINGS[feeAmount]
const newTick = tickLower + tickAmount
const newPrice = tokenA && tokenB ? tickToPrice(tokenA, tokenB, newTick) : undefined
return newPrice ? newPrice.toFixed(10) : ''
} else {
return ''
}
}
const getUpperDecrement = () => {
if (tickUpper && feeAmount && currencyA && currencyB && chainId) {
const tokenA = wrappedCurrency(currencyA, chainId)
const tokenB = wrappedCurrency(currencyB, chainId)
const tickAmount = TICK_SPACINGS[feeAmount]
const newTick = tickUpper - tickAmount
const newPrice = tokenA && tokenB ? tickToPrice(tokenA, tokenB, newTick) : undefined
return newPrice ? newPrice.toFixed(10) : ''
} else {
return ''
}
}
const getUpperIncrement = () => {
if (tickUpper && feeAmount && currencyA && currencyB && chainId) {
const tokenA = wrappedCurrency(currencyA, chainId)
const tokenB = wrappedCurrency(currencyB, chainId)
const tickAmount = TICK_SPACINGS[feeAmount]
const newTick = tickUpper + tickAmount
const newPrice = tokenA && tokenB ? tickToPrice(tokenA, tokenB, newTick) : undefined
return newPrice ? newPrice.toFixed(10) : ''
} else {
return ''
}
}
const handleRateToggle = useCallback(() => {
if (currencyA && currencyB) {
const currencyIdA = currencyId(currencyA)
const currencyIdB = currencyId(currencyB)
// reset inputs
onLowerRangeInput('')
onUpperRangeInput('')
onStartPriceInput('')
onFieldAInput('')
onFieldBInput('')
history.push(`/add/${currencyIdB}/${currencyIdA}/${feeAmount ?? ''}`)
}
}, [
currencyA,
currencyB,
const { getDecrementLower, getIncrementLower, getDecrementUpper, getIncrementUpper } = useRangeHopCallbacks(
baseCurrency ?? undefined,
quoteCurrency ?? undefined,
feeAmount,
history,
onFieldAInput,
onFieldBInput,
onLowerRangeInput,
onStartPriceInput,
onUpperRangeInput,
])
tickLower,
tickUpper
)
return (
<ScrollablePage>
......@@ -523,11 +453,15 @@ export default function AddLiquidity({
</BlueCard>
<RowBetween>
<TYPE.label>{t('selectStartingPrice')}</TYPE.label>
{currencyA && currencyB ? (
{baseCurrency && quoteCurrency ? (
<RateToggle
currencyA={currencyA}
currencyB={currencyB}
handleRateToggle={handleRateToggle}
currencyA={baseCurrency}
currencyB={quoteCurrency}
handleRateToggle={() => {
onLeftRangeInput('')
onRightRangeInput('')
setBaseCurrency(quoteCurrency)
}}
/>
) : null}
</RowBetween>
......@@ -543,7 +477,9 @@ export default function AddLiquidity({
<TYPE.main>Starting Price</TYPE.main>
{price ? (
<TYPE.main>
1 {currencyA?.symbol} = {price?.toSignificant(8)} {currencyB?.symbol}
1 {currencyA?.symbol} ={' '}
{invertPrice ? price?.invert()?.toSignificant(8) : price?.toSignificant(8)}{' '}
{currencyB?.symbol}
</TYPE.main>
) : (
'-'
......@@ -559,16 +495,25 @@ export default function AddLiquidity({
>
<RowBetween>
<TYPE.label>{t('selectLiquidityRange')}</TYPE.label>
{currencyA && currencyB && !noLiquidity && (
<RateToggle currencyA={currencyA} currencyB={currencyB} handleRateToggle={handleRateToggle} />
)}
{baseCurrency && quoteCurrency ? (
<RateToggle
currencyA={baseCurrency}
currencyB={quoteCurrency}
handleRateToggle={() => {
onLeftRangeInput('')
onRightRangeInput('')
setBaseCurrency(quoteCurrency)
}}
/>
) : null}
</RowBetween>
{price && currencyA && !noLiquidity && (
{price && baseCurrency && quoteCurrency && !noLiquidity && (
<RowBetween style={{ backgroundColor: theme.bg6, padding: '12px', borderRadius: '12px' }}>
<TYPE.main>{t('currentRate', { label: currencyA.symbol })}</TYPE.main>
<TYPE.main>Current Price</TYPE.main>
<TYPE.main>
{price.toSignificant(3)} {currencyB?.symbol}
{invertPrice ? price.invert().toSignificant(3) : price.toSignificant(3)}{' '}
{quoteCurrency?.symbol} = 1 {baseCurrency.symbol}
</TYPE.main>
</RowBetween>
)}
......@@ -576,17 +521,15 @@ export default function AddLiquidity({
<RangeSelector
priceLower={priceLower}
priceUpper={priceUpper}
getLowerDecrement={getLowerDecrement}
getLowerIncrement={getLowerIncrement}
getUpperDecrement={getUpperDecrement}
getUpperIncrement={getUpperIncrement}
onLowerRangeInput={onLowerRangeInput}
onUpperRangeInput={onUpperRangeInput}
currencyA={currencyA}
currencyB={currencyB}
getDecrementLower={getDecrementLower}
getIncrementLower={getIncrementLower}
getDecrementUpper={getDecrementUpper}
getIncrementUpper={getIncrementUpper}
onLeftRangeInput={onLeftRangeInput}
onRightRangeInput={onRightRangeInput}
currencyA={baseCurrency}
currencyB={quoteCurrency}
feeAmount={feeAmount}
fixedValueLower={fixedValueLower}
fixedValueUpper={fixedValueUpper}
/>
{outOfRange ? (
......@@ -634,9 +577,11 @@ export default function AddLiquidity({
showCommonBases
locked={depositADisabled}
/>
<ColumnCenter>
<Link2 stroke={theme.text2} size={'24px'} />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
disableCurrencySelect={true}
......
......@@ -29,10 +29,10 @@ import { useUserSlippageTolerance } from 'state/user/hooks'
import ReactGA from 'react-ga'
import { TransactionResponse } from '@ethersproject/providers'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
import { useDerivedMintInfo, useMintActionHandlers } from 'state/mint/hooks'
import { useDerivedMintInfo, useMintActionHandlers, useRangeHopCallbacks } from 'state/mint/hooks'
import { Bound } from 'state/mint/actions'
import { useTranslation } from 'react-i18next'
import { ChevronDown } from 'react-feather'
import { AlertTriangle, ChevronDown } from 'react-feather'
import FeeSelector from 'components/FeeSelector'
import RangeSelector from 'components/RangeSelector'
import RateToggle from 'components/RateToggle'
......@@ -42,6 +42,7 @@ import { splitSignature } from '@ethersproject/bytes'
import { BigNumber } from '@ethersproject/bignumber'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import { formatTokenAmount } from 'utils/formatTokenAmount'
import useTheme from 'hooks/useTheme'
const ZERO = JSBI.BigInt(0)
......@@ -104,6 +105,7 @@ function V2PairMigration({
}) {
const { t } = useTranslation()
const { chainId, account, library } = useActiveWeb3React()
const theme = useTheme()
const deadline = useTransactionDeadline() // custom from users settings
const blockTimestamp = useCurrentBlockTimestamp()
......@@ -141,31 +143,39 @@ function V2PairMigration({
const largePriceDifference = priceDifferenceFraction && !priceDifferenceFraction?.lessThan(JSBI.BigInt(4))
const [invertPrice, setInvertPrice] = useState(false)
// the following is a small hack to get access to price range data/input handlers
const { ticks, pricesAtTicks } = useDerivedMintInfo(
invertPrice ? token1 : token0,
invertPrice ? token0 : token1,
feeAmount
const [baseToken, setBaseToken] = useState(token0)
const { ticks, pricesAtTicks, invertPrice, invalidRange, outOfRange } = useDerivedMintInfo(
token0,
token1,
feeAmount,
baseToken
)
// get value and prices at ticks
const { [Bound.LOWER]: tickLower, [Bound.UPPER]: tickUpper } = ticks
const { [Bound.LOWER]: priceLower, [Bound.UPPER]: priceUpper } = pricesAtTicks
const { onLowerRangeInput, onUpperRangeInput } = useMintActionHandlers(noLiquidity)
const { getDecrementLower, getIncrementLower, getDecrementUpper, getIncrementUpper } = useRangeHopCallbacks(
baseToken,
baseToken.equals(token0) ? token1 : token0,
feeAmount,
tickLower,
tickUpper
)
const { onLeftRangeInput, onRightRangeInput } = useMintActionHandlers(noLiquidity)
// the v3 tick is either the pool's tickCurrent, or the tick closest to the v2 spot price
const tick = pool?.tickCurrent ?? priceToClosestTick(v2SpotPrice)
// the price is either the current v3 price, or the price at the tick
const sqrtPrice = pool?.sqrtRatioX96 ?? TickMath.getSqrtRatioAtTick(tick)
const position =
typeof tickLower === 'number' && typeof tickUpper === 'number'
typeof tickLower === 'number' && typeof tickUpper === 'number' && !invalidRange
? Position.fromAmounts({
pool: pool ?? new Pool(token0, token1, feeAmount, sqrtPrice, 0, tick, []),
tickLower: invertPrice ? tickUpper : tickLower,
tickUpper: invertPrice ? tickLower : tickUpper,
tickLower,
tickUpper,
amount0: token0Value.raw,
amount1: token1Value.raw,
})
......@@ -340,8 +350,8 @@ function V2PairMigration({
token0: token0.address,
token1: token1.address,
fee: feeAmount,
tickLower: invertPrice ? tickUpper : tickLower,
tickUpper: invertPrice ? tickLower : tickUpper,
tickLower,
tickUpper,
amount0Min: `0x${v3Amount0Min.raw.toString(16)}`,
amount1Min: `0x${v3Amount1Min.raw.toString(16)}`,
recipient: account,
......@@ -379,7 +389,6 @@ function V2PairMigration({
token1,
feeAmount,
pairBalance,
invertPrice,
tickLower,
tickUpper,
sqrtPrice,
......@@ -475,9 +484,9 @@ function V2PairMigration({
currencyA={invertPrice ? token1 : token0}
currencyB={invertPrice ? token0 : token1}
handleRateToggle={() => {
onLowerRangeInput('')
onUpperRangeInput('')
setInvertPrice((invertPrice) => !invertPrice)
onLeftRangeInput('')
onRightRangeInput('')
setBaseToken((base) => (base.equals(token0) ? token1 : token0))
}}
/>
</RowBetween>
......@@ -485,12 +494,39 @@ function V2PairMigration({
<RangeSelector
priceLower={priceLower}
priceUpper={priceUpper}
onLowerRangeInput={onLowerRangeInput}
onUpperRangeInput={onUpperRangeInput}
getDecrementLower={getDecrementLower}
getIncrementLower={getIncrementLower}
getDecrementUpper={getDecrementUpper}
getIncrementUpper={getIncrementUpper}
onLeftRangeInput={onLeftRangeInput}
onRightRangeInput={onRightRangeInput}
currencyA={invertPrice ? token1 : token0}
currencyB={invertPrice ? token0 : token1}
feeAmount={feeAmount}
/>
{outOfRange ? (
<YellowCard padding="8px 12px" borderRadius="12px">
<RowBetween>
<AlertTriangle stroke={theme.yellow3} size="16px" />
<TYPE.yellow ml="12px" fontSize="12px">
{t('inactiveRangeWarning')}
</TYPE.yellow>
</RowBetween>
</YellowCard>
) : null}
{invalidRange ? (
<YellowCard padding="8px 12px" borderRadius="12px">
<RowBetween>
<AlertTriangle stroke={theme.yellow3} size="16px" />
<TYPE.yellow ml="12px" fontSize="12px">
{t('invalidRangeWarning')}
</TYPE.yellow>
</RowBetween>
</YellowCard>
) : null}
<LightCard>
{v3Amount0Min && v3Amount1Min ? (
<LiquidityInfo token0Amount={v3Amount0Min} token1Amount={v3Amount1Min} />
......@@ -506,6 +542,7 @@ function V2PairMigration({
signatureData !== null ||
!v3Amount0Min ||
!v3Amount1Min ||
invalidRange ||
confirmingMigration
}
onClick={approve}
......@@ -526,6 +563,7 @@ function V2PairMigration({
disabled={
!v3Amount0Min ||
!v3Amount1Min ||
invalidRange ||
(approval !== ApprovalState.APPROVED && signatureData === null) ||
confirmingMigration ||
isMigrationPending ||
......
......@@ -122,10 +122,10 @@ export function PositionPage({
return undefined
}, [liquidity, pool, tickLower, tickUpper])
const price0Lower = position ? position.token0PriceLower : undefined
const price0Upper = position ? position.token0PriceUpper : undefined
const price1Lower = price0Upper ? price0Upper.invert() : undefined
const price1Upper = price0Lower ? price0Lower.invert() : undefined
const price0Lower = position?.token0PriceLower
const price0Upper = position?.token0PriceUpper
const price1Lower = price0Lower?.invert()
const price1Upper = price0Upper?.invert()
// check if price is within range
const outOfRange: boolean =
......@@ -322,13 +322,13 @@ export function PositionPage({
<RowFixed>
<TYPE.label>{price0Lower?.toSignificant(4)}</TYPE.label>
<TYPE.label ml="10px">
{currency0?.symbol} / {currency1?.symbol}
{currency1?.symbol} / {currency0?.symbol}
</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label>{price1Lower?.toSignificant(4)}</TYPE.label>
<TYPE.label ml="10px">
{currency1?.symbol} / {currency0?.symbol}
{currency0?.symbol} / {currency1?.symbol}
</TYPE.label>
</RowFixed>
<DarkBadge>
......@@ -342,17 +342,17 @@ export function PositionPage({
</DarkGreyCard>
<DarkGreyCard width="49%">
<AutoColumn gap="sm" justify="flex-start">
<TYPE.main>Lower Limit</TYPE.main>
<TYPE.main>Upper Limit</TYPE.main>
<RowFixed>
<TYPE.label>{price0Upper?.toSignificant(4)}</TYPE.label>
<TYPE.label ml="10px">
{currency0?.symbol} / {currency1?.symbol}
{currency1?.symbol} / {currency0?.symbol}
</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label>{price1Upper?.toSignificant(4)}</TYPE.label>
<TYPE.label ml="10px">
{currency1?.symbol} / {currency0?.symbol}
{currency0?.symbol} / {currency1?.symbol}
</TYPE.label>
</RowFixed>
<DarkBadge>
......
......@@ -17,7 +17,7 @@ export enum RangeType {
}
export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('mint/typeInputMint')
export const typeLowerRangeInput = createAction<{ typedValue: string }>('mint/typeLowerRangeInput')
export const typeUpperRangeInput = createAction<{ typedValue: string }>('mint/typeUpperRangeInput')
export const typeStartPriceInput = createAction<{ typedValue: string }>('mint/typeStartPriceInput')
export const typeLeftRangeInput = createAction<{ typedValue: string }>('mint/typeLeftRangeInput')
export const typeRightRangeInput = createAction<{ typedValue: string }>('mint/typeRightRangeInput')
export const resetMintState = createAction<void>('mint/resetMintState')
......@@ -2,8 +2,16 @@ import { BIG_INT_ZERO } from './../../constants/index'
import { getTickToPrice } from 'utils/getTickToPrice'
import JSBI from 'jsbi'
import { PoolState } from '../../hooks/usePools'
import { Pool, FeeAmount, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk/dist/'
import { Currency, CurrencyAmount, ETHER, Price } from '@uniswap/sdk-core'
import {
Pool,
FeeAmount,
Position,
priceToClosestTick,
TickMath,
tickToPrice,
TICK_SPACINGS,
} from '@uniswap/v3-sdk/dist/'
import { Currency, CurrencyAmount, ETHER, Price, Rounding } from '@uniswap/sdk-core'
import { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useActiveWeb3React } from '../../hooks'
......@@ -11,7 +19,7 @@ import { wrappedCurrency, wrappedCurrencyAmount } from '../../utils/wrappedCurre
import { AppDispatch, AppState } from '../index'
import { tryParseAmount } from '../swap/hooks'
import { useCurrencyBalances } from '../wallet/hooks'
import { Field, Bound, typeInput, typeLowerRangeInput, typeUpperRangeInput, typeStartPriceInput } from './actions'
import { Field, Bound, typeInput, typeStartPriceInput, typeLeftRangeInput, typeRightRangeInput } from './actions'
import { tryParseTick } from './utils'
import { usePool } from 'hooks/usePools'
......@@ -24,8 +32,8 @@ export function useMintActionHandlers(
): {
onFieldAInput: (typedValue: string) => void
onFieldBInput: (typedValue: string) => void
onLowerRangeInput: (typedValue: string) => void
onUpperRangeInput: (typedValue: string) => void
onLeftRangeInput: (typedValue: string) => void
onRightRangeInput: (typedValue: string) => void
onStartPriceInput: (typedValue: string) => void
} {
const dispatch = useDispatch<AppDispatch>()
......@@ -44,16 +52,16 @@ export function useMintActionHandlers(
[dispatch, noLiquidity]
)
const onLowerRangeInput = useCallback(
const onLeftRangeInput = useCallback(
(typedValue: string) => {
dispatch(typeLowerRangeInput({ typedValue }))
dispatch(typeLeftRangeInput({ typedValue }))
},
[dispatch]
)
const onUpperRangeInput = useCallback(
const onRightRangeInput = useCallback(
(typedValue: string) => {
dispatch(typeUpperRangeInput({ typedValue }))
dispatch(typeRightRangeInput({ typedValue }))
},
[dispatch]
)
......@@ -68,18 +76,19 @@ export function useMintActionHandlers(
return {
onFieldAInput,
onFieldBInput,
onLowerRangeInput,
onUpperRangeInput,
onLeftRangeInput,
onRightRangeInput,
onStartPriceInput,
}
}
export function useDerivedMintInfo(
currencyA: Currency | undefined,
currencyB: Currency | undefined,
feeAmount: FeeAmount | undefined,
currencyA?: Currency,
currencyB?: Currency,
feeAmount?: FeeAmount,
baseCurrency?: Currency,
// override for existing position
existingPosition?: Position | undefined
existingPosition?: Position
): {
pool?: Pool | null
poolState: PoolState
......@@ -100,14 +109,15 @@ export function useDerivedMintInfo(
invalidRange: boolean
depositADisabled: boolean
depositBDisabled: boolean
invertPrice: boolean
} {
const { account, chainId } = useActiveWeb3React()
const {
independentField,
typedValue,
lowerRangeTypedValue,
upperRangeTypedValue,
leftRangeTypedValue,
rightRangeTypedValue,
startPriceTypedValue,
} = useMintState()
......@@ -116,25 +126,33 @@ export function useDerivedMintInfo(
// currencies
const currencies: { [field in Field]?: Currency } = useMemo(
() => ({
[Field.CURRENCY_A]: currencyA ?? undefined,
[Field.CURRENCY_B]: currencyB ?? undefined,
[Field.CURRENCY_A]: currencyA,
[Field.CURRENCY_B]: currencyB,
}),
[currencyA, currencyB]
)
// formatted with tokens
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
chainId,
currencyA,
currencyB,
])
const [tokenA, tokenB, baseToken] = useMemo(
() => [
wrappedCurrency(currencyA, chainId),
wrappedCurrency(currencyB, chainId),
wrappedCurrency(baseCurrency, chainId),
],
[chainId, currencyA, currencyB, baseCurrency]
)
const [token0, token1] = useMemo(
() =>
tokenA && tokenB ? (tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]) : [undefined, undefined],
[tokenA, tokenB]
)
// balances
const balances = useCurrencyBalances(account ?? undefined, [
currencies[Field.CURRENCY_A],
currencies[Field.CURRENCY_B],
])
const currencyBalances: { [field in Field]?: CurrencyAmount } = {
[Field.CURRENCY_A]: balances[0],
[Field.CURRENCY_B]: balances[1],
......@@ -144,20 +162,28 @@ export function useDerivedMintInfo(
const [poolState, pool] = usePool(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B], feeAmount)
const noLiquidity = poolState === PoolState.NOT_EXISTS
// note to parse inputs in reverse
const invertPrice = Boolean(baseToken && token0 && !baseToken.equals(token0))
// always returns the price with 0 as base token
const price = useMemo(() => {
// if no liquidity use typed value
if (noLiquidity) {
const parsedAmount = tryParseAmount(startPriceTypedValue, tokenB)
if (parsedAmount && tokenA && tokenB) {
const amountOne = tryParseAmount('1', tokenA)
return amountOne ? new Price(tokenA, tokenB, amountOne.raw, parsedAmount.raw) : undefined
const parsedQuoteAmount = tryParseAmount(startPriceTypedValue, invertPrice ? token0 : token1)
if (parsedQuoteAmount && token0 && token1) {
const baseAmount = tryParseAmount('1', invertPrice ? token1 : token0)
const price =
baseAmount && parsedQuoteAmount
? new Price(baseAmount.currency, parsedQuoteAmount.currency, baseAmount.raw, parsedQuoteAmount.raw)
: undefined
return (invertPrice ? price?.invert() : price) ?? undefined
}
return undefined
} else {
// get the amount of quote currency
return pool && tokenA ? pool.priceOf(tokenA) : undefined
return pool && token0 ? pool.priceOf(token0) : undefined
}
}, [noLiquidity, startPriceTypedValue, tokenA, tokenB, pool])
}, [noLiquidity, startPriceTypedValue, invertPrice, token1, token0, pool])
// used for ratio calculation when pool not initialized
const mockPool = useMemo(() => {
......@@ -174,40 +200,42 @@ export function useDerivedMintInfo(
const poolForPosition: Pool | undefined = pool ?? mockPool
// parse typed range values and determine closest ticks
// lower should always be a smaller tick
const ticks: {
[key: string]: number | undefined
} = useMemo(() => {
return {
[Bound.LOWER]: existingPosition
? existingPosition.tickLower
: tryParseTick(tokenA, tokenB, feeAmount, lowerRangeTypedValue),
[Bound.UPPER]: existingPosition
? existingPosition.tickUpper
: tryParseTick(tokenA, tokenB, feeAmount, upperRangeTypedValue),
[Bound.LOWER]: existingPosition?.tickLower
? existingPosition?.tickLower
: invertPrice
? tryParseTick(token1, token0, feeAmount, rightRangeTypedValue)
: tryParseTick(token0, token1, feeAmount, leftRangeTypedValue),
[Bound.UPPER]: existingPosition?.tickUpper
? existingPosition?.tickUpper
: invertPrice
? tryParseTick(token1, token0, feeAmount, leftRangeTypedValue)
: tryParseTick(token0, token1, feeAmount, rightRangeTypedValue),
}
}, [existingPosition, feeAmount, lowerRangeTypedValue, tokenA, tokenB, upperRangeTypedValue])
}, [existingPosition, feeAmount, invertPrice, leftRangeTypedValue, rightRangeTypedValue, token0, token1])
const { [Bound.LOWER]: tickLower, [Bound.UPPER]: tickUpper } = ticks || {}
const sortedTicks = useMemo(
() =>
tickLower !== undefined && tickUpper !== undefined
? tickLower < tickUpper
? [tickLower, tickUpper]
: [tickUpper, tickLower]
: undefined,
[tickLower, tickUpper]
)
// mark invalid range
const invalidRange = Boolean(typeof tickLower === 'number' && typeof tickUpper === 'number' && tickLower >= tickUpper)
// always returns the price with 0 as base token
const pricesAtTicks = useMemo(() => {
return {
[Bound.LOWER]: getTickToPrice(tokenA, tokenB, ticks[Bound.LOWER]),
[Bound.UPPER]: getTickToPrice(tokenA, tokenB, ticks[Bound.UPPER]),
[Bound.LOWER]: getTickToPrice(token0, token1, ticks[Bound.LOWER]),
[Bound.UPPER]: getTickToPrice(token0, token1, ticks[Bound.UPPER]),
}
}, [tokenA, tokenB, ticks])
}, [token0, token1, ticks])
const { [Bound.LOWER]: lowerPrice, [Bound.UPPER]: upperPrice } = pricesAtTicks
// mark invalid range
const invalidRange = Boolean(lowerPrice && upperPrice && lowerPrice.greaterThan(upperPrice))
// liquidity range warning
const outOfRange = Boolean(
!invalidRange && price && lowerPrice && upperPrice && (price.lessThan(lowerPrice) || price.greaterThan(upperPrice))
)
// amounts
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
......@@ -215,37 +243,30 @@ export function useDerivedMintInfo(
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
// we wrap the currencies just to get the price in terms of the other token
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
const dependentCurrency = dependentField === Field.CURRENCY_B ? currencyB : currencyA
if (
feeAmount &&
independentAmount &&
tokenA &&
tokenB &&
wrappedIndependentAmount &&
price &&
lowerPrice &&
upperPrice &&
sortedTicks &&
typeof tickLower === 'number' &&
typeof tickUpper === 'number' &&
poolForPosition
) {
// if price is out of range or invalid range - retun 0 (single deposit with be independent)
if (price.lessThan(lowerPrice) || price.greaterThan(upperPrice) || invalidRange) {
// if price is out of range or invalid range - return 0 (single deposit will be independent)
if (outOfRange || invalidRange) {
return undefined
}
const position: Position | undefined = wrappedIndependentAmount.token.equals(poolForPosition.token0)
? Position.fromAmount0({
pool: poolForPosition,
tickLower: sortedTicks[0],
tickUpper: sortedTicks[1],
tickLower,
tickUpper,
amount0: independentAmount.raw,
})
: Position.fromAmount1({
pool: poolForPosition,
tickLower: sortedTicks[0],
tickUpper: sortedTicks[1],
tickLower,
tickUpper,
amount1: independentAmount.raw,
})
......@@ -259,16 +280,12 @@ export function useDerivedMintInfo(
}, [
independentAmount,
chainId,
outOfRange,
dependentField,
currencyB,
currencyA,
feeAmount,
tokenA,
tokenB,
price,
sortedTicks,
lowerPrice,
upperPrice,
tickLower,
tickUpper,
poolForPosition,
invalidRange,
])
......@@ -281,22 +298,37 @@ export function useDerivedMintInfo(
}, [dependentAmount, independentAmount, independentField])
// single deposit only if price is out of range
const deposit1Disabled = Boolean(sortedTicks && poolForPosition && poolForPosition.tickCurrent <= sortedTicks[0])
const deposit0Disabled = Boolean(sortedTicks && poolForPosition && poolForPosition.tickCurrent >= sortedTicks[1])
// sorted for token order
const depositADisabled = Boolean(
(deposit0Disabled && poolForPosition && tokenA && poolForPosition.token0.equals(tokenA)) ||
(deposit1Disabled && poolForPosition && tokenA && poolForPosition.token1.equals(tokenA))
const deposit0Disabled = Boolean(
typeof tickUpper === 'number' && poolForPosition && poolForPosition.tickCurrent >= tickUpper
)
const depositBDisabled = Boolean(
(deposit0Disabled && poolForPosition && tokenB && poolForPosition.token0.equals(tokenB)) ||
(deposit1Disabled && poolForPosition && tokenB && poolForPosition.token1.equals(tokenB))
const deposit1Disabled = Boolean(
typeof tickLower === 'number' && poolForPosition && poolForPosition.tickCurrent <= tickLower
)
// sorted for token order
const depositADisabled =
invalidRange ||
Boolean(
(deposit0Disabled && poolForPosition && tokenA && poolForPosition.token0.equals(tokenA)) ||
(deposit1Disabled && poolForPosition && tokenA && poolForPosition.token1.equals(tokenA))
)
const depositBDisabled =
invalidRange ||
Boolean(
(deposit0Disabled && poolForPosition && tokenB && poolForPosition.token0.equals(tokenB)) ||
(deposit1Disabled && poolForPosition && tokenB && poolForPosition.token1.equals(tokenB))
)
// create position entity based on users selection
const position: Position | undefined = useMemo(() => {
if (!poolForPosition || !tokenA || !tokenB || !sortedTicks) {
if (
!poolForPosition ||
!tokenA ||
!tokenB ||
typeof tickLower !== 'number' ||
typeof tickUpper !== 'number' ||
invalidRange
) {
return undefined
}
......@@ -311,24 +343,25 @@ export function useDerivedMintInfo(
if (amount0 !== undefined && amount1 !== undefined) {
return Position.fromAmounts({
pool: poolForPosition,
tickLower: sortedTicks[0],
tickUpper: sortedTicks[1],
amount0: amount0,
amount1: amount1,
tickLower,
tickUpper,
amount0,
amount1,
})
} else {
return undefined
}
}, [parsedAmounts, poolForPosition, sortedTicks, tokenA, tokenB, deposit0Disabled, deposit1Disabled])
// liquiidty range warning
const outOfRange = Boolean(
price &&
lowerPrice &&
upperPrice &&
!invalidRange &&
(lowerPrice.greaterThan(price) || price.greaterThan(upperPrice))
)
}, [
parsedAmounts,
poolForPosition,
tokenA,
tokenB,
deposit0Disabled,
deposit1Disabled,
invalidRange,
tickLower,
tickUpper,
])
let errorMessage: string | undefined
if (!account) {
......@@ -376,5 +409,52 @@ export function useDerivedMintInfo(
outOfRange,
depositADisabled,
depositBDisabled,
invertPrice,
}
}
export function useRangeHopCallbacks(
baseCurrency: Currency | undefined,
quoteCurrency: Currency | undefined,
feeAmount: FeeAmount | undefined,
tickLower: number | undefined,
tickUpper: number | undefined
) {
const { chainId } = useActiveWeb3React()
const baseToken = useMemo(() => wrappedCurrency(baseCurrency, chainId), [baseCurrency, chainId])
const quoteToken = useMemo(() => wrappedCurrency(quoteCurrency, chainId), [quoteCurrency, chainId])
const getDecrementLower = useCallback(() => {
if (baseToken && quoteToken && typeof tickLower === 'number' && feeAmount) {
const newPrice = tickToPrice(baseToken, quoteToken, tickLower - TICK_SPACINGS[feeAmount])
return newPrice.toSignificant(5, undefined, Rounding.ROUND_UP)
}
return ''
}, [baseToken, quoteToken, tickLower, feeAmount])
const getIncrementLower = useCallback(() => {
if (baseToken && quoteToken && typeof tickLower === 'number' && feeAmount) {
const newPrice = tickToPrice(baseToken, quoteToken, tickLower + TICK_SPACINGS[feeAmount])
return newPrice.toSignificant(5, undefined, Rounding.ROUND_UP)
}
return ''
}, [baseToken, quoteToken, tickLower, feeAmount])
const getDecrementUpper = useCallback(() => {
if (baseToken && quoteToken && typeof tickUpper === 'number' && feeAmount) {
const newPrice = tickToPrice(baseToken, quoteToken, tickUpper - TICK_SPACINGS[feeAmount])
return newPrice.toSignificant(5, undefined, Rounding.ROUND_UP)
}
return ''
}, [baseToken, quoteToken, tickUpper, feeAmount])
const getIncrementUpper = useCallback(() => {
if (baseToken && quoteToken && typeof tickUpper === 'number' && feeAmount) {
const newPrice = tickToPrice(baseToken, quoteToken, tickUpper + TICK_SPACINGS[feeAmount])
return newPrice.toSignificant(5, undefined, Rounding.ROUND_UP)
}
return ''
}, [baseToken, quoteToken, tickUpper, feeAmount])
return { getDecrementLower, getIncrementLower, getDecrementUpper, getIncrementUpper }
}
......@@ -3,9 +3,9 @@ import {
Field,
resetMintState,
typeInput,
typeLowerRangeInput,
typeUpperRangeInput,
typeStartPriceInput,
typeLeftRangeInput,
typeRightRangeInput,
} from './actions'
export interface MintState {
......@@ -13,8 +13,8 @@ export interface MintState {
readonly typedValue: string
readonly otherTypedValue: string // for the case when there's no liquidity
readonly startPriceTypedValue: string // for the case when there's no liquidity
readonly lowerRangeTypedValue: string
readonly upperRangeTypedValue: string
readonly leftRangeTypedValue: string
readonly rightRangeTypedValue: string
}
export const initialState: MintState = {
......@@ -22,29 +22,29 @@ export const initialState: MintState = {
typedValue: '',
otherTypedValue: '',
startPriceTypedValue: '',
lowerRangeTypedValue: '',
upperRangeTypedValue: '',
leftRangeTypedValue: '',
rightRangeTypedValue: '',
}
export default createReducer<MintState>(initialState, (builder) =>
builder
.addCase(resetMintState, () => initialState)
.addCase(typeLowerRangeInput, (state, { payload: { typedValue } }) => {
.addCase(typeStartPriceInput, (state, { payload: { typedValue } }) => {
return {
...state,
lowerRangeTypedValue: typedValue,
startPriceTypedValue: typedValue,
}
})
.addCase(typeUpperRangeInput, (state, { payload: { typedValue } }) => {
.addCase(typeLeftRangeInput, (state, { payload: { typedValue } }) => {
return {
...state,
upperRangeTypedValue: typedValue,
leftRangeTypedValue: typedValue,
}
})
.addCase(typeStartPriceInput, (state, { payload: { typedValue } }) => {
.addCase(typeRightRangeInput, (state, { payload: { typedValue } }) => {
return {
...state,
startPriceTypedValue: typedValue,
rightRangeTypedValue: typedValue,
}
})
.addCase(typeInput, (state, { payload: { field, typedValue, noLiquidity } }) => {
......
......@@ -18,9 +18,10 @@ export function tryParseTick(
if (!amount || !amountOne) return undefined
// parse the typed value into a price, token0 should always be base currency based on url
// parse the typed value into a price
const price = new Price(baseToken, quoteToken, amountOne.raw, amount.raw)
// this function is agnostic to the base, will always return the correct tick
const tick = priceToClosestTick(price)
return nearestUsableTick(tick, TICK_SPACINGS[feeAmount])
......
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