Skip to content

Commit

Permalink
fix: limit order custom limit price state lost (#8299)
Browse files Browse the repository at this point in the history
  • Loading branch information
woodenfurniture authored Dec 9, 2024
1 parent f04cbc7 commit 8d09123
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 198 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
Stack,
} from '@chakra-ui/react'
import type { Asset } from '@shapeshiftoss/types'
import { bn, bnOrZero } from '@shapeshiftoss/utils'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { bnOrZero } from '@shapeshiftoss/utils'
import { useCallback, useMemo, useRef } from 'react'
import type { NumberFormatValues } from 'react-number-format'
import NumberFormat from 'react-number-format'
import { StyledAssetMenuButton } from 'components/AssetSelection/components/AssetMenuButton'
Expand All @@ -22,27 +22,23 @@ import { Text } from 'components/Text'
import { useActions } from 'hooks/useActions'
import { useLocaleFormatter } from 'hooks/useLocaleFormatter/useLocaleFormatter'
import { assertUnreachable } from 'lib/utils'
import { ExpiryOption, PriceDirection } from 'state/slices/limitOrderInputSlice/constants'
import {
ExpiryOption,
LimitPriceMode,
PriceDirection,
} from 'state/slices/limitOrderInputSlice/constants'
import { limitOrderInput } from 'state/slices/limitOrderInputSlice/limitOrderInputSlice'
import {
selectExpiry,
selectLimitPriceDirection,
selectLimitPriceForSelectedPriceDirection,
selectLimitPriceOppositeDirection,
selectLimitPriceMode,
} from 'state/slices/limitOrderInputSlice/selectors'
import { allowedDecimalSeparators } from 'state/slices/preferencesSlice/preferencesSlice'
import { useAppSelector } from 'state/store'

import { AmountInput } from '../../TradeAmountInput'

enum PresetLimit {
Market = 'market',
OnePercent = 'onePercent',
TwoPercent = 'twoPercent',
FivePercent = 'fivePercent',
TenPercent = 'tenPercent',
}

const EXPIRY_OPTIONS = [
ExpiryOption.OneHour,
ExpiryOption.OneDay,
Expand Down Expand Up @@ -91,32 +87,15 @@ export const LimitOrderConfig = ({
}: LimitOrderConfigProps) => {
const priceAmountRef = useRef<string | null>(null)

const [presetLimit, setPresetLimit] = useState<PresetLimit | undefined>(PresetLimit.Market)

const limitPriceForSelectedPriceDirection = useAppSelector(
selectLimitPriceForSelectedPriceDirection,
)
const priceDirection = useAppSelector(selectLimitPriceDirection)
const oppositePriceDirection = useAppSelector(selectLimitPriceOppositeDirection)
const expiry = useAppSelector(selectExpiry)
const limitPriceMode = useAppSelector(selectLimitPriceMode)

const { setLimitPriceDirection, setExpiry, setLimitPrice } = useActions(limitOrderInput.actions)

// Reset the user config when the assets change
useEffect(
() => {
setLimitPriceDirection(PriceDirection.BuyAssetDenomination)
setPresetLimit(PresetLimit.Market)
setExpiry(ExpiryOption.SevenDays)
setLimitPrice({
[PriceDirection.BuyAssetDenomination]: marketPriceBuyAsset,
[PriceDirection.SellAssetDenomination]: bn(1).div(marketPriceBuyAsset).toFixed(),
})
},
// NOTE: we DO NOT want to react to `marketPriceBuyAsset` here, because polling will reset it
// every time!
// eslint-disable-next-line react-hooks/exhaustive-deps
[sellAsset, buyAsset, setLimitPrice],
const { setLimitPriceDirection, setExpiry, setLimitPrice, setLimitPriceMode } = useActions(
limitOrderInput.actions,
)

const {
Expand Down Expand Up @@ -153,51 +132,33 @@ export const LimitOrderConfig = ({
}, [priceDirection])

const handleSetPresetLimit = useCallback(
(presetLimit: PresetLimit) => {
setPresetLimit(presetLimit)
const multiplier = (() => {
switch (presetLimit) {
case PresetLimit.Market:
return '1.00'
case PresetLimit.OnePercent:
return '1.01'
case PresetLimit.TwoPercent:
return '1.02'
case PresetLimit.FivePercent:
return '1.05'
case PresetLimit.TenPercent:
return '1.10'
default:
assertUnreachable(presetLimit)
}
})()
const adjustedLimitPriceBuyAsset = bn(marketPriceBuyAsset).times(multiplier).toFixed()
setLimitPrice({
[PriceDirection.BuyAssetDenomination]: adjustedLimitPriceBuyAsset,
[PriceDirection.SellAssetDenomination]: bn(1).div(adjustedLimitPriceBuyAsset).toFixed(),
})
(limitPriceMode: LimitPriceMode) => {
if (limitPriceMode === LimitPriceMode.CustomValue) return

setLimitPriceMode(limitPriceMode)
setLimitPrice({ marketPriceBuyAsset })
},
[marketPriceBuyAsset, setLimitPrice],
[marketPriceBuyAsset, setLimitPrice, setLimitPriceMode],
)

const handleSetMarketLimit = useCallback(() => {
handleSetPresetLimit(PresetLimit.Market)
handleSetPresetLimit(LimitPriceMode.Market)
}, [handleSetPresetLimit])

const handleSetOnePercentLimit = useCallback(() => {
handleSetPresetLimit(PresetLimit.OnePercent)
handleSetPresetLimit(LimitPriceMode.OnePercent)
}, [handleSetPresetLimit])

const handleSetTwoPercentLimit = useCallback(() => {
handleSetPresetLimit(PresetLimit.TwoPercent)
handleSetPresetLimit(LimitPriceMode.TwoPercent)
}, [handleSetPresetLimit])

const handleSetFivePercentLimit = useCallback(() => {
handleSetPresetLimit(PresetLimit.FivePercent)
handleSetPresetLimit(LimitPriceMode.FivePercent)
}, [handleSetPresetLimit])

const handleSetTenPercentLimit = useCallback(() => {
handleSetPresetLimit(PresetLimit.TenPercent)
handleSetPresetLimit(LimitPriceMode.TenPercent)
}, [handleSetPresetLimit])

const handleTogglePriceDirection = useCallback(() => {
Expand All @@ -211,34 +172,24 @@ export const LimitOrderConfig = ({
const handlePriceChange = useCallback(() => {
// onChange will send us the formatted value
// To get around this we need to get the value from the onChange using a ref
// Now when the max buttons are clicked the onChange will not fire
setLimitPrice({
[priceDirection]: priceAmountRef.current ?? '0',
[oppositePriceDirection]: bnOrZero(priceAmountRef.current).isZero()
? '0'
: bn(1)
.div(priceAmountRef.current ?? NaN) // Never zero or nullish, stfu typescript
.toFixed(),
} as Record<PriceDirection, string>)
// Now when the preset price mode buttons are clicked the onChange will not fire
setLimitPriceMode(LimitPriceMode.CustomValue)
setLimitPrice({ marketPriceBuyAsset: priceAmountRef.current ?? '0' })
}, [setLimitPrice, setLimitPriceMode])

// Unset the preset limit, as this is a custom value
setPresetLimit(undefined)
}, [oppositePriceDirection, priceDirection, setLimitPrice])
const handleValueChange = useCallback((values: NumberFormatValues) => {
// This fires anytime value changes including setting it on preset price mode click
// Store the value in a ref to send when we actually want the onChange to fire
priceAmountRef.current = values.value

const handleValueChange = useCallback(
(values: NumberFormatValues) => {
// This fires anytime value changes including setting it on max click
// Store the value in a ref to send when we actually want the onChange to fire
priceAmountRef.current = values.value
setLimitPrice({
[priceDirection]: values.value,
[oppositePriceDirection]: bnOrZero(values.value).isZero()
? '0'
: bn(1).div(values.value).toFixed(),
} as Record<PriceDirection, string>)
},
[oppositePriceDirection, priceDirection, setLimitPrice],
)
// TODO: Remove me?
// setLimitPrice({
// [priceDirection]: values.value,
// [oppositePriceDirection]: bnOrZero(values.value).isZero()
// ? '0'
// : bn(1).div(values.value).toFixed(),
// } as Record<PriceDirection, string>)
}, [])

const expiryOptionTranslation = useMemo(() => {
return getExpiryOptionTranslation(expiry)
Expand Down Expand Up @@ -296,7 +247,7 @@ export const LimitOrderConfig = ({
<Button
variant='ghost'
size='sm'
isActive={presetLimit === PresetLimit.Market}
isActive={limitPriceMode === LimitPriceMode.Market}
onClick={handleSetMarketLimit}
isDisabled={isLoading}
>
Expand All @@ -305,7 +256,7 @@ export const LimitOrderConfig = ({
<Button
variant='ghost'
size='sm'
isActive={presetLimit === PresetLimit.OnePercent}
isActive={limitPriceMode === LimitPriceMode.OnePercent}
onClick={handleSetOnePercentLimit}
isDisabled={isLoading}
>
Expand All @@ -314,7 +265,7 @@ export const LimitOrderConfig = ({
<Button
variant='ghost'
size='sm'
isActive={presetLimit === PresetLimit.TwoPercent}
isActive={limitPriceMode === LimitPriceMode.TwoPercent}
onClick={handleSetTwoPercentLimit}
isDisabled={isLoading}
>
Expand All @@ -323,7 +274,7 @@ export const LimitOrderConfig = ({
<Button
variant='ghost'
size='sm'
isActive={presetLimit === PresetLimit.FivePercent}
isActive={limitPriceMode === LimitPriceMode.FivePercent}
onClick={handleSetFivePercentLimit}
isDisabled={isLoading}
>
Expand All @@ -332,7 +283,7 @@ export const LimitOrderConfig = ({
<Button
variant='ghost'
size='sm'
isActive={presetLimit === PresetLimit.TenPercent}
isActive={limitPriceMode === LimitPriceMode.TenPercent}
onClick={handleSetTenPercentLimit}
isDisabled={isLoading}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export const LimitOrderConfirm = () => {
borderColor='border.base'
bg='background.surface.raised.base'
>
<CardHeader px={6} pt={4} borderWidth={0}>
<CardHeader px={6} pt={4} borderBottomWidth={0}>
<WithBackButton onBack={handleBack}>
<Heading textAlign='center' fontSize='lg'>
<Text translation='limitOrder.confirm' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { useActions } from 'hooks/useActions'
import { useWallet } from 'hooks/useWallet/useWallet'
import { useQuoteLimitOrderQuery } from 'state/apis/limit-orders/limitOrderApi'
import { selectCalculatedFees, selectIsVotingPowerLoading } from 'state/apis/snapshot/selectors'
import { PriceDirection } from 'state/slices/limitOrderInputSlice/constants'
import { LimitPriceMode } from 'state/slices/limitOrderInputSlice/constants'
import { expiryOptionToUnixTimestamp } from 'state/slices/limitOrderInputSlice/helpers'
import { limitOrderInput } from 'state/slices/limitOrderInputSlice/limitOrderInputSlice'
import {
Expand All @@ -40,6 +40,7 @@ import {
selectInputSellAsset,
selectIsInputtingFiatSellAmount,
selectLimitPrice,
selectLimitPriceMode,
selectSellAccountId,
selectSellAssetBalanceCryptoBaseUnit,
selectUserSlippagePercentage,
Expand Down Expand Up @@ -110,6 +111,7 @@ export const LimitOrderInput = ({
const networkFeeUserCurrency = useAppSelector(selectActiveQuoteNetworkFeeUserCurrency)
const expiry = useAppSelector(selectExpiry)
const sellAssetBalanceCryptoBaseUnit = useAppSelector(selectSellAssetBalanceCryptoBaseUnit)
const limitPriceMode = useAppSelector(selectLimitPriceMode)

const {
switchAssets,
Expand Down Expand Up @@ -237,15 +239,13 @@ export const LimitOrderInput = ({
.toFixed()
}, [buyAsset.precision, quoteResponse, sellAsset.precision, limitOrderQuoteParams])

// Reset the limit price when the market price changes.
// TODO: If we introduce polling of quotes, we will need to add logic inside `LimitOrderConfig` to
// not reset the user's config unless the asset pair changes.
// Update the limit price when the market price changes.
useEffect(() => {
setLimitPrice({
[PriceDirection.BuyAssetDenomination]: marketPriceBuyAsset,
[PriceDirection.SellAssetDenomination]: bn(1).div(marketPriceBuyAsset).toFixed(),
} as Record<PriceDirection, string>)
}, [marketPriceBuyAsset, setLimitPrice])
// Don't update if the user has a custom value configured.
if (limitPriceMode !== LimitPriceMode.CustomValue) {
setLimitPrice({ marketPriceBuyAsset })
}
}, [limitPriceMode, marketPriceBuyAsset, setLimitPrice])

const onSubmit = useCallback(() => {
// No preview happening if wallet isn't connected i.e is using the demo wallet
Expand Down
Loading

0 comments on commit 8d09123

Please sign in to comment.