diff --git a/apps/trading/pages/party-active-orders-handler.tsx b/apps/trading/pages/party-active-orders-handler.tsx index 8e60ead80..2a5db1af3 100644 --- a/apps/trading/pages/party-active-orders-handler.tsx +++ b/apps/trading/pages/party-active-orders-handler.tsx @@ -1,15 +1,8 @@ -import { useDataProvider } from '@vegaprotocol/data-provider'; -import { activeOrdersProvider } from '@vegaprotocol/orders'; +import { useActiveOrders } from '@vegaprotocol/orders'; import { useVegaWallet } from '@vegaprotocol/wallet-react'; export const PartyActiveOrdersHandler = () => { const { pubKey } = useVegaWallet(); - const variables = { partyId: pubKey || '' }; - const skip = !pubKey; - useDataProvider({ - dataProvider: activeOrdersProvider, - variables, - skip, - }); + useActiveOrders(pubKey); return null; }; diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.spec.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.spec.tsx index 444bd81fb..a3089d677 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.spec.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.spec.tsx @@ -2,7 +2,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { generateMarket } from '../../test-helpers'; -import { StopOrder } from './deal-ticket-stop-order'; +import { NoOpenVolumeWarning, StopOrder } from './deal-ticket-stop-order'; import * as Schema from '@vegaprotocol/types'; import { MockedProvider } from '@apollo/client/testing'; import { @@ -84,6 +84,22 @@ jest.mock('@vegaprotocol/data-provider', () => ({ useDataProvider: jest.fn((...args) => mockDataProvider(...args)), })); +const mockUseOpenVolume = jest.fn(() => ({ + openVolume: '0', +})); + +jest.mock('@vegaprotocol/positions', () => ({ + ...jest.requireActual('@vegaprotocol/positions'), + useOpenVolume: jest.fn(() => mockUseOpenVolume()), +})); + +const mockActiveOrders = jest.fn(() => ({})); + +jest.mock('@vegaprotocol/orders', () => ({ + ...jest.requireActual('@vegaprotocol/orders'), + useActiveOrders: jest.fn(() => mockActiveOrders()), +})); + describe('StopOrder', () => { beforeEach(() => { localStorage.clear(); @@ -576,3 +592,57 @@ describe('StopOrder', () => { expect(screen.getByTestId(numberOfActiveOrdersLimit)).toBeInTheDocument(); }); }); + +describe('NoOpenVolumeWarning', () => { + const testId = 'stop-order-warning-position'; + + it('shows warning if there is no possible position to reduce', () => { + const activeOrders = [ + { + side: Schema.Side.SIDE_BUY, + remaining: '8', + }, + { + side: Schema.Side.SIDE_BUY, + remaining: '2', + }, + { + side: Schema.Side.SIDE_SELL, + remaining: '7', + }, + { + side: Schema.Side.SIDE_SELL, + remaining: '3', + }, + ]; + mockActiveOrders.mockReturnValue({ data: activeOrders }); + + mockUseOpenVolume.mockReturnValue({ openVolume: '10' }); + const result = render( + + ); + // side buy, volume + remaining 20 + expect(screen.getByTestId(testId)).toBeInTheDocument(); + + result.rerender( + + ); + // side sell, volume - remaining = 0 + expect(screen.queryByTestId(testId)).not.toBeInTheDocument(); + + result.rerender( + + ); + // side sell, volume - remaining = 0 + expect(screen.queryByTestId(testId)).toBeInTheDocument(); + + mockUseOpenVolume.mockReturnValue({ openVolume: '2' }); + render(); + // side buy, volume + remaining = 12 + expect(screen.queryByTestId(testId)).toBeInTheDocument(); + + render(); + // side sell, volume - remaining = -8 + expect(screen.getByTestId(testId)).toBeInTheDocument(); + }); +}); diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx index 87a253e71..7800c4562 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx @@ -56,9 +56,10 @@ import { validateExpiration } from '../../utils'; import { NOTIONAL_SIZE_TOOLTIP_TEXT } from '../../constants'; import { KeyValue } from './key-value'; import { useDataProvider } from '@vegaprotocol/data-provider'; -import { stopOrdersProvider } from '@vegaprotocol/orders'; +import { useActiveOrders, stopOrdersProvider } from '@vegaprotocol/orders'; import { useT } from '../../use-t'; import { determinePriceStep, determineSizeStep } from '@vegaprotocol/utils'; +import { useOpenVolume } from '@vegaprotocol/positions'; export interface StopOrderProps { market: Market; @@ -453,6 +454,47 @@ const Price = ({ ); }; +export const NoOpenVolumeWarning = ({ + side, + partyId, + marketId, +}: { + side: Schema.Side; + partyId?: string; + marketId: string; +}) => { + const { data: activeOrders } = useActiveOrders(partyId, marketId); + const t = useT(); + const { openVolume } = useOpenVolume(partyId, marketId) || {}; + const volume = BigInt(openVolume || 0); + const remaining = activeOrders + ? activeOrders.reduce((size, order) => { + if (side !== order.side) { + size += BigInt(order.remaining); + } + return size; + }, BigInt(0)) + : BigInt(0); + + if ( + (side === Schema.Side.SIDE_BUY && volume - remaining < BigInt(0)) || + (side === Schema.Side.SIDE_SELL && volume + remaining > BigInt(0)) + ) { + return null; + } + return ( +
+ +
+ ); +}; + const TimeInForce = ({ control, oco, @@ -1188,8 +1230,13 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => { )} /> - ) : null} - + ) : ( + + )} ({ DealTicketFeeDetails: () =>
, +})); +jest.mock('./deal-ticket-margin-details', () => ({ DealTicketMarginDetails: () => (
), diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx index 6e1accfef..a8c4b59ea 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -36,7 +36,7 @@ import { formatForInput, formatValue, } from '@vegaprotocol/utils'; -import { activeOrdersProvider } from '@vegaprotocol/orders'; +import { useActiveOrders } from '@vegaprotocol/orders'; import { getAsset, getDerivedPrice, @@ -252,11 +252,7 @@ export const DealTicket = ({ market.positionDecimalPlaces ); - const { data: activeOrders } = useDataProvider({ - dataProvider: activeOrdersProvider, - variables: { partyId: pubKey || '', marketId: market.id }, - skip: !pubKey, - }); + const { data: activeOrders } = useActiveOrders(pubKey, market.id); const { data: margin } = useDataProvider({ dataProvider: marginModeDataProvider, variables: { partyId: pubKey || '', marketId: market.id }, diff --git a/libs/deal-ticket/src/components/deal-ticket/margin-mode-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/margin-mode-selector.tsx index 5eedd1e8f..da8c19d9f 100644 --- a/libs/deal-ticket/src/components/deal-ticket/margin-mode-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/margin-mode-selector.tsx @@ -25,7 +25,7 @@ import { useMarginAccountBalance, } from '@vegaprotocol/accounts'; import { useMaxLeverage, useOpenVolume } from '@vegaprotocol/positions'; -import { activeOrdersProvider } from '@vegaprotocol/orders'; +import { useActiveOrders } from '@vegaprotocol/orders'; import { usePositionEstimate } from '../../hooks/use-position-estimate'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import { getAsset, useMarket } from '@vegaprotocol/markets'; @@ -64,10 +64,7 @@ export const MarginChange = ({ openVolume: '0', averageEntryPrice: '0', }; - const { data: activeOrders } = useDataProvider({ - dataProvider: activeOrdersProvider, - variables: { partyId: partyId || '', marketId }, - }); + const { data: activeOrders } = useActiveOrders(partyId, marketId); const orders = activeOrders ? activeOrders.map((order) => ({ isMarketOrder: order.type === Schema.OrderType.TYPE_MARKET, diff --git a/libs/i18n/src/locales/en/deal-ticket.json b/libs/i18n/src/locales/en/deal-ticket.json index d3d599b43..203697f8d 100644 --- a/libs/i18n/src/locales/en/deal-ticket.json +++ b/libs/i18n/src/locales/en/deal-ticket.json @@ -106,6 +106,7 @@ "Stop Limit": "Stop Limit", "Stop Market": "Stop Market", "Stop order will be triggered immediately": "Stop order will be triggered immediately", + "Stop orders are reduce only and this order would increase your position.": "Stop orders are reduce only and this order would increase your position.", "Strategy": "Strategy", "Submit": "Submit", "Subtotal": "Subtotal", diff --git a/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts b/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts index 4cb2747a0..a77e870ad 100644 --- a/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts +++ b/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts @@ -4,6 +4,7 @@ import { makeDataProvider, makeDerivedDataProvider, defaultAppend as append, + useDataProvider, } from '@vegaprotocol/data-provider'; import { type Market } from '@vegaprotocol/markets'; import { marketsMapProvider } from '@vegaprotocol/markets'; @@ -208,6 +209,18 @@ export const activeOrdersProvider = makeDerivedDataProvider< } ); +export const useActiveOrders = ( + partyId: string | undefined, + marketId?: string +) => + useDataProvider({ + dataProvider: activeOrdersProvider, + variables: marketId + ? { partyId: partyId || '', marketId } + : { partyId: partyId || '' }, + skip: !partyId, + }); + export const ordersWithMarketProvider = makeDerivedDataProvider< (Order & Cursor)[], never, diff --git a/libs/types/src/global-types-mappings.ts b/libs/types/src/global-types-mappings.ts index 5fbc370c0..4ddd2b573 100644 --- a/libs/types/src/global-types-mappings.ts +++ b/libs/types/src/global-types-mappings.ts @@ -10,7 +10,10 @@ import { type TransferStatus, MarketUpdateType, } from './__generated__/types'; -import type { AccountType } from './__generated__/types'; +import type { + AccountType, + StopOrderRejectionReason, +} from './__generated__/types'; import type { AuctionTrigger, DataSourceSpecStatus, @@ -285,6 +288,29 @@ export const StopOrderStatusMapping: { STATUS_UNSPECIFIED: 'Unspecified', }; +/** + * Stop order rejection reason mappings. + */ +export const StopOrderRejectionReasonMapping: { + [T in StopOrderRejectionReason]: string; +} = { + REJECTION_REASON_TRADING_NOT_ALLOWED: 'Trading is not allowed yet', + REJECTION_REASON_EXPIRY_IN_THE_PAST: + 'Expiry of the stop order is in the past', + REJECTION_REASON_MUST_BE_REDUCE_ONLY: + 'Stop orders submission must be reduce only', + REJECTION_REASON_MAX_STOP_ORDERS_PER_PARTY_REACHED: + 'Party has reached the maximum stop orders allowed for this market', + REJECTION_REASON_STOP_ORDER_NOT_ALLOWED_WITHOUT_A_POSITION: + 'Stop orders are not allowed without a position', + REJECTION_REASON_STOP_ORDER_NOT_CLOSING_THE_POSITION: + 'This stop order does not close the position', + REJECTION_REASON_STOP_ORDER_NOT_ALLOWED_DURING_OPENING_AUCTION: + 'Stop orders are not allowed during the opening auction', + REJECTION_REASON_STOP_ORDER_CANNOT_MATCH_OCO_EXPIRY_TIMES: + 'Stop order cannot have matching OCO expiry times', +}; + /** * Valid order types, these determine what happens when an order is added to the book */ diff --git a/libs/web3/src/lib/Orders.graphql b/libs/web3/src/lib/Orders.graphql index ea41306f5..c8515d2e2 100644 --- a/libs/web3/src/lib/Orders.graphql +++ b/libs/web3/src/lib/Orders.graphql @@ -42,6 +42,7 @@ query StopOrderById($stopOrderId: ID!) { expiryStrategy triggerDirection status + rejectionReason createdAt updatedAt partyId diff --git a/libs/web3/src/lib/__generated__/Orders.ts b/libs/web3/src/lib/__generated__/Orders.ts index e53c1f02c..628b2f892 100644 --- a/libs/web3/src/lib/__generated__/Orders.ts +++ b/libs/web3/src/lib/__generated__/Orders.ts @@ -15,7 +15,7 @@ export type StopOrderByIdQueryVariables = Types.Exact<{ }>; -export type StopOrderByIdQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null }; +export type StopOrderByIdQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, rejectionReason?: Types.StopOrderRejectionReason | null, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null }; export const OrderByIdDocument = gql` @@ -92,6 +92,7 @@ export const StopOrderByIdDocument = gql` expiryStrategy triggerDirection status + rejectionReason createdAt updatedAt partyId diff --git a/libs/web3/src/lib/use-vega-transaction-store.tsx b/libs/web3/src/lib/use-vega-transaction-store.tsx index 0ae4a2a0e..21da971a1 100644 --- a/libs/web3/src/lib/use-vega-transaction-store.tsx +++ b/libs/web3/src/lib/use-vega-transaction-store.tsx @@ -47,6 +47,7 @@ export interface VegaTransactionStore { ) => void; dismiss: (index: number) => void; delete: (index: number) => void; + getTransaction: (txHash: string) => VegaStoredTxState | undefined; updateWithdrawal: ( withdrawal: NonNullable, withdrawalApproval: NonNullable @@ -60,6 +61,12 @@ export interface VegaTransactionStore { export const useVegaTransactionStore = create()( subscribeWithSelector((set, get) => ({ transactions: [] as (VegaStoredTxState | undefined)[], + getTransaction: (txHash: string) => { + return get().transactions.find( + (transaction) => + transaction?.txHash && transaction.txHash.toLowerCase() === txHash + ); + }, create: (body: Transaction, order?: OrderTxUpdateFieldsFragment) => { const transactions = get().transactions; const now = new Date(); diff --git a/libs/web3/src/lib/use-vega-transaction-updater.spec.tsx b/libs/web3/src/lib/use-vega-transaction-updater.spec.tsx index fb5c9df79..6238e3b0c 100644 --- a/libs/web3/src/lib/use-vega-transaction-updater.spec.tsx +++ b/libs/web3/src/lib/use-vega-transaction-updater.spec.tsx @@ -23,6 +23,9 @@ import { OrderTimeInForce, OrderType, Side, + StopOrderRejectionReason, + StopOrderRejectionReasonMapping, + StopOrderStatus, WithdrawalStatus, } from '@vegaprotocol/types'; @@ -48,14 +51,22 @@ jest.mock('./wait-for-withdrawal-approval', () => ({ waitForWithdrawalApproval: () => mockWaitForWithdrawalApproval(), })); +const mockWaitForStopOrder = jest.fn(); + +jest.mock('./wait-for-stop-order', () => ({ + waitForStopOrder: () => mockWaitForStopOrder(), +})); + const updateWithdrawal = jest.fn(); const updateOrder = jest.fn(); const updateTransactionResult = jest.fn(); +const getTransaction = jest.fn(); const defaultState: Partial = { updateWithdrawal, updateOrder, updateTransactionResult, + getTransaction, }; const mockTransactionStoreState = jest.fn, []>(); @@ -180,6 +191,29 @@ describe('useVegaTransactionManager', () => { }); }); + it('waits for stop order and sets error if rejected', async () => { + getTransaction.mockReturnValueOnce({ + signature: 'signature', + body: { + stopOrdersSubmission: {}, + }, + }); + const rejectionReason = + StopOrderRejectionReason.REJECTION_REASON_STOP_ORDER_NOT_CLOSING_THE_POSITION; + mockTransactionStoreState.mockReturnValue(defaultState); + mockWaitForStopOrder.mockResolvedValueOnce({ + status: StopOrderStatus.STATUS_REJECTED, + rejectionReason, + }); + render([mockedTransactionResultBusEvent]); + await waitFor(() => { + expect(updateTransactionResult).toHaveBeenCalledWith({ + ...transactionResultBusEvent, + error: StopOrderRejectionReasonMapping[rejectionReason], + }); + }); + }); + it('updates withdrawal on WithdrawalBusEvents', async () => { mockTransactionStoreState.mockReturnValue(defaultState); const erc20WithdrawalApproval = {}; diff --git a/libs/web3/src/lib/use-vega-transaction-updater.tsx b/libs/web3/src/lib/use-vega-transaction-updater.tsx index 6319317f0..553d132dd 100644 --- a/libs/web3/src/lib/use-vega-transaction-updater.tsx +++ b/libs/web3/src/lib/use-vega-transaction-updater.tsx @@ -7,6 +7,16 @@ import { } from './__generated__/TransactionResult'; import { useVegaTransactionStore } from './use-vega-transaction-store'; import { waitForWithdrawalApproval } from './wait-for-withdrawal-approval'; +import { + determineId, + isStopOrdersSubmissionTransaction, +} from '@vegaprotocol/wallet'; +import { waitForStopOrder } from './wait-for-stop-order'; +import { + StopOrderRejectionReasonMapping, + StopOrderStatus, + StopOrderStatusMapping, +} from '@vegaprotocol/types'; export const useVegaTransactionUpdater = () => { const client = useApolloClient(); @@ -17,6 +27,9 @@ export const useVegaTransactionUpdater = () => { const updateTransaction = useVegaTransactionStore( (state) => state.updateTransactionResult ); + const getTransaction = useVegaTransactionStore( + (state) => state.getTransaction + ); const { pubKey } = useVegaWallet(); const variables = { partyId: pubKey || '' }; @@ -51,11 +64,43 @@ export const useVegaTransactionUpdater = () => { variables, skip, fetchPolicy: 'no-cache', - onData: ({ data: result }) => - result.data?.busEvents?.forEach((event) => { - if (event.event.__typename === 'TransactionResult') { - updateTransaction(event.event); + onData: ({ data: result }) => { + result.data?.busEvents?.forEach(({ event }) => { + if (event.__typename === 'TransactionResult') { + let updateImmediately = true; + if (event.status && !event.error) { + const transaction = getTransaction(event.hash.toLocaleLowerCase()); + if ( + transaction && + transaction.signature && + isStopOrdersSubmissionTransaction(transaction.body) + ) { + waitForStopOrder(determineId(transaction.signature), client).then( + (stopOrder) => { + updateTransaction( + stopOrder && + stopOrder.status === StopOrderStatus.STATUS_REJECTED + ? { + ...event, + error: + (stopOrder.rejectionReason && + StopOrderRejectionReasonMapping[ + stopOrder.rejectionReason + ]) || + StopOrderStatusMapping[stopOrder.status], + } + : event + ); + } + ); + updateImmediately = false; + } + } + if (updateImmediately) { + updateTransaction(event); + } } - }), + }); + }, }); }; diff --git a/libs/web3/src/lib/wait-for-stop-order.spec.tsx b/libs/web3/src/lib/wait-for-stop-order.spec.tsx new file mode 100644 index 000000000..a4672d1eb --- /dev/null +++ b/libs/web3/src/lib/wait-for-stop-order.spec.tsx @@ -0,0 +1,72 @@ +import { ApolloClient, InMemoryCache } from '@apollo/client'; +import { MockLink } from '@apollo/client/testing'; +import type { StopOrderByIdQuery } from './__generated__/Orders'; +import { StopOrderByIdDocument } from './__generated__/Orders'; +import type { MockedResponse } from '@apollo/client/testing'; +import { waitForStopOrder } from './wait-for-stop-order'; +import { + OrderTimeInForce, + OrderType, + Side, + StopOrderStatus, + StopOrderTriggerDirection, +} from '@vegaprotocol/types'; + +const stopOrderId = + 'ad427c4f5cb599e73ffb6f0ae371d1e0fcba89b6be2401a06e61cab982668d63'; + +const stopOrder: StopOrderByIdQuery['stopOrder'] = { + __typename: 'StopOrder', + id: stopOrderId, + ocoLinkId: null, + expiresAt: null, + expiryStrategy: null, + triggerDirection: StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE, + status: StopOrderStatus.STATUS_PENDING, + rejectionReason: null, + createdAt: '2024-03-25T10:18:48.946943Z', + updatedAt: null, + partyId: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65', + marketId: '00788b763b999ef555ac5da17de155ff4237dd14aa6671a303d1285f27f094f0', + trigger: { + __typename: 'StopOrderPrice', + price: '700000', + }, + submission: { + __typename: 'OrderSubmission', + marketId: + '00788b763b999ef555ac5da17de155ff4237dd14aa6671a303d1285f27f094f0', + price: '0', + size: '1', + side: Side.SIDE_BUY, + timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK, + expiresAt: null, + type: OrderType.TYPE_MARKET, + reference: '', + peggedOrder: null, + postOnly: false, + reduceOnly: true, + }, +}; + +const mockedStopOrderById: MockedResponse = { + request: { + query: StopOrderByIdDocument, + variables: { stopOrderId }, + }, + result: { + data: { + stopOrder, + }, + }, +}; + +describe('waitForStopOrder', () => { + it('resolves with matching stopOrder', async () => { + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new MockLink([mockedStopOrderById]), + }); + expect(await waitForStopOrder(stopOrderId, client)).toEqual(stopOrder); + }); +}); diff --git a/libs/web3/src/lib/wait-for-stop-order.tsx b/libs/web3/src/lib/wait-for-stop-order.tsx new file mode 100644 index 000000000..596356b81 --- /dev/null +++ b/libs/web3/src/lib/wait-for-stop-order.tsx @@ -0,0 +1,32 @@ +import { type ApolloClient } from '@apollo/client'; +import { + StopOrderByIdDocument, + type StopOrderByIdQuery, + type StopOrderByIdQueryVariables, +} from './__generated__/Orders'; + +export const waitForStopOrder = ( + stopOrderId: string, + client: ApolloClient +) => + new Promise((resolve) => { + const interval = setInterval(async () => { + try { + const res = await client.query< + StopOrderByIdQuery, + StopOrderByIdQueryVariables + >({ + query: StopOrderByIdDocument, + variables: { stopOrderId }, + fetchPolicy: 'network-only', + }); + + if (res.data) { + clearInterval(interval); + resolve(res.data.stopOrder); + } + } catch (err) { + // no op as the query will error until the approval is created + } + }, 1000); + });