import { Bank, U64_MAX_BN } from '@blockworks-foundation/mango-v4' import { PerpMarket, PerpOrder, PerpOrderType, Serum3Market, Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side, } from '@blockworks-foundation/mango-v4' import Input from '@components/forms/Input' import { IconButton } from '@components/shared/Button' import ConnectEmptyState from '@components/shared/ConnectEmptyState' import FormatNumericValue from '@components/shared/FormatNumericValue' import Loading from '@components/shared/Loading' import SheenLoader from '@components/shared/SheenLoader' import SideBadge from '@components/shared/SideBadge' import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements' import Tooltip from '@components/shared/Tooltip' import { CheckIcon, NoSymbolIcon, PencilIcon, TrashIcon, XMarkIcon, } from '@heroicons/react/20/solid' import { Order } from '@project-serum/serum/lib/market' import { useWallet } from '@solana/wallet-adapter-react' import { PublicKey } from '@solana/web3.js' import mangoStore from '@store/mangoStore' import useMangoAccount from 'hooks/useMangoAccount' import useSelectedMarket from 'hooks/useSelectedMarket' import useUnownedAccount from 'hooks/useUnownedAccount' import useFilledOrders from 'hooks/useFilledOrders' import { useViewport } from 'hooks/useViewport' import { useTranslation } from 'next-i18next' import Link from 'next/link' import { ChangeEvent, useCallback, useState } from 'react' import { isMangoError } from 'types' import { notify } from 'utils/notifications' import { getDecimalCount } from 'utils/numbers' import { breakpoints } from 'utils/theme' import MarketLogos from './MarketLogos' import PerpSideBadge from './PerpSideBadge' import TableMarketName from './TableMarketName' export const findSerum3MarketPkInOpenOrders = ( o: Order, ): string | undefined => { const openOrders = mangoStore.getState().mangoAccount.openOrders let foundedMarketPk: string | undefined = undefined for (const [marketPk, orders] of Object.entries(openOrders)) { for (const order of orders) { if (order.orderId.eq(o.orderId)) { foundedMarketPk = marketPk break } } if (foundedMarketPk) { break } } return foundedMarketPk } const OpenOrders = () => { const { t } = useTranslation(['common', 'trade']) const openOrders = mangoStore((s) => s.mangoAccount.openOrders) const [cancelId, setCancelId] = useState('') const [modifyOrderId, setModifyOrderId] = useState( undefined, ) const [loadingModifyOrder, setLoadingModifyOrder] = useState(false) const [modifiedOrderSize, setModifiedOrderSize] = useState('') const [modifiedOrderPrice, setModifiedOrderPrice] = useState('') const { width } = useViewport() const showTableView = width ? width > breakpoints.md : false const { mangoAccountAddress } = useMangoAccount() const { connected } = useWallet() const { isUnownedAccount } = useUnownedAccount() const { selectedMarket } = useSelectedMarket() const { filledOrders, fetchingFilledOrders } = useFilledOrders() const handleCancelSerumOrder = useCallback( async (o: Order) => { const client = mangoStore.getState().client const group = mangoStore.getState().group const mangoAccount = mangoStore.getState().mangoAccount.current const actions = mangoStore.getState().actions if (!group || !mangoAccount) return const marketPk = findSerum3MarketPkInOpenOrders(o) if (!marketPk) return const market = group.getSerum3MarketByExternalMarket( new PublicKey(marketPk), ) setCancelId(o.orderId.toString()) try { const { signature: tx } = await client.serum3CancelOrder( group, mangoAccount, market!.serumMarketExternal, o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask, o.orderId, ) actions.fetchOpenOrders() notify({ type: 'success', title: 'Transaction successful', txid: tx, }) } catch (e) { console.error('Error canceling', e) if (isMangoError(e)) { notify({ title: t('trade:cancel-order-error'), description: e.message, txid: e.txid, type: 'error', }) } } finally { setCancelId('') } }, [t], ) const modifyOrder = useCallback( async (o: PerpOrder | Order) => { const client = mangoStore.getState().client const group = mangoStore.getState().group const mangoAccount = mangoStore.getState().mangoAccount.current const actions = mangoStore.getState().actions const baseSize = modifiedOrderSize ? Number(modifiedOrderSize) : o.size const price = modifiedOrderPrice ? Number(modifiedOrderPrice) : o.price if (!group || !mangoAccount) return setLoadingModifyOrder(true) try { let tx if (o instanceof PerpOrder) { tx = await client.modifyPerpOrder( group, mangoAccount, o.perpMarketIndex, o.orderId, o.side, price, Math.abs(baseSize), undefined, // maxQuoteQuantity Date.now(), PerpOrderType.limit, undefined, undefined, ) } else { const marketPk = findSerum3MarketPkInOpenOrders(o) if (!marketPk) return const market = group.getSerum3MarketByExternalMarket( new PublicKey(marketPk), ) tx = await client.modifySerum3Order( group, o.orderId, mangoAccount, market.serumMarketExternal, o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask, price, baseSize, Serum3SelfTradeBehavior.decrementTake, Serum3OrderType.limit, Date.now(), 10, ) } actions.fetchOpenOrders() notify({ type: 'success', title: 'Transaction successful', txid: tx.signature, }) } catch (e) { console.error('Error canceling', e) if (isMangoError(e)) { notify({ title: 'Unable to modify order', description: e.message, txid: e.txid, type: 'error', }) } } finally { cancelEditOrderForm() } }, [t, modifiedOrderSize, modifiedOrderPrice], ) const handleCancelPerpOrder = useCallback( async (o: PerpOrder) => { const client = mangoStore.getState().client const group = mangoStore.getState().group const mangoAccount = mangoStore.getState().mangoAccount.current const actions = mangoStore.getState().actions if (!group || !mangoAccount) return setCancelId(o.orderId.toString()) try { const { signature: tx } = await client.perpCancelOrder( group, mangoAccount, o.perpMarketIndex, o.orderId, ) actions.fetchOpenOrders(true) notify({ type: 'success', title: 'Transaction successful', txid: tx, }) } catch (e) { console.error('Error canceling', e) if (isMangoError(e)) { notify({ title: t('trade:cancel-order-error'), description: e.message, txid: e.txid, type: 'error', }) } } finally { setCancelId('') } }, [t], ) const showEditOrderForm = (order: Order | PerpOrder, tickSize: number) => { setModifyOrderId(order.orderId.toString()) setModifiedOrderSize(order.size.toString()) setModifiedOrderPrice(order.price.toFixed(getDecimalCount(tickSize))) } const cancelEditOrderForm = () => { setModifyOrderId(undefined) setLoadingModifyOrder(false) setModifiedOrderSize('') setModifiedOrderPrice('') } return mangoAccountAddress && Object.values(openOrders).flat().length ? ( showTableView ? ( {!isUnownedAccount ? ( {Object.entries(openOrders) .sort() .map(([marketPk, orders]) => { return orders.map((o) => { const group = mangoStore.getState().group! let market: PerpMarket | Serum3Market let tickSize: number let minOrderSize: number let expiryTimestamp: number | undefined let value: number let filledQuantity = 0 if (o instanceof PerpOrder) { market = group.getPerpMarketByMarketIndex(o.perpMarketIndex) tickSize = market.tickSize minOrderSize = market.minOrderSize expiryTimestamp = o.expiryTimestamp === U64_MAX_BN ? 0 : o.expiryTimestamp.toNumber() value = o.size * o.price // Find the filled perp order, // the api returns client order ids for perps, but PerpOrder[] only has orderId const mangoAccount = mangoStore.getState().mangoAccount.current const perpClientId = mangoAccount?.perpOpenOrders?.find((p) => p.id.eq(o.orderId), )?.clientId if (perpClientId) { const filledOrder = filledOrders?.fills?.find( (f) => f.order_id == perpClientId.toString(), ) filledQuantity = filledOrder ? filledOrder.quantity : 0 } } else { market = group.getSerum3MarketByExternalMarket( new PublicKey(marketPk), ) const serumMarket = group.getSerum3ExternalMarket( market.serumMarketExternal, ) const quoteBank = group.getFirstBankByTokenIndex( market.quoteTokenIndex, ) tickSize = serumMarket.tickSize minOrderSize = serumMarket.minOrderSize value = o.size * o.price * quoteBank.uiPrice const filledOrder = filledOrders?.fills?.find( (f) => o.orderId.toString() === f.order_id, ) filledQuantity = filledOrder ? filledOrder.quantity : 0 } const side = o instanceof PerpOrder ? 'bid' in o.side ? 'long' : 'short' : o.side return ( {modifyOrderId !== o.orderId.toString() ? ( <> ) : ( <> )} {!isUnownedAccount ? ( ) : null} ) }) }) .flat()}
{t('market')} {t('trade:size')} {t('price')} {t('trade:filled')} {t('value')} ) : null}
) => setModifiedOrderSize(e.target.value) } /> ) => setModifiedOrderPrice(e.target.value) } /> {fetchingFilledOrders ? (
) : ( )}
{expiryTimestamp ? (
{`Expires ${new Date( expiryTimestamp * 1000, ).toLocaleTimeString()}`}
) : null}
{modifyOrderId !== o.orderId.toString() ? ( <> showEditOrderForm(o, tickSize)} size="small" > o instanceof PerpOrder ? handleCancelPerpOrder(o) : handleCancelSerumOrder(o) } size="small" > {cancelId === o.orderId.toString() ? ( ) : ( )} ) : ( <> modifyOrder(o)} size="small" > {loadingModifyOrder ? ( ) : ( )} )}
) : (
{Object.entries(openOrders).map(([marketPk, orders]) => { return orders.map((o) => { const group = mangoStore.getState().group! let market: PerpMarket | Serum3Market let tickSize: number let minOrderSize: number let quoteBank: Bank | undefined if (o instanceof PerpOrder) { market = group.getPerpMarketByMarketIndex(o.perpMarketIndex) tickSize = market.tickSize minOrderSize = market.minOrderSize } else { market = group.getSerum3MarketByExternalMarket( new PublicKey(marketPk), ) const serumMarket = group.getSerum3ExternalMarket( market.serumMarketExternal, ) quoteBank = group.getFirstBankByTokenIndex(market.quoteTokenIndex) tickSize = serumMarket.tickSize minOrderSize = serumMarket.minOrderSize } return (
{modifyOrderId !== o.orderId.toString() ? (
{selectedMarket?.name === market.name ? ( {market.name} ) : ( {market.name} )} {o instanceof PerpOrder ? ( ) : ( )}

{' at '} {' '} {quoteBank && quoteBank.name !== 'USDC' ? ( {quoteBank.name} ) : null}

) : ( <>

{`${t('edit')} ${ market.name } ${t('order')}`}

{t('trade:size')}

) => setModifiedOrderSize(e.target.value) } />

{t('price')}

) => setModifiedOrderPrice(e.target.value) } />
)}
{!isUnownedAccount ? (
{modifyOrderId !== o.orderId.toString() ? ( <> showEditOrderForm(o, tickSize)} size="small" > o instanceof PerpOrder ? handleCancelPerpOrder(o) : handleCancelSerumOrder(o) } size="small" > {cancelId === o.orderId.toString() ? ( ) : ( )} ) : ( <> modifyOrder(o)} size="small" > {loadingModifyOrder ? ( ) : ( )} )}
) : null}
) }) })}
) ) : mangoAccountAddress || connected ? (

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

) : (
) } export default OpenOrders