fix: update max slippage state (#3268)

* fix: max slippage state

* chore: rename to useAllowedSlippage

* nit: maxSlippageInput name
This commit is contained in:
Zach Pomerantz 2022-02-09 15:52:14 -08:00 committed by GitHub
parent fad55b8dbc
commit da36e638c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 44 deletions

@ -4,9 +4,9 @@ import { useAtom } from 'jotai'
import Popover from 'lib/components/Popover'
import { TooltipHandlers, useTooltip } from 'lib/components/Tooltip'
import { AlertTriangle, Check, Icon, LargeIcon, XOctagon } from 'lib/icons'
import { MAX_VALID_SLIPPAGE, maxSlippageAtom, MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
import { autoSlippageAtom, MAX_VALID_SLIPPAGE, maxSlippageAtom, MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
import styled, { Color, ThemedText } from 'lib/theme'
import { memo, PropsWithChildren, ReactNode, useCallback, useEffect, useRef, useState } from 'react'
import { memo, PropsWithChildren, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { BaseButton, TextButton } from '../../Button'
import Column from '../../Column'
@ -98,61 +98,69 @@ const Warning = memo(function Warning({ state, showTooltip }: { state: WarningSt
})
export default function MaxSlippageSelect() {
const [maxSlippage, setMaxSlippage] = useAtom(maxSlippageAtom)
const [custom, setCustom] = useState('')
const input = useRef<HTMLInputElement>(null)
const focus = useCallback(() => input.current?.focus(), [input])
const [autoSlippage, setAutoSlippage] = useAtom(autoSlippageAtom)
const [maxSlippage, setMaxSlippage] = useAtom(maxSlippageAtom)
const maxSlippageInput = useMemo(() => maxSlippage?.toString() || '', [maxSlippage])
const [warning, setWarning] = useState(WarningState.NONE)
const [showTooltip, setShowTooltip, tooltipProps] = useTooltip()
useEffect(() => setShowTooltip(true), [warning, setShowTooltip]) // enables the tooltip if a warning is set
const processInput = useCallback(() => {
const numerator = Math.floor(+custom * 100)
if (numerator) {
const percent = new Percent(numerator, 10_000)
if (percent.greaterThan(MAX_VALID_SLIPPAGE)) {
setWarning(WarningState.INVALID_SLIPPAGE)
setMaxSlippage('auto')
} else if (percent.greaterThan(MIN_HIGH_SLIPPAGE)) {
setWarning(WarningState.HIGH_SLIPPAGE)
setMaxSlippage(percent)
const processInput = useCallback(
(input: number | undefined) => {
const numerator = input && Math.floor(input * 100)
if (numerator) {
const percent = new Percent(numerator, 10_000)
if (percent.greaterThan(MAX_VALID_SLIPPAGE)) {
setWarning(WarningState.INVALID_SLIPPAGE)
setAutoSlippage(true)
setMaxSlippage(input)
} else if (percent.greaterThan(MIN_HIGH_SLIPPAGE)) {
setWarning(WarningState.HIGH_SLIPPAGE)
setAutoSlippage(false)
setMaxSlippage(input)
} else {
setWarning(WarningState.NONE)
setAutoSlippage(false)
setMaxSlippage(input)
}
} else {
setWarning(WarningState.NONE)
setMaxSlippage(percent)
setAutoSlippage(true)
setMaxSlippage(undefined)
}
} else {
setMaxSlippage('auto')
}
}, [custom, setMaxSlippage])
useEffect(processInput, [processInput])
},
[setAutoSlippage, setMaxSlippage]
)
const onInputSelect = useCallback(() => {
focus()
processInput()
}, [focus, processInput])
processInput(maxSlippage)
}, [focus, maxSlippage, processInput])
useEffect(() => processInput(maxSlippage), [maxSlippage, processInput]) // processes any warnings on mount
useEffect(() => setShowTooltip(true), [warning, setShowTooltip]) // enables the tooltip if a warning is set
return (
<Column gap={0.75}>
<Label name={<Trans>Max slippage</Trans>} tooltip={tooltip} />
<Row gap={0.5} grow="last">
<Option wrapper={Button} selected={maxSlippage === 'auto'} onSelect={() => setMaxSlippage('auto')}>
<Option wrapper={Button} selected={autoSlippage} onSelect={() => setAutoSlippage(true)}>
<ThemedText.ButtonMedium>
<Trans>Auto</Trans>
</ThemedText.ButtonMedium>
</Option>
<Option
wrapper={Custom}
selected={maxSlippage !== 'auto'}
selected={!autoSlippage}
onSelect={onInputSelect}
icon={<Warning state={warning} showTooltip={showTooltip} />}
{...tooltipProps}
>
<Row color={warning === WarningState.INVALID_SLIPPAGE ? 'error' : undefined}>
<DecimalInput
size={Math.max(custom.length, 3)}
value={custom}
onChange={setCustom}
size={Math.max(maxSlippageInput.length, 3)}
value={maxSlippageInput}
onChange={(input) => processInput(+input)}
placeholder={placeholder}
ref={input}
/>

@ -1,11 +1,9 @@
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { FeeOptions } from '@uniswap/v3-sdk'
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import { atom } from 'jotai'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
import { maxSlippageAtom } from 'lib/state/settings'
import { feeOptionsAtom, Field, swapAtom } from 'lib/state/swap'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { ReactNode, useEffect, useMemo } from 'react'
@ -13,6 +11,7 @@ import { InterfaceTrade, TradeState } from 'state/routing/types'
import { isAddress } from '../../../utils'
import useActiveWeb3React from '../useActiveWeb3React'
import useAllowedSlippage from '../useAllowedSlippage'
import { useBestTrade } from './useBestTrade'
interface SwapInfo {
@ -90,16 +89,7 @@ function useComputeSwapInfo(): SwapInfo {
[trade.trade?.inputAmount, trade.trade?.outputAmount]
)
/*
* If user has enabled 'auto' slippage, use the default best slippage calculated
* based on the trade. If user has entered custom slippage, use that instead.
*/
const autoSlippageTolerance = useAutoSlippageTolerance(trade.trade)
const maxSlippage = useAtomValue(maxSlippageAtom)
const allowedSlippage = useMemo(
() => (maxSlippage === 'auto' ? autoSlippageTolerance : maxSlippage),
[autoSlippageTolerance, maxSlippage]
)
const allowedSlippage = useAllowedSlippage(trade.trade)
const inputError = useMemo(() => {
let inputError: ReactNode | undefined

@ -0,0 +1,18 @@
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import { useAtomValue } from 'jotai/utils'
import { autoSlippageAtom, maxSlippageAtom } from 'lib/state/settings'
import { InterfaceTrade } from 'state/routing/types'
export function toPercent(maxSlippage: number | undefined): Percent | undefined {
if (!maxSlippage) return undefined
const numerator = Math.floor(maxSlippage * 100)
return new Percent(numerator, 10_000)
}
/** Returns the user-inputted max slippage. */
export default function useMaxSlippage(trade: InterfaceTrade<Currency, Currency, TradeType> | undefined): Percent {
const autoSlippage = useAutoSlippageTolerance(trade)
const maxSlippage = toPercent(useAtomValue(maxSlippageAtom))
return useAtomValue(autoSlippageAtom) ? autoSlippage : maxSlippage ?? autoSlippage
}

@ -7,20 +7,23 @@ export const MAX_VALID_SLIPPAGE = new Percent(1, 2)
export const MIN_HIGH_SLIPPAGE = new Percent(1, 100)
interface Settings {
maxSlippage: Percent | 'auto' // auto will cause slippage to resort to default calculation
autoSlippage: boolean // if true, slippage will use the default calculation
maxSlippage: number | undefined // expressed as a percent
transactionTtl: number | undefined
mockTogglable: boolean
clientSideRouter: boolean // whether to use the client-side router or query the remote API
}
const initialSettings: Settings = {
maxSlippage: 'auto',
autoSlippage: true,
maxSlippage: undefined,
transactionTtl: undefined,
mockTogglable: true,
clientSideRouter: false,
}
export const settingsAtom = atomWithReset(initialSettings)
export const autoSlippageAtom = pickAtom(settingsAtom, 'autoSlippage')
export const maxSlippageAtom = pickAtom(settingsAtom, 'maxSlippage')
export const transactionTtlAtom = pickAtom(settingsAtom, 'transactionTtl')
export const mockTogglableAtom = pickAtom(settingsAtom, 'mockTogglable', setTogglable)