Skip to content

Commit

Permalink
feat: add warnings to open orders when order cannot be filled (#8359)
Browse files Browse the repository at this point in the history
* feat: warning icon for open limit orders that cant execute

* chore: cleanup and comments
  • Loading branch information
woodenfurniture authored Dec 16, 2024
1 parent 6da93df commit 4d6a4e7
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 13 deletions.
8 changes: 7 additions & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,13 @@
},
"orders": "Orders",
"viewOrders": "View Orders",
"loadingOrderList": "Loading your limit orders..."
"loadingOrderList": "Loading your limit orders...",
"orderCard": {
"warning": {
"insufficientBalance": "Your wallet currently has insufficient %{symbol} balance on %{chainName} to execute this order. The order is still open and will become executable when you top up your %{symbol} balance on %{chainName}.",
"insufficientAllowance": "This order requires a additional allowance of %{symbol} on %{chainName} to be approved for CoW Swap to execute this order. The order is still open and will become executable when you complete the allowance approval."
}
}
},
"modals": {
"assetSearch": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import { Box, Button, Center, Flex, Progress, Tag, Tooltip } from '@chakra-ui/react'
import type { AssetId } from '@shapeshiftoss/caip'
import { WarningTwoIcon } from '@chakra-ui/icons'
import { Box, Button, Center, Flex, Progress, Tag, TagLabel, Tooltip } from '@chakra-ui/react'
import type { AccountId, AssetId } from '@shapeshiftoss/caip'
import { fromAccountId } from '@shapeshiftoss/caip'
import { COW_SWAP_VAULT_RELAYER_ADDRESS } from '@shapeshiftoss/swapper'
import { OrderStatus } from '@shapeshiftoss/types'
import { bn, fromBaseUnit } from '@shapeshiftoss/utils'
import { bn, bnOrZero, fromBaseUnit } from '@shapeshiftoss/utils'
import { formatDistanceToNow } from 'date-fns'
import type { FC } from 'react'
import { useCallback, useMemo } from 'react'
import { useTranslate } from 'react-polyglot'
import { useAllowance } from 'react-queries/hooks/useAllowance'
import { Amount } from 'components/Amount/Amount'
import { AssetIconWithBadge } from 'components/AssetIconWithBadge'
import { SwapBoldIcon } from 'components/Icons/SwapBold'
import { RawText, Text } from 'components/Text'
import { useLocaleFormatter } from 'hooks/useLocaleFormatter/useLocaleFormatter'
import { selectAssetById } from 'state/slices/selectors'
import { useAppSelector } from 'state/store'
import { assertGetChainAdapter } from 'lib/utils'
import {
selectAssetById,
selectPortfolioCryptoBalanceBaseUnitByFilter,
} from 'state/slices/selectors'
import { useSelectorWithArgs } from 'state/store'

export type LimitOrderCardProps = {
uid: string
Expand All @@ -23,6 +31,7 @@ export type LimitOrderCardProps = {
validTo?: number
filledDecimalPercentage: number
status: OrderStatus
accountId: AccountId
onCancelClick?: (uid: string) => void
}

Expand All @@ -39,15 +48,52 @@ export const LimitOrderCard: FC<LimitOrderCardProps> = ({
validTo,
filledDecimalPercentage,
status,
accountId,
onCancelClick,
}) => {
const translate = useTranslate()
const {
number: { toCrypto },
} = useLocaleFormatter()

const buyAsset = useAppSelector(state => selectAssetById(state, buyAssetId))
const sellAsset = useAppSelector(state => selectAssetById(state, sellAssetId))
const buyAsset = useSelectorWithArgs(selectAssetById, buyAssetId)
const sellAsset = useSelectorWithArgs(selectAssetById, sellAssetId)

const filter = useMemo(() => {
return {
accountId,
assetId: sellAssetId,
}
}, [accountId, sellAssetId])

const sellAssetBalanceCryptoBaseUnit = useSelectorWithArgs(
selectPortfolioCryptoBalanceBaseUnitByFilter,
filter,
)

const hasSufficientBalance = useMemo(() => {
return bnOrZero(sellAssetBalanceCryptoBaseUnit).gte(sellAmountCryptoBaseUnit)
}, [sellAmountCryptoBaseUnit, sellAssetBalanceCryptoBaseUnit])

const from = useMemo(() => {
return fromAccountId(accountId).account
}, [accountId])

const { data: allowanceOnChainCryptoBaseUnit } = useAllowance({
assetId: sellAssetId,
spender: COW_SWAP_VAULT_RELAYER_ADDRESS,
from,
// Don't fetch allowance if there is insufficient balance, because we wont display the allowance
// warning in this case.
isDisabled: !hasSufficientBalance || status !== OrderStatus.OPEN,
})

const hasSufficientAllowance = useMemo(() => {
// If the request failed, default to true since this is just a helper and not safety critical.
if (!allowanceOnChainCryptoBaseUnit) return true

return bn(sellAmountCryptoBaseUnit).lte(allowanceOnChainCryptoBaseUnit)
}, [allowanceOnChainCryptoBaseUnit, sellAmountCryptoBaseUnit])

const handleCancel = useCallback(() => {
onCancelClick?.(uid)
Expand Down Expand Up @@ -118,6 +164,30 @@ export const LimitOrderCard: FC<LimitOrderCardProps> = ({
[buyAmountCryptoPrecision, toCrypto, buyAsset],
)

const warningText = useMemo(() => {
if (status !== OrderStatus.OPEN) return

const translationProps = {
symbol: sellAsset?.symbol ?? '',
chainName: assertGetChainAdapter(sellAsset?.chainId ?? '')?.getDisplayName() ?? '',
}

if (!hasSufficientBalance) {
return translate('limitOrder.orderCard.warning.insufficientBalance', translationProps)
}

if (!hasSufficientAllowance) {
return translate('limitOrder.orderCard.warning.insufficientAllowance', translationProps)
}
}, [
hasSufficientAllowance,
hasSufficientBalance,
sellAsset?.chainId,
sellAsset?.symbol,
status,
translate,
])

if (!buyAsset || !sellAsset) return null

return (
Expand Down Expand Up @@ -172,9 +242,16 @@ export const LimitOrderCard: FC<LimitOrderCardProps> = ({
</Flex>
</Flex>
{/* Right group - status tag */}
<Tag flexShrink={0} colorScheme={tagColorScheme}>
{translate(`limitOrder.status.${status}`)}
</Tag>
<Flex direction='column' gap={1} align='flex-end' minWidth={0}>
<Tag flexShrink={0} colorScheme={tagColorScheme}>
<TagLabel>{translate(`limitOrder.status.${status}`)}</TagLabel>
</Tag>
{Boolean(warningText) && (
<Tooltip label={warningText}>
<WarningTwoIcon color='text.warning' boxSize={6} />
</Tooltip>
)}
</Flex>
</Flex>

{/* Price row */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,11 @@ export const LimitOrderList: FC<LimitOrderListProps> = ({ cardProps, onBack }) =
)}
{openLimitOrders !== undefined &&
openLimitOrders.length > 0 &&
openLimitOrders.map(({ sellAssetId, buyAssetId, order }) => (
openLimitOrders.map(({ accountId, sellAssetId, buyAssetId, order }) => (
<LimitOrderCard
key={order.uid}
uid={order.uid}
accountId={accountId}
sellAmountCryptoBaseUnit={order.sellAmount}
buyAmountCryptoBaseUnit={order.buyAmount}
buyAssetId={buyAssetId}
Expand All @@ -158,10 +159,11 @@ export const LimitOrderList: FC<LimitOrderListProps> = ({ cardProps, onBack }) =
<TabPanel px={0} py={0}>
<CardBody px={0} overflowY='auto' flex='1 1 auto'>
{historicalLimitOrders !== undefined && historicalLimitOrders.length > 0 ? (
historicalLimitOrders.map(({ sellAssetId, buyAssetId, order }) => (
historicalLimitOrders.map(({ accountId, sellAssetId, buyAssetId, order }) => (
<LimitOrderCard
key={order.uid}
uid={order.uid}
accountId={accountId}
sellAmountCryptoBaseUnit={order.sellAmount}
buyAmountCryptoBaseUnit={order.buyAmount}
buyAssetId={buyAssetId}
Expand Down

0 comments on commit 4d6a4e7

Please sign in to comment.