From 4b89ecbf661e6c30193586c704e23c78b2bfc22b Mon Sep 17 00:00:00 2001 From: Leandro Date: Mon, 14 Oct 2024 17:57:52 +0100 Subject: [PATCH] feat(smart-slippage): update smart slippage text (#4982) * feat: update smart slippage related text * refactor: memoize fns * feat: when smart slippage is set, use that as limit for settings * refactor: remove unused consts * chore: remove dead code * fix: replace geat with settings icon * fix: always show the dynamic slippage text --- .../legacy/components/SwapWarnings/index.tsx | 9 +- .../components/TransactionSettings/index.tsx | 139 +++++++++--------- .../pure/Row/RowSlippageContent/index.tsx | 30 ++-- .../updaters/SmartSlippageUpdater/index.ts | 49 +----- libs/common-const/src/misc.ts | 22 +-- 5 files changed, 100 insertions(+), 149 deletions(-) diff --git a/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx b/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx index b2b1caa0e9..8fdc4411e9 100644 --- a/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/SwapWarnings/index.tsx @@ -188,8 +188,13 @@ export function HighSuggestedSlippageWarning(props: HighSuggestedSlippageWarning
- Beware! High dynamic slippage suggested ({`${slippageBps / 100}`}%) - + Slippage adjusted to {`${slippageBps / 100}`}% to ensure quick execution +
diff --git a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx index ab1060e8df..388726d36c 100644 --- a/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/TransactionSettings/index.tsx @@ -47,7 +47,7 @@ enum DeadlineError { InvalidInput = 'InvalidInput', } -const Option = styled(FancyButton) <{ active: boolean }>` +const Option = styled(FancyButton)<{ active: boolean }>` margin-right: 8px; :hover { @@ -75,7 +75,7 @@ export const Input = styled.input` text-align: right; ` -export const OptionCustom = styled(FancyButton) <{ active?: boolean; warning?: boolean }>` +export const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }>` height: 2rem; position: relative; padding: 0 0.75rem; @@ -84,7 +84,7 @@ export const OptionCustom = styled(FancyButton) <{ active?: boolean; warning?: b :hover { border: ${({ theme, active, warning }) => - active && `1px solid ${warning ? darken(theme.error, 0.1) : darken(theme.bg2, 0.1)}`}; + active && `1px solid ${warning ? darken(theme.error, 0.1) : darken(theme.bg2, 0.1)}`}; } input { @@ -97,6 +97,7 @@ export const OptionCustom = styled(FancyButton) <{ active?: boolean; warning?: b const SlippageEmojiContainer = styled.span` color: #f3841e; + ${Media.upToSmall()} { display: none; } @@ -204,78 +205,84 @@ export function TransactionSettings() { const placeholderSlippage = isSlippageModified ? defaultSwapSlippage : swapSlippage - function parseSlippageInput(value: string) { - // populate what the user typed and clear the error - setSlippageInput(value) - setSlippageError(false) + const parseSlippageInput = useCallback( + (value: string) => { + // populate what the user typed and clear the error + setSlippageInput(value) + setSlippageError(false) - if (value.length === 0) { - slippageToleranceAnalytics('Default', placeholderSlippage.toFixed(2)) - setSwapSlippage(isEoaEthFlow ? percentToBps(minEthFlowSlippage) : null) - } else { - let v = value - - // Prevent inserting more than 2 decimal precision - if (value.split('.')[1]?.length > 2) { - // indexOf + 3 because we are cutting it off at `.XX` - v = value.slice(0, value.indexOf('.') + 3) - // Update the input to remove the extra numbers from UI input - setSlippageInput(v) - } + if (value.length === 0) { + slippageToleranceAnalytics('Default', placeholderSlippage.toFixed(2)) + setSwapSlippage(isEoaEthFlow ? percentToBps(minEthFlowSlippage) : null) + } else { + let v = value + + // Prevent inserting more than 2 decimal precision + if (value.split('.')[1]?.length > 2) { + // indexOf + 3 because we are cutting it off at `.XX` + v = value.slice(0, value.indexOf('.') + 3) + // Update the input to remove the extra numbers from UI input + setSlippageInput(v) + } - const parsed = Math.round(Number.parseFloat(v) * 100) + const parsed = Math.round(Number.parseFloat(v) * 100) - if ( - !Number.isInteger(parsed) || - parsed < (isEoaEthFlow ? minEthFlowSlippageBps : MIN_SLIPPAGE_BPS) || - parsed > MAX_SLIPPAGE_BPS - ) { - if (v !== '.') { - setSlippageError(SlippageError.InvalidInput) + if ( + !Number.isInteger(parsed) || + parsed < (isEoaEthFlow ? minEthFlowSlippageBps : MIN_SLIPPAGE_BPS) || + parsed > MAX_SLIPPAGE_BPS + ) { + if (v !== '.') { + setSlippageError(SlippageError.InvalidInput) + } } - } - slippageToleranceAnalytics('Custom', parsed) - setSwapSlippage(percentToBps(new Percent(parsed, 10_000))) - } - } + slippageToleranceAnalytics('Custom', parsed) + setSwapSlippage(percentToBps(new Percent(parsed, 10_000))) + } + }, + [placeholderSlippage, isEoaEthFlow, minEthFlowSlippage], + ) const tooLow = swapSlippage.lessThan(new Percent(isEoaEthFlow ? minEthFlowSlippageBps : LOW_SLIPPAGE_BPS, 10_000)) const tooHigh = swapSlippage.greaterThan( - new Percent(isEoaEthFlow ? HIGH_ETH_FLOW_SLIPPAGE_BPS : HIGH_SLIPPAGE_BPS, 10_000) + new Percent(isEoaEthFlow ? HIGH_ETH_FLOW_SLIPPAGE_BPS : smartSlippage || HIGH_SLIPPAGE_BPS, 10_000), ) - function parseCustomDeadline(value: string) { - // populate what the user typed and clear the error - setDeadlineInput(value) - setDeadlineError(false) - - if (value.length === 0) { - orderExpirationTimeAnalytics('Default', DEFAULT_DEADLINE_FROM_NOW) - setDeadline(DEFAULT_DEADLINE_FROM_NOW) - } else { - try { - const parsed: number = Math.floor(Number.parseFloat(value) * 60) - if ( - !Number.isInteger(parsed) || // Check deadline is a number - parsed < - (isEoaEthFlow - ? // 10 minute low threshold for eth flow - MINIMUM_ETH_FLOW_DEADLINE_SECONDS - : MINIMUM_ORDER_VALID_TO_TIME_SECONDS) || // Check deadline is not too small - parsed > MAX_DEADLINE_MINUTES * 60 // Check deadline is not too big - ) { + const parseCustomDeadline = useCallback( + (value: string) => { + // populate what the user typed and clear the error + setDeadlineInput(value) + setDeadlineError(false) + + if (value.length === 0) { + orderExpirationTimeAnalytics('Default', DEFAULT_DEADLINE_FROM_NOW) + setDeadline(DEFAULT_DEADLINE_FROM_NOW) + } else { + try { + const parsed: number = Math.floor(Number.parseFloat(value) * 60) + if ( + !Number.isInteger(parsed) || // Check deadline is a number + parsed < + (isEoaEthFlow + ? // 10 minute low threshold for eth flow + MINIMUM_ETH_FLOW_DEADLINE_SECONDS + : MINIMUM_ORDER_VALID_TO_TIME_SECONDS) || // Check deadline is not too small + parsed > MAX_DEADLINE_MINUTES * 60 // Check deadline is not too big + ) { + setDeadlineError(DeadlineError.InvalidInput) + } else { + orderExpirationTimeAnalytics('Custom', parsed) + setDeadline(parsed) + } + } catch (error: any) { + console.error(error) setDeadlineError(DeadlineError.InvalidInput) - } else { - orderExpirationTimeAnalytics('Custom', parsed) - setDeadline(parsed) } - } catch (error: any) { - console.error(error) - setDeadlineError(DeadlineError.InvalidInput) } - } - } + }, + [isEoaEthFlow], + ) const showCustomDeadlineRow = Boolean(chainId) @@ -299,14 +306,14 @@ export function TransactionSettings() { - MEV protected slippage + MEV-protected slippage Your transaction will revert if the price changes unfavorably by more than this percentage. isEoaEthFlow ? getNativeSlippageTooltip(chainId, [nativeCurrency.symbol, getWrappedToken(nativeCurrency).symbol]) - : getNonNativeSlippageTooltip() + : getNonNativeSlippageTooltip(true) } /> @@ -366,8 +373,8 @@ export function TransactionSettings() { - Based on recent volatility observed for this token pair, it's recommended to leave the default - to account for price changes. + CoW Swap has dynamically selected this slippage amount to account for current gas prices and + volatility. Changes may result in slower execution. } /> diff --git a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx index c1105bc66c..07a343a2fd 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/Row/RowSlippageContent/index.tsx @@ -1,4 +1,4 @@ -import { INPUT_OUTPUT_EXPLANATION, MINIMUM_ETH_FLOW_SLIPPAGE, PERCENTAGE_PRECISION } from '@cowprotocol/common-const' +import { MINIMUM_ETH_FLOW_SLIPPAGE, PERCENTAGE_PRECISION } from '@cowprotocol/common-const' import { SupportedChainId } from '@cowprotocol/cow-sdk' import { Command } from '@cowprotocol/types' import { CenteredDots, HoverTooltip, LinkStyledButton, RowFixed, UI } from '@cowprotocol/ui' @@ -49,23 +49,29 @@ export const getNativeSlippageTooltip = (chainId: SupportedChainId, symbols: (st matching, even in volatile market conditions.

- Orders on CoW Swap are always protected from MEV, so your slippage tolerance cannot be exploited. + {symbols?.[0] || 'Native currency'} orders can, in rare cases, be frontrun due to their on-chain component. For more + robust MEV protection, consider wrapping your {symbols?.[0] || 'native currency'} before trading. ) -export const getNonNativeSlippageTooltip = () => ( +export const getNonNativeSlippageTooltip = (isSettingsModal?: boolean) => ( - Your slippage is MEV protected: all orders are submitted with tight spread (0.1%) on-chain. -
-
- The slippage set enables a resubmission of your order in case of unfavourable price movements. -
-
- {INPUT_OUTPUT_EXPLANATION} + CoW Swap dynamically adjusts your slippage tolerance to ensure your trade executes quickly while still getting the + best price.{' '} + {isSettingsModal ? ( + <> + To override this, enter your desired slippage amount. +
+
+ Either way, your slippage is protected from MEV! + + ) : ( + "Trades are protected from MEV, so your slippage can't be exploited!" + )}
) const SUGGESTED_SLIPPAGE_TOOLTIP = - 'Based on recent volatility for the selected token pair, this is the suggested slippage for ensuring quick execution of your order.' + 'This is the recommended slippage tolerance based on current gas prices & volatility. A lower amount may result in slower execution.' export interface RowSlippageContentProps { chainId: SupportedChainId @@ -121,7 +127,7 @@ export function RowSlippageContent(props: RowSlippageContentProps) { ) : ( <> - (Suggested: {smartSlippage}) + (Recommended: {smartSlippage}) diff --git a/apps/cowswap-frontend/src/modules/swap/updaters/SmartSlippageUpdater/index.ts b/apps/cowswap-frontend/src/modules/swap/updaters/SmartSlippageUpdater/index.ts index 5c8b79700e..73e6173e77 100644 --- a/apps/cowswap-frontend/src/modules/swap/updaters/SmartSlippageUpdater/index.ts +++ b/apps/cowswap-frontend/src/modules/swap/updaters/SmartSlippageUpdater/index.ts @@ -1,12 +1,11 @@ import { useSetAtom } from 'jotai' -import { useEffect, useMemo } from 'react' +import { useEffect } from 'react' import { useTradeConfirmState } from 'modules/trade' import { useSmartSlippageFromBff } from './useSmartSlippageFromBff' import { useSmartSlippageFromFeeMultiplier } from './useSmartSlippageFromFeeMultiplier' -import { useDerivedSwapInfo, useHighFeeWarning } from '../../hooks/useSwapState' import { smartSwapSlippageAtom } from '../../state/slippageValueAndTypeAtom' const MAX_BPS = 500 // 5% @@ -16,8 +15,6 @@ export function SmartSlippageUpdater() { const setSmartSwapSlippage = useSetAtom(smartSwapSlippageAtom) const bffSlippageBps = useSmartSlippageFromBff() - // TODO: remove v1 - const tradeSizeSlippageBpsV1 = useSmartSlippageFromFeePercentage() const feeMultiplierSlippageBps = useSmartSlippageFromFeeMultiplier() const { isOpen: isTradeReviewOpen } = useTradeConfirmState() @@ -38,49 +35,5 @@ export function SmartSlippageUpdater() { setSmartSwapSlippage(Math.max(MIN_BPS, Math.min(slippage, MAX_BPS))) }, [bffSlippageBps, setSmartSwapSlippage, feeMultiplierSlippageBps, isTradeReviewOpen]) - // TODO: remove before merging - useEffect(() => { - console.log(`SmartSlippageUpdater`, { - granularSlippage: tradeSizeSlippageBpsV1, - fiftyPercentFeeSlippage: feeMultiplierSlippageBps, - bffSlippageBps, - }) - }, [tradeSizeSlippageBpsV1, feeMultiplierSlippageBps]) - return null } - -// TODO: remove -/** - * Calculates smart slippage in bps, based on trade size in relation to fee - */ -function useSmartSlippageFromFeePercentage(): number | undefined { - const { trade } = useDerivedSwapInfo() || {} - const { feePercentage } = useHighFeeWarning(trade) - - const percentage = feePercentage && +feePercentage.toFixed(3) - - return useMemo(() => { - if (percentage === undefined) { - // Unset, return undefined - return - } - if (percentage < 1) { - // bigger volume compared to the fee, trust on smart slippage from BFF - return - } else if (percentage < 5) { - // Between 1 and 5, 2% - return 200 - } else if (percentage < 10) { - // Between 5 and 10, 5% - return 500 - } else if (percentage < 20) { - // Between 10 and 20, 10% - return 1000 - } - // TODO: more granularity? - - // > 20%, cap it at 20% slippage - return 2000 - }, [percentage]) -} diff --git a/libs/common-const/src/misc.ts b/libs/common-const/src/misc.ts index dcc1b586df..3fd037eb9f 100644 --- a/libs/common-const/src/misc.ts +++ b/libs/common-const/src/misc.ts @@ -1,29 +1,15 @@ -import { Percent, Fraction } from '@uniswap/sdk-core' +import { Fraction, Percent } from '@uniswap/sdk-core' import JSBI from 'jsbi' export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' -export const NetworkContextName = 'NETWORK' - -export const IS_IN_IFRAME = typeof window !== 'undefined' && window.parent !== window - // 30 minutes, denominated in seconds export const DEFAULT_DEADLINE_FROM_NOW = 60 * 30 export const L2_DEADLINE_FROM_NOW = 60 * 5 -// transaction popup dismisal amounts -export const DEFAULT_TXN_DISMISS_MS = 25000 -export const L2_TXN_DISMISS_MS = 5000 - -// used for rewards deadlines -export const BIG_INT_SECONDS_IN_WEEK = JSBI.BigInt(60 * 60 * 24 * 7) - -export const BIG_INT_ZERO = JSBI.BigInt(0) - // one basis JSBI.BigInt const BPS_BASE = JSBI.BigInt(10000) -export const ONE_BPS = new Percent(JSBI.BigInt(1), BPS_BASE) // used for warning states export const ALLOWED_PRICE_IMPACT_LOW: Percent = new Percent(JSBI.BigInt(100), BPS_BASE) // 1% @@ -34,12 +20,6 @@ export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(JSBI.Bi // for non expert mode disable swaps above this export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(1500), BPS_BASE) // 15% -export const BETTER_TRADE_LESS_HOPS_THRESHOLD = new Percent(JSBI.BigInt(50), BPS_BASE) - -export const ZERO_PERCENT = new Percent('0') -export const TWO_PERCENT = new Percent(JSBI.BigInt(200), BPS_BASE) export const ONE_HUNDRED_PERCENT = new Percent('1') -export const IS_SIDE_BANNER_VISIBLE_KEY = 'IS_SIDEBAR_BANNER_VISIBLE' - export const ONE_FRACTION = new Fraction(1, 1)