diff --git a/src/hooks/useSwapSlippageTolerance.ts b/src/hooks/useAutoSlippageTolerance.ts similarity index 92% rename from src/hooks/useSwapSlippageTolerance.ts rename to src/hooks/useAutoSlippageTolerance.ts index 7b75c75085..07696d8b72 100644 --- a/src/hooks/useSwapSlippageTolerance.ts +++ b/src/hooks/useAutoSlippageTolerance.ts @@ -8,7 +8,6 @@ import useNativeCurrency from 'lib/hooks/useNativeCurrency' import { useMemo } from 'react' import { InterfaceTrade } from 'state/routing/types' -import { useUserSlippageToleranceWithDefault } from '../state/user/hooks' import useGasPrice from './useGasPrice' import useUSDCPrice, { useUSDCValue } from './useUSDCPrice' @@ -29,7 +28,10 @@ function guesstimateGas(trade: Trade | undefined) const MIN_AUTO_SLIPPAGE_TOLERANCE = new Percent(5, 1000) // 0.5% const MAX_AUTO_SLIPPAGE_TOLERANCE = new Percent(25, 100) // 25% -export default function useSwapSlippageTolerance( +/** + * Returns slippage tolerance based on values from current trade, gas estimates from api, and active network. + */ +export default function useAutoSlippageTolerance( trade: InterfaceTrade | undefined ): Percent { const { chainId } = useActiveWeb3React() @@ -41,7 +43,7 @@ export default function useSwapSlippageTolerance( const nativeCurrency = useNativeCurrency() const nativeCurrencyPrice = useUSDCPrice(nativeCurrency ?? undefined) - const defaultSlippageTolerance = useMemo(() => { + return useMemo(() => { if (!trade || onL2) return ONE_TENTHS_PERCENT const nativeGasCost = @@ -73,6 +75,4 @@ export default function useSwapSlippageTolerance( return V3_SWAP_DEFAULT_SLIPPAGE }, [trade, onL2, nativeGasPrice, gasEstimate, nativeCurrency, nativeCurrencyPrice, chainId, outputDollarValue]) - - return useUserSlippageToleranceWithDefault(defaultSlippageTolerance) } diff --git a/src/lib/components/Swap/Settings/MaxSlippageSelect.tsx b/src/lib/components/Swap/Settings/MaxSlippageSelect.tsx index 8d7b004103..004c1bf7ff 100644 --- a/src/lib/components/Swap/Settings/MaxSlippageSelect.tsx +++ b/src/lib/components/Swap/Settings/MaxSlippageSelect.tsx @@ -1,7 +1,8 @@ import { t, Trans } from '@lingui/macro' +import { Percent } from '@uniswap/sdk-core' import { useAtom } from 'jotai' import { Check, LargeIcon } from 'lib/icons' -import { MaxSlippage, maxSlippageAtom } from 'lib/state/settings' +import { maxSlippageAtom } from 'lib/state/settings' import styled, { ThemedText } from 'lib/theme' import { ReactNode, useCallback, useRef } from 'react' @@ -54,36 +55,31 @@ function InputOption({ value, children, selected, onSelect }: OptionProps } export default function MaxSlippageSelect() { - const { P01, P05, CUSTOM } = MaxSlippage - const [{ value: maxSlippage, custom }, setMaxSlippage] = useAtom(maxSlippageAtom) + const [maxSlippage, setMaxSlippage] = useAtom(maxSlippageAtom) const input = useRef(null) const focus = useCallback(() => input.current?.focus(), [input]) + + //@TODO(ianlapham): hook up inputs to either set custom slippage or update to auto + //@TODO(ianlapham): update UI to match designs in spec + const onInputSelect = useCallback( - (custom) => { + (custom: Percent | 'auto') => { focus() if (custom !== undefined) { - setMaxSlippage({ value: CUSTOM, custom }) + setMaxSlippage(custom) } }, - [CUSTOM, focus, setMaxSlippage] + [focus, setMaxSlippage] ) return ( diff --git a/src/lib/hooks/swap/useSwapInfo.tsx b/src/lib/hooks/swap/useSwapInfo.tsx index bebe1f33f9..90f7515bba 100644 --- a/src/lib/hooks/swap/useSwapInfo.tsx +++ b/src/lib/hooks/swap/useSwapInfo.tsx @@ -1,9 +1,11 @@ import { Trans } from '@lingui/macro' import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' +import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance' import { useClientSideV3Trade } from 'hooks/useClientSideV3Trade' import { atom } from 'jotai' import { useAtomValue, useUpdateAtom } from 'jotai/utils' import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance' +import { maxSlippageAtom } from 'lib/state/settings' import { Field, swapAtom } from 'lib/state/swap' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import { ReactNode, useEffect, useMemo } from 'react' @@ -86,9 +88,16 @@ function useComputeSwapInfo(): SwapInfo { [trade.trade?.inputAmount, trade.trade?.outputAmount] ) - // TODO(ianlapham): Fix swap slippage tolerance - // const allowedSlippage = useSwapSlippageTolerance(trade.trade ?? undefined) - const allowedSlippage = useMemo(() => new Percent(100), []) + /* + * 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 inputError = useMemo(() => { let inputError: ReactNode | undefined @@ -154,7 +163,7 @@ export function SwapInfoUpdater() { return null } -/** Requires that SwapInfoUpdater be installed in the DOM tree. */ +/** Requires that SwapInfoUpdater be installed in the DOM tree. **/ export default function useSwapInfo(): SwapInfo { return useAtomValue(swapInfoAtom) } diff --git a/src/lib/state/atoms.ts b/src/lib/state/atoms.ts index 14f666b16f..da9b26e432 100644 --- a/src/lib/state/atoms.ts +++ b/src/lib/state/atoms.ts @@ -34,43 +34,6 @@ export function pickAtom, Up ) } -/** - * Typing for a customizable enum; see setCustomizable. - * This is not exported because an enum may not extend another interface. - */ -interface CustomizableEnum { - CUSTOM: -1 - DEFAULT: T -} - -/** - * Typing for a customizable enum; see setCustomizable. - * The first value is used, unless it is CUSTOM, in which case the second is used. - */ -export type Customizable = { value: T; custom?: number } - -/** Sets a customizable enum, validating the tuple and falling back to the default. */ -export function setCustomizable>(customizable: Enum) { - return (draft: Customizable, update: T | Customizable) => { - // normalize the update - if (typeof update === 'number') { - update = { value: update } - } - - draft.value = update.value - if (draft.value === customizable.CUSTOM) { - draft.custom = update.custom - - // prevent invalid state - if (draft.custom === undefined) { - draft.value = customizable.DEFAULT - } - } - - return draft - } -} - /** Sets a togglable atom to invert its state at the next render. */ export function setTogglable(draft: boolean) { return !draft diff --git a/src/lib/state/settings.ts b/src/lib/state/settings.ts index c098bf31c0..dff634a9d3 100644 --- a/src/lib/state/settings.ts +++ b/src/lib/state/settings.ts @@ -1,34 +1,26 @@ +import { Percent } from '@uniswap/sdk-core' import { atomWithReset } from 'jotai/utils' -import { Customizable, pickAtom, setCustomizable, setTogglable } from './atoms' - -/** Max slippage, as a percentage. */ -export enum MaxSlippage { - P01 = 0.1, - P05 = 0.5, - // Members to satisfy CustomizableEnum; see setCustomizable - CUSTOM = -1, - DEFAULT = P05, -} +import { pickAtom, setTogglable } from './atoms' export const TRANSACTION_TTL_DEFAULT = 40 interface Settings { - maxSlippage: Customizable + maxSlippage: Percent | 'auto' // auto will cause slippage to resort to default calculation transactionTtl: number | undefined mockTogglable: boolean clientSideRouter: boolean // wether to use } const initialSettings: Settings = { - maxSlippage: { value: MaxSlippage.DEFAULT }, + maxSlippage: 'auto', transactionTtl: undefined, mockTogglable: true, clientSideRouter: false, } export const settingsAtom = atomWithReset(initialSettings) -export const maxSlippageAtom = pickAtom(settingsAtom, 'maxSlippage', setCustomizable(MaxSlippage)) +export const maxSlippageAtom = pickAtom(settingsAtom, 'maxSlippage') export const transactionTtlAtom = pickAtom(settingsAtom, 'transactionTtl') export const mockTogglableAtom = pickAtom(settingsAtom, 'mockTogglable', setTogglable) export const clientSideRouterAtom = pickAtom(settingsAtom, 'clientSideRouter') diff --git a/src/state/swap/hooks.tsx b/src/state/swap/hooks.tsx index 8c3c61fea1..b91da25691 100644 --- a/src/state/swap/hooks.tsx +++ b/src/state/swap/hooks.tsx @@ -1,17 +1,18 @@ import { Trans } from '@lingui/macro' import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' import useActiveWeb3React from 'hooks/useActiveWeb3React' +import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance' import { useBestTrade } from 'hooks/useBestTrade' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import { ParsedQs } from 'qs' import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react' import { useAppDispatch, useAppSelector } from 'state/hooks' import { InterfaceTrade, TradeState } from 'state/routing/types' +import { useUserSlippageToleranceWithDefault } from 'state/user/hooks' import { useCurrency } from '../../hooks/Tokens' import useENS from '../../hooks/useENS' import useParsedQueryString from '../../hooks/useParsedQueryString' -import useSwapSlippageTolerance from '../../hooks/useSwapSlippageTolerance' import { isAddress } from '../../utils' import { AppState } from '../index' import { useCurrencyBalances } from '../wallet/hooks' @@ -133,7 +134,10 @@ export function useDerivedSwapInfo(): { [inputCurrency, outputCurrency] ) - const allowedSlippage = useSwapSlippageTolerance(trade.trade ?? undefined) + // allowed slippage is either auto slippage, or custom user defined slippage if auto slippage disabled + const autoSlippageTolerance = useAutoSlippageTolerance(trade.trade) + const allowedSlippage = useUserSlippageToleranceWithDefault(autoSlippageTolerance) + const inputError = useMemo(() => { let inputError: ReactNode | undefined