@metamask-previews/perps-controller 9.3.0-preview-68ba7f84e → 10.0.0-preview-5959fe813

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.
@@ -0,0 +1,106 @@
1
+ import type { OrderBookData } from "../types/index.mjs";
2
+ /**
3
+ * A single L2 book price level as delivered by Hyperliquid's `l2Book`
4
+ * subscription. Declared locally to avoid coupling to the SDK's exported type
5
+ * names (and to keep this the only file that references the SDK's shapes).
6
+ */
7
+ type HyperliquidL2BookLevel = {
8
+ /** Price. */
9
+ px: string;
10
+ /** Total size resting at this price. */
11
+ sz: string;
12
+ /** Number of individual orders. */
13
+ n: number;
14
+ };
15
+ /** `l2Book` snapshot event (index 0 = bids, index 1 = asks). */
16
+ type HyperliquidL2BookEvent = {
17
+ coin: string;
18
+ time: number;
19
+ levels: [bids: HyperliquidL2BookLevel[], asks: HyperliquidL2BookLevel[]];
20
+ spread?: string;
21
+ };
22
+ /**
23
+ * Health of the dedicated order-book socket, surfaced to the UI so the panel
24
+ * can show a reconnect affordance.
25
+ *
26
+ * - `connecting`: socket opening or reconnecting after a transient drop.
27
+ * - `connected`: subscription is live.
28
+ * - `error`: dropped and automatic reconnection was exhausted; needs a manual reconnect.
29
+ */
30
+ export type OrderBookConnectionStatus = 'connecting' | 'connected' | 'error';
31
+ export type SubscribeAggregatedOrderBookParams = {
32
+ /** Market symbol (e.g. 'BTC'). */
33
+ symbol: string;
34
+ /** Number of levels per side to keep. */
35
+ levels?: number;
36
+ /**
37
+ * Server-side aggregation significant figures. Required: omitting it would
38
+ * request the raw, full-precision book instead of an aggregated one, which
39
+ * contradicts this service's contract.
40
+ */
41
+ nSigFigs: 2 | 3 | 4 | 5;
42
+ /** Mantissa refinement when `nSigFigs` is 5. */
43
+ mantissa?: 2 | 5;
44
+ /** Invoked with each processed snapshot. */
45
+ callback: (data: OrderBookData) => void;
46
+ /** Invoked when the underlying socket's health changes. */
47
+ onStatusChange?: (status: OrderBookConnectionStatus) => void;
48
+ };
49
+ export type AggregatedOrderBookConnectionOptions = {
50
+ /** Resolves the current network at subscribe time. */
51
+ isTestnet: () => boolean;
52
+ };
53
+ /**
54
+ * Transforms a raw Hyperliquid `l2Book` snapshot into the `OrderBookData` shape
55
+ * the UI consumes. Mirrors the subscription service's internal
56
+ * `processOrderBookData` so this dedicated connection is a drop-in replacement
57
+ * for `subscribeToOrderBook` on the aggregated channel.
58
+ *
59
+ * @param data - Raw `l2Book` event.
60
+ * @param levels - Number of levels per side to keep.
61
+ * @returns Processed order-book snapshot.
62
+ */
63
+ export declare function processAggregatedOrderBook(data: HyperliquidL2BookEvent, levels: number): OrderBookData;
64
+ /**
65
+ * Owns a dedicated Hyperliquid WebSocket connection used solely for the
66
+ * order-book panel's server-aggregated `l2Book` subscription.
67
+ *
68
+ * The main connection (managed by the subscription service) multiplexes every
69
+ * subscription onto a single socket. The Hyperliquid SDK dispatches `l2Book`
70
+ * events by `coin` only, so running the raw (full-precision) and the aggregated
71
+ * (`nSigFigs`) subscriptions for the same coin on that shared socket
72
+ * cross-contaminates them — the coarse ladder and the precise spread/slippage
73
+ * clobber each other. Giving the aggregated subscription its own socket removes
74
+ * the collision entirely: this socket only ever carries a single `l2Book`
75
+ * stream, and the main socket is never touched by the panel's grouping.
76
+ *
77
+ * The socket is created lazily on the first subscription and torn down once the
78
+ * last subscription is removed, so it exists only while an order-book panel is
79
+ * open. Because network is a global setting, the transport is recreated if
80
+ * `isTestnet` changes between (re)subscriptions.
81
+ */
82
+ export declare class AggregatedOrderBookConnection {
83
+ #private;
84
+ constructor({ isTestnet }: AggregatedOrderBookConnectionOptions);
85
+ /**
86
+ * Opens an aggregated `l2Book` subscription on the dedicated socket.
87
+ *
88
+ * Mirrors the subscription service's synchronous-unsubscribe contract: the
89
+ * returned function can be called before the async subscribe resolves and
90
+ * will cancel the pending subscription.
91
+ *
92
+ * Only one `l2Book` payload per asset may be active at a time. Subscribing to
93
+ * an asset that already has a live subscription with different params (e.g. a
94
+ * different `nSigFigs` or `mantissa`) throws, because the shared socket
95
+ * dispatches by `coin` and the conflicting streams would clobber each other.
96
+ *
97
+ * @param params - Subscription parameters.
98
+ * @returns An unsubscribe function.
99
+ * @throws If the asset already has an active subscription with different params.
100
+ */
101
+ subscribe(params: SubscribeAggregatedOrderBookParams): () => void;
102
+ /** Closes the dedicated socket and drops all subscriptions. */
103
+ close(): void;
104
+ }
105
+ export {};
106
+ //# sourceMappingURL=AggregatedOrderBookConnection.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AggregatedOrderBookConnection.d.mts","sourceRoot":"","sources":["../../src/services/AggregatedOrderBookConnection.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,2BAAiB;AAE9C;;;;GAIG;AACH,KAAK,sBAAsB,GAAG;IAC5B,aAAa;IACb,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,mCAAmC;IACnC,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAEF,gEAAgE;AAChE,KAAK,sBAAsB,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,IAAI,EAAE,sBAAsB,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,CAAC;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,yBAAyB,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;AAE7E,MAAM,MAAM,kCAAkC,GAAG;IAC/C,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,4CAA4C;IAC5C,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,2DAA2D;IAC3D,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,yBAAyB,KAAK,IAAI,CAAC;CAC9D,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG;IACjD,sDAAsD;IACtD,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B,CAAC;AAMF;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,sBAAsB,EAC5B,MAAM,EAAE,MAAM,GACb,aAAa,CAyDf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,6BAA6B;;gBA8B5B,EAAE,SAAS,EAAE,EAAE,oCAAoC;IAI/D;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,MAAM,EAAE,kCAAkC,GAAG,MAAM,IAAI;IAyNjE,+DAA+D;IAC/D,KAAK,IAAI,IAAI;CAkEd"}
@@ -0,0 +1,400 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _AggregatedOrderBookConnection_instances, _AggregatedOrderBookConnection_isTestnet, _AggregatedOrderBookConnection_transport, _AggregatedOrderBookConnection_transportIsTestnet, _AggregatedOrderBookConnection_activeCount, _AggregatedOrderBookConnection_payloads, _AggregatedOrderBookConnection_activeSubscriptions, _AggregatedOrderBookConnection_terminated, _AggregatedOrderBookConnection_ensureTransport, _AggregatedOrderBookConnection_closeTransport;
13
+ import { SubscriptionClient, WebSocketTransport } from "@nktkas/hyperliquid";
14
+ import { HYPERLIQUID_TRANSPORT_CONFIG } from "../constants/hyperLiquidConfig.mjs";
15
+ // Fast mode streams 5 levels per side (slow mode streams 20). We run fast mode
16
+ // for lower-latency ladder updates, so the book never carries more than this.
17
+ const DEFAULT_LEVELS = 5;
18
+ /**
19
+ * Transforms a raw Hyperliquid `l2Book` snapshot into the `OrderBookData` shape
20
+ * the UI consumes. Mirrors the subscription service's internal
21
+ * `processOrderBookData` so this dedicated connection is a drop-in replacement
22
+ * for `subscribeToOrderBook` on the aggregated channel.
23
+ *
24
+ * @param data - Raw `l2Book` event.
25
+ * @param levels - Number of levels per side to keep.
26
+ * @returns Processed order-book snapshot.
27
+ */
28
+ export function processAggregatedOrderBook(data, levels) {
29
+ const bidsRaw = data?.levels?.[0] ?? [];
30
+ const asksRaw = data?.levels?.[1] ?? [];
31
+ let bidCumulativeSize = 0;
32
+ let bidCumulativeNotional = 0;
33
+ const bids = bidsRaw.slice(0, levels).map((level) => {
34
+ const price = Number.parseFloat(level.px);
35
+ const size = Number.parseFloat(level.sz);
36
+ const notional = price * size;
37
+ bidCumulativeSize += size;
38
+ bidCumulativeNotional += notional;
39
+ return {
40
+ price: level.px,
41
+ size: level.sz,
42
+ total: bidCumulativeSize.toString(),
43
+ notional: notional.toFixed(2),
44
+ totalNotional: bidCumulativeNotional.toFixed(2),
45
+ };
46
+ });
47
+ let askCumulativeSize = 0;
48
+ let askCumulativeNotional = 0;
49
+ const asks = asksRaw.slice(0, levels).map((level) => {
50
+ const price = Number.parseFloat(level.px);
51
+ const size = Number.parseFloat(level.sz);
52
+ const notional = price * size;
53
+ askCumulativeSize += size;
54
+ askCumulativeNotional += notional;
55
+ return {
56
+ price: level.px,
57
+ size: level.sz,
58
+ total: askCumulativeSize.toString(),
59
+ notional: notional.toFixed(2),
60
+ totalNotional: askCumulativeNotional.toFixed(2),
61
+ };
62
+ });
63
+ const bestBid = bids[0];
64
+ const bestAsk = asks[0];
65
+ const bidPrice = bestBid ? Number.parseFloat(bestBid.price) : 0;
66
+ const askPrice = bestAsk ? Number.parseFloat(bestAsk.price) : 0;
67
+ const spread = askPrice > 0 && bidPrice > 0 ? askPrice - bidPrice : 0;
68
+ const midPrice = askPrice > 0 && bidPrice > 0 ? (askPrice + bidPrice) / 2 : 0;
69
+ const spreadPercentage = midPrice > 0 ? ((spread / midPrice) * 100).toFixed(4) : '0';
70
+ const maxTotal = Math.max(bidCumulativeSize, askCumulativeSize).toString();
71
+ return {
72
+ bids,
73
+ asks,
74
+ spread: spread.toFixed(5),
75
+ spreadPercentage,
76
+ midPrice: midPrice.toFixed(5),
77
+ lastUpdated: Date.now(),
78
+ maxTotal,
79
+ };
80
+ }
81
+ /**
82
+ * Owns a dedicated Hyperliquid WebSocket connection used solely for the
83
+ * order-book panel's server-aggregated `l2Book` subscription.
84
+ *
85
+ * The main connection (managed by the subscription service) multiplexes every
86
+ * subscription onto a single socket. The Hyperliquid SDK dispatches `l2Book`
87
+ * events by `coin` only, so running the raw (full-precision) and the aggregated
88
+ * (`nSigFigs`) subscriptions for the same coin on that shared socket
89
+ * cross-contaminates them — the coarse ladder and the precise spread/slippage
90
+ * clobber each other. Giving the aggregated subscription its own socket removes
91
+ * the collision entirely: this socket only ever carries a single `l2Book`
92
+ * stream, and the main socket is never touched by the panel's grouping.
93
+ *
94
+ * The socket is created lazily on the first subscription and torn down once the
95
+ * last subscription is removed, so it exists only while an order-book panel is
96
+ * open. Because network is a global setting, the transport is recreated if
97
+ * `isTestnet` changes between (re)subscriptions.
98
+ */
99
+ export class AggregatedOrderBookConnection {
100
+ constructor({ isTestnet }) {
101
+ _AggregatedOrderBookConnection_instances.add(this);
102
+ _AggregatedOrderBookConnection_isTestnet.set(this, void 0);
103
+ _AggregatedOrderBookConnection_transport.set(this, null);
104
+ _AggregatedOrderBookConnection_transportIsTestnet.set(this, false);
105
+ _AggregatedOrderBookConnection_activeCount.set(this, 0);
106
+ // Tracks the single `l2Book` payload the dedicated socket carries per asset,
107
+ // keyed by symbol. The SDK dispatches `l2Book` events by `coin` only, so two
108
+ // subscriptions for the same asset with different params (e.g. `nSigFigs`)
109
+ // would cross-contaminate on this shared socket — exactly the collision this
110
+ // connection exists to avoid. `count` refcounts the (identical) subscriptions
111
+ // sharing a payload so the entry is dropped once the last one unsubscribes.
112
+ _AggregatedOrderBookConnection_payloads.set(this, new Map());
113
+ // Force-terminate callback for every currently-active subscription. When a
114
+ // transport rebuild (`#closeTransport`) shuts the socket down out from under
115
+ // live subscriptions, these tear each one down — notifying the caller and
116
+ // releasing its SDK subscription / socket listeners — instead of orphaning
117
+ // them (stale handle, no more updates, and no further status because
118
+ // reporting is suppressed once `transport !== this.#transport`).
119
+ _AggregatedOrderBookConnection_activeSubscriptions.set(this, new Set());
120
+ // Set when the socket's auto-reconnection is exhausted (its
121
+ // `terminationSignal` aborts). A terminated socket cannot recover, so the next
122
+ // subscribe must build a fresh transport instead of reusing the dead one.
123
+ _AggregatedOrderBookConnection_terminated.set(this, false);
124
+ __classPrivateFieldSet(this, _AggregatedOrderBookConnection_isTestnet, isTestnet, "f");
125
+ }
126
+ /**
127
+ * Opens an aggregated `l2Book` subscription on the dedicated socket.
128
+ *
129
+ * Mirrors the subscription service's synchronous-unsubscribe contract: the
130
+ * returned function can be called before the async subscribe resolves and
131
+ * will cancel the pending subscription.
132
+ *
133
+ * Only one `l2Book` payload per asset may be active at a time. Subscribing to
134
+ * an asset that already has a live subscription with different params (e.g. a
135
+ * different `nSigFigs` or `mantissa`) throws, because the shared socket
136
+ * dispatches by `coin` and the conflicting streams would clobber each other.
137
+ *
138
+ * @param params - Subscription parameters.
139
+ * @returns An unsubscribe function.
140
+ * @throws If the asset already has an active subscription with different params.
141
+ */
142
+ subscribe(params) {
143
+ const levels = params.levels ?? DEFAULT_LEVELS;
144
+ // The `l2Book` subscription params the socket carries. `levels` is
145
+ // client-side only (it slices each snapshot), so it is deliberately excluded
146
+ // from the params and their signature.
147
+ const l2BookParams = {
148
+ coin: params.symbol,
149
+ nSigFigs: params.nSigFigs,
150
+ mantissa: params.mantissa ?? null,
151
+ fast: true,
152
+ };
153
+ const signature = JSON.stringify(l2BookParams);
154
+ const transport = __classPrivateFieldGet(this, _AggregatedOrderBookConnection_instances, "m", _AggregatedOrderBookConnection_ensureTransport).call(this, __classPrivateFieldGet(this, _AggregatedOrderBookConnection_isTestnet, "f").call(this));
155
+ // Reject a conflicting payload for an asset already on this socket. A
156
+ // recreated transport (first use, network change, or terminate) starts with
157
+ // an empty payload map, so this can only trip on the reuse path — the shared
158
+ // socket that would actually suffer the collision.
159
+ const existingPayload = __classPrivateFieldGet(this, _AggregatedOrderBookConnection_payloads, "f").get(params.symbol);
160
+ if (existingPayload && existingPayload.signature !== signature) {
161
+ throw new Error(`AggregatedOrderBookConnection: "${params.symbol}" is already subscribed with different params; only one l2Book payload per asset is allowed on the dedicated socket.`);
162
+ }
163
+ const { socket } = transport;
164
+ let cancelled = false;
165
+ let subscription = null;
166
+ __classPrivateFieldSet(this, _AggregatedOrderBookConnection_activeCount, __classPrivateFieldGet(this, _AggregatedOrderBookConnection_activeCount, "f") + 1, "f");
167
+ if (existingPayload) {
168
+ existingPayload.count += 1;
169
+ }
170
+ else {
171
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_payloads, "f").set(params.symbol, { signature, count: 1 });
172
+ }
173
+ // Set once this subscription's socket terminates (reconnection exhausted).
174
+ // The `error` state is terminal until teardown/resubscribe, so once set we
175
+ // suppress any late `connected`/`connecting` — e.g. from a subscribe promise
176
+ // that resolves *after* the socket died — which would otherwise flip the UI
177
+ // back to a healthy state on a dead socket and hide the manual-reconnect
178
+ // affordance.
179
+ let terminated = false;
180
+ const reportStatus = (status) => {
181
+ // Suppress reports from a subscription that no longer drives the UI: it
182
+ // was unsubscribed (`cancelled`), its transport was replaced by a network
183
+ // flip or recreate (`transport !== this.#transport`, so its socket is
184
+ // dead), or its socket permanently terminated and `error` is now sticky
185
+ // until teardown (`terminated`).
186
+ if (cancelled ||
187
+ transport !== __classPrivateFieldGet(this, _AggregatedOrderBookConnection_transport, "f") ||
188
+ (terminated && status !== 'error')) {
189
+ return;
190
+ }
191
+ params.onStatusChange?.(status);
192
+ };
193
+ // Reflect the socket's live health. Every drop dispatches a `close` event;
194
+ // the reconnecting socket only exposes permanent termination through its
195
+ // `terminationSignal` (an `AbortSignal`), which it aborts *before* the final
196
+ // close. So an aborted signal on close — unless it was our own `close()`
197
+ // (`TERMINATED_BY_USER`) — means automatic reconnection is exhausted: the
198
+ // unrecoverable state the UI surfaces with a manual reconnect button. A
199
+ // still-live signal means a transient drop the socket will auto-reconnect.
200
+ const handleOpen = () => reportStatus('connected');
201
+ const handleClose = () => {
202
+ // A torn-down subscription must not mutate shared connection state. Its
203
+ // listeners are normally detached before the socket closes, but guard
204
+ // anyway so a late `close` (e.g. from `transport.close()` racing listener
205
+ // removal) can't wrongly flip `#terminated`.
206
+ if (cancelled) {
207
+ return;
208
+ }
209
+ const { terminationSignal } = socket;
210
+ const terminatedByUser = terminationSignal.reason?.code ===
211
+ 'TERMINATED_BY_USER';
212
+ if (terminationSignal.aborted && !terminatedByUser) {
213
+ __classPrivateFieldSet(this, _AggregatedOrderBookConnection_terminated, true, "f");
214
+ terminated = true;
215
+ reportStatus('error');
216
+ return;
217
+ }
218
+ reportStatus('connecting');
219
+ };
220
+ socket.addEventListener('open', handleOpen);
221
+ socket.addEventListener('close', handleClose);
222
+ const removeSocketListeners = () => {
223
+ socket.removeEventListener('open', handleOpen);
224
+ socket.removeEventListener('close', handleClose);
225
+ };
226
+ // Ends this subscription when its transport is torn down beneath it (network
227
+ // flip, post-termination resubscribe, or `close()`). Unlike `teardown` it
228
+ // leaves the shared refcount/payload state alone — `#closeTransport` clears
229
+ // those wholesale — but still notifies the caller and releases this
230
+ // subscription's resources so nothing leaks on the dead socket.
231
+ const forceTerminate = () => {
232
+ if (cancelled) {
233
+ return;
234
+ }
235
+ // Notify directly rather than via `reportStatus`: `#closeTransport`
236
+ // detaches `#transport` before invoking these callbacks (so a reentrant
237
+ // subscribe from this handler builds a fresh transport instead of binding
238
+ // to the dying one), which would otherwise trip `reportStatus`'s
239
+ // stale-transport guard. A subscription whose socket already terminated
240
+ // has reported `error`; a still-live one (abandoned by a network flip or
241
+ // `close()`) needs the terminal signal so the caller stops trusting a
242
+ // now-dead book.
243
+ if (!terminated) {
244
+ params.onStatusChange?.('error');
245
+ }
246
+ cancelled = true;
247
+ removeSocketListeners();
248
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_activeSubscriptions, "f").delete(forceTerminate);
249
+ if (subscription) {
250
+ subscription.unsubscribe().catch(() => undefined);
251
+ subscription = null;
252
+ }
253
+ };
254
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_activeSubscriptions, "f").add(forceTerminate);
255
+ // Releases this subscription's refcount and tears down the socket once no
256
+ // subscriptions remain. Idempotent via `cancelled`, so it's safe whether it
257
+ // runs from the returned unsubscribe or from a failed subscribe.
258
+ const teardown = () => {
259
+ if (cancelled) {
260
+ return;
261
+ }
262
+ cancelled = true;
263
+ removeSocketListeners();
264
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_activeSubscriptions, "f").delete(forceTerminate);
265
+ if (subscription) {
266
+ subscription.unsubscribe().catch(() => undefined);
267
+ subscription = null;
268
+ }
269
+ // Only touch the refcount/current socket if this subscription still
270
+ // belongs to the active transport. If the transport was recreated (network
271
+ // change or terminate), this subscription's socket is already dead and
272
+ // `#activeCount` now tracks only the new transport's subscriptions — so an
273
+ // older unsubscribe must not decrement it and tear down the live socket.
274
+ if (transport === __classPrivateFieldGet(this, _AggregatedOrderBookConnection_transport, "f")) {
275
+ __classPrivateFieldSet(this, _AggregatedOrderBookConnection_activeCount, Math.max(0, __classPrivateFieldGet(this, _AggregatedOrderBookConnection_activeCount, "f") - 1), "f");
276
+ const entry = __classPrivateFieldGet(this, _AggregatedOrderBookConnection_payloads, "f").get(params.symbol);
277
+ if (entry) {
278
+ entry.count -= 1;
279
+ if (entry.count <= 0) {
280
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_payloads, "f").delete(params.symbol);
281
+ }
282
+ }
283
+ if (__classPrivateFieldGet(this, _AggregatedOrderBookConnection_activeCount, "f") === 0) {
284
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_instances, "m", _AggregatedOrderBookConnection_closeTransport).call(this);
285
+ }
286
+ }
287
+ };
288
+ // Surfaces a subscription failure the same way regardless of when it
289
+ // happens: report `error` (before teardown flips `cancelled`, which gates
290
+ // status updates) then release the refcount so the dead subscription doesn't
291
+ // keep the dedicated socket open. Used for both the initial subscribe
292
+ // rejection (`.catch`) and post-confirmation failures the SDK reports only
293
+ // through `onError` — e.g. the server rejecting the re-subscription after a
294
+ // reconnect, which removes the listener and stops all further events (a
295
+ // frozen order book that would otherwise still read as `connected`).
296
+ // Idempotent via `teardown`'s `cancelled` guard.
297
+ const handleSubscriptionError = () => {
298
+ reportStatus('error');
299
+ teardown();
300
+ };
301
+ reportStatus('connecting');
302
+ // Subscribe through the typed `l2Book` client so the params are validated
303
+ // before they reach the wire (`fast: true` requests fast mode — 5 levels at
304
+ // ~0.5s). The listener receives the decoded snapshot directly.
305
+ new SubscriptionClient({ transport })
306
+ .l2Book(l2BookParams, (data) => {
307
+ if (cancelled || data?.coin !== params.symbol || !data?.levels) {
308
+ return;
309
+ }
310
+ params.callback(processAggregatedOrderBook(data, levels));
311
+ },
312
+ // `onError` fires at most once for an *already confirmed* subscription
313
+ // that later fails (rejected re-subscription after reconnect, permanent
314
+ // termination, or a drop while re-subscription is disabled). The SDK
315
+ // removes the listener and emits nothing further, so treat it exactly
316
+ // like an initial failure.
317
+ { onError: handleSubscriptionError })
318
+ .then(async (sub) => {
319
+ // Stale if this subscription was unsubscribed (`cancelled`) or its
320
+ // transport was replaced (network flip / recreate) before the subscribe
321
+ // settled — either way the captured socket is dead, so clean up the SDK
322
+ // subscription instead of storing it or announcing `connected`.
323
+ if (cancelled || transport !== __classPrivateFieldGet(this, _AggregatedOrderBookConnection_transport, "f")) {
324
+ try {
325
+ await sub.unsubscribe();
326
+ }
327
+ catch {
328
+ // Ignore cleanup errors on an already-cancelled/stale subscription.
329
+ }
330
+ return undefined;
331
+ }
332
+ subscription = sub;
333
+ reportStatus('connected');
334
+ return undefined;
335
+ })
336
+ .catch(handleSubscriptionError);
337
+ return teardown;
338
+ }
339
+ /** Closes the dedicated socket and drops all subscriptions. */
340
+ close() {
341
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_instances, "m", _AggregatedOrderBookConnection_closeTransport).call(this);
342
+ }
343
+ }
344
+ _AggregatedOrderBookConnection_isTestnet = new WeakMap(), _AggregatedOrderBookConnection_transport = new WeakMap(), _AggregatedOrderBookConnection_transportIsTestnet = new WeakMap(), _AggregatedOrderBookConnection_activeCount = new WeakMap(), _AggregatedOrderBookConnection_payloads = new WeakMap(), _AggregatedOrderBookConnection_activeSubscriptions = new WeakMap(), _AggregatedOrderBookConnection_terminated = new WeakMap(), _AggregatedOrderBookConnection_instances = new WeakSet(), _AggregatedOrderBookConnection_ensureTransport = function _AggregatedOrderBookConnection_ensureTransport(isTestnet) {
345
+ if (__classPrivateFieldGet(this, _AggregatedOrderBookConnection_transport, "f") &&
346
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_transportIsTestnet, "f") === isTestnet &&
347
+ !__classPrivateFieldGet(this, _AggregatedOrderBookConnection_terminated, "f")) {
348
+ return __classPrivateFieldGet(this, _AggregatedOrderBookConnection_transport, "f");
349
+ }
350
+ // First use, the network changed, or the previous socket was terminated —
351
+ // (re)create the dedicated transport. Reuse the package's transport config
352
+ // so this socket shares the finite five-attempt reconnection policy; without
353
+ // it the SDK defaults `maxRetries` to Infinity and a sustained outage would
354
+ // never exhaust reconnection to reach the `error`/manual-reconnect state.
355
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_instances, "m", _AggregatedOrderBookConnection_closeTransport).call(this);
356
+ // `#closeTransport` notifies subscribers, which may synchronously re-enter
357
+ // `subscribe` and build a matching transport. Reuse it instead of orphaning
358
+ // it (which would leak the reentrant subscription on an unreferenced socket).
359
+ if (__classPrivateFieldGet(this, _AggregatedOrderBookConnection_transport, "f") &&
360
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_transportIsTestnet, "f") === isTestnet &&
361
+ !__classPrivateFieldGet(this, _AggregatedOrderBookConnection_terminated, "f")) {
362
+ return __classPrivateFieldGet(this, _AggregatedOrderBookConnection_transport, "f");
363
+ }
364
+ const transport = new WebSocketTransport({
365
+ isTestnet,
366
+ ...HYPERLIQUID_TRANSPORT_CONFIG,
367
+ reconnect: HYPERLIQUID_TRANSPORT_CONFIG.reconnect,
368
+ });
369
+ __classPrivateFieldSet(this, _AggregatedOrderBookConnection_transport, transport, "f");
370
+ __classPrivateFieldSet(this, _AggregatedOrderBookConnection_transportIsTestnet, isTestnet, "f");
371
+ return transport;
372
+ }, _AggregatedOrderBookConnection_closeTransport = function _AggregatedOrderBookConnection_closeTransport() {
373
+ const transport = __classPrivateFieldGet(this, _AggregatedOrderBookConnection_transport, "f");
374
+ // Snapshot the subscriptions to force-terminate, then detach ALL shared
375
+ // state (the set, `#transport`, refcounts) *before* invoking any callback.
376
+ // Those callbacks notify subscribers via `onStatusChange`, which can
377
+ // synchronously re-enter `subscribe`; detaching first guarantees a reentrant
378
+ // subscribe builds a fresh transport (rather than reusing this dying one)
379
+ // and registers itself in a clean set (rather than being swept up by, or
380
+ // lingering past, this teardown). Subscriptions torn down normally have
381
+ // already removed themselves, so their entry is a no-op here.
382
+ const subscriptions = [...__classPrivateFieldGet(this, _AggregatedOrderBookConnection_activeSubscriptions, "f")];
383
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_activeSubscriptions, "f").clear();
384
+ __classPrivateFieldSet(this, _AggregatedOrderBookConnection_transport, null, "f");
385
+ __classPrivateFieldSet(this, _AggregatedOrderBookConnection_activeCount, 0, "f");
386
+ __classPrivateFieldGet(this, _AggregatedOrderBookConnection_payloads, "f").clear();
387
+ __classPrivateFieldSet(this, _AggregatedOrderBookConnection_terminated, false, "f");
388
+ // Force-terminate (which detaches each subscription's socket listeners)
389
+ // BEFORE closing the transport. `close()` on an already-exhausted socket
390
+ // dispatches a final `close`; if a stale `handleClose` were still attached
391
+ // it would re-set `#terminated` right after we cleared it, making the next
392
+ // `#ensureTransport` tear down the healthy replacement socket.
393
+ for (const forceTerminate of subscriptions) {
394
+ forceTerminate();
395
+ }
396
+ if (transport) {
397
+ transport.close();
398
+ }
399
+ };
400
+ //# sourceMappingURL=AggregatedOrderBookConnection.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AggregatedOrderBookConnection.mjs","sourceRoot":"","sources":["../../src/services/AggregatedOrderBookConnection.ts"],"names":[],"mappings":"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","sourcesContent":["import { SubscriptionClient, WebSocketTransport } from '@nktkas/hyperliquid';\nimport type { ISubscription } from '@nktkas/hyperliquid';\n\nimport { HYPERLIQUID_TRANSPORT_CONFIG } from '../constants/hyperLiquidConfig';\nimport type { OrderBookData } from '../types';\n\n/**\n * A single L2 book price level as delivered by Hyperliquid's `l2Book`\n * subscription. Declared locally to avoid coupling to the SDK's exported type\n * names (and to keep this the only file that references the SDK's shapes).\n */\ntype HyperliquidL2BookLevel = {\n /** Price. */\n px: string;\n /** Total size resting at this price. */\n sz: string;\n /** Number of individual orders. */\n n: number;\n};\n\n/** `l2Book` snapshot event (index 0 = bids, index 1 = asks). */\ntype HyperliquidL2BookEvent = {\n coin: string;\n time: number;\n levels: [bids: HyperliquidL2BookLevel[], asks: HyperliquidL2BookLevel[]];\n spread?: string;\n};\n\n/**\n * Health of the dedicated order-book socket, surfaced to the UI so the panel\n * can show a reconnect affordance.\n *\n * - `connecting`: socket opening or reconnecting after a transient drop.\n * - `connected`: subscription is live.\n * - `error`: dropped and automatic reconnection was exhausted; needs a manual reconnect.\n */\nexport type OrderBookConnectionStatus = 'connecting' | 'connected' | 'error';\n\nexport type SubscribeAggregatedOrderBookParams = {\n /** Market symbol (e.g. 'BTC'). */\n symbol: string;\n /** Number of levels per side to keep. */\n levels?: number;\n /**\n * Server-side aggregation significant figures. Required: omitting it would\n * request the raw, full-precision book instead of an aggregated one, which\n * contradicts this service's contract.\n */\n nSigFigs: 2 | 3 | 4 | 5;\n /** Mantissa refinement when `nSigFigs` is 5. */\n mantissa?: 2 | 5;\n /** Invoked with each processed snapshot. */\n callback: (data: OrderBookData) => void;\n /** Invoked when the underlying socket's health changes. */\n onStatusChange?: (status: OrderBookConnectionStatus) => void;\n};\n\nexport type AggregatedOrderBookConnectionOptions = {\n /** Resolves the current network at subscribe time. */\n isTestnet: () => boolean;\n};\n\n// Fast mode streams 5 levels per side (slow mode streams 20). We run fast mode\n// for lower-latency ladder updates, so the book never carries more than this.\nconst DEFAULT_LEVELS = 5;\n\n/**\n * Transforms a raw Hyperliquid `l2Book` snapshot into the `OrderBookData` shape\n * the UI consumes. Mirrors the subscription service's internal\n * `processOrderBookData` so this dedicated connection is a drop-in replacement\n * for `subscribeToOrderBook` on the aggregated channel.\n *\n * @param data - Raw `l2Book` event.\n * @param levels - Number of levels per side to keep.\n * @returns Processed order-book snapshot.\n */\nexport function processAggregatedOrderBook(\n data: HyperliquidL2BookEvent,\n levels: number,\n): OrderBookData {\n const bidsRaw = data?.levels?.[0] ?? [];\n const asksRaw = data?.levels?.[1] ?? [];\n\n let bidCumulativeSize = 0;\n let bidCumulativeNotional = 0;\n const bids = bidsRaw.slice(0, levels).map((level) => {\n const price = Number.parseFloat(level.px);\n const size = Number.parseFloat(level.sz);\n const notional = price * size;\n bidCumulativeSize += size;\n bidCumulativeNotional += notional;\n return {\n price: level.px,\n size: level.sz,\n total: bidCumulativeSize.toString(),\n notional: notional.toFixed(2),\n totalNotional: bidCumulativeNotional.toFixed(2),\n };\n });\n\n let askCumulativeSize = 0;\n let askCumulativeNotional = 0;\n const asks = asksRaw.slice(0, levels).map((level) => {\n const price = Number.parseFloat(level.px);\n const size = Number.parseFloat(level.sz);\n const notional = price * size;\n askCumulativeSize += size;\n askCumulativeNotional += notional;\n return {\n price: level.px,\n size: level.sz,\n total: askCumulativeSize.toString(),\n notional: notional.toFixed(2),\n totalNotional: askCumulativeNotional.toFixed(2),\n };\n });\n\n const bestBid = bids[0];\n const bestAsk = asks[0];\n const bidPrice = bestBid ? Number.parseFloat(bestBid.price) : 0;\n const askPrice = bestAsk ? Number.parseFloat(bestAsk.price) : 0;\n const spread = askPrice > 0 && bidPrice > 0 ? askPrice - bidPrice : 0;\n const midPrice = askPrice > 0 && bidPrice > 0 ? (askPrice + bidPrice) / 2 : 0;\n const spreadPercentage =\n midPrice > 0 ? ((spread / midPrice) * 100).toFixed(4) : '0';\n const maxTotal = Math.max(bidCumulativeSize, askCumulativeSize).toString();\n\n return {\n bids,\n asks,\n spread: spread.toFixed(5),\n spreadPercentage,\n midPrice: midPrice.toFixed(5),\n lastUpdated: Date.now(),\n maxTotal,\n };\n}\n\n/**\n * Owns a dedicated Hyperliquid WebSocket connection used solely for the\n * order-book panel's server-aggregated `l2Book` subscription.\n *\n * The main connection (managed by the subscription service) multiplexes every\n * subscription onto a single socket. The Hyperliquid SDK dispatches `l2Book`\n * events by `coin` only, so running the raw (full-precision) and the aggregated\n * (`nSigFigs`) subscriptions for the same coin on that shared socket\n * cross-contaminates them — the coarse ladder and the precise spread/slippage\n * clobber each other. Giving the aggregated subscription its own socket removes\n * the collision entirely: this socket only ever carries a single `l2Book`\n * stream, and the main socket is never touched by the panel's grouping.\n *\n * The socket is created lazily on the first subscription and torn down once the\n * last subscription is removed, so it exists only while an order-book panel is\n * open. Because network is a global setting, the transport is recreated if\n * `isTestnet` changes between (re)subscriptions.\n */\nexport class AggregatedOrderBookConnection {\n readonly #isTestnet: () => boolean;\n\n #transport: WebSocketTransport | null = null;\n\n #transportIsTestnet = false;\n\n #activeCount = 0;\n\n // Tracks the single `l2Book` payload the dedicated socket carries per asset,\n // keyed by symbol. The SDK dispatches `l2Book` events by `coin` only, so two\n // subscriptions for the same asset with different params (e.g. `nSigFigs`)\n // would cross-contaminate on this shared socket — exactly the collision this\n // connection exists to avoid. `count` refcounts the (identical) subscriptions\n // sharing a payload so the entry is dropped once the last one unsubscribes.\n readonly #payloads = new Map<string, { signature: string; count: number }>();\n\n // Force-terminate callback for every currently-active subscription. When a\n // transport rebuild (`#closeTransport`) shuts the socket down out from under\n // live subscriptions, these tear each one down — notifying the caller and\n // releasing its SDK subscription / socket listeners — instead of orphaning\n // them (stale handle, no more updates, and no further status because\n // reporting is suppressed once `transport !== this.#transport`).\n readonly #activeSubscriptions = new Set<() => void>();\n\n // Set when the socket's auto-reconnection is exhausted (its\n // `terminationSignal` aborts). A terminated socket cannot recover, so the next\n // subscribe must build a fresh transport instead of reusing the dead one.\n #terminated = false;\n\n constructor({ isTestnet }: AggregatedOrderBookConnectionOptions) {\n this.#isTestnet = isTestnet;\n }\n\n /**\n * Opens an aggregated `l2Book` subscription on the dedicated socket.\n *\n * Mirrors the subscription service's synchronous-unsubscribe contract: the\n * returned function can be called before the async subscribe resolves and\n * will cancel the pending subscription.\n *\n * Only one `l2Book` payload per asset may be active at a time. Subscribing to\n * an asset that already has a live subscription with different params (e.g. a\n * different `nSigFigs` or `mantissa`) throws, because the shared socket\n * dispatches by `coin` and the conflicting streams would clobber each other.\n *\n * @param params - Subscription parameters.\n * @returns An unsubscribe function.\n * @throws If the asset already has an active subscription with different params.\n */\n subscribe(params: SubscribeAggregatedOrderBookParams): () => void {\n const levels = params.levels ?? DEFAULT_LEVELS;\n // The `l2Book` subscription params the socket carries. `levels` is\n // client-side only (it slices each snapshot), so it is deliberately excluded\n // from the params and their signature.\n const l2BookParams = {\n coin: params.symbol,\n nSigFigs: params.nSigFigs,\n mantissa: params.mantissa ?? null,\n fast: true as const,\n };\n const signature = JSON.stringify(l2BookParams);\n\n const transport = this.#ensureTransport(this.#isTestnet());\n\n // Reject a conflicting payload for an asset already on this socket. A\n // recreated transport (first use, network change, or terminate) starts with\n // an empty payload map, so this can only trip on the reuse path — the shared\n // socket that would actually suffer the collision.\n const existingPayload = this.#payloads.get(params.symbol);\n if (existingPayload && existingPayload.signature !== signature) {\n throw new Error(\n `AggregatedOrderBookConnection: \"${params.symbol}\" is already subscribed with different params; only one l2Book payload per asset is allowed on the dedicated socket.`,\n );\n }\n\n const { socket } = transport;\n\n let cancelled = false;\n let subscription: ISubscription | null = null;\n this.#activeCount += 1;\n if (existingPayload) {\n existingPayload.count += 1;\n } else {\n this.#payloads.set(params.symbol, { signature, count: 1 });\n }\n\n // Set once this subscription's socket terminates (reconnection exhausted).\n // The `error` state is terminal until teardown/resubscribe, so once set we\n // suppress any late `connected`/`connecting` — e.g. from a subscribe promise\n // that resolves *after* the socket died — which would otherwise flip the UI\n // back to a healthy state on a dead socket and hide the manual-reconnect\n // affordance.\n let terminated = false;\n const reportStatus = (status: OrderBookConnectionStatus): void => {\n // Suppress reports from a subscription that no longer drives the UI: it\n // was unsubscribed (`cancelled`), its transport was replaced by a network\n // flip or recreate (`transport !== this.#transport`, so its socket is\n // dead), or its socket permanently terminated and `error` is now sticky\n // until teardown (`terminated`).\n if (\n cancelled ||\n transport !== this.#transport ||\n (terminated && status !== 'error')\n ) {\n return;\n }\n params.onStatusChange?.(status);\n };\n\n // Reflect the socket's live health. Every drop dispatches a `close` event;\n // the reconnecting socket only exposes permanent termination through its\n // `terminationSignal` (an `AbortSignal`), which it aborts *before* the final\n // close. So an aborted signal on close — unless it was our own `close()`\n // (`TERMINATED_BY_USER`) — means automatic reconnection is exhausted: the\n // unrecoverable state the UI surfaces with a manual reconnect button. A\n // still-live signal means a transient drop the socket will auto-reconnect.\n const handleOpen = (): void => reportStatus('connected');\n const handleClose = (): void => {\n // A torn-down subscription must not mutate shared connection state. Its\n // listeners are normally detached before the socket closes, but guard\n // anyway so a late `close` (e.g. from `transport.close()` racing listener\n // removal) can't wrongly flip `#terminated`.\n if (cancelled) {\n return;\n }\n const { terminationSignal } = socket;\n const terminatedByUser =\n (terminationSignal.reason as { code?: string } | undefined)?.code ===\n 'TERMINATED_BY_USER';\n if (terminationSignal.aborted && !terminatedByUser) {\n this.#terminated = true;\n terminated = true;\n reportStatus('error');\n return;\n }\n reportStatus('connecting');\n };\n socket.addEventListener('open', handleOpen);\n socket.addEventListener('close', handleClose);\n\n const removeSocketListeners = (): void => {\n socket.removeEventListener('open', handleOpen);\n socket.removeEventListener('close', handleClose);\n };\n\n // Ends this subscription when its transport is torn down beneath it (network\n // flip, post-termination resubscribe, or `close()`). Unlike `teardown` it\n // leaves the shared refcount/payload state alone — `#closeTransport` clears\n // those wholesale — but still notifies the caller and releases this\n // subscription's resources so nothing leaks on the dead socket.\n const forceTerminate = (): void => {\n if (cancelled) {\n return;\n }\n // Notify directly rather than via `reportStatus`: `#closeTransport`\n // detaches `#transport` before invoking these callbacks (so a reentrant\n // subscribe from this handler builds a fresh transport instead of binding\n // to the dying one), which would otherwise trip `reportStatus`'s\n // stale-transport guard. A subscription whose socket already terminated\n // has reported `error`; a still-live one (abandoned by a network flip or\n // `close()`) needs the terminal signal so the caller stops trusting a\n // now-dead book.\n if (!terminated) {\n params.onStatusChange?.('error');\n }\n cancelled = true;\n removeSocketListeners();\n this.#activeSubscriptions.delete(forceTerminate);\n if (subscription) {\n subscription.unsubscribe().catch(() => undefined);\n subscription = null;\n }\n };\n this.#activeSubscriptions.add(forceTerminate);\n\n // Releases this subscription's refcount and tears down the socket once no\n // subscriptions remain. Idempotent via `cancelled`, so it's safe whether it\n // runs from the returned unsubscribe or from a failed subscribe.\n const teardown = (): void => {\n if (cancelled) {\n return;\n }\n cancelled = true;\n removeSocketListeners();\n this.#activeSubscriptions.delete(forceTerminate);\n if (subscription) {\n subscription.unsubscribe().catch(() => undefined);\n subscription = null;\n }\n // Only touch the refcount/current socket if this subscription still\n // belongs to the active transport. If the transport was recreated (network\n // change or terminate), this subscription's socket is already dead and\n // `#activeCount` now tracks only the new transport's subscriptions — so an\n // older unsubscribe must not decrement it and tear down the live socket.\n if (transport === this.#transport) {\n this.#activeCount = Math.max(0, this.#activeCount - 1);\n const entry = this.#payloads.get(params.symbol);\n if (entry) {\n entry.count -= 1;\n if (entry.count <= 0) {\n this.#payloads.delete(params.symbol);\n }\n }\n if (this.#activeCount === 0) {\n this.#closeTransport();\n }\n }\n };\n\n // Surfaces a subscription failure the same way regardless of when it\n // happens: report `error` (before teardown flips `cancelled`, which gates\n // status updates) then release the refcount so the dead subscription doesn't\n // keep the dedicated socket open. Used for both the initial subscribe\n // rejection (`.catch`) and post-confirmation failures the SDK reports only\n // through `onError` — e.g. the server rejecting the re-subscription after a\n // reconnect, which removes the listener and stops all further events (a\n // frozen order book that would otherwise still read as `connected`).\n // Idempotent via `teardown`'s `cancelled` guard.\n const handleSubscriptionError = (): void => {\n reportStatus('error');\n teardown();\n };\n\n reportStatus('connecting');\n\n // Subscribe through the typed `l2Book` client so the params are validated\n // before they reach the wire (`fast: true` requests fast mode — 5 levels at\n // ~0.5s). The listener receives the decoded snapshot directly.\n new SubscriptionClient({ transport })\n .l2Book(\n l2BookParams,\n (data: HyperliquidL2BookEvent) => {\n if (cancelled || data?.coin !== params.symbol || !data?.levels) {\n return;\n }\n params.callback(processAggregatedOrderBook(data, levels));\n },\n // `onError` fires at most once for an *already confirmed* subscription\n // that later fails (rejected re-subscription after reconnect, permanent\n // termination, or a drop while re-subscription is disabled). The SDK\n // removes the listener and emits nothing further, so treat it exactly\n // like an initial failure.\n { onError: handleSubscriptionError },\n )\n .then(async (sub) => {\n // Stale if this subscription was unsubscribed (`cancelled`) or its\n // transport was replaced (network flip / recreate) before the subscribe\n // settled — either way the captured socket is dead, so clean up the SDK\n // subscription instead of storing it or announcing `connected`.\n if (cancelled || transport !== this.#transport) {\n try {\n await sub.unsubscribe();\n } catch {\n // Ignore cleanup errors on an already-cancelled/stale subscription.\n }\n return undefined;\n }\n subscription = sub;\n reportStatus('connected');\n return undefined;\n })\n .catch(handleSubscriptionError);\n\n return teardown;\n }\n\n /** Closes the dedicated socket and drops all subscriptions. */\n close(): void {\n this.#closeTransport();\n }\n\n #ensureTransport(isTestnet: boolean): WebSocketTransport {\n if (\n this.#transport &&\n this.#transportIsTestnet === isTestnet &&\n !this.#terminated\n ) {\n return this.#transport;\n }\n // First use, the network changed, or the previous socket was terminated —\n // (re)create the dedicated transport. Reuse the package's transport config\n // so this socket shares the finite five-attempt reconnection policy; without\n // it the SDK defaults `maxRetries` to Infinity and a sustained outage would\n // never exhaust reconnection to reach the `error`/manual-reconnect state.\n this.#closeTransport();\n // `#closeTransport` notifies subscribers, which may synchronously re-enter\n // `subscribe` and build a matching transport. Reuse it instead of orphaning\n // it (which would leak the reentrant subscription on an unreferenced socket).\n if (\n this.#transport &&\n this.#transportIsTestnet === isTestnet &&\n !this.#terminated\n ) {\n return this.#transport;\n }\n const transport = new WebSocketTransport({\n isTestnet,\n ...HYPERLIQUID_TRANSPORT_CONFIG,\n reconnect: HYPERLIQUID_TRANSPORT_CONFIG.reconnect,\n });\n this.#transport = transport;\n this.#transportIsTestnet = isTestnet;\n return transport;\n }\n\n #closeTransport(): void {\n const transport = this.#transport;\n // Snapshot the subscriptions to force-terminate, then detach ALL shared\n // state (the set, `#transport`, refcounts) *before* invoking any callback.\n // Those callbacks notify subscribers via `onStatusChange`, which can\n // synchronously re-enter `subscribe`; detaching first guarantees a reentrant\n // subscribe builds a fresh transport (rather than reusing this dying one)\n // and registers itself in a clean set (rather than being swept up by, or\n // lingering past, this teardown). Subscriptions torn down normally have\n // already removed themselves, so their entry is a no-op here.\n const subscriptions = [...this.#activeSubscriptions];\n this.#activeSubscriptions.clear();\n this.#transport = null;\n this.#activeCount = 0;\n this.#payloads.clear();\n this.#terminated = false;\n // Force-terminate (which detaches each subscription's socket listeners)\n // BEFORE closing the transport. `close()` on an already-exhausted socket\n // dispatches a final `close`; if a stale `handleClose` were still attached\n // it would re-set `#terminated` right after we cleared it, making the next\n // `#ensureTransport` tear down the healthy replacement socket.\n for (const forceTerminate of subscriptions) {\n forceTerminate();\n }\n if (transport) {\n transport.close();\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/perps-controller",
3
- "version": "9.3.0-preview-68ba7f84e",
3
+ "version": "10.0.0-preview-5959fe813",
4
4
  "description": "Controller for perpetual trading functionality in MetaMask",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -119,13 +119,13 @@
119
119
  "@metamask/network-controller": "^34.0.0",
120
120
  "@metamask/profile-sync-controller": "^28.3.0",
121
121
  "@metamask/remote-feature-flag-controller": "^4.2.2",
122
- "@metamask/transaction-controller": "^69.1.0",
122
+ "@metamask/transaction-controller": "^69.2.1",
123
123
  "@ts-bridge/cli": "^0.6.4",
124
- "@types/jest": "^29.5.14",
124
+ "@types/jest": "^30.0.0",
125
125
  "@types/uuid": "^8.3.0",
126
126
  "deepmerge": "^4.2.2",
127
- "jest": "^29.7.0",
128
- "ts-jest": "^29.2.5",
127
+ "jest": "^30.4.2",
128
+ "ts-jest": "^29.4.11",
129
129
  "tsx": "^4.20.5",
130
130
  "typedoc": "^0.25.13",
131
131
  "typedoc-plugin-missing-exports": "^2.0.0",