Skip to content

Commit

Permalink
fix: limit orders get zero slippage (#8453)
Browse files Browse the repository at this point in the history
  • Loading branch information
woodenfurniture authored Jan 3, 2025
1 parent 93086d0 commit f3f978a
Show file tree
Hide file tree
Showing 9 changed files with 46 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { Button, Divider, HStack, Stack, useMediaQuery } from '@chakra-ui/react'
import { Button, Divider, Stack, useMediaQuery } from '@chakra-ui/react'
import { skipToken } from '@reduxjs/toolkit/query'
import type { ChainId } from '@shapeshiftoss/caip'
import { fromAccountId, fromAssetId } from '@shapeshiftoss/caip'
import {
COW_SWAP_VAULT_RELAYER_ADDRESS,
getCowNetwork,
getDefaultSlippageDecimalPercentageForSwapper,
SwapperName,
} from '@shapeshiftoss/swapper'
import { COW_SWAP_VAULT_RELAYER_ADDRESS, getCowNetwork, SwapperName } from '@shapeshiftoss/swapper'
import { isNativeEvmAsset } from '@shapeshiftoss/swapper/dist/swappers/utils/helpers/helpers'
import type { Asset, CowSwapError } from '@shapeshiftoss/types'
import { BigNumber, bn, bnOrZero } from '@shapeshiftoss/utils'
Expand Down Expand Up @@ -46,8 +41,6 @@ import {
selectLimitPriceMode,
selectSellAccountId,
selectSellAssetBalanceCryptoBaseUnit,
selectUserSlippagePercentage,
selectUserSlippagePercentageDecimal,
} from 'state/slices/limitOrderInputSlice/selectors'
import { calcLimitPriceBuyAsset } from 'state/slices/limitOrderSlice/helpers'
import { limitOrderSlice } from 'state/slices/limitOrderSlice/limitOrderSlice'
Expand All @@ -64,7 +57,6 @@ import {
import { useAppSelector } from 'state/store'
import { breakpoints } from 'theme/theme'

import { SharedSlippagePopover } from '../../SharedTradeInput/SharedSlippagePopover'
import { SharedTradeInput } from '../../SharedTradeInput/SharedTradeInput'
import { SharedTradeInputBody } from '../../SharedTradeInput/SharedTradeInputBody'
import { SharedTradeInputFooter } from '../../SharedTradeInput/SharedTradeInputFooter/SharedTradeInputFooter'
Expand Down Expand Up @@ -96,8 +88,6 @@ export const LimitOrderInput = ({
const { showErrorToast } = useErrorToast()
const [isSmallerThanXl] = useMediaQuery(`(max-width: ${breakpoints.xl})`, { ssr: false })

const userSlippagePercentageDecimal = useAppSelector(selectUserSlippagePercentageDecimal)
const userSlippagePercentage = useAppSelector(selectUserSlippagePercentage)
const sellAsset = useAppSelector(selectInputSellAsset)
const buyAsset = useAppSelector(selectInputBuyAsset)
const limitPrice = useAppSelector(selectLimitPrice)
Expand Down Expand Up @@ -128,7 +118,6 @@ export const LimitOrderInput = ({
setSellAccountId,
setBuyAccountId,
setLimitPrice,
setSlippagePreferencePercentage,
setIsInputtingFiatSellAmount,
setSellAmountCryptoPrecision,
} = useActions(limitOrderInput.actions)
Expand All @@ -142,13 +131,6 @@ export const LimitOrderInput = ({

const { feeUsd, feeBps } = useAppSelector(state => selectCalculatedFees(state, feeParams))

const defaultSlippagePercentageDecimal = useMemo(() => {
return getDefaultSlippageDecimalPercentageForSwapper(SwapperName.CowSwap)
}, [])
const defaultSlippagePercentage = useMemo(() => {
return bn(defaultSlippagePercentageDecimal).times(100).toString()
}, [defaultSlippagePercentageDecimal])

const { isRecipientAddressEntryActive, renderedRecipientAddress, recipientAddress } =
useLimitOrderRecipientAddress({
buyAsset,
Expand Down Expand Up @@ -210,8 +192,6 @@ export const LimitOrderInput = ({
sellAssetId: sellAsset.assetId,
buyAssetId: buyAsset.assetId,
chainId: sellAsset.chainId,
slippageTolerancePercentageDecimal:
userSlippagePercentageDecimal ?? defaultSlippagePercentageDecimal,
affiliateBps: feeBps.toFixed(0),
sellAccountAddress,
sellAmountCryptoBaseUnit,
Expand All @@ -222,8 +202,6 @@ export const LimitOrderInput = ({
sellAsset.assetId,
sellAsset.chainId,
buyAsset.assetId,
userSlippagePercentageDecimal,
defaultSlippagePercentageDecimal,
feeBps,
sellAccountAddress,
recipientAddress,
Expand Down Expand Up @@ -372,29 +350,13 @@ export const LimitOrderInput = ({
])

const headerRightContent = useMemo(() => {
if (!(isCompact || isSmallerThanXl)) return <></>
return (
<HStack>
{Boolean(isCompact || isSmallerThanXl) && (
<Button size='xs' borderRadius='full' onClick={handleShowLimitOrdersList}>
<Text translation='limitOrder.viewOrders' />
</Button>
)}
<SharedSlippagePopover
defaultSlippagePercentage={defaultSlippagePercentage}
quoteSlippagePercentage={undefined} // No slippage returned by CoW
userSlippagePercentage={userSlippagePercentage}
setUserSlippagePercentage={setSlippagePreferencePercentage}
/>
</HStack>
<Button size='xs' borderRadius='full' onClick={handleShowLimitOrdersList}>
<Text translation='limitOrder.viewOrders' />
</Button>
)
}, [
isCompact,
isSmallerThanXl,
defaultSlippagePercentage,
setSlippagePreferencePercentage,
handleShowLimitOrdersList,
userSlippagePercentage,
])
}, [isCompact, isSmallerThanXl, handleShowLimitOrdersList])

const bodyContent = useMemo(() => {
return (
Expand Down
5 changes: 3 additions & 2 deletions src/state/apis/limit-orders/limitOrderApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export type LimitOrderQuoteParams = {
sellAssetId: AssetId
buyAssetId: AssetId
chainId: ChainId
slippageTolerancePercentageDecimal: string
affiliateBps: string
sellAccountAddress: Address | undefined
sellAmountCryptoBaseUnit: string
Expand All @@ -69,7 +68,6 @@ export const limitOrderApi = createApi({
sellAssetId,
buyAssetId,
chainId,
slippageTolerancePercentageDecimal,
affiliateBps,
sellAccountAddress,
sellAmountCryptoBaseUnit,
Expand All @@ -79,6 +77,9 @@ export const limitOrderApi = createApi({
const baseUrl = config.REACT_APP_COWSWAP_BASE_URL
const network = assertGetCowNetwork(chainId)

// Limit orders request 0 slippage.
const slippageTolerancePercentageDecimal = '0'

const affiliateAppDataFragment = getAffiliateAppDataFragmentByChainId({
affiliateBps,
chainId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const createTradeInputBaseSelectors = <T extends TradeInputBaseState>(
sliceName: keyof ReduxState,
) => {
// Base selector to get the slice
const selectBaseSlice = (state: ReduxState) => state[sliceName] as T
const selectBaseSlice = (state: ReduxState) => state[sliceName] as unknown as T

// Create reusable selectors
const selectInputBuyAsset = createDeepEqualOutputSelector(
Expand Down Expand Up @@ -82,20 +82,6 @@ export const createTradeInputBaseSelectors = <T extends TradeInputBaseState>(
},
)

const selectUserSlippagePercentage = createSelector(
selectBaseSlice,
tradeInput => tradeInput.slippagePreferencePercentage,
)

// User input comes in as an actual percentage e.g 1 for 1%, so we need to convert it to a decimal e.g 0.01 for 1%
const selectUserSlippagePercentageDecimal = createSelector(
selectUserSlippagePercentage,
slippagePercentage => {
if (!slippagePercentage) return
return bn(slippagePercentage).div(100).toString()
},
)

// selects the account ID we're selling from
const selectSellAccountId = createSelector(
selectBaseSlice,
Expand Down Expand Up @@ -235,8 +221,6 @@ export const createTradeInputBaseSelectors = <T extends TradeInputBaseState>(
selectInputBuyAssetUsdRate,
selectInputSellAssetUserCurrencyRate,
selectInputBuyAssetUserCurrencyRate,
selectUserSlippagePercentage,
selectUserSlippagePercentageDecimal,
selectSellAccountId,
selectBuyAccountId,
selectInputSellAmountCryptoBaseUnit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export interface TradeInputBaseState {
isManualReceiveAddressValidating: boolean
isManualReceiveAddressEditing: boolean
isManualReceiveAddressValid: boolean | undefined
slippagePreferencePercentage: string | undefined
}

const getBaseReducers = <T extends TradeInputBaseState>(initialState: T) => ({
Expand Down Expand Up @@ -99,9 +98,6 @@ const getBaseReducers = <T extends TradeInputBaseState>(initialState: T) => ({
setIsInputtingFiatSellAmount: (state: Draft<T>, action: PayloadAction<boolean>) => {
state.isInputtingFiatSellAmount = action.payload
},
setSlippagePreferencePercentage: (state: Draft<T>, action: PayloadAction<string | undefined>) => {
state.slippagePreferencePercentage = action.payload
},
})

export type BaseReducers<T extends TradeInputBaseState> = ReturnType<typeof getBaseReducers<T>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const initialState: LimitOrderInputState = {
isManualReceiveAddressValidating: false,
isManualReceiveAddressValid: undefined,
isManualReceiveAddressEditing: false,
slippagePreferencePercentage: undefined,
limitPriceDirection: PriceDirection.BuyAssetDenomination,
limitPrice: {
[PriceDirection.BuyAssetDenomination]: '0',
Expand All @@ -52,6 +51,7 @@ const resetLimitOrderConfig = (state: LimitOrderInputState) => {
export const limitOrderInput = createTradeInputBaseSlice({
name: 'limitOrderInput',
initialState,
// Add any reducers specific to limitOrderInput slice here that aren't shared with other slices
extraReducers: (baseReducers: BaseReducers<LimitOrderInputState>) => ({
setLimitPrice: (
state: LimitOrderInputState,
Expand Down
2 changes: 0 additions & 2 deletions src/state/slices/limitOrderInputSlice/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ export const {
selectInputBuyAssetUsdRate,
selectInputSellAssetUserCurrencyRate,
selectInputBuyAssetUserCurrencyRate,
selectUserSlippagePercentage,
selectUserSlippagePercentageDecimal,
selectSellAccountId,
selectBuyAccountId,
selectInputSellAmountCryptoBaseUnit,
Expand Down
22 changes: 18 additions & 4 deletions src/state/slices/tradeInputSlice/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createSelector } from '@reduxjs/toolkit'
import type { SwapperName, TradeQuote, TradeRate } from '@shapeshiftoss/swapper'
import { isExecutableTradeStep } from '@shapeshiftoss/swapper'
import { bn } from '@shapeshiftoss/utils'
import type { Selector } from 'react-redux'
import type { ApiQuote } from 'state/apis/swapper/types'
import type { ReduxState } from 'state/reducer'
Expand All @@ -10,6 +11,7 @@ import { createTradeInputBaseSelectors } from '../common/tradeInputBase/createTr
import { selectAccountIdByAccountNumberAndChainId } from '../portfolioSlice/selectors'
import { getActiveQuoteMetaOrDefault, sortTradeQuotes } from '../tradeQuoteSlice/helpers'
import type { ActiveQuoteMeta } from '../tradeQuoteSlice/types'
import type { TradeInputState } from './tradeInputSlice'

// Shared selectors from the base trade input slice that handle common functionality like input
// assets, rates, and slippage preferences
Expand All @@ -20,8 +22,6 @@ export const {
selectInputBuyAssetUsdRate,
selectInputSellAssetUserCurrencyRate,
selectInputBuyAssetUserCurrencyRate,
selectUserSlippagePercentage,
selectUserSlippagePercentageDecimal,
selectInputSellAmountCryptoBaseUnit,
selectManualReceiveAddress,
selectIsManualReceiveAddressValidating,
Expand All @@ -35,9 +35,9 @@ export const {
selectInputSellAmountCryptoPrecision,
// We don't want to export some of the selectors so we can give them more specific names
...privateSelectors
} = createTradeInputBaseSelectors('tradeInput')
} = createTradeInputBaseSelectors<TradeInputState>('tradeInput')

const { selectSellAccountId, selectBuyAccountId } = privateSelectors
const { selectBaseSlice, selectSellAccountId, selectBuyAccountId } = privateSelectors

// We rename this to include the specific hop to avoid confusion in multi-hop contexts
// Selects the account ID we're selling from for the first hop
Expand Down Expand Up @@ -103,6 +103,20 @@ const selectSecondHop: Selector<ReduxState, TradeQuote['steps'][number] | undefi
export const selectIsActiveQuoteMultiHop: Selector<ReduxState, boolean | undefined> =
createSelector(selectActiveQuote, quote => (quote ? quote?.steps.length > 1 : undefined))

export const selectUserSlippagePercentage = createSelector(
selectBaseSlice,
tradeInput => tradeInput.slippagePreferencePercentage,
)

// User input comes in as an actual percentage e.g 1 for 1%, so we need to convert it to a decimal e.g 0.01 for 1%
export const selectUserSlippagePercentageDecimal = createSelector(
selectUserSlippagePercentage,
slippagePercentage => {
if (!slippagePercentage) return
return bn(slippagePercentage).div(100).toString()
},
)

// if multi-hop, selects the account ID we're selling from for fee asset of the last hop and its account number
// else, selects none because there's obviously no AccountId if there's no hop
export const selectSecondHopSellAccountId = createSelector(
Expand Down
21 changes: 16 additions & 5 deletions src/state/slices/tradeInputSlice/tradeInputSlice.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { SliceCaseReducers } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import { ethAssetId, foxAssetId } from '@shapeshiftoss/caip'
import { localAssetData } from 'lib/asset-service'

import { defaultAsset } from '../assetsSlice/assetsSlice'
import type { TradeInputBaseState } from '../common/tradeInputBase/createTradeInputBaseSlice'
import type {
BaseReducers,
TradeInputBaseState,
} from '../common/tradeInputBase/createTradeInputBaseSlice'
import { createTradeInputBaseSlice } from '../common/tradeInputBase/createTradeInputBaseSlice'

type TradeInputState = TradeInputBaseState
export type TradeInputState = {
slippagePreferencePercentage: string | undefined
} & TradeInputBaseState

const initialState: TradeInputState = {
buyAsset: localAssetData[foxAssetId] ?? defaultAsset,
Expand All @@ -25,7 +30,13 @@ const initialState: TradeInputState = {
export const tradeInput = createTradeInputBaseSlice({
name: 'tradeInput',
initialState,
extraReducers: (_baseReducers: SliceCaseReducers<TradeInputState>) => ({
// Add any reducers specific to tradeInput slice here that aren't shared with other slices
// Add any reducers specific to tradeInput slice here that aren't shared with other slices
extraReducers: (_baseReducers: BaseReducers<TradeInputState>) => ({
setSlippagePreferencePercentage: (
state: TradeInputState,
action: PayloadAction<string | undefined>,
) => {
state.slippagePreferencePercentage = action.payload
},
}),
})
1 change: 0 additions & 1 deletion src/test/mocks/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ export const mockStore: ReduxState = {
isManualReceiveAddressValidating: false,
isManualReceiveAddressEditing: false,
isManualReceiveAddressValid: undefined,
slippagePreferencePercentage: undefined,
limitPrice: {
[PriceDirection.BuyAssetDenomination]: '0',
[PriceDirection.SellAssetDenomination]: '0',
Expand Down

0 comments on commit f3f978a

Please sign in to comment.