@0xmonaco/types 1.0.51 → 1.0.54

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.
@@ -102,39 +102,38 @@ export interface CommonOrderEventData {
102
102
  /**
103
103
  * `true` when the order was admitted post-only (maker-only), so a subscriber
104
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`.
105
+ * Carried on the `OrderPlaced` acknowledgement, fills in **both roles** —
106
+ * the maker fill gained it in 0XM-2729 — and the resting order's lifecycle:
107
+ * `OrderPartiallyCancelled` (an in-place reduction, which is how an MM's own
108
+ * `remainingQuantityTarget` replace is announced), `OrderCancelled` and
109
+ * `OrderExpired`.
109
110
  *
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.
111
+ * The maker fill is the one that matters: a post-only order that would have
112
+ * crossed is refused at admission and emits nothing, so an admitted
113
+ * post-only order can never produce a taker fill — it rests, and every fill
114
+ * it ever has is a maker fill. Until 0XM-2729 that meant the flag was absent
115
+ * on precisely the fills a post-only order produces; now the fill that
116
+ * partially fills or retires a post-only quote carries it.
116
117
  *
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*.
118
+ * Two variants never carry it, and a post-only order can reach neither.
119
+ * `OrderRejected` only describes a MARKET order with no liquidity or an
120
+ * unfilled IOC/FOK. And `OrderCancelled` has two server payloads: the
121
+ * resting-order cancellation listed above carries the field, while the one
122
+ * reporting an IOC or market order's REMAINDER after a partial fill is a
123
+ * different payload that declares no `post_only` at all. Post-only is
124
+ * refused on MARKET and IOC/FOK alike, so neither is reachable — which is
125
+ * why the single `OrderCancelledEvent` interface can declare the field
126
+ * without lying about any frame a post-only order actually produces.
121
127
  *
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.
128
+ * Only ever present as `true`: an order that is not post-only omits the key
129
+ * rather than sending `postOnly: false`, exactly as the REST read model's
130
+ * `postOnly?: boolean` does — so `event.data.postOnly ?? false` reads the
131
+ * same as REST on every carrying variant, maker fills included. Absent is
132
+ * not evidence the flag was dropped: it is the ordinary shape of a
133
+ * non-post-only order, and also what an order placed before the flag was
134
+ * persisted reports (a maker fill emitted by a server predating 0XM-2729
135
+ * likewise omits it). The order-level value on the acknowledgement or a
136
+ * REST read remains the authority for such orders.
138
137
  * Wire: `post_only`.
139
138
  */
140
139
  postOnly?: boolean;
@@ -5,8 +5,8 @@
5
5
  * liquidation price, isolated margin, leverage).
6
6
  *
7
7
  * This is an authenticated channel - requires the signed session-key handshake.
8
- * Lag policy is Conflate: a lagging subscriber is fast-forwarded to the latest
9
- * state rather than disconnected, so there is no gap signal to reconcile.
8
+ * Detected broadcast lag closes the connection with 1013 and requires resync.
9
+ * A lost terminal position update need not have a later replacement.
10
10
  */
11
11
  /**
12
12
  * Position side. The server emits `LONG` or `SHORT` for an open position.
@@ -5,6 +5,6 @@
5
5
  * liquidation price, isolated margin, leverage).
6
6
  *
7
7
  * This is an authenticated channel - requires the signed session-key handshake.
8
- * Lag policy is Conflate: a lagging subscriber is fast-forwarded to the latest
9
- * state rather than disconnected, so there is no gap signal to reconcile.
8
+ * Detected broadcast lag closes the connection with 1013 and requires resync.
9
+ * A lost terminal position update need not have a later replacement.
10
10
  */
@@ -7,3 +7,4 @@ export * from "./base";
7
7
  export * from "./client";
8
8
  export * from "./clients/orderbook-client";
9
9
  export * from "./events";
10
+ export * from "./snapshots";
@@ -7,3 +7,4 @@ export * from "./base";
7
7
  export * from "./client";
8
8
  export * from "./clients/orderbook-client";
9
9
  export * from "./events";
10
+ export * from "./snapshots";
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Subscribe-time WebSocket snapshot types.
3
+ *
4
+ * The server injects one `Snapshot` frame per subscription, after the
5
+ * `Subscribed` ack and before that subscription's first live diff. Its `data`
6
+ * is the channel's COMPLETE current state as a flat array — a consumer that
7
+ * applies it wholesale needs no REST baseline read for that channel.
8
+ *
9
+ * Two rules hold across every channel:
10
+ *
11
+ * - **A snapshot row is the channel's terminal-state payload, not its event
12
+ * envelope.** The live frame wraps its payload (`{ event_type, user_id, data }`
13
+ * for the authenticated channels); a snapshot row IS the payload, with no
14
+ * wrapper and no change metadata. That is why the rows are delivered to their
15
+ * own callback instead of the event handler: reducing both would apply the
16
+ * same state twice, in two different shapes.
17
+ * - **The row type is the channel's own `…EventData`**, so the baseline and the
18
+ * diffs that follow it describe state identically. `orders` is the one
19
+ * exception and has its own {@link OrderSnapshotItem}: a resting order carries
20
+ * fields no single order event does (`status` alongside the fill totals) and
21
+ * carries none of the change-only fill metadata.
22
+ *
23
+ * A channel whose server arm is not implemented yet simply never sends the
24
+ * frame, so its callback is never invoked. That is a silent no-op, never an
25
+ * error.
26
+ */
27
+ import type { OrderSide, OrderType, TimeInForce, TradingMode } from "../trading";
28
+ /**
29
+ * Receives one channel's complete subscribe-time state.
30
+ *
31
+ * Invoked at most once per `Snapshot` frame, always with an array — an empty
32
+ * baseline arrives as `[]` rather than being skipped, so a consumer can tell
33
+ * "nothing is open" apart from "no baseline yet".
34
+ *
35
+ * Delivery is all-or-nothing: a frame with any unreadable row is withheld
36
+ * entirely rather than delivered short, because the array is contractually
37
+ * COMPLETE and a consumer that replaced state from a truncated one would delete
38
+ * whatever the unreadable row described.
39
+ *
40
+ * A frame is produced per WIRE subscription, so on a channel this client
41
+ * already holds a second subscriber's callback waits for the next reconnect;
42
+ * see `MonacoWebSocket` for the full rule.
43
+ */
44
+ export type SnapshotHandler<T> = (items: T[]) => void;
45
+ /**
46
+ * One resting order in an `orders` channel snapshot.
47
+ *
48
+ * The server builds this to be field-compatible with the live order event's
49
+ * `data` — same names, same casing, decimals as strings — and injects the three
50
+ * identity fields the read model does not itself carry (`userId`,
51
+ * `applicationId`, and the resolved `tradingPair` symbol).
52
+ *
53
+ * The change-only fill metadata of an order EVENT is deliberately absent: a
54
+ * snapshot describes resting state, not a fill, so there is no `eventType`,
55
+ * `tradeId`, `executionPrice`, `lastFillQuantity`, `role`, `fee`, `feeRaw`,
56
+ * `tradesCount` or slippage. The cumulative `filledQuantity` /
57
+ * `averageFillPrice` totals are present, because those are state.
58
+ *
59
+ * Nullability mirrors the wire exactly. Every optional field below is a Rust
60
+ * `Option` serialized WITHOUT `skip_serializing_if`, so the key is always
61
+ * present and carries `null` when unset — hence `| null` rather than `?`.
62
+ * `clientOrderId` is the sole field the server omits outright, so it is the
63
+ * sole `?`.
64
+ *
65
+ * The value unions are widened (`Union | (string & {})`) for the same reason
66
+ * the order event's are: the parser hands an unrecognized value through rather
67
+ * than dropping the row, so a closed type would promise something the runtime
68
+ * does not honour.
69
+ */
70
+ export interface OrderSnapshotItem {
71
+ /** Order identifier (UUID) */
72
+ orderId: string;
73
+ /** Owning user (UUID), taken from the authenticated session */
74
+ userId: string;
75
+ /** Owning application (UUID), taken from the authenticated session */
76
+ applicationId: string;
77
+ /** Trading pair identifier (UUID) */
78
+ tradingPairId: string;
79
+ /** Trading pair symbol, e.g. "BTC/USDC" */
80
+ tradingPair: string;
81
+ /** Trading mode (e.g. "SPOT", "MARGIN") */
82
+ tradingMode: TradingMode | (string & {});
83
+ /** Order side */
84
+ side: OrderSide | (string & {});
85
+ /** Order type */
86
+ orderType: OrderType | (string & {});
87
+ /** Limit price (decimal string); `null` for a market order */
88
+ price: string | null;
89
+ /** Order quantity (decimal string) */
90
+ quantity: string;
91
+ /** `quantity` in RAW (integer, base-unit) precision */
92
+ quantityRaw: string;
93
+ /** Cumulative quantity filled (decimal string) */
94
+ filledQuantity: string;
95
+ /** `filledQuantity` in RAW (integer, base-unit) precision */
96
+ filledQuantityRaw: string;
97
+ /** Quantity still resting (decimal string) */
98
+ remainingQuantity: string | null;
99
+ /** `remainingQuantity` in RAW (integer, base-unit) precision */
100
+ remainingQuantityRaw: string | null;
101
+ /** Order status; the snapshot carries only resting statuses */
102
+ status: string;
103
+ /** Time in force */
104
+ timeInForce: TimeInForce | (string & {}) | null;
105
+ /** Average fill price (decimal string); `null` before the first fill */
106
+ averageFillPrice: string | null;
107
+ /** Order creation timestamp (ISO 8601) */
108
+ createdAt: string;
109
+ /** Last update timestamp (ISO 8601) */
110
+ updatedAt: string | null;
111
+ /** Margin account backing a margin/perp order (UUID) */
112
+ marginAccountId: string | null;
113
+ /** Position the order is linked to (UUID) */
114
+ positionId: string | null;
115
+ /** Leverage for a margin/perp order (decimal string) */
116
+ leverage: string | null;
117
+ /** Whether the order can only reduce existing exposure */
118
+ reduceOnly: boolean;
119
+ /**
120
+ * Client-assigned correlation handle, carried so the baseline and the live
121
+ * events that follow it identify orders the same way. Omitted — not `null` —
122
+ * when the order carried none, matching the live event payload.
123
+ */
124
+ clientOrderId?: string;
125
+ /**
126
+ * Sequencer step this order's state was last written by — the SAME counter
127
+ * the live order events and the REST endpoints carry (0XM-2440).
128
+ *
129
+ * The one field here that is not resting state. It is carried because a
130
+ * reconnecting client ranks this frame against the events it has already
131
+ * applied by comparing exactly this value, so the frame would be unusable
132
+ * for its own purpose without it. Compare with `>=`, not `>`: on the stream
133
+ * equal versions mean the same sequencer step rather than identical state,
134
+ * and the snapshot row is that step's final state.
135
+ *
136
+ * Omitted — not `null` — when unknown, which is what an order last written
137
+ * before the field existed reports. Absent means unknown, never zero.
138
+ */
139
+ version?: number;
140
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Subscribe-time WebSocket snapshot types.
3
+ *
4
+ * The server injects one `Snapshot` frame per subscription, after the
5
+ * `Subscribed` ack and before that subscription's first live diff. Its `data`
6
+ * is the channel's COMPLETE current state as a flat array — a consumer that
7
+ * applies it wholesale needs no REST baseline read for that channel.
8
+ *
9
+ * Two rules hold across every channel:
10
+ *
11
+ * - **A snapshot row is the channel's terminal-state payload, not its event
12
+ * envelope.** The live frame wraps its payload (`{ event_type, user_id, data }`
13
+ * for the authenticated channels); a snapshot row IS the payload, with no
14
+ * wrapper and no change metadata. That is why the rows are delivered to their
15
+ * own callback instead of the event handler: reducing both would apply the
16
+ * same state twice, in two different shapes.
17
+ * - **The row type is the channel's own `…EventData`**, so the baseline and the
18
+ * diffs that follow it describe state identically. `orders` is the one
19
+ * exception and has its own {@link OrderSnapshotItem}: a resting order carries
20
+ * fields no single order event does (`status` alongside the fill totals) and
21
+ * carries none of the change-only fill metadata.
22
+ *
23
+ * A channel whose server arm is not implemented yet simply never sends the
24
+ * frame, so its callback is never invoked. That is a silent no-op, never an
25
+ * error.
26
+ */