diff --git a/components/trade/MarketCloseModal.tsx b/components/trade/MarketCloseModal.tsx index dae2ec3c..3892e9ae 100644 --- a/components/trade/MarketCloseModal.tsx +++ b/components/trade/MarketCloseModal.tsx @@ -1,4 +1,4 @@ -import { FunctionComponent, useCallback, useState } from 'react' +import { FunctionComponent, useCallback, useEffect, useState } from 'react' import mangoStore from '@store/mangoStore' import { useTranslation } from 'next-i18next' import { @@ -16,6 +16,7 @@ import { SOUND_SETTINGS_KEY } from 'utils/constants' import { INITIAL_SOUND_SETTINGS } from '@components/settings/SoundSettings' import { Howl } from 'howler' import { isMangoError } from 'types' +import { decodeBook, decodeBookL2 } from './Orderbook' interface MarketCloseModalProps { onClose: () => void @@ -30,6 +31,8 @@ const successSound = new Howl({ volume: 0.5, }) +type BidsAndAsks = number[][] | null + const MarketCloseModal: FunctionComponent = ({ onClose, isOpen, @@ -37,81 +40,155 @@ const MarketCloseModal: FunctionComponent = ({ }) => { const { t } = useTranslation(['common', 'trade']) const [submitting, setSubmitting] = useState(false) - const group = mangoStore.getState().group + const connection = mangoStore((s) => s.connection) + const group = mangoStore((s) => s.group) const perpMarket = group?.getPerpMarketByMarketIndex(position.marketIndex) const [soundSettings] = useLocalStorageState( SOUND_SETTINGS_KEY, INITIAL_SOUND_SETTINGS ) + const [asks, setAsks] = useState(null) + const [bids, setBids] = useState(null) - const handleMarketClose = useCallback(async () => { + // subscribe to the bids and asks orderbook accounts + useEffect(() => { + console.log('setting up orderbook websockets') const client = mangoStore.getState().client - const group = mangoStore.getState().group - const mangoAccount = mangoStore.getState().mangoAccount.current - const actions = mangoStore.getState().actions + if (!group || !perpMarket) return - if (!group || !mangoAccount || !perpMarket) return - setSubmitting(true) - try { - const baseSize = position.getBasePositionUi(perpMarket) - const sideToClose = baseSize > 0 ? 'sell' : 'buy' - const orderbook = mangoStore.getState().selectedMarket.orderbook - const price = calculateEstPriceForBaseSize( - orderbook, - baseSize, - sideToClose + let bidSubscriptionId: number | undefined = undefined + let askSubscriptionId: number | undefined = undefined + let lastSeenBidsSlot: number + let lastSeenAsksSlot: number + const bidsPk = perpMarket.bids + if (bidsPk) { + connection + .getAccountInfoAndContext(bidsPk) + .then(({ context, value: info }) => { + if (!info) return + const decodedBook = decodeBook(client, perpMarket, info, 'bids') + setBids(decodeBookL2(decodedBook)) + lastSeenBidsSlot = context.slot + }) + bidSubscriptionId = connection.onAccountChange( + bidsPk, + (info, context) => { + if (context.slot > lastSeenBidsSlot) { + const decodedBook = decodeBook(client, perpMarket, info, 'bids') + setBids(decodeBookL2(decodedBook)) + } + }, + 'processed' ) + } - const maxSlippage = 0.025 - // const perpOrderType = - // tradeForm.tradeType === 'Market' - // ? PerpOrderType.market - // : tradeForm.ioc - // ? PerpOrderType.immediateOrCancel - // : tradeForm.postOnly - // ? PerpOrderType.postOnly - // : PerpOrderType.limit - const tx = await client.perpPlaceOrder( - group, - mangoAccount, - perpMarket.perpMarketIndex, - sideToClose === 'buy' ? PerpOrderSide.bid : PerpOrderSide.ask, - price * (sideToClose === 'buy' ? 1 + maxSlippage : 1 - maxSlippage), - Math.abs(baseSize) * 2, // send a larger size to ensure full order is closed - undefined, // maxQuoteQuantity - Date.now(), - PerpOrderType.immediateOrCancel, - true, // reduce only - undefined, - undefined + const asksPk = perpMarket.asks + if (asksPk) { + connection + .getAccountInfoAndContext(asksPk) + .then(({ context, value: info }) => { + if (!info) return + const decodedBook = decodeBook(client, perpMarket, info, 'asks') + setAsks(decodeBookL2(decodedBook)) + lastSeenAsksSlot = context.slot + }) + askSubscriptionId = connection.onAccountChange( + asksPk, + (info, context) => { + if (context.slot > lastSeenAsksSlot) { + const decodedBook = decodeBook(client, perpMarket, info, 'asks') + setAsks(decodeBookL2(decodedBook)) + } + }, + 'processed' ) - actions.fetchOpenOrders() - set((s) => { - s.successAnimation.trade = true - }) - if (soundSettings['swap-success']) { - successSound.play() + } + return () => { + if (typeof bidSubscriptionId !== 'undefined') { + connection.removeAccountChangeListener(bidSubscriptionId) } - notify({ - type: 'success', - title: 'Transaction successful', - txid: tx, - }) - } catch (e) { - if (isMangoError(e)) { + if (typeof askSubscriptionId !== 'undefined') { + connection.removeAccountChangeListener(askSubscriptionId) + } + } + }, [connection, perpMarket]) + + const handleMarketClose = useCallback( + async (bids: BidsAndAsks, asks: BidsAndAsks) => { + const client = mangoStore.getState().client + const mangoAccount = mangoStore.getState().mangoAccount.current + const actions = mangoStore.getState().actions + + if (!group || !mangoAccount || !perpMarket || !bids || !asks) { notify({ - title: 'There was an issue.', - description: e.message, - txid: e?.txid, + title: 'Something went wrong. Try again later', type: 'error', }) + return } - console.error('Place trade error:', e) - } finally { - setSubmitting(false) - onClose() - } - }, []) + setSubmitting(true) + try { + const baseSize = position.getBasePositionUi(perpMarket) + const sideToClose = baseSize > 0 ? 'sell' : 'buy' + const orderbook = { bids, asks } + const price = calculateEstPriceForBaseSize( + orderbook, + baseSize, + sideToClose + ) + + const maxSlippage = 0.025 + // const perpOrderType = + // tradeForm.tradeType === 'Market' + // ? PerpOrderType.market + // : tradeForm.ioc + // ? PerpOrderType.immediateOrCancel + // : tradeForm.postOnly + // ? PerpOrderType.postOnly + // : PerpOrderType.limit + const tx = await client.perpPlaceOrder( + group, + mangoAccount, + perpMarket.perpMarketIndex, + sideToClose === 'buy' ? PerpOrderSide.bid : PerpOrderSide.ask, + price * (sideToClose === 'buy' ? 1 + maxSlippage : 1 - maxSlippage), + Math.abs(baseSize) * 2, // send a larger size to ensure full order is closed + undefined, // maxQuoteQuantity + Date.now(), + PerpOrderType.immediateOrCancel, + true, // reduce only + undefined, + undefined + ) + actions.fetchOpenOrders() + set((s) => { + s.successAnimation.trade = true + }) + if (soundSettings['swap-success']) { + successSound.play() + } + notify({ + type: 'success', + title: 'Transaction successful', + txid: tx, + }) + } catch (e) { + if (isMangoError(e)) { + notify({ + title: 'There was an issue.', + description: e.message, + txid: e?.txid, + type: 'error', + }) + } + console.error('Place trade error:', e) + } finally { + setSubmitting(false) + onClose() + } + }, + [] + ) return ( @@ -121,7 +198,7 @@ const MarketCloseModal: FunctionComponent = ({
{t('trade:price-expect')}