feat(deal-ticket): improve stop order rejection handling (#6084)
This commit is contained in:
parent
1e15032c06
commit
a77bb06b2f
@ -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;
|
||||
};
|
||||
|
@ -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(
|
||||
<NoOpenVolumeWarning side={Schema.Side.SIDE_BUY} marketId="" />
|
||||
);
|
||||
// side buy, volume + remaining 20
|
||||
expect(screen.getByTestId(testId)).toBeInTheDocument();
|
||||
|
||||
result.rerender(
|
||||
<NoOpenVolumeWarning side={Schema.Side.SIDE_SELL} marketId="" />
|
||||
);
|
||||
// side sell, volume - remaining = 0
|
||||
expect(screen.queryByTestId(testId)).not.toBeInTheDocument();
|
||||
|
||||
result.rerender(
|
||||
<NoOpenVolumeWarning side={Schema.Side.SIDE_BUY} marketId="" />
|
||||
);
|
||||
// side sell, volume - remaining = 0
|
||||
expect(screen.queryByTestId(testId)).toBeInTheDocument();
|
||||
|
||||
mockUseOpenVolume.mockReturnValue({ openVolume: '2' });
|
||||
render(<NoOpenVolumeWarning side={Schema.Side.SIDE_SELL} marketId="" />);
|
||||
// side buy, volume + remaining = 12
|
||||
expect(screen.queryByTestId(testId)).toBeInTheDocument();
|
||||
|
||||
render(<NoOpenVolumeWarning side={Schema.Side.SIDE_SELL} marketId="" />);
|
||||
// side sell, volume - remaining = -8
|
||||
expect(screen.getByTestId(testId)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -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 (
|
||||
<div className="mb-2">
|
||||
<Notification
|
||||
intent={Intent.Warning}
|
||||
testId={'stop-order-warning-position'}
|
||||
message={t(
|
||||
'Stop orders are reduce only and this order would increase your position.'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TimeInForce = ({
|
||||
control,
|
||||
oco,
|
||||
@ -1188,8 +1230,13 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
) : (
|
||||
<NoOpenVolumeWarning
|
||||
side={side}
|
||||
partyId={pubKey}
|
||||
marketId={market.id}
|
||||
/>
|
||||
)}
|
||||
<SubmitButton
|
||||
assetUnit={assetUnit}
|
||||
market={market}
|
||||
|
@ -31,6 +31,8 @@ import {
|
||||
jest.mock('zustand');
|
||||
jest.mock('./deal-ticket-fee-details', () => ({
|
||||
DealTicketFeeDetails: () => <div data-testid="deal-ticket-fee-details" />,
|
||||
}));
|
||||
jest.mock('./deal-ticket-margin-details', () => ({
|
||||
DealTicketMarginDetails: () => (
|
||||
<div data-testid="deal-ticket-margin-details" />
|
||||
),
|
||||
|
@ -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 },
|
||||
|
@ -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<Schema.OrderInfo>((order) => ({
|
||||
isMarketOrder: order.type === Schema.OrderType.TYPE_MARKET,
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -42,6 +42,7 @@ query StopOrderById($stopOrderId: ID!) {
|
||||
expiryStrategy
|
||||
triggerDirection
|
||||
status
|
||||
rejectionReason
|
||||
createdAt
|
||||
updatedAt
|
||||
partyId
|
||||
|
3
libs/web3/src/lib/__generated__/Orders.ts
generated
3
libs/web3/src/lib/__generated__/Orders.ts
generated
@ -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
|
||||
|
@ -47,6 +47,7 @@ export interface VegaTransactionStore {
|
||||
) => void;
|
||||
dismiss: (index: number) => void;
|
||||
delete: (index: number) => void;
|
||||
getTransaction: (txHash: string) => VegaStoredTxState | undefined;
|
||||
updateWithdrawal: (
|
||||
withdrawal: NonNullable<VegaStoredTxState['withdrawal']>,
|
||||
withdrawalApproval: NonNullable<VegaStoredTxState['withdrawalApproval']>
|
||||
@ -60,6 +61,12 @@ export interface VegaTransactionStore {
|
||||
export const useVegaTransactionStore = create<VegaTransactionStore>()(
|
||||
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();
|
||||
|
@ -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<VegaTransactionStore> = {
|
||||
updateWithdrawal,
|
||||
updateOrder,
|
||||
updateTransactionResult,
|
||||
getTransaction,
|
||||
};
|
||||
|
||||
const mockTransactionStoreState = jest.fn<Partial<VegaTransactionStore>, []>();
|
||||
@ -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 = {};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
72
libs/web3/src/lib/wait-for-stop-order.spec.tsx
Normal file
72
libs/web3/src/lib/wait-for-stop-order.spec.tsx
Normal file
@ -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<StopOrderByIdQuery> = {
|
||||
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);
|
||||
});
|
||||
});
|
32
libs/web3/src/lib/wait-for-stop-order.tsx
Normal file
32
libs/web3/src/lib/wait-for-stop-order.tsx
Normal file
@ -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<object>
|
||||
) =>
|
||||
new Promise<StopOrderByIdQuery['stopOrder']>((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);
|
||||
});
|
Loading…
Reference in New Issue
Block a user