Skip to content

Commit

Permalink
feat(smart-slippage): update smart slippage text (#4982)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
alfetopito authored Oct 14, 2024
1 parent 8e6bdd5 commit 4b89ecb
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,13 @@ export function HighSuggestedSlippageWarning(props: HighSuggestedSlippageWarning
<WarningContainer {...rest} level={LOW_TIER_FEE}>
<div>
<AlertTriangle size={24} />
Beware! High dynamic slippage suggested ({`${slippageBps / 100}`}%)
<HoverTooltip wrapInContainer content={"It's not thaaat bad. Just to make sure you noticed 😉"}>
Slippage adjusted to {`${slippageBps / 100}`}% to ensure quick execution
<HoverTooltip
wrapInContainer
content={
'CoW Swap dynamically adjusts your slippage tolerance based on current volatility. You can set a custom slippage using the settings icon above.'
}
>
<ErrorStyledInfoIcon />
</HoverTooltip>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ enum DeadlineError {
InvalidInput = 'InvalidInput',
}

const Option = styled(FancyButton) <{ active: boolean }>`
const Option = styled(FancyButton)<{ active: boolean }>`
margin-right: 8px;
:hover {
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -97,6 +97,7 @@ export const OptionCustom = styled(FancyButton) <{ active?: boolean; warning?: b

const SlippageEmojiContainer = styled.span`
color: #f3841e;
${Media.upToSmall()} {
display: none;
}
Expand Down Expand Up @@ -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)

Expand All @@ -299,14 +306,14 @@ export function TransactionSettings() {
<AutoColumn gap="sm">
<RowFixed>
<ThemedText.Black fontWeight={400} fontSize={14}>
<Trans>MEV protected slippage</Trans>
<Trans>MEV-protected slippage</Trans>
</ThemedText.Black>
<HelpTooltip
text={
// <Trans>Your transaction will revert if the price changes unfavorably by more than this percentage.</Trans>
isEoaEthFlow
? getNativeSlippageTooltip(chainId, [nativeCurrency.symbol, getWrappedToken(nativeCurrency).symbol])
: getNonNativeSlippageTooltip()
: getNonNativeSlippageTooltip(true)
}
/>
</RowFixed>
Expand Down Expand Up @@ -366,8 +373,8 @@ export function TransactionSettings() {
<HelpTooltip
text={
<Trans>
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.
</Trans>
}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -49,23 +49,29 @@ export const getNativeSlippageTooltip = (chainId: SupportedChainId, symbols: (st
matching, even in volatile market conditions.
<br />
<br />
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.
</Trans>
)
export const getNonNativeSlippageTooltip = () => (
export const getNonNativeSlippageTooltip = (isSettingsModal?: boolean) => (
<Trans>
Your slippage is MEV protected: all orders are submitted with tight spread (0.1%) on-chain.
<br />
<br />
The slippage set enables a resubmission of your order in case of unfavourable price movements.
<br />
<br />
{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.
<br />
<br />
Either way, your slippage is protected from MEV!
</>
) : (
"Trades are protected from MEV, so your slippage can't be exploited!"
)}
</Trans>
)

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
Expand Down Expand Up @@ -121,7 +127,7 @@ export function RowSlippageContent(props: RowSlippageContentProps) {
<CenteredDots />
) : (
<>
<LinkStyledButton onClick={setAutoSlippage}>(Suggested: {smartSlippage})</LinkStyledButton>
<LinkStyledButton onClick={setAutoSlippage}>(Recommended: {smartSlippage})</LinkStyledButton>
<HoverTooltip wrapInContainer content={SUGGESTED_SLIPPAGE_TOOLTIP}>
<StyledInfoIcon size={16} />
</HoverTooltip>
Expand Down
Original file line number Diff line number Diff line change
@@ -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%
Expand All @@ -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()
Expand All @@ -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])
}
22 changes: 1 addition & 21 deletions libs/common-const/src/misc.ts
Original file line number Diff line number Diff line change
@@ -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%
Expand All @@ -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)

0 comments on commit 4b89ecb

Please sign in to comment.