@agg-build/sdk 4.17.4 → 4.19.0

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.
package/dist/index.mjs CHANGED
@@ -1,3 +1,65 @@
1
+ import {
2
+ CandleBuilder
3
+ } from "./chunk-VD5UTAVD.mjs";
4
+ import {
5
+ ACTIVE_VENUES,
6
+ AccountProvider,
7
+ AccountType,
8
+ AggWebSocket,
9
+ CONFIRMED_MATCH_STATUSES,
10
+ Chain,
11
+ DEFAULT_BALANCE_REFILL_DAILY_CAP_RAW,
12
+ DEFAULT_BALANCE_REFILL_MAX_FEE_RAW,
13
+ IMAGE_SIZES,
14
+ INACTIVE_VENUES,
15
+ ImageSize,
16
+ LIMIT_PRICE_RAW_SCALE,
17
+ MarketStatus,
18
+ MatchStatus,
19
+ MatchType,
20
+ OrderStatus,
21
+ OrderType,
22
+ RETINA_IMAGE_SIZES,
23
+ TimeInForce,
24
+ TradeSide,
25
+ VENUES,
26
+ VENUE_CHAIN_IDS,
27
+ Venue,
28
+ adjustLimitPriceRawToTick,
29
+ applyOrderbookDelta,
30
+ buildVenueUrl,
31
+ computeBestSplitsByAmount,
32
+ computeChecksum,
33
+ enumGuard,
34
+ formatMarketQuestion,
35
+ formatOutcomeLabel,
36
+ formatOutcomeTitle,
37
+ getBalanceRefillMaximumRaw,
38
+ getEffectiveDisabledVenues,
39
+ getWalletAddressFromUserProfile,
40
+ hasShape,
41
+ isBalanceRefillWithinDailyCap,
42
+ isEmail,
43
+ isEnum,
44
+ isFiniteNonNeg,
45
+ isNonEmptyString,
46
+ isVenueActive,
47
+ isVenueDataVisible,
48
+ isVenueInactive,
49
+ loadImmutableHistory,
50
+ normalizeVenueMarketCluster,
51
+ optimizedImageUrl,
52
+ orderbookCrc32,
53
+ parse,
54
+ parseEmail,
55
+ parseEmailStrict,
56
+ projectedOrderbookChecksum,
57
+ readChartHistoryText,
58
+ retinaImageUrl,
59
+ safeParse,
60
+ snapshotToOrderbook,
61
+ sortVenues
62
+ } from "./chunk-QXBVA5TW.mjs";
1
63
  import {
2
64
  __async,
3
65
  __objRest,
@@ -5,686 +67,26 @@ import {
5
67
  __spreadValues
6
68
  } from "./chunk-WNQUEZJF.mjs";
7
69
 
8
- // ../common/src/enums/venue.ts
9
- var Venue = /* @__PURE__ */ ((Venue3) => {
10
- Venue3["kalshi"] = "kalshi";
11
- Venue3["polymarket"] = "polymarket";
12
- Venue3["limitless"] = "limitless";
13
- Venue3["opinion"] = "opinion";
14
- Venue3["predict"] = "predict";
15
- Venue3["pred"] = "pred";
16
- Venue3["probable"] = "probable";
17
- Venue3["myriad"] = "myriad";
18
- Venue3["hyperliquid"] = "hyperliquid";
19
- Venue3["novig"] = "novig";
20
- Venue3["prophetx"] = "prophetx";
21
- return Venue3;
22
- })(Venue || {});
23
- var VENUES = [
24
- "kalshi" /* kalshi */,
25
- "polymarket" /* polymarket */,
26
- "limitless" /* limitless */,
27
- "opinion" /* opinion */,
28
- "predict" /* predict */,
29
- "pred" /* pred */,
30
- "probable" /* probable */,
31
- "myriad" /* myriad */,
32
- "hyperliquid" /* hyperliquid */,
33
- "novig" /* novig */,
34
- "prophetx" /* prophetx */
35
- ];
36
- var VENUE_CHAIN_IDS = {
37
- ["kalshi" /* kalshi */]: 792703809,
38
- ["polymarket" /* polymarket */]: 137,
39
- ["limitless" /* limitless */]: 8453,
40
- ["predict" /* predict */]: 56,
41
- ["pred" /* pred */]: 8453,
42
- ["myriad" /* myriad */]: 56,
43
- ["hyperliquid" /* hyperliquid */]: 1337,
44
- ["novig" /* novig */]: 792703809,
45
- ["prophetx" /* prophetx */]: 792703809
46
- };
47
- var ACTIVE_VENUES = [
48
- "kalshi" /* kalshi */,
49
- "polymarket" /* polymarket */,
50
- "limitless" /* limitless */,
51
- "opinion" /* opinion */,
52
- "predict" /* predict */,
53
- "pred" /* pred */,
54
- "probable" /* probable */,
55
- "myriad" /* myriad */,
56
- "hyperliquid" /* hyperliquid */,
57
- "novig" /* novig */,
58
- "prophetx" /* prophetx */
59
- ];
60
- var INACTIVE_VENUES = [];
61
- var DEFAULT_DISABLED_VENUES = ["pred" /* pred */, "novig" /* novig */, "prophetx" /* prophetx */];
62
- var ACTIVE_VENUE_SET = new Set(ACTIVE_VENUES);
63
- var INACTIVE_VENUE_SET = new Set(INACTIVE_VENUES);
64
- function normalizeVenue(venue) {
65
- return venue.trim().toLowerCase();
66
- }
67
- function isVenueActive(venue) {
68
- if (ACTIVE_VENUE_SET.has(venue)) return true;
69
- if (INACTIVE_VENUE_SET.has(venue)) return false;
70
- return ACTIVE_VENUE_SET.has(normalizeVenue(venue));
71
- }
72
- function isVenueInactive(venue) {
73
- if (INACTIVE_VENUE_SET.has(venue)) return true;
74
- if (ACTIVE_VENUE_SET.has(venue)) return false;
75
- return INACTIVE_VENUE_SET.has(normalizeVenue(venue));
76
- }
77
- function isVenueDataVisible(venue, dataVisibleOverride = []) {
78
- if (isVenueActive(venue)) return true;
79
- const n = normalizeVenue(venue);
80
- return dataVisibleOverride.some((v) => normalizeVenue(v) === n);
81
- }
82
- function getEffectiveDisabledVenues(disabledVenues, dataVisibleVenues = []) {
83
- if (disabledVenues === void 0) {
84
- const disabled2 = new Set(DEFAULT_DISABLED_VENUES);
85
- for (const venue of dataVisibleVenues) disabled2.delete(normalizeVenue(venue));
86
- return VENUES.filter((venue) => disabled2.has(venue));
87
- }
88
- const disabled = new Set(disabledVenues.map(normalizeVenue));
89
- return VENUES.filter((venue) => disabled.has(venue));
90
- }
91
-
92
- // ../common/src/enums/image-size.ts
93
- var ImageSize = /* @__PURE__ */ ((ImageSize2) => {
94
- ImageSize2[ImageSize2["sm"] = 44] = "sm";
95
- ImageSize2[ImageSize2["md"] = 48] = "md";
96
- ImageSize2[ImageSize2["lg"] = 60] = "lg";
97
- ImageSize2[ImageSize2["original"] = 0] = "original";
98
- return ImageSize2;
99
- })(ImageSize || {});
100
- var IMAGE_SIZES = [44 /* sm */, 48 /* md */, 60 /* lg */];
101
-
102
- // ../common/src/enums/account.ts
103
- var AccountType = /* @__PURE__ */ ((AccountType2) => {
104
- AccountType2["oauth"] = "oauth";
105
- AccountType2["siwe"] = "siwe";
106
- AccountType2["siws"] = "siws";
107
- AccountType2["email"] = "email";
108
- return AccountType2;
109
- })(AccountType || {});
110
- var AccountProvider = /* @__PURE__ */ ((AccountProvider2) => {
111
- AccountProvider2["wallet"] = "wallet";
112
- AccountProvider2["solana_wallet"] = "solana_wallet";
113
- AccountProvider2["google"] = "google";
114
- AccountProvider2["twitter"] = "twitter";
115
- AccountProvider2["apple"] = "apple";
116
- AccountProvider2["email"] = "email";
117
- return AccountProvider2;
118
- })(AccountProvider || {});
119
-
120
- // ../common/src/enums/chain.ts
121
- var Chain = /* @__PURE__ */ ((Chain2) => {
122
- Chain2["evm"] = "evm";
123
- Chain2["svm"] = "svm";
124
- return Chain2;
125
- })(Chain || {});
126
-
127
- // ../common/src/enums/market-status.ts
128
- var MatchStatus = /* @__PURE__ */ ((MatchStatus3) => {
129
- MatchStatus3["pending"] = "pending";
130
- MatchStatus3["unmatched"] = "unmatched";
131
- MatchStatus3["review"] = "review";
132
- MatchStatus3["matched"] = "matched";
133
- MatchStatus3["verified"] = "verified";
134
- MatchStatus3["rejected"] = "rejected";
135
- return MatchStatus3;
136
- })(MatchStatus || {});
137
- var MatchType = /* @__PURE__ */ ((MatchType2) => {
138
- MatchType2["manual"] = "manual";
139
- MatchType2["llm"] = "llm";
140
- return MatchType2;
141
- })(MatchType || {});
142
- var CONFIRMED_MATCH_STATUSES = ["matched" /* matched */, "verified" /* verified */];
143
- var MarketStatus = /* @__PURE__ */ ((MarketStatus3) => {
144
- MarketStatus3["open"] = "open";
145
- MarketStatus3["closed"] = "closed";
146
- MarketStatus3["resolved"] = "resolved";
147
- MarketStatus3["unopened"] = "unopened";
148
- MarketStatus3["paused"] = "paused";
149
- return MarketStatus3;
150
- })(MarketStatus || {});
151
-
152
- // ../common/src/enums/trading.ts
153
- var OrderStatus = /* @__PURE__ */ ((OrderStatus2) => {
154
- OrderStatus2["pending"] = "pending";
155
- OrderStatus2["signing"] = "signing";
156
- OrderStatus2["pending_bridge"] = "pending_bridge";
157
- OrderStatus2["submitting"] = "submitting";
158
- OrderStatus2["submitted"] = "submitted";
159
- OrderStatus2["open"] = "open";
160
- OrderStatus2["partially_filled_open"] = "partially_filled_open";
161
- OrderStatus2["cancel_pending"] = "cancel_pending";
162
- OrderStatus2["filled"] = "filled";
163
- OrderStatus2["partial_fill"] = "partial_fill";
164
- OrderStatus2["failed"] = "failed";
165
- OrderStatus2["expired"] = "expired";
166
- OrderStatus2["cancelled"] = "cancelled";
167
- return OrderStatus2;
168
- })(OrderStatus || {});
169
- var TradeSide = /* @__PURE__ */ ((TradeSide2) => {
170
- TradeSide2["Buy"] = "buy";
171
- TradeSide2["Sell"] = "sell";
172
- return TradeSide2;
173
- })(TradeSide || {});
174
- var OrderType = /* @__PURE__ */ ((OrderType2) => {
175
- OrderType2["Market"] = "market";
176
- OrderType2["Limit"] = "limit";
177
- return OrderType2;
178
- })(OrderType || {});
179
- var TimeInForce = /* @__PURE__ */ ((TimeInForce2) => {
180
- TimeInForce2["GTC"] = "GTC";
181
- TimeInForce2["GTD"] = "GTD";
182
- TimeInForce2["FOK"] = "FOK";
183
- TimeInForce2["FAK"] = "FAK";
184
- TimeInForce2["IOC"] = "IOC";
185
- TimeInForce2["ALO"] = "ALO";
186
- return TimeInForce2;
187
- })(TimeInForce || {});
188
-
189
- // ../common/src/utils/format-market-display.ts
190
- function formatOutcomeLabel(venue, outcome, _market) {
191
- return outcome.label;
192
- }
193
- function formatMarketQuestion(venue, market) {
194
- var _a, _b, _c, _d;
195
- if ((_a = market.shortTitle) == null ? void 0 : _a.trim()) return market.shortTitle.trim();
196
- if (venue === "kalshi" /* kalshi */) {
197
- const firstTitle = (_d = (_c = (_b = market.venueMarketOutcomes) == null ? void 0 : _b[0]) == null ? void 0 : _c.title) == null ? void 0 : _d.trim();
198
- if (firstTitle) return firstTitle;
199
- }
200
- return market.question.trim();
201
- }
202
- function formatOutcomeTitle(_venue, outcome, market) {
203
- var _a, _b;
204
- const outcomes = (_a = market.venueMarketOutcomes) != null ? _a : [];
205
- if (outcomes.length >= 2) {
206
- const normalized = outcomes.map((o) => {
207
- var _a2, _b2;
208
- return (_b2 = (_a2 = o.title) == null ? void 0 : _a2.trim()) != null ? _b2 : "";
209
- });
210
- const hasAnyTitle = normalized.some((t) => t.length > 0);
211
- if (hasAnyTitle && normalized.every((t) => t === normalized[0])) return null;
212
- }
213
- const title = (_b = outcome.title) == null ? void 0 : _b.trim();
214
- if (title) return title;
215
- return deriveOverUnderTitle(outcome.label, market.question);
216
- }
217
- var OVER_UNDER_QUESTION = /\bO\/U\s+\d/i;
218
- function deriveOverUnderTitle(label, question) {
219
- if (!question || !OVER_UNDER_QUESTION.test(question)) return null;
220
- const normalized = label.trim().toLowerCase();
221
- if (normalized === "yes") return "Over";
222
- if (normalized === "no") return "Under";
223
- return null;
224
- }
225
-
226
- // ../common/src/utils/auth.ts
227
- function getWalletAddressFromUserProfile(user) {
228
- var _a, _b, _c, _d, _e;
229
- const oauthWallet = (_b = (_a = user == null ? void 0 : user.accounts) == null ? void 0 : _a.find(
230
- (a) => a.type === "oauth" /* oauth */ && String(a.provider).toLowerCase() === "wallet"
231
- )) != null ? _b : null;
232
- if (oauthWallet == null ? void 0 : oauthWallet.providerAccountId) return oauthWallet.providerAccountId;
233
- const siweAccount = (_d = (_c = user == null ? void 0 : user.accounts) == null ? void 0 : _c.find((a) => a.type === "siwe" /* siwe */)) != null ? _d : null;
234
- return (_e = siweAccount == null ? void 0 : siweAccount.providerAccountId) != null ? _e : void 0;
235
- }
236
-
237
- // ../common/src/utils/validate.ts
238
- function safeParse(value, guard) {
239
- if (guard(value)) return { success: true, data: value };
240
- return { success: false };
241
- }
242
- function parse(value, guard, message = "Validation failed") {
243
- if (guard(value)) return value;
244
- const err = new Error(message);
245
- err.issues = [{ message }];
246
- throw err;
247
- }
248
- function isEnum(values) {
249
- return (v) => typeof v === "string" && values.has(v);
250
- }
251
- function enumGuard(source) {
252
- const values = new Set(Array.isArray(source) ? source : Object.values(source));
253
- return isEnum(values);
254
- }
255
- function isFiniteNonNeg(v) {
256
- return typeof v === "number" && Number.isFinite(v) && v >= 0;
257
- }
258
- function isNonEmptyString(v) {
259
- return typeof v === "string" && v.length > 0;
260
- }
261
- function hasShape(v, checks) {
262
- if (typeof v !== "object" || v === null) return false;
263
- const obj = v;
264
- for (const [key, check] of Object.entries(checks)) {
265
- if (!(key in obj) || !check(obj[key])) return false;
266
- }
267
- return true;
70
+ // src/retry-after.ts
71
+ function parseRetryAfterSeconds(value) {
72
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
73
+ const milliseconds = value * 1e3;
74
+ return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
268
75
  }
269
- var EMAIL_MSG = "Enter a valid email address.";
270
- var EMAIL_MAX_LEN = 254;
271
- function isEmail(v) {
272
- const n = v.length;
273
- if (n === 0 || n > EMAIL_MAX_LEN) return false;
274
- let atIdx = -1;
275
- for (let i = 0; i < n; i++) {
276
- const c = v.charCodeAt(i);
277
- if (c === 32 || c >= 9 && c <= 13) return false;
278
- if (c === 64) {
279
- if (atIdx !== -1) return false;
280
- atIdx = i;
281
- }
282
- }
283
- if (atIdx <= 0 || atIdx === n - 1) return false;
284
- for (let i = atIdx + 2; i < n - 1; i++) {
285
- if (v.charCodeAt(i) === 46) return true;
286
- }
287
- return false;
288
- }
289
- function parseEmail(raw) {
290
- const email = raw.trim();
291
- if (!isEmail(email)) {
292
- return { success: false, error: { issues: [{ message: EMAIL_MSG }] } };
293
- }
294
- return { success: true, data: { email } };
295
- }
296
- function parseEmailStrict(raw) {
297
- var _a;
298
- const result = parseEmail(raw);
299
- if (!result.success) {
300
- const err = new Error(EMAIL_MSG);
301
- err.issues = (_a = result.error) == null ? void 0 : _a.issues;
302
- throw err;
303
- }
304
- return result.data;
76
+ function parseRetryAfterHeader(value, now = Date.now()) {
77
+ if (!value) return void 0;
78
+ const text = value.trim();
79
+ if (/^\d+$/.test(text)) return parseRetryAfterSeconds(Number(text));
80
+ if (!/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(
81
+ text
82
+ ))
83
+ return void 0;
84
+ const timestamp = Date.parse(text);
85
+ if (!Number.isFinite(timestamp) || new Date(timestamp).toUTCString() !== text) return void 0;
86
+ const milliseconds = timestamp - now;
87
+ return Number.isSafeInteger(milliseconds) && milliseconds >= 0 ? milliseconds : void 0;
305
88
  }
306
89
 
307
- // ../common/src/utils/trading-splits.ts
308
- function computeBestSplitsByAmount(selections, side, maxDollarAmount) {
309
- var _a;
310
- if (selections.length === 0 || maxDollarAmount <= 0) {
311
- return { splits: [], totalSize: 0, totalCost: 0 };
312
- }
313
- const levels = [];
314
- for (const o of selections) {
315
- const venue = o.market.venue;
316
- const venueMarketOutcomeId = o.outcome.id;
317
- const outcomeExternalIdentifier = (_a = o.outcome.externalIdentifier) != null ? _a : void 0;
318
- if (side === "buy" /* Buy */) {
319
- [...o.orderbook.asks].sort((a, b) => a.price - b.price).forEach(
320
- (l) => {
321
- var _a2;
322
- return levels.push({
323
- venue,
324
- venueMarketOutcomeId,
325
- outcomeExternalIdentifier,
326
- price: l.price,
327
- size: l.size,
328
- negRisk: (_a2 = o.orderbook.negRisk) != null ? _a2 : false
329
- });
330
- }
331
- );
332
- } else {
333
- [...o.orderbook.bids].sort((a, b) => b.price - a.price).forEach(
334
- (l) => {
335
- var _a2;
336
- return levels.push({
337
- venue,
338
- venueMarketOutcomeId,
339
- outcomeExternalIdentifier,
340
- price: 1 - l.price,
341
- size: l.size,
342
- negRisk: (_a2 = o.orderbook.negRisk) != null ? _a2 : false
343
- });
344
- }
345
- );
346
- }
347
- }
348
- levels.sort((a, b) => a.price - b.price);
349
- let totalCost = 0;
350
- const fills = [];
351
- for (const l of levels) {
352
- if (totalCost >= maxDollarAmount) break;
353
- const remainingBudget = maxDollarAmount - totalCost;
354
- const maxSizeAtLevel = Math.min(l.size, remainingBudget / l.price);
355
- if (maxSizeAtLevel <= 0) continue;
356
- const take = maxSizeAtLevel;
357
- fills.push({
358
- venue: l.venue,
359
- venueMarketOutcomeId: l.venueMarketOutcomeId,
360
- outcomeExternalIdentifier: l.outcomeExternalIdentifier,
361
- price: l.price,
362
- size: take,
363
- negRisk: l.negRisk
364
- });
365
- totalCost += l.price * take;
366
- }
367
- if (fills.length === 0) {
368
- return { splits: [], totalSize: 0, totalCost: 0 };
369
- }
370
- const byOutcome = {};
371
- for (const f of fills) {
372
- const key = `${f.venue}:${f.venueMarketOutcomeId}`;
373
- const existing = byOutcome[key];
374
- if (!existing) {
375
- byOutcome[key] = {
376
- size: f.size,
377
- venue: f.venue,
378
- venueMarketOutcomeId: f.venueMarketOutcomeId,
379
- outcomeExternalIdentifier: f.outcomeExternalIdentifier,
380
- negRisk: f.negRisk
381
- };
382
- } else {
383
- existing.size += f.size;
384
- }
385
- }
386
- const splits = [];
387
- let totalSize = 0;
388
- for (const key in byOutcome) {
389
- const { size, venue, venueMarketOutcomeId, outcomeExternalIdentifier, negRisk } = byOutcome[key];
390
- totalSize += size;
391
- let price = 0;
392
- if (size > 0) {
393
- const outcomeFills = fills.filter(
394
- (f) => f.venue === venue && f.venueMarketOutcomeId === venueMarketOutcomeId
395
- );
396
- if (side === "buy" /* Buy */) {
397
- const rawPrice = Math.max(...outcomeFills.map((f) => f.price));
398
- price = Math.ceil(rawPrice * 100) / 100;
399
- } else {
400
- const rawPrice = Math.min(...outcomeFills.map((f) => 1 - f.price));
401
- price = Math.floor(rawPrice * 100) / 100;
402
- }
403
- }
404
- splits.push({
405
- venue,
406
- venueMarketOutcomeId,
407
- side,
408
- price,
409
- size,
410
- negRisk,
411
- outcomeExternalIdentifier
412
- });
413
- }
414
- return {
415
- splits,
416
- totalSize,
417
- totalCost
418
- };
419
- }
420
-
421
- // ../common/src/utils/image-url.ts
422
- function pickVariant(px) {
423
- if (px <= 44 /* sm */) return 44 /* sm */;
424
- if (px <= 48 /* md */) return 48 /* md */;
425
- if (px <= 60 /* lg */) return 60 /* lg */;
426
- return 0 /* original */;
427
- }
428
- var VARIANT_RE = /\/(original|raw|\d+)\.webp$/;
429
- function optimizedImageUrl(url, maxPx) {
430
- if (!url) return void 0;
431
- if (!VARIANT_RE.test(url)) return url;
432
- const variant = pickVariant(maxPx);
433
- const filename = variant === 0 /* original */ ? "original.webp" : `${variant}.webp`;
434
- return url.replace(VARIANT_RE, `/${filename}`);
435
- }
436
-
437
- // ../common/src/utils/venue-market-cluster.ts
438
- var toNonEmptyString = (value) => {
439
- if (typeof value !== "string") return void 0;
440
- const trimmedValue = value.trim();
441
- return trimmedValue ? trimmedValue : void 0;
442
- };
443
- var addGraphEdge = (adjacencyById, fromMarketId, toMarketId) => {
444
- var _a, _b;
445
- if (fromMarketId === toMarketId) return;
446
- const fromNeighbors = (_a = adjacencyById.get(fromMarketId)) != null ? _a : /* @__PURE__ */ new Set();
447
- fromNeighbors.add(toMarketId);
448
- adjacencyById.set(fromMarketId, fromNeighbors);
449
- const toNeighbors = (_b = adjacencyById.get(toMarketId)) != null ? _b : /* @__PURE__ */ new Set();
450
- toNeighbors.add(fromMarketId);
451
- adjacencyById.set(toMarketId, toNeighbors);
452
- };
453
- var collectDirectRelationMarkets = (market) => {
454
- var _a, _b, _c;
455
- const relationMarkets = [];
456
- const seenRelationIds = /* @__PURE__ */ new Set();
457
- const addRelationMarket = (relationMarket) => {
458
- if (!relationMarket) return;
459
- if (seenRelationIds.has(relationMarket.id)) return;
460
- seenRelationIds.add(relationMarket.id);
461
- relationMarkets.push(relationMarket);
462
- };
463
- for (const matchedVenueMarket of (_a = market.matchedVenueMarkets) != null ? _a : []) {
464
- addRelationMarket(matchedVenueMarket);
465
- }
466
- addRelationMarket((_c = (_b = market.matchEntry) == null ? void 0 : _b.targetVenueMarket) != null ? _c : void 0);
467
- return relationMarkets;
468
- };
469
- var dedupeSourceMarketsById = (markets) => {
470
- const seenMarketIds = /* @__PURE__ */ new Set();
471
- return markets.filter((market) => {
472
- if (seenMarketIds.has(market.id)) {
473
- return false;
474
- }
475
- seenMarketIds.add(market.id);
476
- return true;
477
- });
478
- };
479
- var collectUniqueRelatedMarkets = (markets, sourceMarketsById) => {
480
- const relatedMarketsById = /* @__PURE__ */ new Map();
481
- for (const market of markets) {
482
- for (const relatedMarket of collectDirectRelationMarkets(market)) {
483
- if (sourceMarketsById.has(relatedMarket.id) || relatedMarketsById.has(relatedMarket.id)) {
484
- continue;
485
- }
486
- relatedMarketsById.set(relatedMarket.id, relatedMarket);
487
- }
488
- }
489
- return Array.from(relatedMarketsById.values());
490
- };
491
- var normalizeVenueMarketCluster = (markets, selectedMarketId) => {
492
- var _a, _b;
493
- if (markets.length === 0) return [];
494
- const orderedSourceMarkets = dedupeSourceMarketsById(markets);
495
- const sourceMarketsById = new Map(orderedSourceMarkets.map((market) => [market.id, market]));
496
- const orderedRelatedMarkets = collectUniqueRelatedMarkets(
497
- orderedSourceMarkets,
498
- sourceMarketsById
499
- );
500
- const relatedMarketsById = new Map(orderedRelatedMarkets.map((market) => [market.id, market]));
501
- const resolvedSelectedMarketId = toNonEmptyString(selectedMarketId);
502
- if (!resolvedSelectedMarketId) {
503
- return [...orderedSourceMarkets, ...orderedRelatedMarkets];
504
- }
505
- const adjacencyById = /* @__PURE__ */ new Map();
506
- for (const market of orderedSourceMarkets) {
507
- for (const relatedMarket of collectDirectRelationMarkets(market)) {
508
- addGraphEdge(adjacencyById, market.id, relatedMarket.id);
509
- }
510
- const targetVenueMarketId = toNonEmptyString((_a = market.matchEntry) == null ? void 0 : _a.targetVenueMarketId);
511
- if (targetVenueMarketId) {
512
- addGraphEdge(adjacencyById, market.id, targetVenueMarketId);
513
- }
514
- }
515
- if (!sourceMarketsById.has(resolvedSelectedMarketId) && !relatedMarketsById.has(resolvedSelectedMarketId)) {
516
- return [];
517
- }
518
- const visitedMarketIds = /* @__PURE__ */ new Set();
519
- const queue = [resolvedSelectedMarketId];
520
- while (queue.length > 0) {
521
- const currentMarketId = queue.shift();
522
- if (!currentMarketId || visitedMarketIds.has(currentMarketId)) {
523
- continue;
524
- }
525
- visitedMarketIds.add(currentMarketId);
526
- const neighborMarketIds = adjacencyById.get(currentMarketId);
527
- for (const neighborMarketId of Array.from(neighborMarketIds != null ? neighborMarketIds : /* @__PURE__ */ new Set())) {
528
- if (!visitedMarketIds.has(neighborMarketId)) {
529
- queue.push(neighborMarketId);
530
- }
531
- }
532
- }
533
- const orderedClusterMarkets = [];
534
- const appendedMarketIds = /* @__PURE__ */ new Set();
535
- const appendMarket = (market) => {
536
- if (!market) return;
537
- if (!visitedMarketIds.has(market.id)) return;
538
- if (appendedMarketIds.has(market.id)) return;
539
- appendedMarketIds.add(market.id);
540
- orderedClusterMarkets.push(market);
541
- };
542
- appendMarket(
543
- (_b = sourceMarketsById.get(resolvedSelectedMarketId)) != null ? _b : relatedMarketsById.get(resolvedSelectedMarketId)
544
- );
545
- for (const market of orderedSourceMarkets) {
546
- appendMarket(market);
547
- }
548
- for (const market of orderedRelatedMarkets) {
549
- appendMarket(market);
550
- }
551
- return orderedClusterMarkets;
552
- };
553
-
554
- // ../common/src/utils/sort-venues.ts
555
- var VENUE_SORT_ORDER = [
556
- "polymarket" /* polymarket */,
557
- "hyperliquid" /* hyperliquid */,
558
- "predict" /* predict */,
559
- "prophetx" /* prophetx */,
560
- "kalshi" /* kalshi */,
561
- "limitless" /* limitless */,
562
- "myriad" /* myriad */,
563
- "pred" /* pred */,
564
- "probable" /* probable */,
565
- "novig" /* novig */,
566
- "opinion" /* opinion */
567
- ];
568
- var venueOrderIndex = new Map(VENUE_SORT_ORDER.map((v, i) => [v, i]));
569
- var getVenueOrder = (venue) => {
570
- var _a;
571
- return (_a = venueOrderIndex.get(venue)) != null ? _a : VENUE_SORT_ORDER.length;
572
- };
573
- var sortVenues = (venues) => [...venues].sort((a, b) => getVenueOrder(a) - getVenueOrder(b));
574
-
575
- // ../common/src/utils/venue-url.ts
576
- function buildVenueUrl(venue, opts) {
577
- switch (venue) {
578
- case "kalshi": {
579
- const series = opts.seriesExternalId;
580
- const event = opts.eventExternalId;
581
- if (series && event) {
582
- return `https://kalshi.com/markets/${series.toLowerCase()}/${event.toLowerCase()}`;
583
- }
584
- return null;
585
- }
586
- case "polymarket": {
587
- if (opts.eventSlug) return `https://polymarket.com/event/${opts.eventSlug}`;
588
- if (opts.conditionId) return `https://polymarket.com/market/${opts.conditionId}`;
589
- return null;
590
- }
591
- case "predict": {
592
- if (opts.eventSlug) return `https://predict.fun/market/${opts.eventSlug}`;
593
- return null;
594
- }
595
- case "pred": {
596
- if (opts.eventSlug) return `https://www.pred.app/trade/${opts.eventSlug}`;
597
- return null;
598
- }
599
- case "limitless": {
600
- if (opts.eventSlug) return `https://limitless.exchange/markets/${opts.eventSlug}`;
601
- return null;
602
- }
603
- case "opinion": {
604
- if (opts.eventSlug) return `https://www.opinion.trade/market/${opts.eventSlug}`;
605
- return null;
606
- }
607
- case "myriad": {
608
- if (opts.eventSlug) return `https://myriad.markets/markets/${opts.eventSlug}`;
609
- return null;
610
- }
611
- case "hyperliquid": {
612
- if (opts.eventSlug) return `https://app.hyperliquid.xyz/trade/${opts.eventSlug}`;
613
- return null;
614
- }
615
- case "probable": {
616
- if (opts.eventSlug) return `https://probable.markets/event/${opts.eventSlug}`;
617
- return null;
618
- }
619
- case "novig": {
620
- if (opts.outcomeExternalId && opts.partnerId) {
621
- return opts.platform === "native" ? `novigapp://events/${opts.outcomeExternalId}/${opts.partnerId}` : `https://novig.com/events/${opts.outcomeExternalId}/${opts.partnerId}`;
622
- }
623
- return null;
624
- }
625
- default:
626
- return null;
627
- }
628
- }
629
-
630
- // ../common/src/utils/limit-price-ticks.ts
631
- var LIMIT_PRICE_RAW_SCALE = BigInt(1e6);
632
- var resolveTickSizeRaw = (tickSize) => {
633
- const scaledTick = tickSize * Number(LIMIT_PRICE_RAW_SCALE);
634
- const roundedTick = Math.round(scaledTick);
635
- if (!Number.isFinite(scaledTick) || roundedTick <= 0 || Math.abs(scaledTick - roundedTick) > 1e-6) {
636
- throw new RangeError(`Unsupported limit-order tick size: ${tickSize}`);
637
- }
638
- return BigInt(roundedTick);
639
- };
640
- var adjustLimitPriceRawToTick = ({
641
- limitPriceRaw,
642
- side,
643
- tickSize
644
- }) => {
645
- const originalPrice = BigInt(limitPriceRaw);
646
- const originalPriceRaw = originalPrice.toString();
647
- if (tickSize == null) {
648
- return {
649
- adjusted: false,
650
- adjustedPriceRaw: originalPriceRaw,
651
- originalPriceRaw,
652
- tickSizeRaw: null
653
- };
654
- }
655
- const tickSizeRaw = resolveTickSizeRaw(tickSize);
656
- const remainder = originalPrice % tickSizeRaw;
657
- if (remainder === BigInt(0)) {
658
- return {
659
- adjusted: false,
660
- adjustedPriceRaw: originalPriceRaw,
661
- originalPriceRaw,
662
- tickSizeRaw: tickSizeRaw.toString()
663
- };
664
- }
665
- const adjustedPrice = side === "buy" ? originalPrice - remainder : originalPrice + (tickSizeRaw - remainder);
666
- return {
667
- adjusted: true,
668
- adjustedPriceRaw: adjustedPrice.toString(),
669
- originalPriceRaw,
670
- tickSizeRaw: tickSizeRaw.toString()
671
- };
672
- };
673
-
674
- // ../common/src/utils/balance-refill.ts
675
- var DEFAULT_BALANCE_REFILL_DAILY_CAP_RAW = "1002000000";
676
- var DEFAULT_BALANCE_REFILL_MAX_FEE_RAW = "2000000";
677
- var getBalanceRefillMaximumRaw = (dailyCapRaw, maxFeeRaw) => {
678
- const dailyCap = BigInt(dailyCapRaw);
679
- const maximumFee = BigInt(maxFeeRaw);
680
- if (dailyCap <= maximumFee) return "0";
681
- return (dailyCap - maximumFee).toString();
682
- };
683
- var isBalanceRefillWithinDailyCap = (minimumRaw, refillAmountRaw, dailyCapRaw, maxFeeRaw) => {
684
- const maximum = BigInt(getBalanceRefillMaximumRaw(dailyCapRaw, maxFeeRaw));
685
- return BigInt(minimumRaw) <= maximum && BigInt(refillAmountRaw) <= maximum;
686
- };
687
-
688
90
  // src/errors.ts
689
91
  var TurnstileChallengeError = class extends Error {
690
92
  constructor(siteKey) {
@@ -700,6 +102,7 @@ var AggApiError = class _AggApiError extends Error {
700
102
  this.status = status;
701
103
  if (init.code !== void 0) this.code = init.code;
702
104
  if (init.retryable !== void 0) this.retryable = init.retryable;
105
+ if (init.retryAfterMs !== void 0) this.retryAfterMs = init.retryAfterMs;
703
106
  if (init.errors !== void 0) this.errors = init.errors;
704
107
  Object.setPrototypeOf(this, _AggApiError.prototype);
705
108
  }
@@ -855,931 +258,6 @@ var RECURRENCE_CADENCES = [
855
258
  "P1Y"
856
259
  ];
857
260
 
858
- // src/orderbook-utils.ts
859
- var PRICE_KEY_SCALE = 1e9;
860
- function crc32(str) {
861
- let crc = 4294967295;
862
- for (let i = 0; i < str.length; i++) {
863
- crc ^= str.charCodeAt(i);
864
- for (let j = 0; j < 8; j++) {
865
- crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
866
- }
867
- }
868
- return (crc ^ 4294967295) >>> 0;
869
- }
870
- function computeChecksum(book) {
871
- let checksum = 0;
872
- for (const b of book.bids) checksum ^= crc32(`B${b.price}:${b.size}`);
873
- for (const a of book.asks) checksum ^= crc32(`A${a.price}:${a.size}`);
874
- return checksum >>> 0;
875
- }
876
- function attributedToLevel(wire) {
877
- return { price: wire[0], size: wire[1], venues: __spreadValues({}, wire[2]) };
878
- }
879
- function venueLevelToLevel(wire) {
880
- return { price: wire[0], size: wire[1] };
881
- }
882
- function convertVenueBooks(raw) {
883
- const result = {};
884
- for (const [venue, book] of Object.entries(raw)) {
885
- result[venue] = {
886
- bids: book.bids.map(venueLevelToLevel),
887
- asks: book.asks.map(venueLevelToLevel)
888
- };
889
- }
890
- return result;
891
- }
892
- function snapshotToOrderbook(snapshot) {
893
- return {
894
- marketId: snapshot.outcomeId,
895
- outcomeId: snapshot.outcomeId,
896
- bids: snapshot.bids.map(attributedToLevel),
897
- asks: snapshot.asks.map(attributedToLevel),
898
- venueOrderbooks: convertVenueBooks(snapshot.venueOrderbooks),
899
- venues: __spreadValues({}, snapshot.venues),
900
- midpoint: snapshot.midpoint,
901
- spread: snapshot.spread,
902
- seq: snapshot.seq,
903
- checksum: snapshot.checksum,
904
- timestamp: snapshot.timestamp
905
- };
906
- }
907
- function applyOrderbookDelta(state, delta) {
908
- var _a, _b;
909
- const bids = applyChanges([...state.bids], delta.bidChanges, "desc");
910
- const asks = applyChanges([...state.asks], delta.askChanges, "asc");
911
- const impliedBidChangesByVenue = deriveVenueChangesFromAttributedChanges(
912
- state.bids,
913
- delta.bidChanges
914
- );
915
- const impliedAskChangesByVenue = deriveVenueChangesFromAttributedChanges(
916
- state.asks,
917
- delta.askChanges
918
- );
919
- const venueOrderbooks = __spreadValues({}, state.venueOrderbooks);
920
- const venuesWithDelta = /* @__PURE__ */ new Set([
921
- ...Object.keys(delta.venueDeltaBooks),
922
- ...Object.keys(impliedBidChangesByVenue),
923
- ...Object.keys(impliedAskChangesByVenue)
924
- ]);
925
- for (const venue of venuesWithDelta) {
926
- const explicitChanges = delta.venueDeltaBooks[venue];
927
- const bidChanges = mergeVenueLevelChanges(
928
- explicitChanges == null ? void 0 : explicitChanges.bidChanges,
929
- impliedBidChangesByVenue[venue]
930
- );
931
- const askChanges = mergeVenueLevelChanges(
932
- explicitChanges == null ? void 0 : explicitChanges.askChanges,
933
- impliedAskChangesByVenue[venue]
934
- );
935
- if (bidChanges.length === 0 && askChanges.length === 0) continue;
936
- const existing = (_a = venueOrderbooks[venue]) != null ? _a : { bids: [], asks: [] };
937
- venueOrderbooks[venue] = {
938
- bids: applyVenueChanges([...existing.bids], bidChanges, "desc"),
939
- asks: applyVenueChanges([...existing.asks], askChanges, "asc")
940
- };
941
- }
942
- return {
943
- marketId: state.marketId,
944
- outcomeId: (_b = state.outcomeId) != null ? _b : delta.outcomeId,
945
- bids,
946
- asks,
947
- venueOrderbooks,
948
- venues: __spreadValues(__spreadValues({}, state.venues), delta.venues),
949
- midpoint: delta.midpoint,
950
- spread: delta.spread,
951
- seq: delta.seq,
952
- checksum: delta.checksum,
953
- timestamp: delta.timestamp
954
- };
955
- }
956
- function toPriceKey(price) {
957
- return String(Math.round(price * PRICE_KEY_SCALE));
958
- }
959
- function applyChanges(levels, changes, sort) {
960
- const levelsByPrice = new Map(levels.map((level) => [toPriceKey(level.price), level]));
961
- for (const [price, size, venues] of changes) {
962
- const key = toPriceKey(price);
963
- if (size === 0) {
964
- levelsByPrice.delete(key);
965
- continue;
966
- }
967
- levelsByPrice.set(key, {
968
- price,
969
- size,
970
- venues: __spreadValues({}, venues != null ? venues : {})
971
- });
972
- }
973
- levels = [...levelsByPrice.values()];
974
- levels.sort(sort === "desc" ? (a, b) => b.price - a.price : (a, b) => a.price - b.price);
975
- return levels;
976
- }
977
- function applyVenueChanges(levels, changes, sort) {
978
- const levelsByPrice = new Map(levels.map((level) => [toPriceKey(level.price), level]));
979
- for (const [price, size] of changes) {
980
- const key = toPriceKey(price);
981
- if (size === 0) {
982
- levelsByPrice.delete(key);
983
- continue;
984
- }
985
- levelsByPrice.set(key, { price, size });
986
- }
987
- levels = [...levelsByPrice.values()];
988
- levels.sort(sort === "desc" ? (a, b) => b.price - a.price : (a, b) => a.price - b.price);
989
- return levels;
990
- }
991
- function mergeVenueLevelChanges(primary, fallback) {
992
- if (!(primary == null ? void 0 : primary.length)) return fallback ? [...fallback] : [];
993
- if (!(fallback == null ? void 0 : fallback.length)) return [...primary];
994
- const changesByPrice = /* @__PURE__ */ new Map();
995
- for (const change of fallback) {
996
- changesByPrice.set(toPriceKey(change[0]), change);
997
- }
998
- for (const change of primary) {
999
- changesByPrice.set(toPriceKey(change[0]), change);
1000
- }
1001
- return [...changesByPrice.values()];
1002
- }
1003
- function deriveVenueChangesFromAttributedChanges(previousLevels, changes) {
1004
- var _a, _b;
1005
- const previousLevelsByPrice = new Map(
1006
- previousLevels.map((level) => [toPriceKey(level.price), level])
1007
- );
1008
- const venueChanges = {};
1009
- for (const [price, size, nextVenuesRaw] of changes) {
1010
- const key = toPriceKey(price);
1011
- const previousLevel = previousLevelsByPrice.get(key);
1012
- const previousVenues = (_a = previousLevel == null ? void 0 : previousLevel.venues) != null ? _a : {};
1013
- const nextVenues = nextVenuesRaw != null ? nextVenuesRaw : {};
1014
- const affectedVenues = /* @__PURE__ */ new Set([...Object.keys(previousVenues), ...Object.keys(nextVenues)]);
1015
- for (const venue of affectedVenues) {
1016
- const nextSize = Number((_b = nextVenues[venue]) != null ? _b : 0);
1017
- if (!Number.isFinite(nextSize)) continue;
1018
- if (!venueChanges[venue]) {
1019
- venueChanges[venue] = [];
1020
- }
1021
- venueChanges[venue].push([price, nextSize]);
1022
- }
1023
- if (size === 0) {
1024
- previousLevelsByPrice.delete(key);
1025
- continue;
1026
- }
1027
- previousLevelsByPrice.set(key, {
1028
- price,
1029
- size,
1030
- venues: __spreadValues({}, nextVenues)
1031
- });
1032
- }
1033
- return venueChanges;
1034
- }
1035
-
1036
- // src/agg-websocket.ts
1037
- var RECONNECT_BASE_MS = 1e3;
1038
- var RECONNECT_MAX_MS = 3e4;
1039
- var STALE_TIMEOUT_MS = 45e3;
1040
- var ORDERBOOK_DEBUG_TOP_LEVELS = 5;
1041
- var AggWebSocket = class {
1042
- constructor(options) {
1043
- this.ws = null;
1044
- // ── Subscription tracking ──
1045
- this.subs = /* @__PURE__ */ new Map();
1046
- this.pendingSubs = /* @__PURE__ */ new Set();
1047
- this.pendingTradeSubs = /* @__PURE__ */ new Set();
1048
- this.pendingResnapshots = /* @__PURE__ */ new Set();
1049
- // ── Post-connect batching (coalesces same-tick sub/unsub calls into one frame) ──
1050
- this.pendingFlushSubs = /* @__PURE__ */ new Set();
1051
- this.pendingFlushUnsubs = /* @__PURE__ */ new Set();
1052
- this.pendingFlushTradeSubs = /* @__PURE__ */ new Set();
1053
- this.pendingFlushTradeUnsubs = /* @__PURE__ */ new Set();
1054
- this.pendingFlushArbSubs = /* @__PURE__ */ new Set();
1055
- this.pendingFlushArbUnsubs = /* @__PURE__ */ new Set();
1056
- this.flushScheduled = false;
1057
- // ── Arb subscriptions ──
1058
- this.arbSubs = /* @__PURE__ */ new Map();
1059
- this.arbFeedCbs = /* @__PURE__ */ new Set();
1060
- /** Latest arb market update per marketId (last-value-wins, loss-tolerant). */
1061
- this.latestArb = /* @__PURE__ */ new Map();
1062
- // ── Orderbook state per market ──
1063
- this.books = /* @__PURE__ */ new Map();
1064
- /** Markets currently resyncing (awaiting snapshot after seq gap / checksum mismatch). */
1065
- this.resyncing = /* @__PURE__ */ new Set();
1066
- // ── Connection state ──
1067
- this.connecting = false;
1068
- this._connected = false;
1069
- this.reconnectAttempts = 0;
1070
- this.reconnectTimer = null;
1071
- this.staleTimer = null;
1072
- this.lastMessageTime = 0;
1073
- this.destroyed = false;
1074
- var _a, _b;
1075
- this.url = options.url;
1076
- this.callbacks = (_a = options.callbacks) != null ? _a : {};
1077
- this.initialToken = options.initialToken;
1078
- this.enableLogs = (_b = options.enableLogs) != null ? _b : false;
1079
- }
1080
- // ── Public API ──
1081
- get connected() {
1082
- return this._connected;
1083
- }
1084
- /** Get the current orderbook state for an outcome (or null if no snapshot received yet). */
1085
- getBook(outcomeId) {
1086
- var _a;
1087
- return (_a = this.books.get(outcomeId)) != null ? _a : null;
1088
- }
1089
- /** Whether an outcome is currently resyncing (waiting for fresh snapshot). */
1090
- isResyncing(outcomeId) {
1091
- return this.resyncing.has(outcomeId);
1092
- }
1093
- /** Get the latest arb update for a market (or null if none received yet). */
1094
- getArb(marketId) {
1095
- var _a;
1096
- return (_a = this.latestArb.get(marketId)) != null ? _a : null;
1097
- }
1098
- /**
1099
- * Subscribe to per-market arb updates for a given marketId.
1100
- * Ref-counted: multiple callers for the same marketId share one server subscription.
1101
- * Returns an unsubscribe function.
1102
- */
1103
- subscribeArb(marketId, cb) {
1104
- let entry = this.arbSubs.get(marketId);
1105
- if (!entry) {
1106
- entry = { refCount: 0, cbs: /* @__PURE__ */ new Set() };
1107
- this.arbSubs.set(marketId, entry);
1108
- this.ensureConnected();
1109
- if (this._connected) {
1110
- this.pendingFlushArbUnsubs.delete(marketId);
1111
- this.pendingFlushArbSubs.add(marketId);
1112
- this.scheduleFlush();
1113
- }
1114
- }
1115
- entry.cbs.add(cb);
1116
- entry.refCount++;
1117
- return () => {
1118
- const e = this.arbSubs.get(marketId);
1119
- if (!e) return;
1120
- e.cbs.delete(cb);
1121
- e.refCount--;
1122
- if (e.refCount <= 0) {
1123
- this.arbSubs.delete(marketId);
1124
- if (this._connected) {
1125
- if (this.pendingFlushArbSubs.has(marketId)) {
1126
- this.pendingFlushArbSubs.delete(marketId);
1127
- } else {
1128
- this.pendingFlushArbUnsubs.add(marketId);
1129
- this.scheduleFlush();
1130
- }
1131
- }
1132
- }
1133
- };
1134
- }
1135
- /**
1136
- * Subscribe to the global arb-feed batches.
1137
- * Only one server subscription is maintained regardless of how many callers.
1138
- * Returns an unsubscribe function.
1139
- */
1140
- subscribeArbFeed(cb) {
1141
- const wasEmpty = this.arbFeedCbs.size === 0;
1142
- this.arbFeedCbs.add(cb);
1143
- if (wasEmpty) {
1144
- this.ensureConnected();
1145
- if (this._connected) {
1146
- this.send({ action: "subscribe", channel: "arb-feed" });
1147
- }
1148
- }
1149
- return () => {
1150
- this.arbFeedCbs.delete(cb);
1151
- if (this.arbFeedCbs.size === 0 && this._connected) {
1152
- this.send({ action: "unsubscribe", channel: "arb-feed" });
1153
- }
1154
- };
1155
- }
1156
- /**
1157
- * Subscribe to a feed by outcome ID.
1158
- * @param outcomeId The venueMarketOutcomeId to subscribe to.
1159
- * @param channel "orderbook" (default) or "trades".
1160
- * Returns an unsubscribe function (ref-counted).
1161
- */
1162
- subscribe(outcomeId, channel = "orderbook") {
1163
- if (channel === "trades") {
1164
- return this._subscribeTrades(outcomeId);
1165
- }
1166
- let sub = this.subs.get(outcomeId);
1167
- if (!sub) {
1168
- sub = { refCount: 0, tradeRefs: 0 };
1169
- this.subs.set(outcomeId, sub);
1170
- this.pendingSubs.add(outcomeId);
1171
- this.ensureConnected();
1172
- if (this._connected) {
1173
- this.pendingFlushUnsubs.delete(outcomeId);
1174
- this.pendingFlushSubs.add(outcomeId);
1175
- this.pendingSubs.delete(outcomeId);
1176
- this.scheduleFlush();
1177
- }
1178
- }
1179
- sub.refCount++;
1180
- return () => {
1181
- const s = this.subs.get(outcomeId);
1182
- if (!s) return;
1183
- s.refCount--;
1184
- if (s.refCount <= 0 && s.tradeRefs <= 0) {
1185
- this.subs.delete(outcomeId);
1186
- this.books.delete(outcomeId);
1187
- this.resyncing.delete(outcomeId);
1188
- if (this._connected) {
1189
- if (this.pendingFlushSubs.has(outcomeId)) {
1190
- this.pendingFlushSubs.delete(outcomeId);
1191
- } else {
1192
- this.pendingFlushUnsubs.add(outcomeId);
1193
- this.scheduleFlush();
1194
- }
1195
- }
1196
- this.pendingSubs.delete(outcomeId);
1197
- this.pendingResnapshots.delete(outcomeId);
1198
- }
1199
- };
1200
- }
1201
- /**
1202
- * Subscribe to an outcome's trade feed.
1203
- * @deprecated Use `subscribe(outcomeId, "trades")` instead.
1204
- * Returns an unsubscribe function.
1205
- */
1206
- subscribeTrades(outcomeId) {
1207
- return this._subscribeTrades(outcomeId);
1208
- }
1209
- _subscribeTrades(outcomeId) {
1210
- let sub = this.subs.get(outcomeId);
1211
- if (!sub) {
1212
- sub = { refCount: 0, tradeRefs: 0 };
1213
- this.subs.set(outcomeId, sub);
1214
- }
1215
- if (sub.tradeRefs === 0) {
1216
- this.pendingTradeSubs.add(outcomeId);
1217
- this.ensureConnected();
1218
- if (this._connected) {
1219
- this.pendingFlushTradeUnsubs.delete(outcomeId);
1220
- this.pendingFlushTradeSubs.add(outcomeId);
1221
- this.pendingTradeSubs.delete(outcomeId);
1222
- this.scheduleFlush();
1223
- }
1224
- }
1225
- sub.tradeRefs++;
1226
- return () => {
1227
- const s = this.subs.get(outcomeId);
1228
- if (!s) return;
1229
- s.tradeRefs--;
1230
- if (s.tradeRefs <= 0) {
1231
- s.tradeRefs = 0;
1232
- if (this._connected) {
1233
- if (this.pendingFlushTradeSubs.has(outcomeId)) {
1234
- this.pendingFlushTradeSubs.delete(outcomeId);
1235
- } else {
1236
- this.pendingFlushTradeUnsubs.add(outcomeId);
1237
- this.scheduleFlush();
1238
- }
1239
- }
1240
- this.pendingTradeSubs.delete(outcomeId);
1241
- this.pendingResnapshots.delete(outcomeId);
1242
- if (s.refCount <= 0) {
1243
- this.subs.delete(outcomeId);
1244
- }
1245
- }
1246
- };
1247
- }
1248
- /** Send a JWT to authenticate (or re-authenticate) the connection. */
1249
- authenticate(token) {
1250
- if (this._connected) {
1251
- this.send({ action: "authenticate", token });
1252
- }
1253
- }
1254
- /** Request a fresh snapshot for one or more subscribed outcomes. */
1255
- resnapshot(outcomeIdOrIds) {
1256
- const outcomeIds = Array.isArray(outcomeIdOrIds) ? outcomeIdOrIds : [outcomeIdOrIds];
1257
- outcomeIds.forEach((outcomeId) => {
1258
- this.requestResnapshot(outcomeId, true);
1259
- });
1260
- }
1261
- /** Connect (idempotent). Called automatically on first subscribe. */
1262
- connect() {
1263
- this.ensureConnected();
1264
- }
1265
- /** Disconnect and clean up. Reusable — call connect() to reconnect. */
1266
- disconnect() {
1267
- var _a, _b;
1268
- this.cancelReconnect();
1269
- this.cancelStaleTimer();
1270
- this._connected = false;
1271
- this.connecting = false;
1272
- if (this.ws) {
1273
- this.ws.onclose = null;
1274
- this.ws.close();
1275
- this.ws = null;
1276
- }
1277
- (_b = (_a = this.callbacks).onConnectionStateChange) == null ? void 0 : _b.call(_a, false);
1278
- }
1279
- /** Permanently destroy. Cannot reconnect after this. */
1280
- destroy() {
1281
- this.destroyed = true;
1282
- this.disconnect();
1283
- this.subs.clear();
1284
- this.books.clear();
1285
- this.resyncing.clear();
1286
- this.pendingSubs.clear();
1287
- this.pendingTradeSubs.clear();
1288
- this.pendingResnapshots.clear();
1289
- this.pendingFlushSubs.clear();
1290
- this.pendingFlushUnsubs.clear();
1291
- this.pendingFlushTradeSubs.clear();
1292
- this.pendingFlushTradeUnsubs.clear();
1293
- this.arbSubs.clear();
1294
- this.arbFeedCbs.clear();
1295
- this.latestArb.clear();
1296
- this.pendingFlushArbSubs.clear();
1297
- this.pendingFlushArbUnsubs.clear();
1298
- this.flushScheduled = false;
1299
- }
1300
- /** Update callbacks (e.g., after React re-render). */
1301
- setCallbacks(callbacks) {
1302
- this.callbacks = callbacks;
1303
- }
1304
- // ── Connection management ──
1305
- ensureConnected() {
1306
- var _a;
1307
- if (this.destroyed) return;
1308
- if (((_a = this.ws) == null ? void 0 : _a.readyState) === WebSocket.OPEN || this.connecting) return;
1309
- this.doConnect();
1310
- }
1311
- doConnect() {
1312
- if (this.destroyed) return;
1313
- this.connecting = true;
1314
- const ws = new WebSocket(this.url);
1315
- this.ws = ws;
1316
- ws.onopen = () => {
1317
- this.connecting = false;
1318
- this.reconnectAttempts = 0;
1319
- this.resetStaleTimer();
1320
- if (this.initialToken) {
1321
- this.send({ action: "authenticate", token: this.initialToken });
1322
- }
1323
- };
1324
- ws.onmessage = (event) => {
1325
- this.lastMessageTime = Date.now();
1326
- this.resetStaleTimer();
1327
- try {
1328
- const raw = JSON.parse(event.data);
1329
- this.handleMessage(raw);
1330
- } catch (e) {
1331
- }
1332
- };
1333
- ws.onclose = () => {
1334
- var _a, _b;
1335
- this.ws = null;
1336
- this.connecting = false;
1337
- const wasConnected = this._connected;
1338
- this._connected = false;
1339
- this.cancelStaleTimer();
1340
- if (wasConnected) {
1341
- (_b = (_a = this.callbacks).onConnectionStateChange) == null ? void 0 : _b.call(_a, false);
1342
- }
1343
- if (!this.destroyed && (this.subs.size > 0 || this.arbSubs.size > 0 || this.arbFeedCbs.size > 0)) {
1344
- this.scheduleReconnect();
1345
- }
1346
- };
1347
- ws.onerror = () => {
1348
- };
1349
- }
1350
- scheduleReconnect() {
1351
- this.cancelReconnect();
1352
- const delay = Math.min(
1353
- RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempts),
1354
- RECONNECT_MAX_MS
1355
- );
1356
- this.reconnectAttempts++;
1357
- this.emitDiagnostic({ type: "reconnect", attempt: this.reconnectAttempts, delayMs: delay });
1358
- this.reconnectTimer = setTimeout(() => {
1359
- this.reconnectTimer = null;
1360
- this.doConnect();
1361
- }, delay);
1362
- }
1363
- cancelReconnect() {
1364
- if (this.reconnectTimer) {
1365
- clearTimeout(this.reconnectTimer);
1366
- this.reconnectTimer = null;
1367
- }
1368
- }
1369
- // ── Staleness detection ──
1370
- resetStaleTimer() {
1371
- this.cancelStaleTimer();
1372
- this.staleTimer = setTimeout(() => {
1373
- const age = Date.now() - this.lastMessageTime;
1374
- this.emitDiagnostic({ type: "stale_connection", lastMessageAge: age });
1375
- if (this.ws) {
1376
- this.ws.close();
1377
- }
1378
- }, STALE_TIMEOUT_MS);
1379
- }
1380
- cancelStaleTimer() {
1381
- if (this.staleTimer) {
1382
- clearTimeout(this.staleTimer);
1383
- this.staleTimer = null;
1384
- }
1385
- }
1386
- // ── Message handling ──
1387
- handleMessage(raw) {
1388
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
1389
- const type = raw.type;
1390
- switch (type) {
1391
- case "orderbook_snapshot":
1392
- this.handleSnapshot(raw);
1393
- break;
1394
- case "orderbook_delta":
1395
- this.handleDelta(raw);
1396
- break;
1397
- case "trade":
1398
- this.handleTrade(raw);
1399
- break;
1400
- case "heartbeat":
1401
- this.handleHeartbeat(raw);
1402
- break;
1403
- case "subscribed":
1404
- (_b = (_a = this.callbacks).onSubscribed) == null ? void 0 : _b.call(_a, raw);
1405
- break;
1406
- case "unsubscribed":
1407
- (_d = (_c = this.callbacks).onUnsubscribed) == null ? void 0 : _d.call(_c, raw);
1408
- break;
1409
- case "connected":
1410
- this.handleConnected(raw);
1411
- break;
1412
- case "authenticated":
1413
- (_f = (_e = this.callbacks).onAuthenticated) == null ? void 0 : _f.call(_e, raw);
1414
- break;
1415
- case "market_resolved":
1416
- this.handleMarketResolved(raw);
1417
- break;
1418
- case "error":
1419
- (_h = (_g = this.callbacks).onError) == null ? void 0 : _h.call(_g, raw);
1420
- break;
1421
- case "order_submitted":
1422
- this.handleOrderSubmitted(raw);
1423
- break;
1424
- case "balance_update":
1425
- this.handleBalanceUpdate(raw);
1426
- break;
1427
- case "order_event":
1428
- (_j = (_i = this.callbacks).onOrderEvent) == null ? void 0 : _j.call(_i, raw);
1429
- break;
1430
- case "redeem_event":
1431
- this.handleRedeemEvent(raw);
1432
- break;
1433
- case "withdrawal_lifecycle":
1434
- (_l = (_k = this.callbacks).onWithdrawalLifecycle) == null ? void 0 : _l.call(_k, raw);
1435
- break;
1436
- case "arb_market_update": {
1437
- const arbMsg = raw;
1438
- this.latestArb.set(arbMsg.marketId, arbMsg);
1439
- const arbEntry = this.arbSubs.get(arbMsg.marketId);
1440
- if (arbEntry) {
1441
- for (const cb of arbEntry.cbs) cb(arbMsg);
1442
- }
1443
- (_n = (_m = this.callbacks).onArbMarket) == null ? void 0 : _n.call(_m, arbMsg);
1444
- break;
1445
- }
1446
- case "arb_feed_batch": {
1447
- const feedMsg = raw;
1448
- for (const cb of this.arbFeedCbs) cb(feedMsg);
1449
- (_p = (_o = this.callbacks).onArbFeed) == null ? void 0 : _p.call(_o, feedMsg);
1450
- break;
1451
- }
1452
- }
1453
- }
1454
- /**
1455
- * Server confirmed the connection — gateway has validated our appId and is
1456
- * ready to accept subscriptions. Now flush all pending subs.
1457
- */
1458
- handleConnected(msg) {
1459
- var _a, _b, _c, _d;
1460
- this._connected = true;
1461
- (_b = (_a = this.callbacks).onConnectionStateChange) == null ? void 0 : _b.call(_a, true);
1462
- (_d = (_c = this.callbacks).onConnected) == null ? void 0 : _d.call(_c, msg);
1463
- const orderbookSet = new Set(this.activeOrderbookMarkets());
1464
- this.pendingSubs.forEach((id) => orderbookSet.add(id));
1465
- this.pendingFlushSubs.forEach((id) => orderbookSet.add(id));
1466
- const orderbookIds = Array.from(orderbookSet);
1467
- if (orderbookIds.length > 0) {
1468
- this.sendSubscribe(orderbookIds);
1469
- }
1470
- this.pendingSubs.clear();
1471
- this.pendingFlushSubs.clear();
1472
- this.pendingFlushUnsubs.clear();
1473
- const tradeSet = new Set(this.activeTradeMarkets());
1474
- this.pendingTradeSubs.forEach((id) => tradeSet.add(id));
1475
- this.pendingFlushTradeSubs.forEach((id) => tradeSet.add(id));
1476
- const tradeIds = Array.from(tradeSet);
1477
- if (tradeIds.length > 0) {
1478
- this.send({ action: "subscribe", channel: "trades", outcomeIds: tradeIds });
1479
- }
1480
- this.pendingTradeSubs.clear();
1481
- this.pendingFlushTradeSubs.clear();
1482
- this.pendingFlushTradeUnsubs.clear();
1483
- const resnapshotIds = Array.from(this.pendingResnapshots);
1484
- if (resnapshotIds.length > 0) {
1485
- this.send({ action: "resnapshot", channel: "orderbook", outcomeIds: resnapshotIds });
1486
- }
1487
- this.pendingResnapshots.clear();
1488
- const arbSet = new Set(this.activeArbMarkets());
1489
- this.pendingFlushArbSubs.forEach((id) => arbSet.add(id));
1490
- const arbIds = Array.from(arbSet);
1491
- if (arbIds.length > 0) {
1492
- this.send({ action: "subscribe", channel: "arb", marketIds: arbIds });
1493
- }
1494
- this.pendingFlushArbSubs.clear();
1495
- this.pendingFlushArbUnsubs.clear();
1496
- if (this.arbFeedCbs.size > 0) {
1497
- this.send({ action: "subscribe", channel: "arb-feed" });
1498
- }
1499
- }
1500
- handleSnapshot(raw) {
1501
- var _a, _b;
1502
- const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1503
- const book = snapshotToOrderbook(msg);
1504
- this.books.set(msg.outcomeId, book);
1505
- this.resyncing.delete(msg.outcomeId);
1506
- this.pendingResnapshots.delete(msg.outcomeId);
1507
- this.debugOrderbook("snapshot_applied", {
1508
- outcomeId: msg.outcomeId,
1509
- seq: msg.seq,
1510
- checksum: msg.checksum,
1511
- bidsTop: this.summarizeTopLevels(book.bids),
1512
- asksTop: this.summarizeTopLevels(book.asks)
1513
- });
1514
- (_b = (_a = this.callbacks).onSnapshot) == null ? void 0 : _b.call(_a, msg.outcomeId, book);
1515
- }
1516
- handleDelta(raw) {
1517
- var _a, _b;
1518
- const outcomeId = raw.outcomeId;
1519
- const current = this.books.get(outcomeId);
1520
- const deltaTop = {
1521
- bids: raw.bidChanges.slice(0, ORDERBOOK_DEBUG_TOP_LEVELS).map(([price, size]) => ({
1522
- price,
1523
- size
1524
- })),
1525
- asks: raw.askChanges.slice(0, ORDERBOOK_DEBUG_TOP_LEVELS).map(([price, size]) => ({
1526
- price,
1527
- size
1528
- }))
1529
- };
1530
- this.debugOrderbook("delta_received", {
1531
- outcomeId,
1532
- seq: raw.seq,
1533
- checksum: raw.checksum,
1534
- hasCurrentBook: Boolean(current),
1535
- deltaTop
1536
- });
1537
- if (!current) {
1538
- this.debugOrderbook("delta_missing_snapshot", { outcomeId, seq: raw.seq });
1539
- this.requestResnapshot(outcomeId);
1540
- return;
1541
- }
1542
- if (raw.seq <= current.seq) {
1543
- this.debugOrderbook("delta_stale_ignored", {
1544
- outcomeId,
1545
- currentSeq: current.seq,
1546
- receivedSeq: raw.seq
1547
- });
1548
- return;
1549
- }
1550
- if (raw.seq > current.seq + 1) {
1551
- this.debugOrderbook("delta_seq_gap", {
1552
- outcomeId,
1553
- expectedSeq: current.seq + 1,
1554
- receivedSeq: raw.seq
1555
- });
1556
- this.emitDiagnostic({
1557
- type: "seq_gap",
1558
- marketId: outcomeId,
1559
- outcomeId,
1560
- expected: current.seq + 1,
1561
- received: raw.seq
1562
- });
1563
- this.requestResnapshot(outcomeId);
1564
- return;
1565
- }
1566
- const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1567
- const newBook = applyOrderbookDelta(current, msg);
1568
- this.debugOrderbook("delta_applied_locally", {
1569
- outcomeId,
1570
- fromSeq: current.seq,
1571
- toSeq: newBook.seq,
1572
- beforeTop: {
1573
- bids: this.summarizeTopLevels(current.bids),
1574
- asks: this.summarizeTopLevels(current.asks)
1575
- },
1576
- afterTop: {
1577
- bids: this.summarizeTopLevels(newBook.bids),
1578
- asks: this.summarizeTopLevels(newBook.asks)
1579
- }
1580
- });
1581
- const computed = computeChecksum(newBook);
1582
- if (computed !== msg.checksum) {
1583
- this.debugOrderbook("delta_checksum_mismatch", {
1584
- outcomeId,
1585
- seq: msg.seq,
1586
- expected: msg.checksum,
1587
- computed
1588
- });
1589
- this.emitDiagnostic({
1590
- type: "checksum_mismatch",
1591
- marketId: outcomeId,
1592
- outcomeId,
1593
- expected: msg.checksum,
1594
- computed
1595
- });
1596
- this.requestResnapshot(outcomeId);
1597
- return;
1598
- }
1599
- this.books.set(outcomeId, newBook);
1600
- this.debugOrderbook("delta_committed", {
1601
- outcomeId,
1602
- seq: newBook.seq,
1603
- checksum: newBook.checksum,
1604
- bidsTop: this.summarizeTopLevels(newBook.bids),
1605
- asksTop: this.summarizeTopLevels(newBook.asks)
1606
- });
1607
- (_b = (_a = this.callbacks).onDelta) == null ? void 0 : _b.call(_a, outcomeId, newBook);
1608
- }
1609
- handleTrade(raw) {
1610
- var _a, _b;
1611
- const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1612
- (_b = (_a = this.callbacks).onTrade) == null ? void 0 : _b.call(_a, msg);
1613
- }
1614
- handleHeartbeat(raw) {
1615
- var _a, _b;
1616
- const msg = __spreadProps(__spreadValues({}, raw), { ts: raw.ts / 1e3 });
1617
- (_b = (_a = this.callbacks).onHeartbeat) == null ? void 0 : _b.call(_a, msg);
1618
- }
1619
- handleMarketResolved(raw) {
1620
- var _a, _b;
1621
- const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1622
- this.books.delete(msg.outcomeId);
1623
- this.pendingResnapshots.delete(msg.outcomeId);
1624
- this.resyncing.delete(msg.outcomeId);
1625
- (_b = (_a = this.callbacks).onMarketResolved) == null ? void 0 : _b.call(_a, msg);
1626
- }
1627
- handleOrderSubmitted(raw) {
1628
- var _a, _b;
1629
- const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1630
- (_b = (_a = this.callbacks).onOrderSubmitted) == null ? void 0 : _b.call(_a, msg);
1631
- }
1632
- handleBalanceUpdate(raw) {
1633
- var _a, _b;
1634
- const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1635
- (_b = (_a = this.callbacks).onBalanceUpdate) == null ? void 0 : _b.call(_a, msg);
1636
- }
1637
- handleRedeemEvent(raw) {
1638
- var _a, _b;
1639
- const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1640
- (_b = (_a = this.callbacks).onRedeemEvent) == null ? void 0 : _b.call(_a, msg);
1641
- }
1642
- // ── Resnapshot ──
1643
- requestResnapshot(outcomeId, force = false) {
1644
- if (this.resyncing.has(outcomeId) && !force) return;
1645
- this.resyncing.add(outcomeId);
1646
- if (this._connected) {
1647
- this.send({ action: "resnapshot", channel: "orderbook", outcomeIds: [outcomeId] });
1648
- this.pendingResnapshots.delete(outcomeId);
1649
- return;
1650
- }
1651
- this.pendingResnapshots.add(outcomeId);
1652
- this.ensureConnected();
1653
- }
1654
- // ── Helpers ──
1655
- /**
1656
- * Schedule a microtask that flushes queued subscribe/unsubscribe frames.
1657
- * Coalesces multiple same-tick sub/unsub calls into at most one frame per
1658
- * channel+action pair.
1659
- */
1660
- scheduleFlush() {
1661
- if (this.flushScheduled) return;
1662
- this.flushScheduled = true;
1663
- queueMicrotask(() => {
1664
- this.flushScheduled = false;
1665
- if (!this._connected) {
1666
- this.pendingFlushSubs.clear();
1667
- this.pendingFlushUnsubs.clear();
1668
- this.pendingFlushTradeSubs.clear();
1669
- this.pendingFlushTradeUnsubs.clear();
1670
- this.pendingFlushArbSubs.clear();
1671
- this.pendingFlushArbUnsubs.clear();
1672
- return;
1673
- }
1674
- this.flushPending();
1675
- });
1676
- }
1677
- flushPending() {
1678
- for (const id of [...this.pendingFlushSubs]) {
1679
- if (this.pendingFlushUnsubs.has(id)) {
1680
- this.pendingFlushSubs.delete(id);
1681
- this.pendingFlushUnsubs.delete(id);
1682
- }
1683
- }
1684
- if (this.pendingFlushSubs.size > 0) {
1685
- this.sendSubscribe(Array.from(this.pendingFlushSubs));
1686
- this.pendingFlushSubs.clear();
1687
- }
1688
- if (this.pendingFlushUnsubs.size > 0) {
1689
- this.send({
1690
- action: "unsubscribe",
1691
- channel: "orderbook",
1692
- outcomeIds: Array.from(this.pendingFlushUnsubs)
1693
- });
1694
- this.pendingFlushUnsubs.clear();
1695
- }
1696
- for (const id of [...this.pendingFlushTradeSubs]) {
1697
- if (this.pendingFlushTradeUnsubs.has(id)) {
1698
- this.pendingFlushTradeSubs.delete(id);
1699
- this.pendingFlushTradeUnsubs.delete(id);
1700
- }
1701
- }
1702
- if (this.pendingFlushTradeSubs.size > 0) {
1703
- this.send({
1704
- action: "subscribe",
1705
- channel: "trades",
1706
- outcomeIds: Array.from(this.pendingFlushTradeSubs)
1707
- });
1708
- this.pendingFlushTradeSubs.clear();
1709
- }
1710
- if (this.pendingFlushTradeUnsubs.size > 0) {
1711
- this.send({
1712
- action: "unsubscribe",
1713
- channel: "trades",
1714
- outcomeIds: Array.from(this.pendingFlushTradeUnsubs)
1715
- });
1716
- this.pendingFlushTradeUnsubs.clear();
1717
- }
1718
- for (const id of [...this.pendingFlushArbSubs]) {
1719
- if (this.pendingFlushArbUnsubs.has(id)) {
1720
- this.pendingFlushArbSubs.delete(id);
1721
- this.pendingFlushArbUnsubs.delete(id);
1722
- }
1723
- }
1724
- if (this.pendingFlushArbSubs.size > 0) {
1725
- this.send({
1726
- action: "subscribe",
1727
- channel: "arb",
1728
- marketIds: Array.from(this.pendingFlushArbSubs)
1729
- });
1730
- this.pendingFlushArbSubs.clear();
1731
- }
1732
- if (this.pendingFlushArbUnsubs.size > 0) {
1733
- this.send({
1734
- action: "unsubscribe",
1735
- channel: "arb",
1736
- marketIds: Array.from(this.pendingFlushArbUnsubs)
1737
- });
1738
- this.pendingFlushArbUnsubs.clear();
1739
- }
1740
- }
1741
- sendSubscribe(outcomeIds) {
1742
- this.send({ action: "subscribe", channel: "orderbook", outcomeIds });
1743
- }
1744
- send(msg) {
1745
- var _a;
1746
- if (((_a = this.ws) == null ? void 0 : _a.readyState) === WebSocket.OPEN) {
1747
- this.ws.send(JSON.stringify(msg));
1748
- }
1749
- }
1750
- activeOrderbookMarkets() {
1751
- const markets = [];
1752
- this.subs.forEach((sub, id) => {
1753
- if (sub.refCount > 0) markets.push(id);
1754
- });
1755
- return markets;
1756
- }
1757
- activeTradeMarkets() {
1758
- const markets = [];
1759
- this.subs.forEach((sub, id) => {
1760
- if (sub.tradeRefs > 0) markets.push(id);
1761
- });
1762
- return markets;
1763
- }
1764
- activeArbMarkets() {
1765
- return Array.from(this.arbSubs.keys());
1766
- }
1767
- emitDiagnostic(event) {
1768
- var _a, _b;
1769
- (_b = (_a = this.callbacks).onDiagnostics) == null ? void 0 : _b.call(_a, event);
1770
- }
1771
- summarizeTopLevels(levels) {
1772
- return levels.slice(0, ORDERBOOK_DEBUG_TOP_LEVELS).map((level) => ({ price: level.price, size: level.size }));
1773
- }
1774
- debugOrderbook(event, payload) {
1775
- if (!this.isOrderbookDebugEnabled()) return;
1776
- console.debug(`[AggWebSocket][orderbook] ${event}`, payload);
1777
- }
1778
- isOrderbookDebugEnabled() {
1779
- return this.enableLogs;
1780
- }
1781
- };
1782
-
1783
261
  // src/client.ts
1784
262
  var COOKIE_REFRESH_DELIVERY = "cookie-refresh";
1785
263
  var DEFAULT_MIDPOINT_IDS_PER_REQUEST = 75;
@@ -1870,7 +348,7 @@ var AggClient = class {
1870
348
  this.authChannel = null;
1871
349
  /** In-flight refresh promise — serializes concurrent 401 retries so only one refresh runs. */
1872
350
  this.pendingRefresh = null;
1873
- var _a, _b;
351
+ var _a, _b, _c;
1874
352
  if (!options.appId && !options.adminKey) {
1875
353
  throw new Error("AggClient requires at least one of appId or adminKey");
1876
354
  }
@@ -1880,9 +358,11 @@ var AggClient = class {
1880
358
  this.signer = options.signer;
1881
359
  this.baseUrl = options.baseUrl.replace(/\/$/, "");
1882
360
  this.wsUrl = options.wsUrl;
361
+ this.preferImmutableHistory = (_a = options.preferImmutableHistory) != null ? _a : false;
362
+ this.webSocketFactory = options.webSocketFactory;
1883
363
  this.authOptions = options.auth;
1884
- this.storageKey = `agg_auth_${(_a = this._appId) != null ? _a : "default"}`;
1885
- this.authDelivery = (_b = options.authDelivery) != null ? _b : "body";
364
+ this.storageKey = `agg_auth_${(_b = this._appId) != null ? _b : "default"}`;
365
+ this.authDelivery = (_c = options.authDelivery) != null ? _c : "body";
1886
366
  this.shouldPersistSession = options.persistSession !== false;
1887
367
  this.restoreSession();
1888
368
  this.initAuthChannel();
@@ -2010,7 +490,7 @@ var AggClient = class {
2010
490
  * @param path - API path (e.g. `/venue-events`)
2011
491
  * @param init - Fetch init options, plus optional `query` object for URL search params
2012
492
  */
2013
- request(path, init) {
493
+ request(path, init, decode) {
2014
494
  return __async(this, null, function* () {
2015
495
  const response = yield this.rawFetch(path, init);
2016
496
  if (response.status === 401 && this.accessToken && !AUTH_PATHS.has(path)) {
@@ -2021,7 +501,7 @@ var AggClient = class {
2021
501
  return this.throwResponseError(retryResponse);
2022
502
  }
2023
503
  if (retryResponse.status === 204) return void 0;
2024
- return retryResponse.json();
504
+ return decode ? decode(retryResponse) : retryResponse.json();
2025
505
  } catch (e) {
2026
506
  this.clearAccessToken();
2027
507
  return this.throwResponseError(response);
@@ -2031,7 +511,7 @@ var AggClient = class {
2031
511
  return this.throwResponseError(response);
2032
512
  }
2033
513
  if (response.status === 204) return void 0;
2034
- return response.json();
514
+ return decode ? decode(response) : response.json();
2035
515
  });
2036
516
  }
2037
517
  rawFetch(path, init) {
@@ -2068,12 +548,15 @@ var AggClient = class {
2068
548
  }
2069
549
  throwResponseError(response) {
2070
550
  return __async(this, null, function* () {
551
+ var _a;
2071
552
  let message = response.status === 404 ? "Market not found" : `AGG API error: ${response.status} ${response.statusText}`;
2072
553
  let code;
2073
554
  let retryable;
555
+ let retryAfterMs = parseRetryAfterHeader((_a = response.headers) == null ? void 0 : _a.get("Retry-After"));
2074
556
  let errors;
2075
557
  try {
2076
558
  const body = yield response.json();
559
+ retryAfterMs != null ? retryAfterMs : retryAfterMs = parseRetryAfterSeconds(body == null ? void 0 : body.retryAfter);
2077
560
  if (typeof body.message === "string" && body.message.length > 0) {
2078
561
  message = body.message;
2079
562
  }
@@ -2092,7 +575,7 @@ var AggClient = class {
2092
575
  }
2093
576
  } catch (e) {
2094
577
  }
2095
- throw new AggApiError(message, response.status, { code, retryable, errors });
578
+ throw new AggApiError(message, response.status, { code, retryable, retryAfterMs, errors });
2096
579
  });
2097
580
  }
2098
581
  /**
@@ -2519,14 +1002,19 @@ Issued At: ${issuedAt}`;
2519
1002
  *
2520
1003
  * Pass `quoteId` to fetch every leg of a multi-venue split route in
2521
1004
  * one call — `useQuoteSummary(quoteId)` reduces the list into a
2522
- * rollup (total cost, share-weighted avg price, to-win).
1005
+ * rollup (total cost, share-weighted avg price, to-win). Pass
1006
+ * `venueMarketIds` to narrow to the orders on those markets. Sent as
1007
+ * repeated params in one request; batch long lists yourself (or use
1008
+ * `useExecutionOrdersForMarkets`) to keep the URL short.
2523
1009
  */
2524
1010
  getExecutionOrders() {
2525
1011
  return __async(this, arguments, function* (params = {}) {
1012
+ var _a;
2526
1013
  const query = {};
2527
1014
  if (params.orderId) query.orderId = params.orderId;
2528
1015
  if (params.quoteId) query.quoteId = params.quoteId;
2529
1016
  if (params.externalId) query.externalId = params.externalId;
1017
+ if ((_a = params.venueMarketIds) == null ? void 0 : _a.length) query.venueMarketIds = params.venueMarketIds;
2530
1018
  if (params.status) query.status = params.status;
2531
1019
  if (params.orderType) query.orderType = params.orderType;
2532
1020
  if (params.mode) query.mode = params.mode;
@@ -2563,6 +1051,23 @@ Issued At: ${issuedAt}`;
2563
1051
  });
2564
1052
  });
2565
1053
  }
1054
+ /**
1055
+ * Per-app trader leaderboard ranked by realized P&L or volume over an
1056
+ * inclusive UTC day range. `rank` is exact within the top 100.
1057
+ */
1058
+ getLeaderboard() {
1059
+ return __async(this, arguments, function* (params = {}) {
1060
+ const query = {};
1061
+ if (params.from) query.from = params.from;
1062
+ if (params.to) query.to = params.to;
1063
+ if (params.metric) query.metric = params.metric;
1064
+ if (params.limit != null) query.limit = String(params.limit);
1065
+ if (params.cursor) query.cursor = params.cursor;
1066
+ return this.request("/leaderboard", {
1067
+ query: Object.keys(query).length ? query : void 0
1068
+ });
1069
+ });
1070
+ }
2566
1071
  /** List execution positions for the authenticated user (cursor pagination). */
2567
1072
  getExecutionPositions() {
2568
1073
  return __async(this, arguments, function* (params = {}) {
@@ -2576,16 +1081,92 @@ Issued At: ${issuedAt}`;
2576
1081
  });
2577
1082
  });
2578
1083
  }
2579
- /** Get managed wallet balances, including per-chain cash balances and per-venue position balances. */
1084
+ /**
1085
+ * Get cash balances and per-venue position balances.
1086
+ *
1087
+ * `custody: "self"` returns the wallets linked to this account — plus their
1088
+ * Polymarket deposit and legacy wallets — instead of the managed wallet. Each
1089
+ * chain row then carries `address` and `custodyKind`; `positions` is
1090
+ * account-wide either way.
1091
+ *
1092
+ * Caveat on the headline number: a chain row with `custodyKind:
1093
+ * "POLYMARKET_LEGACY"` and `legacyKind: "proxy"` holds funds that can only
1094
+ * be spent on Polymarket, but it still counts towards its token's
1095
+ * `totalRaw`/`availableRaw` (a token's totals are always the sum of its
1096
+ * chain rows). If you show "spendable anywhere", subtract those rows.
1097
+ */
2580
1098
  getManagedBalances() {
2581
1099
  return __async(this, arguments, function* (params = {}) {
2582
1100
  const query = {};
2583
1101
  if (params.mode) query.mode = params.mode;
1102
+ if (params.custody) query.custody = params.custody;
2584
1103
  return this.request("/execution/balances", {
2585
1104
  query: Object.keys(query).length ? query : void 0
2586
1105
  });
2587
1106
  });
2588
1107
  }
1108
+ /** Read hosted custody status without creating a signing wallet or account. */
1109
+ getHostedVenueAccount(_0) {
1110
+ return __async(this, arguments, function* (venue, params = {}) {
1111
+ return this.request(
1112
+ `/users/me/venue-accounts/${encodeURIComponent(venue)}`,
1113
+ {
1114
+ query: params.mode ? { mode: params.mode } : void 0
1115
+ }
1116
+ );
1117
+ });
1118
+ }
1119
+ /** Owned exact ledger entries; not balances, fill fees or withdrawal finality. */
1120
+ getHostedVenueActivity(_0) {
1121
+ return __async(this, arguments, function* (venue, params = {}) {
1122
+ return this.request(
1123
+ `/users/me/venue-accounts/${encodeURIComponent(venue)}/activity`,
1124
+ {
1125
+ query: __spreadValues(__spreadValues({}, params.mode ? { mode: params.mode } : {}), params.page !== void 0 ? { page: String(params.page) } : {})
1126
+ }
1127
+ );
1128
+ });
1129
+ }
1130
+ /** Explicit hosted-account creation; no signing wallet or credential upload. */
1131
+ provisionHostedVenueAccount(venue, body) {
1132
+ return __async(this, null, function* () {
1133
+ return this.request(
1134
+ `/users/me/venue-accounts/${encodeURIComponent(venue)}`,
1135
+ {
1136
+ method: "POST",
1137
+ body: JSON.stringify(body)
1138
+ }
1139
+ );
1140
+ });
1141
+ }
1142
+ /** Fetch the owned hosted account's verified Solana USDC deposit address. */
1143
+ getHostedVenueDepositAddress(_0) {
1144
+ return __async(this, arguments, function* (venue, params = {}) {
1145
+ return this.request(
1146
+ `/users/me/venue-accounts/${encodeURIComponent(venue)}/deposit-address`,
1147
+ { query: params.mode ? { mode: params.mode } : void 0 }
1148
+ );
1149
+ });
1150
+ }
1151
+ /** Submit exactly one explicit hosted withdrawal under a caller idempotency key. */
1152
+ withdrawHostedVenue(venue, body) {
1153
+ return __async(this, null, function* () {
1154
+ return this.request(
1155
+ `/users/me/venue-accounts/${encodeURIComponent(venue)}/withdrawals`,
1156
+ { method: "POST", body: JSON.stringify(body) }
1157
+ );
1158
+ });
1159
+ }
1160
+ /** Read only Agg's persisted withdrawal state; this never replays a native request. */
1161
+ getHostedVenueWithdrawal(_0, _1) {
1162
+ return __async(this, arguments, function* (venue, withdrawalId, params = {}) {
1163
+ if (!withdrawalId) throw new Error("withdrawalId is required");
1164
+ return this.request(
1165
+ `/users/me/venue-accounts/${encodeURIComponent(venue)}/withdrawals/${encodeURIComponent(withdrawalId)}`,
1166
+ { query: params.mode ? { mode: params.mode } : void 0 }
1167
+ );
1168
+ });
1169
+ }
2589
1170
  /** Store or replace encrypted credentials for a venue. Secrets are never returned. */
2590
1171
  upsertVenueKey(body) {
2591
1172
  return __async(this, null, function* () {
@@ -2708,21 +1289,32 @@ Issued At: ${issuedAt}`;
2708
1289
  });
2709
1290
  }
2710
1291
  /**
2711
- * Cancel an execution by order id. Self-custody needs Polymarket ClobAuth
1292
+ * Cancel an agg order by id. Self-custody needs Polymarket ClobAuth
2712
1293
  * or a fresh Hyperliquid cancel action signed by the EOA/client-held agent.
2713
1294
  */
2714
1295
  cancelManagedOrder(orderId, options) {
2715
1296
  return __async(this, null, function* () {
2716
- return this.request(
2717
- `/execution/orders/${encodeURIComponent(orderId)}/cancel`,
2718
- {
2719
- method: "POST",
2720
- body: JSON.stringify(
2721
- (options == null ? void 0 : options.cancelSignature) ? { cancelSignature: options.cancelSignature } : {}
2722
- ),
2723
- signal: options == null ? void 0 : options.signal
2724
- }
2725
- );
1297
+ return this.request("/execution/orders/cancel", {
1298
+ method: "POST",
1299
+ body: JSON.stringify(__spreadValues({
1300
+ orderId
1301
+ }, (options == null ? void 0 : options.cancelSignature) ? { cancelSignature: options.cancelSignature } : {})),
1302
+ signal: options == null ? void 0 : options.signal
1303
+ });
1304
+ });
1305
+ }
1306
+ /**
1307
+ * Cancel a Polymarket or Hyperliquid limit order that one of the user's
1308
+ * linked self-custody wallets placed outside AGG. `signerAddress` must be
1309
+ * that wallet and must have produced `cancelSignature`.
1310
+ */
1311
+ cancelVenueOrder(params, options) {
1312
+ return __async(this, null, function* () {
1313
+ return this.request("/execution/orders/cancel", {
1314
+ method: "POST",
1315
+ body: JSON.stringify(params),
1316
+ signal: options == null ? void 0 : options.signal
1317
+ });
2726
1318
  });
2727
1319
  }
2728
1320
  // --- Venue events & venue markets (primary) ---
@@ -2755,6 +1347,17 @@ Issued At: ${issuedAt}`;
2755
1347
  });
2756
1348
  }
2757
1349
  /** Get a single venue event by ID. Requires appId or admin auth. */
1350
+ getEventBootstrap(eventId, options) {
1351
+ return __async(this, null, function* () {
1352
+ return this.request(
1353
+ `/venue-events/${encodeURIComponent(eventId)}/bootstrap`,
1354
+ {
1355
+ query: (options == null ? void 0 : options.limit) ? { limit: String(options.limit) } : void 0,
1356
+ signal: options == null ? void 0 : options.signal
1357
+ }
1358
+ );
1359
+ });
1360
+ }
2758
1361
  getVenueEventById(id, options) {
2759
1362
  return __async(this, null, function* () {
2760
1363
  return this.request(`/venue-events/${encodeURIComponent(id)}`, {
@@ -2883,14 +1486,15 @@ Issued At: ${issuedAt}`;
2883
1486
  });
2884
1487
  }
2885
1488
  /** List venue markets with optional filters. Requires appId or admin auth. Supports cursor-based pagination. */
2886
- getVenueMarkets(options) {
1489
+ getVenueMarkets(options, requestOptions) {
2887
1490
  return __async(this, null, function* () {
2888
- var _a;
1491
+ var _a, _b;
2889
1492
  const query = {};
2890
1493
  if (options == null ? void 0 : options.venue) query.venue = options.venue;
1494
+ if ((_a = options == null ? void 0 : options.externalIdentifier) == null ? void 0 : _a.length) query.externalIdentifier = options.externalIdentifier;
2891
1495
  if (options == null ? void 0 : options.venueEventId) query.venueEventId = options.venueEventId;
2892
1496
  if ((options == null ? void 0 : options.cluster) === false) query.cluster = "false";
2893
- if ((_a = options == null ? void 0 : options.search) == null ? void 0 : _a.trim()) query.search = options.search.trim();
1497
+ if ((_b = options == null ? void 0 : options.search) == null ? void 0 : _b.trim()) query.search = options.search.trim();
2894
1498
  if (options == null ? void 0 : options.matchStatus) query.matchStatus = options.matchStatus;
2895
1499
  if (options == null ? void 0 : options.status) query.status = options.status;
2896
1500
  if ((options == null ? void 0 : options.aggKey) && options.aggKey.length > 0) query.aggKey = options.aggKey;
@@ -2910,7 +1514,8 @@ Issued At: ${issuedAt}`;
2910
1514
  if (options == null ? void 0 : options.sortDir) query.sortDir = options.sortDir;
2911
1515
  if (options == null ? void 0 : options.context) query.context = options.context;
2912
1516
  return this.request("/venue-markets", {
2913
- query: Object.keys(query).length > 0 ? query : void 0
1517
+ query: Object.keys(query).length > 0 ? query : void 0,
1518
+ signal: requestOptions == null ? void 0 : requestOptions.signal
2914
1519
  });
2915
1520
  });
2916
1521
  }
@@ -3012,7 +1617,7 @@ Issued At: ${issuedAt}`;
3012
1617
  * format intentionally omits the param so the server hits the cheaper
3013
1618
  * (and longer-TTL) light cache slot.
3014
1619
  */
3015
- search(params) {
1620
+ search(params, options) {
3016
1621
  return __async(this, null, function* () {
3017
1622
  const query = {
3018
1623
  q: params.q,
@@ -3023,8 +1628,10 @@ Issued At: ${issuedAt}`;
3023
1628
  if (params.limit != null) query.limit = String(params.limit);
3024
1629
  if (params.cursor) query.cursor = params.cursor;
3025
1630
  if (params.deep) query.deepSearch = "true";
1631
+ if (params.mode) query.mode = params.mode;
3026
1632
  return this.request("/search", {
3027
- query
1633
+ query,
1634
+ signal: options == null ? void 0 : options.signal
3028
1635
  });
3029
1636
  });
3030
1637
  }
@@ -3086,6 +1693,7 @@ Issued At: ${issuedAt}`;
3086
1693
  /** Get the canonical TradingView-style bar series for a single venue market outcome. */
3087
1694
  getChartBars(params, options) {
3088
1695
  return __async(this, null, function* () {
1696
+ var _a, _b;
3089
1697
  const resolution = mapChartResolution(params.resolution);
3090
1698
  const query = {
3091
1699
  venueMarketOutcomeId: params.venueMarketOutcomeId,
@@ -3094,12 +1702,39 @@ Issued At: ${issuedAt}`;
3094
1702
  if (resolution) query.resolution = resolution;
3095
1703
  if (params.from != null) query.from = String(params.from);
3096
1704
  if (params.countBack != null) query.countBack = String(params.countBack);
1705
+ if (options == null ? void 0 : options.decode) {
1706
+ const text = yield this.request(
1707
+ "/charts/bars",
1708
+ { query, signal: options.signal },
1709
+ (response) => readChartHistoryText(response)
1710
+ );
1711
+ (_a = options.signal) == null ? void 0 : _a.throwIfAborted();
1712
+ const decoded = yield options.decode(text);
1713
+ (_b = options.signal) == null ? void 0 : _b.throwIfAborted();
1714
+ return decoded;
1715
+ }
3097
1716
  return this.request("/charts/bars", {
3098
1717
  query,
3099
1718
  signal: options == null ? void 0 : options.signal
3100
1719
  });
3101
1720
  });
3102
1721
  }
1722
+ getChartHistoryManifest(params, options) {
1723
+ return __async(this, null, function* () {
1724
+ const query = {
1725
+ venueMarketOutcomeId: params.venueMarketOutcomeId,
1726
+ to: String(params.to)
1727
+ };
1728
+ const resolution = mapChartResolution(params.resolution);
1729
+ if (resolution) query.resolution = resolution;
1730
+ if (params.from != null) query.from = String(params.from);
1731
+ if (params.countBack != null) query.countBack = String(params.countBack);
1732
+ return this.request("/charts/history/manifest", {
1733
+ query,
1734
+ signal: options == null ? void 0 : options.signal
1735
+ });
1736
+ });
1737
+ }
3103
1738
  // --- Batch orderbooks ---
3104
1739
  /** Get live per-venue orderbooks for the explicit venueMarketIds requested. */
3105
1740
  getOrderbooks(params, options) {
@@ -3194,22 +1829,34 @@ Issued At: ${issuedAt}`;
3194
1829
  return params.side ? __spreadProps(__spreadValues({}, response), { side: params.side }) : response;
3195
1830
  });
3196
1831
  }
1832
+ /** Seed subscribed outcomes with one bounded batch of compatible snapshots. */
1833
+ getOutcomeOrderbooks(params, options) {
1834
+ return __async(this, null, function* () {
1835
+ return this.request("/orderbooks/outcomes", {
1836
+ query: __spreadValues({
1837
+ venueMarketOutcomeIds: params.venueMarketOutcomeIds.join(",")
1838
+ }, params.depth == null ? {} : { depth: String(params.depth) }),
1839
+ signal: options == null ? void 0 : options.signal
1840
+ });
1841
+ });
1842
+ }
3197
1843
  /**
3198
1844
  * Create an AggWebSocket instance configured with this client's appId and optional auth token.
3199
1845
  * Requires `wsUrl` to be set in the client options.
3200
1846
  */
3201
1847
  createWebSocket(callbacks, options) {
3202
- var _a;
1848
+ var _a, _b;
3203
1849
  if (!this.wsUrl) {
3204
1850
  throw new Error("AggClient.createWebSocket() requires wsUrl to be set in client options");
3205
1851
  }
3206
1852
  const url = new URL(this.wsUrl);
3207
1853
  if (this._appId) url.searchParams.set("appId", this._appId);
3208
- return new AggWebSocket({
1854
+ const createSocket = (_a = this.webSocketFactory) != null ? _a : ((socketOptions) => new AggWebSocket(socketOptions));
1855
+ return createSocket({
3209
1856
  url: url.toString(),
3210
1857
  callbacks,
3211
1858
  initialToken: this.accessToken,
3212
- enableLogs: (_a = options == null ? void 0 : options.enableLogs) != null ? _a : false
1859
+ enableLogs: (_b = options == null ? void 0 : options.enableLogs) != null ? _b : false
3213
1860
  });
3214
1861
  }
3215
1862
  /** Execute a previously quoted managed trade. Returns pending order IDs. */
@@ -3505,138 +2152,6 @@ Issued At: ${issuedAt}`;
3505
2152
  // src/best-split.ts
3506
2153
  var computeBestSplitsByAmount2 = computeBestSplitsByAmount;
3507
2154
 
3508
- // src/candle-builder.ts
3509
- var INTERVAL_SECS = {
3510
- "1m": 60,
3511
- "5m": 300,
3512
- "1h": 3600,
3513
- "1d": 86400
3514
- };
3515
- var ALL_INTERVALS = ["1m", "5m", "1h", "1d"];
3516
- var MAX_CLOSED = 500;
3517
- var CandleBuilder = class {
3518
- constructor() {
3519
- this.forming = /* @__PURE__ */ new Map();
3520
- this.closedMap = /* @__PURE__ */ new Map();
3521
- this.listeners = /* @__PURE__ */ new Set();
3522
- this._midpoint = null;
3523
- this.dirty = false;
3524
- this.scheduled = false;
3525
- for (const interval of ALL_INTERVALS) {
3526
- this.closedMap.set(interval, []);
3527
- }
3528
- }
3529
- /** Feed a midpoint (from orderbook delta). Updates O/H/L/C for all intervals. */
3530
- addMidpoint(midpoint, timestampSecs) {
3531
- if (!Number.isFinite(midpoint) || !Number.isFinite(timestampSecs)) return;
3532
- this._midpoint = midpoint;
3533
- for (const interval of ALL_INTERVALS) {
3534
- this.updateCandle(interval, midpoint, timestampSecs);
3535
- }
3536
- this.markDirty();
3537
- }
3538
- /** Feed a trade. Updates volume + refines H/L/C from trade price. */
3539
- addTrade(price, size, timestampSecs) {
3540
- if (!Number.isFinite(price) || !Number.isFinite(size) || !Number.isFinite(timestampSecs))
3541
- return;
3542
- for (const interval of ALL_INTERVALS) {
3543
- this.updateCandle(interval, price, timestampSecs, size);
3544
- }
3545
- this.markDirty();
3546
- }
3547
- /** Current forming candle for an interval. */
3548
- getForming(interval) {
3549
- var _a;
3550
- return (_a = this.forming.get(interval)) != null ? _a : null;
3551
- }
3552
- /** Closed candles for an interval (ring buffer, max 500, newest last). */
3553
- getClosed(interval) {
3554
- var _a;
3555
- return (_a = this.closedMap.get(interval)) != null ? _a : [];
3556
- }
3557
- /** Current midpoint (latest value fed). */
3558
- get midpoint() {
3559
- return this._midpoint;
3560
- }
3561
- /**
3562
- * Subscribe to state changes. Returns unsubscribe function.
3563
- * BATCHED: callbacks fire at most once per rAF frame (~16ms).
3564
- * In non-browser environments, falls back to 16ms setTimeout.
3565
- */
3566
- onChange(listener) {
3567
- this.listeners.add(listener);
3568
- return () => {
3569
- this.listeners.delete(listener);
3570
- };
3571
- }
3572
- /** Clear all state (on reconnect). */
3573
- reset() {
3574
- this.forming.clear();
3575
- for (const interval of ALL_INTERVALS) {
3576
- this.closedMap.set(interval, []);
3577
- }
3578
- this._midpoint = null;
3579
- this.dirty = false;
3580
- }
3581
- /** Clear all state AND unsubscribe all listeners. Called on store eviction. */
3582
- dispose() {
3583
- this.reset();
3584
- this.listeners.clear();
3585
- }
3586
- updateCandle(interval, price, timestampSecs, tradeSize) {
3587
- const secs = INTERVAL_SECS[interval];
3588
- const openTime = Math.floor(timestampSecs / secs) * secs;
3589
- const current = this.forming.get(interval);
3590
- if (current && current.openTime !== openTime) {
3591
- const closed = this.closedMap.get(interval);
3592
- closed.push(__spreadProps(__spreadValues({}, current), { isFinal: true }));
3593
- if (closed.length > MAX_CLOSED) closed.shift();
3594
- this.forming.set(interval, {
3595
- openTime,
3596
- o: price,
3597
- h: price,
3598
- l: price,
3599
- c: price,
3600
- v: tradeSize != null ? tradeSize : 0,
3601
- tradeCount: tradeSize != null ? 1 : 0,
3602
- isFinal: false
3603
- });
3604
- } else if (!current) {
3605
- this.forming.set(interval, {
3606
- openTime,
3607
- o: price,
3608
- h: price,
3609
- l: price,
3610
- c: price,
3611
- v: tradeSize != null ? tradeSize : 0,
3612
- tradeCount: tradeSize != null ? 1 : 0,
3613
- isFinal: false
3614
- });
3615
- } else {
3616
- current.h = Math.max(current.h, price);
3617
- current.l = Math.min(current.l, price);
3618
- current.c = price;
3619
- if (tradeSize != null) {
3620
- current.v += tradeSize;
3621
- current.tradeCount += 1;
3622
- }
3623
- }
3624
- }
3625
- markDirty() {
3626
- this.dirty = true;
3627
- if (this.scheduled) return;
3628
- this.scheduled = true;
3629
- const tick = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 16);
3630
- tick(() => {
3631
- this.scheduled = false;
3632
- if (this.dirty) {
3633
- this.dirty = false;
3634
- this.listeners.forEach((l) => l());
3635
- }
3636
- });
3637
- }
3638
- };
3639
-
3640
2155
  // src/market-aggregator.ts
3641
2156
  function mergeCandles(candles) {
3642
2157
  const valid = candles.filter((c2) => c2 != null);
@@ -3715,6 +2230,7 @@ export {
3715
2230
  OrderStatus,
3716
2231
  OrderType,
3717
2232
  RECURRENCE_CADENCES,
2233
+ RETINA_IMAGE_SIZES,
3718
2234
  SELF_CUSTODY_FILL_TIMEOUT_MS,
3719
2235
  SELF_CUSTODY_POLL_INTERVAL_MS,
3720
2236
  TERMINAL_EXECUTION_STATES,
@@ -3748,13 +2264,17 @@ export {
3748
2264
  isVenueActive,
3749
2265
  isVenueDataVisible,
3750
2266
  isVenueInactive,
2267
+ loadImmutableHistory,
3751
2268
  mergeCandles,
3752
2269
  mergeClosedCandles,
3753
2270
  normalizeVenueMarketCluster,
3754
2271
  optimizedImageUrl,
2272
+ orderbookCrc32,
3755
2273
  parse,
3756
2274
  parseEmail,
3757
2275
  parseEmailStrict,
2276
+ projectedOrderbookChecksum,
2277
+ retinaImageUrl,
3758
2278
  safeParse,
3759
2279
  snapshotToOrderbook,
3760
2280
  sortVenues,