Skip to content

Commit

Permalink
feat: custom slippage improvements for UI and cowswap swapper (#5908)
Browse files Browse the repository at this point in the history
* feat: handle quotes that dont support custom slippage

* fix: tests

* chore: unify slippage tolerance vernacular across the app

* chore: de-obfuscate custom slippage warning flag
  • Loading branch information
woodenfurniture authored Dec 21, 2023
1 parent 84ae90e commit 0f05cf8
Show file tree
Hide file tree
Showing 26 changed files with 204 additions and 107 deletions.
6 changes: 3 additions & 3 deletions packages/swapper/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ type CommonTradeInput = {
potentialAffiliateBps: string
affiliateBps: string
allowMultiHop: boolean
slippageTolerancePercentage?: string
slippageTolerancePercentageDecimal?: string
}

export type GetEvmTradeQuoteInput = CommonTradeInput & {
Expand Down Expand Up @@ -150,7 +150,7 @@ export type TradeQuote = {
potentialAffiliateBps: string | undefined // undefined if affiliate fees aren't supported by the swapper
affiliateBps: string | undefined // undefined if affiliate fees aren't supported by the swapper
isStreaming?: boolean
slippageTolerancePercentage?: string
slippageTolerancePercentageDecimal: string | undefined // undefined if slippage limit is not provided or specified by the swapper
isLongtail?: boolean
}

Expand All @@ -163,7 +163,7 @@ export type CowSwapOrder = {
buyAmount: string
validTo: number
appData: string
appDataHash?: string
appDataHash: string
feeAmount: string
kind: string
partiallyFillable: boolean
Expand Down
7 changes: 6 additions & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,12 @@
"swap": "Swap on %{sellChainName} via %{swapperName}"
},
"approvalTitle": "Token allowance approval",
"assetSummaryDescription": "%{amountFiatFormatted} on %{chainName}"
"assetSummaryDescription": "%{amountFiatFormatted} on %{chainName}",
"quote": {
"cantSetSlippage": "We are unable to set a custom slippage (%{userSlippageFormatted}) for %{swapperName}",
"slippage": "Slippage: %{slippageFormatted}"
}

},
"modals": {
"popup": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
Stepper,
} from '@chakra-ui/react'
import type { SwapperName, TradeQuoteStep } from '@shapeshiftoss/swapper'
import { getDefaultSlippageDecimalPercentageForSwapper } from 'constants/constants'
import prettyMilliseconds from 'pretty-ms'
import { useMemo } from 'react'
import { FaGasPump } from 'react-icons/fa'
Expand Down Expand Up @@ -46,12 +45,14 @@ export const Hop = ({
tradeQuoteStep,
hopIndex,
isOpen,
slippageTolerancePercentageDecimal,
onToggleIsOpen,
}: {
swapperName: SwapperName
tradeQuoteStep: TradeQuoteStep
hopIndex: number
isOpen: boolean
slippageTolerancePercentageDecimal: string | undefined
onToggleIsOpen?: () => void
}) => {
const translate = useTranslate()
Expand Down Expand Up @@ -115,11 +116,6 @@ export const Hop = ({
}
}, [hopExecutionState, hopIndex])

const slippageDecimalPercentage = useMemo(
() => getDefaultSlippageDecimalPercentageForSwapper(swapperName),
[swapperName],
)

const title = useMemo(() => {
const isBridge = tradeQuoteStep.buyAsset.chainId !== tradeQuoteStep.sellAsset.chainId

Expand Down Expand Up @@ -218,20 +214,20 @@ export const Hop = ({

{/* Hovering over this should render a popover with details */}
<Flex alignItems='center' gap={2}>
{/* Placeholder - use correct icon here */}
<Flex color='text.subtle'>
<ProtocolIcon />
</Flex>
<Amount.Fiat value={protocolFeeFiatPrecision ?? '0'} display='inline' />
</Flex>

<Flex alignItems='center' gap={2}>
{/* Placeholder - use correct icon here */}
<Flex color='text.subtle'>
<SlippageIcon />
{slippageTolerancePercentageDecimal !== undefined && (
<Flex alignItems='center' gap={2}>
<Flex color='text.subtle'>
<SlippageIcon />
</Flex>
<Amount.Percent value={slippageTolerancePercentageDecimal} display='inline' />
</Flex>
<Amount.Percent value={slippageDecimalPercentage} display='inline' />
</Flex>
)}
</HStack>
</CardFooter>
</Card>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Box, Stack } from '@chakra-ui/react'
import { memo, useMemo } from 'react'
import {
selectActiveQuote,
selectActiveSwapperName,
selectFirstHop,
selectIsActiveQuoteMultiHop,
Expand All @@ -22,14 +23,15 @@ export const Hops = memo((props: HopsProps) => {
const swapperName = useAppSelector(selectActiveSwapperName)
const firstHop = useAppSelector(selectFirstHop)
const lastHop = useAppSelector(selectLastHop)
const activeQuote = useAppSelector(selectActiveQuote)
const isMultiHopTrade = useAppSelector(selectIsActiveQuoteMultiHop)

const divider = useMemo(
() => <Box height={2} borderColor='border.bold' bg='background.surface.base' />,
[],
)

if (!firstHop || !swapperName) return null
if (!activeQuote || !firstHop || !swapperName) return null

return (
<Stack spacing={0} divider={divider} borderColor='border.base'>
Expand All @@ -39,6 +41,7 @@ export const Hops = memo((props: HopsProps) => {
hopIndex={0}
isOpen={isFirstHopOpen}
onToggleIsOpen={onToggleFirstHop}
slippageTolerancePercentageDecimal={activeQuote.slippageTolerancePercentageDecimal}
/>
{isMultiHopTrade && lastHop && (
<Hop
Expand All @@ -47,6 +50,7 @@ export const Hops = memo((props: HopsProps) => {
hopIndex={1}
isOpen={isSecondHopOpen}
onToggleIsOpen={onToggleSecondHop}
slippageTolerancePercentageDecimal={activeQuote.slippageTolerancePercentageDecimal}
/>
)}
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import { SlippagePopover } from 'components/MultiHopTrade/components/SlippagePop
import { TradeAssetInput } from 'components/MultiHopTrade/components/TradeAssetInput'
import { ReceiveSummary } from 'components/MultiHopTrade/components/TradeConfirm/ReceiveSummary'
import { ManualAddressEntry } from 'components/MultiHopTrade/components/TradeInput/components/ManualAddressEntry'
import { getSwapperSupportsSlippage } from 'components/MultiHopTrade/components/TradeInput/getSwapperSupportsSlippage'
import { getMixpanelEventData } from 'components/MultiHopTrade/helpers'
import { useActiveQuoteStatus } from 'components/MultiHopTrade/hooks/quoteValidation/useActiveQuoteStatus'
import { usePriceImpact } from 'components/MultiHopTrade/hooks/quoteValidation/usePriceImpact'
Expand Down Expand Up @@ -175,7 +174,6 @@ export const TradeInput = memo(() => {
const activeQuote = useAppSelector(selectActiveQuote)
const activeQuoteError = useAppSelector(selectActiveQuoteError)
const activeSwapperName = useAppSelector(selectActiveSwapperName)
const activeSwapperSupportsSlippage = getSwapperSupportsSlippage(activeSwapperName)
const sortedQuotes = useAppSelector(selectSwappersApiTradeQuotes)
const rate = activeQuote?.steps[0].rate

Expand Down Expand Up @@ -596,10 +594,7 @@ export const TradeInput = memo(() => {
</FadeTransition>
)}
</AnimatePresence>

{(activeSwapperSupportsSlippage || sortedQuotes.length === 0) && (
<SlippagePopover />
)}
<SlippagePopover />
</Flex>
</Flex>
</CardHeader>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { WarningIcon } from '@chakra-ui/icons'
import {
Card,
CardBody,
Expand All @@ -17,9 +18,11 @@ import { BsLayers } from 'react-icons/bs'
import { FaGasPump, FaRegClock } from 'react-icons/fa'
import { useTranslate } from 'react-polyglot'
import { Amount } from 'components/Amount/Amount'
import { SlippageIcon } from 'components/Icons/Slippage'
import { quoteStatusTranslation } from 'components/MultiHopTrade/components/TradeInput/components/TradeQuotes/getQuoteErrorTranslation'
import { useIsTradingActive } from 'components/MultiHopTrade/hooks/useIsTradingActive'
import { RawText } from 'components/Text'
import { useLocaleFormatter } from 'hooks/useLocaleFormatter/useLocaleFormatter'
import { bn, bnOrZero } from 'lib/bignumber/bignumber'
import type { ApiQuote } from 'state/apis/swappers'
import {
Expand All @@ -29,6 +32,7 @@ import {
selectMarketDataById,
selectSellAmountCryptoPrecision,
selectSellAsset,
selectUserSlippagePercentageDecimal,
} from 'state/slices/selectors'
import {
getBuyAmountAfterFeesCryptoPrecision,
Expand Down Expand Up @@ -66,13 +70,18 @@ export const TradeQuoteLoaded: FC<TradeQuoteProps> = ({
const redColor = useColorModeValue('red.500', 'red.200')
const focusColor = useColorModeValue('blackAlpha.400', 'whiteAlpha.400')

const {
number: { toPercent },
} = useLocaleFormatter()

const { quote, error } = quoteData

const { isTradingActive } = useIsTradingActive()

const buyAsset = useAppSelector(selectBuyAsset)
const sellAsset = useAppSelector(selectSellAsset)
const assetsById = useAppSelector(selectAssets)
const userSlippagePercentageDecimal = useAppSelector(selectUserSlippagePercentageDecimal)

const buyAssetMarketData = useAppSelector(state =>
selectMarketDataById(state, buyAsset.assetId ?? ''),
Expand Down Expand Up @@ -193,6 +202,52 @@ export const TradeQuoteLoaded: FC<TradeQuoteProps> = ({
[quote?.steps],
)

const slippage = useMemo(() => {
if (!quote) return

// user slippage setting was not applied if:
// - the user did not input a custom value
// - the slippage on the quote is different to the custom value
const isUserSlippageNotApplied =
userSlippagePercentageDecimal !== undefined &&
quote.slippageTolerancePercentageDecimal !== userSlippagePercentageDecimal

if (!isUserSlippageNotApplied && quote.slippageTolerancePercentageDecimal === undefined) {
return
}

const tooltip = (() => {
if (isUserSlippageNotApplied) {
return translate('trade.quote.cantSetSlippage', {
userSlippageFormatted: toPercent(userSlippagePercentageDecimal),
swapperName: quoteData.swapperName,
})
}

return translate('trade.quote.slippage', {
slippageFormatted: toPercent(quote.slippageTolerancePercentageDecimal ?? '0'),
})
})()

return (
<Skeleton isLoaded={!isLoading}>
<Tooltip label={tooltip}>
<Flex gap={2} alignItems='center'>
<RawText color={isUserSlippageNotApplied ? 'text.error' : 'text.subtle'}>
<SlippageIcon />
</RawText>
{quote.slippageTolerancePercentageDecimal !== undefined && (
<RawText color={isUserSlippageNotApplied ? 'text.error' : undefined}>
{toPercent(quote.slippageTolerancePercentageDecimal)}
</RawText>
)}
{isUserSlippageNotApplied && <WarningIcon color='text.error' />}
</Flex>
</Tooltip>
</Skeleton>
)
}, [isLoading, quote, quoteData.swapperName, toPercent, translate, userSlippagePercentageDecimal])

return showSwapper ? (
<>
<Card
Expand Down Expand Up @@ -266,16 +321,6 @@ export const TradeQuoteLoaded: FC<TradeQuoteProps> = ({
{quote && (
<CardFooter px={4} pb={4}>
<Flex justifyContent='left' alignItems='left' gap={8}>
{totalEstimatedExecutionTimeMs !== undefined && totalEstimatedExecutionTimeMs > 0 && (
<Skeleton isLoaded={!isLoading}>
<Flex gap={2} alignItems='center'>
<RawText color='text.subtle'>
<FaRegClock />
</RawText>
{prettyMilliseconds(totalEstimatedExecutionTimeMs)}
</Flex>
</Skeleton>
)}
<Skeleton isLoaded={!isLoading}>
<Flex gap={2} alignItems='center'>
<RawText color='text.subtle'>
Expand All @@ -292,6 +337,17 @@ export const TradeQuoteLoaded: FC<TradeQuoteProps> = ({
}
</Flex>
</Skeleton>
{slippage}
{totalEstimatedExecutionTimeMs !== undefined && totalEstimatedExecutionTimeMs > 0 && (
<Skeleton isLoaded={!isLoading}>
<Flex gap={2} alignItems='center'>
<RawText color='text.subtle'>
<FaRegClock />
</RawText>
{prettyMilliseconds(totalEstimatedExecutionTimeMs)}
</Flex>
</Skeleton>
)}
<Skeleton isLoaded={!isLoading}>
{quote?.steps.length > 1 && (
<Tooltip label={translate('trade.numHops', { numHops: quote?.steps.length })}>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type GetTradeQuoteInputArgs = {
sellAccountNumber: number
wallet: HDWallet
receiveAddress: string
slippageTolerancePercentage?: string
slippageTolerancePercentageDecimal?: string
receiveAccountNumber?: number
sellAmountBeforeFeesCryptoPrecision: string
allowMultiHop: boolean
Expand All @@ -44,7 +44,7 @@ export const getTradeQuoteArgs = async ({
allowMultiHop,
affiliateBps,
potentialAffiliateBps,
slippageTolerancePercentage,
slippageTolerancePercentageDecimal,
pubKey,
}: GetTradeQuoteInputArgs): Promise<GetTradeQuoteInput | undefined> => {
if (!sellAsset || !buyAsset) return undefined
Expand All @@ -60,7 +60,7 @@ export const getTradeQuoteArgs = async ({
affiliateBps: affiliateBps ?? '0',
potentialAffiliateBps: potentialAffiliateBps ?? '0',
allowMultiHop,
slippageTolerancePercentage,
slippageTolerancePercentageDecimal,
}
if (isEvmSwap(sellAsset?.chainId) || isCosmosSdkSwap(sellAsset?.chainId)) {
const supportsEIP1559 = supportsETH(wallet) && (await wallet.ethSupportsEIP1559())
Expand Down
Loading

0 comments on commit 0f05cf8

Please sign in to comment.