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