import { IconButton, LinkButton } from '@components/shared/Button' import ConnectEmptyState from '@components/shared/ConnectEmptyState' import { SortableColumnHeader, Table, Td, Th, TrBody, TrHead, } from '@components/shared/TableElements' import { ChevronDownIcon, NoSymbolIcon, TrashIcon, } from '@heroicons/react/20/solid' import { BN } from '@project-serum/anchor' import { useWallet } from '@solana/wallet-adapter-react' import mangoStore from '@store/mangoStore' import useMangoAccount from 'hooks/useMangoAccount' import useMangoGroup from 'hooks/useMangoGroup' import { useSortableData } from 'hooks/useSortableData' import { useViewport } from 'hooks/useViewport' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { notify } from 'utils/notifications' import { floorToDecimal, formatNumericValue } from 'utils/numbers' import { breakpoints } from 'utils/theme' import * as sentry from '@sentry/nextjs' import { isMangoError } from 'types' import Loading from '@components/shared/Loading' import SideBadge from '@components/shared/SideBadge' import { Disclosure, Transition } from '@headlessui/react' import SheenLoader from '@components/shared/SheenLoader' import { formatTokenSymbol } from 'utils/tokens' export const handleCancelTriggerOrder = async ( id: BN, setCancelId?: (id: string) => void, ) => { try { const client = mangoStore.getState().client const group = mangoStore.getState().group const actions = mangoStore.getState().actions const mangoAccount = mangoStore.getState().mangoAccount.current if (!mangoAccount || !group) return if (setCancelId) { setCancelId(id.toString()) } try { const { signature: tx, slot } = await client.tokenConditionalSwapCancel( group, mangoAccount, id, ) notify({ title: 'Transaction confirmed', type: 'success', txid: tx, noSound: true, }) actions.fetchGroup() await actions.reloadMangoAccount(slot) } catch (e) { console.error('failed to cancel swap order', e) sentry.captureException(e) if (isMangoError(e)) { notify({ title: 'Transaction failed', description: e.message, txid: e?.txid, type: 'error', }) } } } catch (e) { console.error('failed to cancel trigger order', e) } finally { if (setCancelId) { setCancelId('') } } } export const handleCancelAll = async ( setCancelId: (id: '' | 'all') => void, ) => { try { const client = mangoStore.getState().client const group = mangoStore.getState().group const actions = mangoStore.getState().actions const mangoAccount = mangoStore.getState().mangoAccount.current if (!mangoAccount || !group) return setCancelId('all') try { const { signature: tx, slot } = await client.tokenConditionalSwapCancelAll(group, mangoAccount) notify({ title: 'Transaction confirmed', type: 'success', txid: tx, noSound: true, }) actions.fetchGroup() await actions.reloadMangoAccount(slot) } catch (e) { console.error('failed to cancel trigger orders', e) sentry.captureException(e) if (isMangoError(e)) { notify({ title: 'Transaction failed', description: e.message, txid: e?.txid, type: 'error', }) } } } catch (e) { console.error('failed to cancel swap order', e) } finally { setCancelId('') } } const SwapOrders = () => { const { t } = useTranslation(['common', 'swap', 'trade']) const { width } = useViewport() const showTableView = width ? width > breakpoints.md : false const { mangoAccount, mangoAccountAddress } = useMangoAccount() const { group } = useMangoGroup() const { connected } = useWallet() const [cancelId, setCancelId] = useState('') const orders = useMemo(() => { if (!mangoAccount) return [] return mangoAccount.tokenConditionalSwaps.filter((tcs) => tcs.hasData) }, [mangoAccount]) const formattedTableData = useCallback(() => { if (!group) return [] const formatted = [] for (const order of orders) { const buyBank = group.getFirstBankByTokenIndex(order.buyTokenIndex) const sellBank = group.getFirstBankByTokenIndex(order.sellTokenIndex) const maxBuy = floorToDecimal( order.getMaxBuyUi(group), buyBank.mintDecimals, ).toNumber() const maxSell = floorToDecimal( order.getMaxSellUi(group), sellBank.mintDecimals, ).toNumber() let size let side if (maxBuy === 0 || maxBuy > maxSell) { size = maxSell side = 'sell' } else { size = maxBuy side = 'buy' } const formattedBuyTokenName = formatTokenSymbol(buyBank.name) const formattedSellTokenName = formatTokenSymbol(sellBank.name) const pair = side === 'sell' ? `${formattedSellTokenName}/${formattedBuyTokenName}` : `${formattedBuyTokenName}/${formattedSellTokenName}` const triggerPrice = order.getThresholdPriceUi(group) const pricePremium = order.getPricePremium() const filled = order.getSoldUi(group) const currentPrice = order.getCurrentPairPriceUi(group) const sellTokenPerBuyToken = !!Object.prototype.hasOwnProperty.call( order.priceDisplayStyle, 'sellTokenPerBuyToken', ) const baseTokenName = side === 'buy' ? formattedBuyTokenName : formattedSellTokenName const quoteTokenName = !sellTokenPerBuyToken ? formattedBuyTokenName : formattedSellTokenName const quoteDecimals = !sellTokenPerBuyToken ? buyBank.mintDecimals : sellBank.mintDecimals const triggerDirection = triggerPrice < currentPrice ? '<=' : '>=' const data = { ...order, baseTokenName, currentPrice, fee: pricePremium, filled, pair, quoteDecimals, quoteTokenName, side, size, triggerDirection, triggerPrice, } formatted.push(data) } return formatted }, [group, orders]) const { items: tableData, requestSort, sortConfig, } = useSortableData(formattedTableData()) return orders.length ? ( showTableView ? ( {tableData.map((data, i) => { const { baseTokenName, currentPrice, fee, filled, pair, quoteDecimals, quoteTokenName, side, size, triggerDirection, triggerPrice, } = data return ( ) })}
requestSort('pair')} sortConfig={sortConfig} title={t('swap:pair')} />
requestSort('side')} sortConfig={sortConfig} title={t('trade:side')} />
requestSort('size')} sortConfig={sortConfig} title={t('trade:size')} />
requestSort('filled')} sortConfig={sortConfig} title={t('trade:filled')} />
requestSort('currentPrice')} sortConfig={sortConfig} title={t('trade:current-price')} />
requestSort('triggerPrice')} sortConfig={sortConfig} title={t('trade:trigger-price')} />
requestSort('fee')} sortConfig={sortConfig} title={t('trade:est-slippage')} />
handleCancelAll(setCancelId)}> {t('trade:cancel-all')}
{pair}

{size} {' '} {baseTokenName}

{filled}/{size} {' '} {baseTokenName}

{formatNumericValue(currentPrice, quoteDecimals)} {' '} {quoteTokenName}

{triggerDirection}{' '} {formatNumericValue(triggerPrice, quoteDecimals)} {' '} {quoteTokenName}

{fee.toFixed(2)}%

handleCancelTriggerOrder(data.id, setCancelId) } size="small" > {cancelId === data.id.toString() || cancelId === 'all' ? ( ) : ( )}
) : (
{tableData.map((data, i) => { const { baseTokenName, currentPrice, fee, filled, pair, quoteDecimals, quoteTokenName, side, size, triggerDirection, triggerPrice, } = data return ( {({ open }) => ( <>
{pair}

{size} {' '} {baseTokenName} {' at '} {formatNumericValue(triggerPrice, quoteDecimals)} {' '} {quoteTokenName}

{t('trade:size')}

{size} {' '} {baseTokenName}

{t('trade:filled')}

{filled}/{size} {' '} {baseTokenName}

{t('trade:current-price')}

{formatNumericValue(currentPrice, quoteDecimals)} {' '} {quoteTokenName}

{t('trade:trigger-price')}

{triggerDirection}{' '} {formatNumericValue(triggerPrice, quoteDecimals)} {' '} {quoteTokenName}

{t('trade:est-slippage')}

{fee.toFixed(2)}%

{t('cancel')}

handleCancelTriggerOrder(data.id, setCancelId) } > {cancelId === data.id.toString() ? (
) : ( t('trade:cancel-order') )}
)} ) })}
) ) : mangoAccountAddress || connected ? (

{t('trade:no-trigger-orders')}

) : (
) } export default SwapOrders