@0xmonaco/types 1.0.50 → 1.0.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,4 +7,43 @@
7
7
  * surface — the channel-coverage lock in
8
8
  * `tests/websocket/channel-coverage.test.ts` parses this file as the source
9
9
  * of truth for the SDK's channel surface.
10
+ *
11
+ * ## Subscribe-time snapshots
12
+ *
13
+ * Every subscription whose channel has a server-side baseline takes an
14
+ * optional trailing `onSnapshot`. It receives that channel's COMPLETE current
15
+ * state as one array, once, before the first live event — so a consumer can
16
+ * seed state from the socket instead of a REST read, and replace it wholesale
17
+ * after a reconnect.
18
+ *
19
+ * The argument is deliberately trailing and optional on every method: adding a
20
+ * required parameter, or inserting one ahead of an existing optional filter,
21
+ * would break every existing call site.
22
+ *
23
+ * Snapshot rows go to `onSnapshot` and nowhere else. They are the channel's
24
+ * terminal-state payload rather than its event envelope (see
25
+ * {@link SnapshotHandler}), so a consumer that reduced them through the event
26
+ * handler too would apply the same baseline twice.
27
+ *
28
+ * Snapshot frames are routed by the FULL canonical channel string, unlike live
29
+ * events, which are prefix-matched. That is what keeps two pair-scoped
30
+ * subscriptions' baselines on their own callbacks.
31
+ *
32
+ * **One baseline per wire subscription, not per call.** The client sends one
33
+ * `Subscribe` for a channel however many local handlers it has, and the server
34
+ * snapshots only genuinely new subscriptions — so on a channel this client
35
+ * already holds, a second subscriber's `onSnapshot` is not invoked until the
36
+ * next reconnect resubscribes. `instruments` is the exception: it caches its
37
+ * baseline locally and replays it to a late subscriber. The other channels
38
+ * cannot reuse that cache — `orders` snapshot rows are not the shape its live
39
+ * events carry, so there is nothing to keep the cache current with — and a
40
+ * stale replayed baseline would be worse than none. Subscribe once per channel
41
+ * and fan out in application code (a React hook already does this), or read the
42
+ * baseline over REST for the extra consumer.
43
+ *
44
+ * Where the server has no snapshot arm for a channel yet, the callback is
45
+ * simply never invoked — a silent no-op, not an error. `orderbook`, `ohlcv`,
46
+ * `trades` and `movements` take no `onSnapshot` at all: they stream events
47
+ * rather than state, and the channel-coverage lock pins that classification
48
+ * against the server's own snapshot dispatch.
10
49
  */
@@ -45,7 +45,7 @@ export interface UserBalanceEventData {
45
45
  /** Total balance in raw format (wei) */
46
46
  totalRaw: string;
47
47
  /** Reason for the balance update */
48
- reason: BalanceUpdateReason;
48
+ reason: BalanceUpdateReason | (string & {});
49
49
  /** Reference ID for related entity (e.g., order ID, deposit ID) */
50
50
  referenceId: string | null;
51
51
  /** Timestamp when the balance was updated (ISO 8601) */
@@ -13,7 +13,10 @@ export type ConditionalOrderEventReason = "created" | "activated" | "cancelled"
13
13
  * A conditional (TP/SL) order's current state.
14
14
  *
15
15
  * **The value unions below are the values the server can send today, not a
16
- * runtime guarantee.** `@0xmonaco/core` parses this frame leniently — an
16
+ * runtime guarantee** — which is why each of those fields is typed
17
+ * `Union | (string & {})`: autocomplete still offers the known values, but a
18
+ * `switch` over one needs a `default` branch because an unrecognized value
19
+ * really can arrive (0XM-2628). `@0xmonaco/core` parses this frame leniently — an
17
20
  * unrecognized `orderType`, `state`, `side`, `positionSide`, `conditionType`,
18
21
  * `triggerSource`, `associationType` or `timeInForce` is passed through as-is
19
22
  * rather than validated, because throwing inside the WebSocket message handler
@@ -36,13 +39,13 @@ export interface ConditionalOrderEventData {
36
39
  marginAccountId: string;
37
40
  positionId?: string;
38
41
  parentOrderId?: string;
39
- associationType?: "POSITION" | "PARENT_ORDER";
42
+ associationType?: "POSITION" | "PARENT_ORDER" | (string & {});
40
43
  linkedGroupId?: string;
41
- conditionType: ConditionalOrderConditionType;
42
- triggerSource: ConditionalOrderTriggerSource;
44
+ conditionType: ConditionalOrderConditionType | (string & {});
45
+ triggerSource: ConditionalOrderTriggerSource | (string & {});
43
46
  triggerPrice: string;
44
- side: OrderSide;
45
- positionSide: PositionSide;
47
+ side: OrderSide | (string & {});
48
+ positionSide: PositionSide | (string & {});
46
49
  /**
47
50
  * Order shape the conditional order places when it triggers.
48
51
  *
@@ -58,7 +61,7 @@ export interface ConditionalOrderEventData {
58
61
  * the value would arrive as e.g. `STOPLOSS` rather than the canonical
59
62
  * `STOP_LOSS`. The parser passes such a value through untouched.
60
63
  */
61
- orderType: OrderType;
64
+ orderType: OrderType | (string & {});
62
65
  limitPrice?: string;
63
66
  quantity?: string;
64
67
  slippageToleranceBps?: number;
@@ -71,8 +74,8 @@ export interface ConditionalOrderEventData {
71
74
  * the server maps exhaustively over every variant, so a value written by any
72
75
  * other path reaches the wire as-is. Narrow before relying on the subset.
73
76
  */
74
- timeInForce?: TimeInForce;
75
- state: ConditionalOrderState;
77
+ timeInForce?: TimeInForce | (string & {});
78
+ state: ConditionalOrderState | (string & {});
76
79
  triggeredOrderId?: string;
77
80
  triggeredAt?: string;
78
81
  activatedAt?: string;
@@ -87,6 +90,28 @@ export interface ConditionalOrderEventData {
87
90
  */
88
91
  reason: string;
89
92
  updatedAt: string;
93
+ /**
94
+ * Opaque, compare-only revision counter for this conditional order's state —
95
+ * the sequencer step that wrote it (0XM-2440). A strictly higher value is
96
+ * strictly newer state for that conditional order.
97
+ *
98
+ * Present on REST reads and on the subscribe-time `conditional_orders`
99
+ * snapshot frame. **Absent on live conditional events**, by design: those are
100
+ * emitted from a model captured before the sequencer acted, so the only value
101
+ * available there is the one the row held *before* the change being
102
+ * announced — a client merging on `>=` would let a snapshot read at that
103
+ * version win the tie and undo the transition.
104
+ *
105
+ * So seed conditional state from the snapshot and REST, comparing with
106
+ * `snapshot.version >= local.version`, and apply live events as state without
107
+ * ranking them. `>=` rather than `>`: one sequencer step can write several
108
+ * conditional rows (a trigger writes the triggered row and cancels its OCO
109
+ * sibling), so equal means "same step", not identical state.
110
+ *
111
+ * Absent means unknown — a row last written before the field existed — never
112
+ * zero. Wire: `version`.
113
+ */
114
+ version?: number;
90
115
  }
91
116
  export interface ConditionalOrderEvent {
92
117
  eventType: "conditional_order_update";
@@ -36,8 +36,18 @@ export type InstrumentChangeKind = "listing" | "delisting" | "halt" | "unhalt" |
36
36
  * Full current configuration for a market on the `instrument` channel.
37
37
  */
38
38
  export interface InstrumentEventData {
39
- /** What changed for this market */
40
- change: InstrumentChangeKind;
39
+ /**
40
+ * What changed for this market.
41
+ *
42
+ * Widened past {@link InstrumentChangeKind} on purpose: `@0xmonaco/core`
43
+ * parses this frame leniently, so a change kind added server-side after this
44
+ * SDK version ships is delivered as-is rather than dropping the frame — and
45
+ * with it the full market config the frame carries, which can hide a market
46
+ * going inactive behind an unfamiliar label (0XM-2628).
47
+ * `InstrumentChangeKind` names the values the server sends today — narrow
48
+ * with a `switch` that keeps a `default` branch.
49
+ */
50
+ change: InstrumentChangeKind | (string & {});
41
51
  /** Trading pair identifier (UUID) */
42
52
  tradingPairId: string;
43
53
  /** Market symbol, e.g. "BTC/USDC" */
@@ -85,6 +85,11 @@ export interface MarketStatsEvent {
85
85
  /**
86
86
  * A single market's row in the all-markets summary frame. A lighter shape than
87
87
  * {@link MarketStatsData}: enough to render a markets table.
88
+ *
89
+ * `marketStatus` is typed `MarketStatus | (string & {})`: `@0xmonaco/core`
90
+ * parses this frame leniently, so an unrecognized status is passed through
91
+ * rather than throwing and dropping the summary row (0XM-2628). `MarketStatus`
92
+ * names the values the server sends today — match it with a `default` branch.
88
93
  */
89
94
  export interface MarketStatsSummary {
90
95
  /** Trading pair identifier (UUID) */
@@ -114,7 +119,7 @@ export interface MarketStatsSummary {
114
119
  /** Next funding time as a millisecond epoch. Perp-only; omitted for spot. */
115
120
  fundingTimestamp?: number;
116
121
  /** Tradability label (always present) */
117
- marketStatus: MarketStatus;
122
+ marketStatus: MarketStatus | (string & {});
118
123
  }
119
124
  /**
120
125
  * All-markets summary event on the `market_stats` channel: one
@@ -21,15 +21,20 @@ export interface OHLCVEvent {
21
21
  /** Trading pair ID (UUID) */
22
22
  tradingPairId: string;
23
23
  /** Candlestick interval */
24
- interval: Interval;
24
+ interval: Interval | (string & {});
25
25
  /** Trading mode */
26
- tradingMode: TradingMode;
26
+ tradingMode: TradingMode | (string & {});
27
27
  /**
28
28
  * Price series this candle belongs to. Always a concrete series on the wire
29
29
  * (`trade` or `mark`, never the `*` selector); lets a wildcard subscriber
30
30
  * tell the two streams apart. Absent only on legacy frames that omit it.
31
+ *
32
+ * Widened past the two known values on purpose: a wildcard subscription
33
+ * matches any suffix, so a series added server-side after this version still
34
+ * arrives here, and telling the streams apart is the entire point of the
35
+ * field. Match it with a `default` branch.
31
36
  */
32
- priceType?: "trade" | "mark";
37
+ priceType?: "trade" | "mark" | (string & {});
33
38
  /** OHLCV candlestick data */
34
39
  candlestick: Candlestick;
35
40
  }
@@ -29,7 +29,7 @@ export interface OrderbookLevel {
29
29
  */
30
30
  export interface PriceChange {
31
31
  /** Order side (BUY or SELL) */
32
- side: OrderSide;
32
+ side: OrderSide | (string & {});
33
33
  /** Old price before the change */
34
34
  oldPrice?: string;
35
35
  /** New price after the change */
@@ -46,7 +46,7 @@ export interface OrderbookEvent {
46
46
  /** Trading pair ID (UUID) */
47
47
  tradingPairId: string;
48
48
  /** Trading mode (SPOT, MARGIN) */
49
- tradingMode: TradingMode;
49
+ tradingMode: TradingMode | (string & {});
50
50
  /** Bid orders (buy orders) - Full depth */
51
51
  bids: OrderbookLevel[];
52
52
  /** Ask orders (sell orders) - Full depth */
@@ -18,7 +18,7 @@ export interface OrderEvent {
18
18
  /** Order ID (UUID) */
19
19
  orderId: string;
20
20
  /** Event type */
21
- eventType: OrderEventType;
21
+ eventType: OrderEventType | (string & {});
22
22
  /** ISO 8601 timestamp */
23
23
  timestamp: string;
24
24
  /** Event-specific data */
@@ -46,11 +46,11 @@ export interface CommonOrderEventData {
46
46
  */
47
47
  tradingPair: string;
48
48
  /** Trading mode (e.g., "SPOT", "MARGIN") */
49
- tradingMode: TradingMode;
49
+ tradingMode: TradingMode | (string & {});
50
50
  /** Order type - present in most events */
51
- orderType?: OrderType;
51
+ orderType?: OrderType | (string & {});
52
52
  /** Order side - present in most events */
53
- side?: OrderSide;
53
+ side?: OrderSide | (string & {});
54
54
  /** Order price - present in most events */
55
55
  price?: string;
56
56
  /** Order quantity - present in most events */
@@ -58,7 +58,7 @@ export interface CommonOrderEventData {
58
58
  /** Order quantity in RAW (integer, base-unit) precision. Wire: `quantity_raw`. */
59
59
  quantityRaw?: string;
60
60
  /** Time in force - present in most events */
61
- timeInForce?: TimeInForce;
61
+ timeInForce?: TimeInForce | (string & {});
62
62
  /**
63
63
  * Client-assigned correlation handle, echoed so an event can be matched to
64
64
  * the request that placed the order without a round trip to `getOrder`.
@@ -79,6 +79,65 @@ export interface CommonOrderEventData {
79
79
  * silence. Omitted when the order carried none. Wire: `client_order_id`.
80
80
  */
81
81
  clientOrderId?: string;
82
+ /**
83
+ * Sequencer step that produced this event (0XM-2440) — the SAME counter
84
+ * `getOrder` and `getPaginatedOrders` return, so the two are directly
85
+ * comparable. Higher is strictly newer for that order.
86
+ *
87
+ * This is what makes a reconnect reconcilable without buffering: apply
88
+ * events as they arrive, and when the REST snapshot lands take the snapshot
89
+ * for an order when `snapshot.version >= local.version`, else keep local.
90
+ *
91
+ * **Equal does NOT mean identical state here.** One sequencer step emits
92
+ * several events for one order (an `OrderPlaced` and then its fills), and a
93
+ * batch place/replace shares one step across every item — so equal means
94
+ * "same step". A snapshot row is that step's FINAL state, which is why the
95
+ * rule is `>=` and not `>`: on a tie the snapshot is the authority.
96
+ *
97
+ * Values are sparse per order (it comes from a global sequence) and are not
98
+ * comparable between different orders. Omitted, never zero, when unknown —
99
+ * an order last written before the field existed. Wire: `version`.
100
+ */
101
+ version?: number;
102
+ /**
103
+ * `true` when the order was admitted post-only (maker-only), so a subscriber
104
+ * can confirm the flag was honoured without a round trip to `getOrder`.
105
+ * Carried on the `OrderPlaced` acknowledgement, the taker fill, and the
106
+ * resting order's lifecycle — `OrderPartiallyCancelled` (an in-place
107
+ * reduction, which is how an MM's own `remainingQuantityTarget` replace is
108
+ * announced), `OrderCancelled` and `OrderExpired`.
109
+ *
110
+ * Declared on the shared block, but two variants can never carry it, and
111
+ * this optional field is the only thing that says so: a **maker fill**,
112
+ * which is out of scope because a maker already learned the flag from its
113
+ * own acknowledgement; and **`OrderRejected`**, which only ever describes a
114
+ * MARKET order with no liquidity or an unfilled IOC/FOK — and post-only is
115
+ * refused on both, so no post-only order can reach it.
116
+ *
117
+ * Only ever present as `true`: on a variant that CARRIES the field, an order
118
+ * that is not post-only omits the key rather than sending `postOnly: false`,
119
+ * exactly as the REST read model's `postOnly?: boolean` does, so
120
+ * `event.data.postOnly ?? false` reads the same as REST *for that event*.
121
+ *
122
+ * That equivalence is per-event, and it does NOT hold on a maker fill.
123
+ * A post-only order rests and therefore fills as a maker, and the maker
124
+ * payload never declares the field — so `?? false` there would return
125
+ * `false` for an order REST reports as `true`. Absence on a maker fill
126
+ * carries NO information. Track `postOnly` as order-level state learned from
127
+ * the `OrderPlaced` acknowledgement (or a REST read) and carry it forward;
128
+ * never re-derive it from whichever event arrived last.
129
+ *
130
+ * On a carrying variant, absent is not evidence the flag was dropped — it is
131
+ * the ordinary shape of a non-post-only order, and also what an order placed
132
+ * before the flag was persisted reports.
133
+ *
134
+ * A post-only order that WOULD have crossed the book is rejected before
135
+ * acceptance and emits no event at all (see `clientOrderId`), so in practice
136
+ * the flag reaches a subscriber on a resting order's lifecycle — never on a
137
+ * taker fill, which an admitted post-only order cannot produce.
138
+ * Wire: `post_only`.
139
+ */
140
+ postOnly?: boolean;
82
141
  }
83
142
  /**
84
143
  * OrderPlaced event - emitted when a new order is placed
@@ -87,54 +146,94 @@ export interface OrderPlacedEvent extends OrderEvent {
87
146
  eventType: "OrderPlaced";
88
147
  data: CommonOrderEventData & {
89
148
  /** Order status */
90
- status: OrderStatus;
149
+ status: OrderStatus | (string & {});
91
150
  };
92
151
  }
93
152
  /**
94
153
  * OrderMatched event - emitted when an order is matched but not necessarily filled
95
- * This event is only emitted for taker orders
154
+ * This event is only emitted for taker orders.
155
+ *
156
+ * Carries the SAME payload as `OrderFilled`/`OrderPartiallyFilled`: the
157
+ * matching engine builds one `TakerOrderEventData` and picks the event type
158
+ * from the resulting status, so every execution field below is on the wire
159
+ * here too.
160
+ *
161
+ * Because this event carries only that one shape, the fields below follow it
162
+ * exactly rather than being uniformly optional. A match that filled nothing
163
+ * still SENDS the keys — `tradeId`, `executionPrice` and `executedAt` arrive as
164
+ * `null`, not absent. Only `lastFillQuantity` and `lastFillQuantityRaw` are
165
+ * ever omitted, being the pair the server skips when there is no incremental
166
+ * fill.
96
167
  */
97
168
  export interface OrderMatchedEvent extends OrderEvent {
98
169
  eventType: "OrderMatched";
99
170
  data: CommonOrderEventData & {
100
- /** Taker order ID */
101
- takerOrderId?: string;
102
- /** Reference price used for slippage calculation */
103
- referencePrice?: string;
104
- /** Best price achieved in matching */
105
- bestPrice?: string;
106
- /** Worst price achieved in matching */
107
- worstPrice?: string;
108
- /** Actual slippage in basis points */
109
- actualSlippageBps?: number;
110
- /** Maximum allowed slippage in basis points */
111
- maxSlippageBps?: number;
112
- /** Number of trades executed (taker only) */
113
- tradesCount?: number;
114
- /** Total quantity filled (taker only) */
115
- totalFilled?: string;
116
- /** Remaining quantity to be filled (taker only) */
117
- remainingQuantity?: string;
118
- /** Average fill price across all trades */
119
- averageFillPrice?: string;
120
- /** Order status */
121
- status?: OrderStatus;
171
+ /** Fill role of this order (`taker` on this event). */
172
+ role: OrderRole | (string & {});
173
+ /** Trade ID of the first trade this event covers. `null` when it covers none. */
174
+ tradeId: string | null;
175
+ /** Execution price of the first trade this event covers. `null` when it covers none. */
176
+ executionPrice: string | null;
177
+ /** Cumulative quantity filled on the order at this event. Wire: `filled_quantity`. */
178
+ filledQuantity: string;
179
+ /** `filledQuantity` in RAW (integer, base-unit) precision. Wire: `filled_quantity_raw`. */
180
+ filledQuantityRaw: string;
181
+ /** Incremental quantity filled by THIS event. Omitted when there is no fill. Wire: `last_fill_quantity`. */
182
+ lastFillQuantity?: string;
183
+ /** `lastFillQuantity` in RAW (integer, base-unit) precision. Omitted with it. Wire: `last_fill_quantity_raw`. */
184
+ lastFillQuantityRaw?: string;
185
+ /** `remainingQuantity` in RAW (integer, base-unit) precision. Wire: `remaining_quantity_raw`. */
186
+ remainingQuantityRaw: string;
187
+ /**
188
+ * Monaco fee for the fills this event covers, in normalized decimal QUOTE
189
+ * units (negative = maker rebate). Excludes the application taker fee, so
190
+ * it is smaller than the order echo's `totalTakerFees` for an application
191
+ * that charges one, and equal to it for one that does not. Wire: `fee`.
192
+ */
193
+ fee: string;
194
+ /**
195
+ * `fee` in RAW quote-unit precision (scaled by 10^quoteDecimals). Always a
196
+ * whole number -- the engine truncates every fee to an integral raw unit
197
+ * when it stores it -- so `fee` is exactly this value divided by
198
+ * 10^quoteDecimals, and `BigInt(feeRaw)` is safe. Wire: `fee_raw`.
199
+ */
200
+ feeRaw: string;
201
+ /** Execution timestamp of the first trade this event covers (ISO 8601). `null` when it covers none. */
202
+ executedAt: string | null;
203
+ /** Reference price used for slippage calculation. `null` when unset. */
204
+ referencePrice: string | null;
205
+ /** Best price achieved in matching. `null` when unset. */
206
+ bestPrice: string | null;
207
+ /** Worst price achieved in matching. `null` when unset. */
208
+ worstPrice: string | null;
209
+ /** Actual slippage in basis points. `null` when unset. */
210
+ actualSlippageBps: number | null;
211
+ /** Maximum allowed slippage in basis points. `null` when unset. */
212
+ maxSlippageBps: number | null;
213
+ /** Number of trades executed. */
214
+ tradesCount: number;
215
+ /** Remaining quantity to be filled. */
216
+ remainingQuantity: string;
217
+ /** Average fill price across all trades. `null` when there is none. */
218
+ averageFillPrice: string | null;
219
+ /** Order status. */
220
+ status: OrderStatus | (string & {});
122
221
  };
123
222
  }
124
223
  /**
125
224
  * OrderPartiallyFilled event - emitted when an order is partially filled
126
- * Can be emitted for both taker and maker orders with different field sets
225
+ * Can be emitted for both taker and maker orders with different field sets.
127
226
  *
128
- * Taker events include: trades_count, total_filled, slippage data
129
- * Maker events include: trade_id, execution_price, execution_quantity, maker_fee
227
+ * Taker events add `trades_count` and the slippage fields; maker events add
228
+ * `trade_id` and `execution_price`. Both roles report the cumulative fill as
229
+ * `filled_quantity` and this event's increment as `last_fill_quantity`, and
230
+ * both carry the fill charge as `fee` (0XM-2389). `total_filled`,
231
+ * `execution_quantity` and `maker_fee` were never emitted on a fill payload
232
+ * and are not declared here.
130
233
  */
131
234
  export interface OrderPartiallyFilledEvent extends OrderEvent {
132
235
  eventType: "OrderPartiallyFilled";
133
236
  data: CommonOrderEventData & {
134
- /** Taker order ID (taker orders only) */
135
- takerOrderId?: string;
136
- /** Maker order ID (maker orders only) */
137
- makerOrderId?: string;
138
237
  /** Reference price (taker orders only) */
139
238
  referencePrice?: string;
140
239
  /** Best price (taker orders only) */
@@ -147,14 +246,12 @@ export interface OrderPartiallyFilledEvent extends OrderEvent {
147
246
  maxSlippageBps?: number;
148
247
  /** Number of trades (taker orders only - not present for maker) */
149
248
  tradesCount?: number;
150
- /** Total filled quantity (taker orders only - not present for maker) */
151
- totalFilled?: string;
152
249
  /** Remaining quantity (present for both taker and maker) */
153
250
  remainingQuantity?: string;
154
251
  /** Average fill price (taker orders only) */
155
252
  averageFillPrice?: string;
156
253
  /** Order status (taker orders only) */
157
- status?: OrderStatus;
254
+ status?: OrderStatus | (string & {});
158
255
  /** Trade ID (maker orders only) */
159
256
  tradeId?: string;
160
257
  /** Execution price (maker orders only) */
@@ -180,30 +277,43 @@ export interface OrderPartiallyFilledEvent extends OrderEvent {
180
277
  /** `remainingQuantity` in RAW (integer, base-unit) precision. Wire: `remaining_quantity_raw`. */
181
278
  remainingQuantityRaw?: string;
182
279
  /**
183
- * Fee charged for this fill (maker orders only). The wire field is `fee`
184
- * (→ `fee`), not `makerFee`; taker fills carry the same `fee` field.
280
+ * Monaco fee for the fills this event covers, in normalized decimal QUOTE
281
+ * units (negative = maker rebate). The wire field is `fee`, not `makerFee`;
282
+ * taker and maker carry the same field. A taker event aggregates every
283
+ * trade it covers; a maker event is one trade.
284
+ *
285
+ * Excludes the application taker fee, so it is NOT the order echo's
286
+ * `totalTakerFees` (which is `fee + applicationTakerFee`). It previously
287
+ * held the raw integer value now exposed as `feeRaw`.
185
288
  */
186
289
  fee?: string;
290
+ /**
291
+ * `fee` in RAW quote-unit precision (scaled by 10^quoteDecimals). Always a
292
+ * whole number -- the engine truncates every fee to an integral raw unit
293
+ * when it stores it -- so `fee` is exactly this value divided by
294
+ * 10^quoteDecimals, and `BigInt(feeRaw)` is safe. Wire: `fee_raw`.
295
+ */
296
+ feeRaw?: string;
187
297
  /** Trade execution timestamp (maker orders only) */
188
298
  executedAt?: string;
189
299
  /** Role of the order in the trade (taker or maker) */
190
- role?: OrderRole;
300
+ role?: OrderRole | (string & {});
191
301
  };
192
302
  }
193
303
  /**
194
304
  * OrderFilled event - emitted when an order is completely filled
195
- * Can be emitted for both taker and maker orders with different field sets
305
+ * Can be emitted for both taker and maker orders with different field sets.
196
306
  *
197
- * Taker events include: trades_count, total_filled, slippage data
198
- * Maker events include: trade_id, execution_price, execution_quantity, maker_fee
307
+ * Taker events add `trades_count` and the slippage fields; maker events add
308
+ * `trade_id` and `execution_price`. Both roles report the cumulative fill as
309
+ * `filled_quantity` and this event's increment as `last_fill_quantity`, and
310
+ * both carry the fill charge as `fee` (0XM-2389). `total_filled`,
311
+ * `execution_quantity` and `maker_fee` were never emitted on a fill payload
312
+ * and are not declared here.
199
313
  */
200
314
  export interface OrderFilledEvent extends OrderEvent {
201
315
  eventType: "OrderFilled";
202
316
  data: CommonOrderEventData & {
203
- /** Taker order ID (taker orders only) */
204
- takerOrderId?: string;
205
- /** Maker order ID (maker orders only) */
206
- makerOrderId?: string;
207
317
  /** Reference price (taker orders only) */
208
318
  referencePrice?: string;
209
319
  /** Best price (taker orders only) */
@@ -216,14 +326,12 @@ export interface OrderFilledEvent extends OrderEvent {
216
326
  maxSlippageBps?: number;
217
327
  /** Number of trades (taker orders only - not present for maker) */
218
328
  tradesCount?: number;
219
- /** Total filled quantity (taker orders only - not present for maker) */
220
- totalFilled?: string;
221
329
  /** Remaining quantity (present for both, should be 0 for filled orders) */
222
330
  remainingQuantity?: string;
223
331
  /** Average fill price (taker orders only) */
224
332
  averageFillPrice?: string;
225
333
  /** Order status (taker orders only) */
226
- status?: OrderStatus;
334
+ status?: OrderStatus | (string & {});
227
335
  /** Trade ID (maker orders only) */
228
336
  tradeId?: string;
229
337
  /** Execution price (maker orders only) */
@@ -249,14 +357,27 @@ export interface OrderFilledEvent extends OrderEvent {
249
357
  /** `remainingQuantity` in RAW (integer, base-unit) precision. Wire: `remaining_quantity_raw`. */
250
358
  remainingQuantityRaw?: string;
251
359
  /**
252
- * Fee charged for this fill (maker orders only). The wire field is `fee`
253
- * (→ `fee`), not `makerFee`; taker fills carry the same `fee` field.
360
+ * Monaco fee for the fills this event covers, in normalized decimal QUOTE
361
+ * units (negative = maker rebate). The wire field is `fee`, not `makerFee`;
362
+ * taker and maker carry the same field. A taker event aggregates every
363
+ * trade it covers; a maker event is one trade.
364
+ *
365
+ * Excludes the application taker fee, so it is NOT the order echo's
366
+ * `totalTakerFees` (which is `fee + applicationTakerFee`). It previously
367
+ * held the raw integer value now exposed as `feeRaw`.
254
368
  */
255
369
  fee?: string;
370
+ /**
371
+ * `fee` in RAW quote-unit precision (scaled by 10^quoteDecimals). Always a
372
+ * whole number -- the engine truncates every fee to an integral raw unit
373
+ * when it stores it -- so `fee` is exactly this value divided by
374
+ * 10^quoteDecimals, and `BigInt(feeRaw)` is safe. Wire: `fee_raw`.
375
+ */
376
+ feeRaw?: string;
256
377
  /** Trade execution timestamp (maker orders only) */
257
378
  executedAt?: string;
258
379
  /** Role of the order in the trade (taker or maker) */
259
- role?: OrderRole;
380
+ role?: OrderRole | (string & {});
260
381
  };
261
382
  }
262
383
  /**
@@ -268,7 +389,7 @@ export interface OrderCancelledEvent extends OrderEvent {
268
389
  /** Reason for cancellation */
269
390
  reason?: string;
270
391
  /** Stable machine-readable cancellation cause */
271
- terminalReason?: OrderTerminalReason;
392
+ terminalReason?: OrderTerminalReason | (string & {});
272
393
  /**
273
394
  * Cumulative order fill at cancellation, mirroring `filledQuantity` on the
274
395
  * taker/maker fill events so consumers read one field across every order
@@ -308,11 +429,15 @@ export interface OrderRejectedEvent extends OrderEvent {
308
429
  /** Reason for rejection */
309
430
  reason: string;
310
431
  /** Order status */
311
- status: OrderStatus;
432
+ status: OrderStatus | (string & {});
312
433
  /** Total filled quantity (may-be partially filled) */
313
434
  totalFilled: string;
435
+ /** `totalFilled` in RAW (integer, base-unit) precision. Wire: `total_filled_raw`. */
436
+ totalFilledRaw: string;
314
437
  /** Remaining quantity that couldn't be filled */
315
438
  remainingQuantity: string;
439
+ /** `remainingQuantity` in RAW (integer, base-unit) precision. Wire: `remaining_quantity_raw`. */
440
+ remainingQuantityRaw: string;
316
441
  };
317
442
  }
318
443
  /**
@@ -321,12 +446,16 @@ export interface OrderRejectedEvent extends OrderEvent {
321
446
  export interface OrderExpiredEvent extends OrderEvent {
322
447
  eventType: "OrderExpired";
323
448
  data: CommonOrderEventData & {
324
- /** Filled quantity before expiration */
325
- filledQuantity?: string;
326
- /** Remaining quantity at expiration */
327
- remainingQuantity?: string;
328
- /** Average fill price (if any fills occurred) */
329
- averageFillPrice?: string;
449
+ /** Filled quantity before expiration. `null` when the order never filled. */
450
+ filledQuantity: string | null;
451
+ /** `filledQuantity` in RAW (integer, base-unit) precision. Wire: `filled_quantity_raw`. */
452
+ filledQuantityRaw: string | null;
453
+ /** Remaining quantity at expiration. `null` when unset. */
454
+ remainingQuantity: string | null;
455
+ /** `remainingQuantity` in RAW (integer, base-unit) precision. Wire: `remaining_quantity_raw`. */
456
+ remainingQuantityRaw: string | null;
457
+ /** Average fill price. `null` when no fills occurred. */
458
+ averageFillPrice: string | null;
330
459
  /** Reason for expiration */
331
460
  reason: string;
332
461
  };
@@ -342,7 +471,7 @@ export interface OrderPartiallyCancelledEvent extends OrderEvent {
342
471
  eventType: "OrderPartiallyCancelled";
343
472
  data: CommonOrderEventData & {
344
473
  /** Order status — non-terminal (the order still rests) */
345
- status: OrderStatus;
474
+ status: OrderStatus | (string & {});
346
475
  /** Cumulative filled quantity, unchanged by the reduction */
347
476
  filledQuantity: string;
348
477
  /** Cumulative filled quantity in RAW precision. Wire: `filled_quantity_raw`. */
@@ -23,6 +23,12 @@ export type PositionEventSide = "LONG" | "SHORT";
23
23
  export type PositionEventStatus = "OPEN" | "LIQUIDATING" | "CLOSED" | "LIQUIDATED" | "EXPIRED";
24
24
  /**
25
25
  * Position event data containing the current state of a single position.
26
+ *
27
+ * `side` and `status` are typed `Union | (string & {})`: `@0xmonaco/core`
28
+ * parses this frame leniently, so an unrecognized value is passed through
29
+ * rather than throwing inside the WebSocket message handler and dropping a
30
+ * risk-critical update entirely (0XM-2627/0XM-2628). The unions name the values
31
+ * the server sends today — `switch` over them with a `default` branch.
26
32
  */
27
33
  export interface PositionEventData {
28
34
  /** Position identifier (UUID) */
@@ -32,9 +38,9 @@ export interface PositionEventData {
32
38
  /** Trading pair identifier (UUID) */
33
39
  tradingPairId: string;
34
40
  /** Position side */
35
- side: PositionEventSide;
41
+ side: PositionEventSide | (string & {});
36
42
  /** Position lifecycle status (SCREAMING_SNAKE_CASE on the wire, e.g. "OPEN") */
37
- status: PositionEventStatus;
43
+ status: PositionEventStatus | (string & {});
38
44
  /** Absolute position size (decimal string) */
39
45
  size: string;
40
46
  /** Average entry price (decimal string) */