@agg-build/sdk 1.0.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/README.md +355 -0
- package/dist/chunk-AXBFBHS2.mjs +45 -0
- package/dist/index.d.mts +2546 -0
- package/dist/index.d.ts +2546 -0
- package/dist/index.js +2581 -0
- package/dist/index.mjs +2483 -0
- package/dist/server.d.mts +155 -0
- package/dist/server.d.ts +155 -0
- package/dist/server.js +254 -0
- package/dist/server.mjs +194 -0
- package/package.json +82 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2581 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
22
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
var __copyProps = (to, from, except, desc) => {
|
|
28
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
29
|
+
for (let key of __getOwnPropNames(from))
|
|
30
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
31
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
32
|
+
}
|
|
33
|
+
return to;
|
|
34
|
+
};
|
|
35
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
|
+
var __async = (__this, __arguments, generator) => {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
var fulfilled = (value) => {
|
|
39
|
+
try {
|
|
40
|
+
step(generator.next(value));
|
|
41
|
+
} catch (e) {
|
|
42
|
+
reject(e);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var rejected = (value) => {
|
|
46
|
+
try {
|
|
47
|
+
step(generator.throw(value));
|
|
48
|
+
} catch (e) {
|
|
49
|
+
reject(e);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
53
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/index.ts
|
|
58
|
+
var index_exports = {};
|
|
59
|
+
__export(index_exports, {
|
|
60
|
+
AccountProvider: () => AccountProvider,
|
|
61
|
+
AccountType: () => AccountType,
|
|
62
|
+
AggClient: () => AggClient,
|
|
63
|
+
AggWebSocket: () => AggWebSocket,
|
|
64
|
+
CONFIRMED_MATCH_STATUSES: () => CONFIRMED_MATCH_STATUSES,
|
|
65
|
+
CandleBuilder: () => CandleBuilder,
|
|
66
|
+
Chain: () => Chain,
|
|
67
|
+
IMAGE_SIZES: () => IMAGE_SIZES,
|
|
68
|
+
ImageSize: () => ImageSize,
|
|
69
|
+
MarketStatus: () => MarketStatus,
|
|
70
|
+
MatchStatus: () => MatchStatus,
|
|
71
|
+
MatchType: () => MatchType,
|
|
72
|
+
OrderStatus: () => OrderStatus,
|
|
73
|
+
TradeSide: () => TradeSide,
|
|
74
|
+
TurnstileChallengeError: () => TurnstileChallengeError,
|
|
75
|
+
VENUES: () => VENUES,
|
|
76
|
+
Venue: () => Venue,
|
|
77
|
+
aggregateMidpoint: () => aggregateMidpoint,
|
|
78
|
+
applyOrderbookDelta: () => applyOrderbookDelta,
|
|
79
|
+
computeBestSplitsByAmount: () => computeBestSplitsByAmount2,
|
|
80
|
+
computeChecksum: () => computeChecksum,
|
|
81
|
+
createAggClient: () => createAggClient,
|
|
82
|
+
enumGuard: () => enumGuard,
|
|
83
|
+
formatMarketQuestion: () => formatMarketQuestion,
|
|
84
|
+
formatOutcomeLabel: () => formatOutcomeLabel,
|
|
85
|
+
getWalletAddressFromUserProfile: () => getWalletAddressFromUserProfile,
|
|
86
|
+
hasShape: () => hasShape,
|
|
87
|
+
isEmail: () => isEmail,
|
|
88
|
+
isEnum: () => isEnum,
|
|
89
|
+
isFiniteNonNeg: () => isFiniteNonNeg,
|
|
90
|
+
isNonEmptyString: () => isNonEmptyString,
|
|
91
|
+
mergeCandles: () => mergeCandles,
|
|
92
|
+
mergeClosedCandles: () => mergeClosedCandles,
|
|
93
|
+
normalizeVenueMarketCluster: () => normalizeVenueMarketCluster,
|
|
94
|
+
optimizedImageUrl: () => optimizedImageUrl,
|
|
95
|
+
parse: () => parse,
|
|
96
|
+
parseEmail: () => parseEmail,
|
|
97
|
+
parseEmailStrict: () => parseEmailStrict,
|
|
98
|
+
safeParse: () => safeParse,
|
|
99
|
+
snapshotToOrderbook: () => snapshotToOrderbook,
|
|
100
|
+
sortVenues: () => sortVenues
|
|
101
|
+
});
|
|
102
|
+
module.exports = __toCommonJS(index_exports);
|
|
103
|
+
|
|
104
|
+
// ../common/src/enums/venue.ts
|
|
105
|
+
var Venue = /* @__PURE__ */ ((Venue3) => {
|
|
106
|
+
Venue3["kalshi"] = "kalshi";
|
|
107
|
+
Venue3["polymarket"] = "polymarket";
|
|
108
|
+
Venue3["limitless"] = "limitless";
|
|
109
|
+
Venue3["opinion"] = "opinion";
|
|
110
|
+
Venue3["predict"] = "predict";
|
|
111
|
+
Venue3["probable"] = "probable";
|
|
112
|
+
Venue3["myriad"] = "myriad";
|
|
113
|
+
Venue3["hyperliquid"] = "hyperliquid";
|
|
114
|
+
return Venue3;
|
|
115
|
+
})(Venue || {});
|
|
116
|
+
var VENUES = [
|
|
117
|
+
"kalshi" /* kalshi */,
|
|
118
|
+
"polymarket" /* polymarket */,
|
|
119
|
+
"limitless" /* limitless */,
|
|
120
|
+
"opinion" /* opinion */,
|
|
121
|
+
"predict" /* predict */,
|
|
122
|
+
"probable" /* probable */,
|
|
123
|
+
"myriad" /* myriad */,
|
|
124
|
+
"hyperliquid" /* hyperliquid */
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
// ../common/src/enums/image-size.ts
|
|
128
|
+
var ImageSize = /* @__PURE__ */ ((ImageSize2) => {
|
|
129
|
+
ImageSize2[ImageSize2["sm"] = 44] = "sm";
|
|
130
|
+
ImageSize2[ImageSize2["md"] = 48] = "md";
|
|
131
|
+
ImageSize2[ImageSize2["lg"] = 60] = "lg";
|
|
132
|
+
ImageSize2[ImageSize2["original"] = 0] = "original";
|
|
133
|
+
return ImageSize2;
|
|
134
|
+
})(ImageSize || {});
|
|
135
|
+
var IMAGE_SIZES = [44 /* sm */, 48 /* md */, 60 /* lg */];
|
|
136
|
+
|
|
137
|
+
// ../common/src/enums/account.ts
|
|
138
|
+
var AccountType = /* @__PURE__ */ ((AccountType2) => {
|
|
139
|
+
AccountType2["oauth"] = "oauth";
|
|
140
|
+
AccountType2["siwe"] = "siwe";
|
|
141
|
+
AccountType2["siws"] = "siws";
|
|
142
|
+
AccountType2["email"] = "email";
|
|
143
|
+
return AccountType2;
|
|
144
|
+
})(AccountType || {});
|
|
145
|
+
var AccountProvider = /* @__PURE__ */ ((AccountProvider2) => {
|
|
146
|
+
AccountProvider2["wallet"] = "wallet";
|
|
147
|
+
AccountProvider2["solana_wallet"] = "solana_wallet";
|
|
148
|
+
AccountProvider2["google"] = "google";
|
|
149
|
+
AccountProvider2["twitter"] = "twitter";
|
|
150
|
+
AccountProvider2["apple"] = "apple";
|
|
151
|
+
AccountProvider2["email"] = "email";
|
|
152
|
+
return AccountProvider2;
|
|
153
|
+
})(AccountProvider || {});
|
|
154
|
+
|
|
155
|
+
// ../common/src/enums/chain.ts
|
|
156
|
+
var Chain = /* @__PURE__ */ ((Chain2) => {
|
|
157
|
+
Chain2["evm"] = "evm";
|
|
158
|
+
Chain2["svm"] = "svm";
|
|
159
|
+
return Chain2;
|
|
160
|
+
})(Chain || {});
|
|
161
|
+
|
|
162
|
+
// ../common/src/enums/market-status.ts
|
|
163
|
+
var MatchStatus = /* @__PURE__ */ ((MatchStatus3) => {
|
|
164
|
+
MatchStatus3["pending"] = "pending";
|
|
165
|
+
MatchStatus3["unmatched"] = "unmatched";
|
|
166
|
+
MatchStatus3["review"] = "review";
|
|
167
|
+
MatchStatus3["matched"] = "matched";
|
|
168
|
+
MatchStatus3["verified"] = "verified";
|
|
169
|
+
MatchStatus3["rejected"] = "rejected";
|
|
170
|
+
return MatchStatus3;
|
|
171
|
+
})(MatchStatus || {});
|
|
172
|
+
var MatchType = /* @__PURE__ */ ((MatchType2) => {
|
|
173
|
+
MatchType2["manual"] = "manual";
|
|
174
|
+
MatchType2["llm"] = "llm";
|
|
175
|
+
return MatchType2;
|
|
176
|
+
})(MatchType || {});
|
|
177
|
+
var CONFIRMED_MATCH_STATUSES = ["matched" /* matched */, "verified" /* verified */];
|
|
178
|
+
var MarketStatus = /* @__PURE__ */ ((MarketStatus3) => {
|
|
179
|
+
MarketStatus3["open"] = "open";
|
|
180
|
+
MarketStatus3["closed"] = "closed";
|
|
181
|
+
MarketStatus3["resolved"] = "resolved";
|
|
182
|
+
MarketStatus3["unopened"] = "unopened";
|
|
183
|
+
MarketStatus3["paused"] = "paused";
|
|
184
|
+
return MarketStatus3;
|
|
185
|
+
})(MarketStatus || {});
|
|
186
|
+
|
|
187
|
+
// ../common/src/enums/trading.ts
|
|
188
|
+
var OrderStatus = /* @__PURE__ */ ((OrderStatus2) => {
|
|
189
|
+
OrderStatus2["pending"] = "pending";
|
|
190
|
+
OrderStatus2["signing"] = "signing";
|
|
191
|
+
OrderStatus2["pending_bridge"] = "pending_bridge";
|
|
192
|
+
OrderStatus2["submitting"] = "submitting";
|
|
193
|
+
OrderStatus2["submitted"] = "submitted";
|
|
194
|
+
OrderStatus2["filled"] = "filled";
|
|
195
|
+
OrderStatus2["partial_fill"] = "partial_fill";
|
|
196
|
+
return OrderStatus2;
|
|
197
|
+
})(OrderStatus || {});
|
|
198
|
+
var TradeSide = /* @__PURE__ */ ((TradeSide2) => {
|
|
199
|
+
TradeSide2["Buy"] = "buy";
|
|
200
|
+
TradeSide2["Sell"] = "sell";
|
|
201
|
+
return TradeSide2;
|
|
202
|
+
})(TradeSide || {});
|
|
203
|
+
|
|
204
|
+
// ../common/src/utils/format-market-display.ts
|
|
205
|
+
function formatOutcomeLabel(venue, outcome, _market) {
|
|
206
|
+
return outcome.label;
|
|
207
|
+
}
|
|
208
|
+
function formatMarketQuestion(venue, market) {
|
|
209
|
+
var _a, _b, _c, _d;
|
|
210
|
+
if ((_a = market.shortTitle) == null ? void 0 : _a.trim()) return market.shortTitle.trim();
|
|
211
|
+
if (venue === "kalshi" /* kalshi */) {
|
|
212
|
+
const firstTitle = (_d = (_c = (_b = market.venueMarketOutcomes) == null ? void 0 : _b[0]) == null ? void 0 : _c.title) == null ? void 0 : _d.trim();
|
|
213
|
+
if (firstTitle) return firstTitle;
|
|
214
|
+
}
|
|
215
|
+
return market.question.trim();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ../common/src/utils/auth.ts
|
|
219
|
+
function getWalletAddressFromUserProfile(user) {
|
|
220
|
+
var _a, _b, _c, _d, _e;
|
|
221
|
+
const oauthWallet = (_b = (_a = user == null ? void 0 : user.accounts) == null ? void 0 : _a.find(
|
|
222
|
+
(a) => a.type === "oauth" /* oauth */ && String(a.provider).toLowerCase() === "wallet"
|
|
223
|
+
)) != null ? _b : null;
|
|
224
|
+
if (oauthWallet == null ? void 0 : oauthWallet.providerAccountId) return oauthWallet.providerAccountId;
|
|
225
|
+
const siweAccount = (_d = (_c = user == null ? void 0 : user.accounts) == null ? void 0 : _c.find((a) => a.type === "siwe" /* siwe */)) != null ? _d : null;
|
|
226
|
+
return (_e = siweAccount == null ? void 0 : siweAccount.providerAccountId) != null ? _e : void 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ../common/src/utils/validate.ts
|
|
230
|
+
function safeParse(value, guard) {
|
|
231
|
+
if (guard(value)) return { success: true, data: value };
|
|
232
|
+
return { success: false };
|
|
233
|
+
}
|
|
234
|
+
function parse(value, guard, message = "Validation failed") {
|
|
235
|
+
if (guard(value)) return value;
|
|
236
|
+
const err = new Error(message);
|
|
237
|
+
err.issues = [{ message }];
|
|
238
|
+
throw err;
|
|
239
|
+
}
|
|
240
|
+
function isEnum(values) {
|
|
241
|
+
return (v) => typeof v === "string" && values.has(v);
|
|
242
|
+
}
|
|
243
|
+
function enumGuard(source) {
|
|
244
|
+
const values = new Set(Array.isArray(source) ? source : Object.values(source));
|
|
245
|
+
return isEnum(values);
|
|
246
|
+
}
|
|
247
|
+
function isFiniteNonNeg(v) {
|
|
248
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
249
|
+
}
|
|
250
|
+
function isNonEmptyString(v) {
|
|
251
|
+
return typeof v === "string" && v.length > 0;
|
|
252
|
+
}
|
|
253
|
+
function hasShape(v, checks) {
|
|
254
|
+
if (typeof v !== "object" || v === null) return false;
|
|
255
|
+
const obj = v;
|
|
256
|
+
for (const [key, check] of Object.entries(checks)) {
|
|
257
|
+
if (!(key in obj) || !check(obj[key])) return false;
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
var EMAIL_MSG = "Enter a valid email address.";
|
|
262
|
+
var EMAIL_MAX_LEN = 254;
|
|
263
|
+
function isEmail(v) {
|
|
264
|
+
const n = v.length;
|
|
265
|
+
if (n === 0 || n > EMAIL_MAX_LEN) return false;
|
|
266
|
+
let atIdx = -1;
|
|
267
|
+
for (let i = 0; i < n; i++) {
|
|
268
|
+
const c = v.charCodeAt(i);
|
|
269
|
+
if (c === 32 || c >= 9 && c <= 13) return false;
|
|
270
|
+
if (c === 64) {
|
|
271
|
+
if (atIdx !== -1) return false;
|
|
272
|
+
atIdx = i;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (atIdx <= 0 || atIdx === n - 1) return false;
|
|
276
|
+
for (let i = atIdx + 2; i < n - 1; i++) {
|
|
277
|
+
if (v.charCodeAt(i) === 46) return true;
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
function parseEmail(raw) {
|
|
282
|
+
const email = raw.trim();
|
|
283
|
+
if (!isEmail(email)) {
|
|
284
|
+
return { success: false, error: { issues: [{ message: EMAIL_MSG }] } };
|
|
285
|
+
}
|
|
286
|
+
return { success: true, data: { email } };
|
|
287
|
+
}
|
|
288
|
+
function parseEmailStrict(raw) {
|
|
289
|
+
var _a;
|
|
290
|
+
const result = parseEmail(raw);
|
|
291
|
+
if (!result.success) {
|
|
292
|
+
const err = new Error(EMAIL_MSG);
|
|
293
|
+
err.issues = (_a = result.error) == null ? void 0 : _a.issues;
|
|
294
|
+
throw err;
|
|
295
|
+
}
|
|
296
|
+
return result.data;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ../common/src/utils/trading-splits.ts
|
|
300
|
+
function computeBestSplitsByAmount(selections, side, maxDollarAmount) {
|
|
301
|
+
var _a;
|
|
302
|
+
if (selections.length === 0 || maxDollarAmount <= 0) {
|
|
303
|
+
return { splits: [], totalSize: 0, totalCost: 0 };
|
|
304
|
+
}
|
|
305
|
+
const levels = [];
|
|
306
|
+
for (const o of selections) {
|
|
307
|
+
const venue = o.market.venue;
|
|
308
|
+
const venueMarketOutcomeId = o.outcome.id;
|
|
309
|
+
const outcomeExternalIdentifier = (_a = o.outcome.externalIdentifier) != null ? _a : void 0;
|
|
310
|
+
if (side === "buy" /* Buy */) {
|
|
311
|
+
[...o.orderbook.asks].sort((a, b) => a.price - b.price).forEach(
|
|
312
|
+
(l) => {
|
|
313
|
+
var _a2;
|
|
314
|
+
return levels.push({
|
|
315
|
+
venue,
|
|
316
|
+
venueMarketOutcomeId,
|
|
317
|
+
outcomeExternalIdentifier,
|
|
318
|
+
price: l.price,
|
|
319
|
+
size: l.size,
|
|
320
|
+
negRisk: (_a2 = o.orderbook.negRisk) != null ? _a2 : false
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
);
|
|
324
|
+
} else {
|
|
325
|
+
[...o.orderbook.bids].sort((a, b) => b.price - a.price).forEach(
|
|
326
|
+
(l) => {
|
|
327
|
+
var _a2;
|
|
328
|
+
return levels.push({
|
|
329
|
+
venue,
|
|
330
|
+
venueMarketOutcomeId,
|
|
331
|
+
outcomeExternalIdentifier,
|
|
332
|
+
price: 1 - l.price,
|
|
333
|
+
size: l.size,
|
|
334
|
+
negRisk: (_a2 = o.orderbook.negRisk) != null ? _a2 : false
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
levels.sort((a, b) => a.price - b.price);
|
|
341
|
+
let totalCost = 0;
|
|
342
|
+
const fills = [];
|
|
343
|
+
for (const l of levels) {
|
|
344
|
+
if (totalCost >= maxDollarAmount) break;
|
|
345
|
+
const remainingBudget = maxDollarAmount - totalCost;
|
|
346
|
+
const maxSizeAtLevel = Math.min(l.size, remainingBudget / l.price);
|
|
347
|
+
if (maxSizeAtLevel <= 0) continue;
|
|
348
|
+
const take = maxSizeAtLevel;
|
|
349
|
+
fills.push({
|
|
350
|
+
venue: l.venue,
|
|
351
|
+
venueMarketOutcomeId: l.venueMarketOutcomeId,
|
|
352
|
+
outcomeExternalIdentifier: l.outcomeExternalIdentifier,
|
|
353
|
+
price: l.price,
|
|
354
|
+
size: take,
|
|
355
|
+
negRisk: l.negRisk
|
|
356
|
+
});
|
|
357
|
+
totalCost += l.price * take;
|
|
358
|
+
}
|
|
359
|
+
if (fills.length === 0) {
|
|
360
|
+
return { splits: [], totalSize: 0, totalCost: 0 };
|
|
361
|
+
}
|
|
362
|
+
const byOutcome = {};
|
|
363
|
+
for (const f of fills) {
|
|
364
|
+
const key = `${f.venue}:${f.venueMarketOutcomeId}`;
|
|
365
|
+
const existing = byOutcome[key];
|
|
366
|
+
if (!existing) {
|
|
367
|
+
byOutcome[key] = {
|
|
368
|
+
size: f.size,
|
|
369
|
+
venue: f.venue,
|
|
370
|
+
venueMarketOutcomeId: f.venueMarketOutcomeId,
|
|
371
|
+
outcomeExternalIdentifier: f.outcomeExternalIdentifier,
|
|
372
|
+
negRisk: f.negRisk
|
|
373
|
+
};
|
|
374
|
+
} else {
|
|
375
|
+
existing.size += f.size;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const splits = [];
|
|
379
|
+
let totalSize = 0;
|
|
380
|
+
for (const key in byOutcome) {
|
|
381
|
+
const { size, venue, venueMarketOutcomeId, outcomeExternalIdentifier, negRisk } = byOutcome[key];
|
|
382
|
+
totalSize += size;
|
|
383
|
+
let price = 0;
|
|
384
|
+
if (size > 0) {
|
|
385
|
+
const outcomeFills = fills.filter(
|
|
386
|
+
(f) => f.venue === venue && f.venueMarketOutcomeId === venueMarketOutcomeId
|
|
387
|
+
);
|
|
388
|
+
if (side === "buy" /* Buy */) {
|
|
389
|
+
const rawPrice = Math.max(...outcomeFills.map((f) => f.price));
|
|
390
|
+
price = Math.ceil(rawPrice * 100) / 100;
|
|
391
|
+
} else {
|
|
392
|
+
const rawPrice = Math.min(...outcomeFills.map((f) => 1 - f.price));
|
|
393
|
+
price = Math.floor(rawPrice * 100) / 100;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
splits.push({
|
|
397
|
+
venue,
|
|
398
|
+
venueMarketOutcomeId,
|
|
399
|
+
side,
|
|
400
|
+
price,
|
|
401
|
+
size,
|
|
402
|
+
negRisk,
|
|
403
|
+
outcomeExternalIdentifier
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
splits,
|
|
408
|
+
totalSize,
|
|
409
|
+
totalCost
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ../common/src/utils/image-url.ts
|
|
414
|
+
function pickVariant(px) {
|
|
415
|
+
if (px <= 44 /* sm */) return 44 /* sm */;
|
|
416
|
+
if (px <= 48 /* md */) return 48 /* md */;
|
|
417
|
+
if (px <= 60 /* lg */) return 60 /* lg */;
|
|
418
|
+
return 0 /* original */;
|
|
419
|
+
}
|
|
420
|
+
var VARIANT_RE = /\/(original|raw|\d+)\.webp$/;
|
|
421
|
+
function optimizedImageUrl(url, maxPx) {
|
|
422
|
+
if (!url) return void 0;
|
|
423
|
+
if (!VARIANT_RE.test(url)) return url;
|
|
424
|
+
const variant = pickVariant(maxPx);
|
|
425
|
+
const filename = variant === 0 /* original */ ? "original.webp" : `${variant}.webp`;
|
|
426
|
+
return url.replace(VARIANT_RE, `/${filename}`);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ../common/src/utils/venue-market-cluster.ts
|
|
430
|
+
var toNonEmptyString = (value) => {
|
|
431
|
+
if (typeof value !== "string") return void 0;
|
|
432
|
+
const trimmedValue = value.trim();
|
|
433
|
+
return trimmedValue ? trimmedValue : void 0;
|
|
434
|
+
};
|
|
435
|
+
var addGraphEdge = (adjacencyById, fromMarketId, toMarketId) => {
|
|
436
|
+
var _a, _b;
|
|
437
|
+
if (fromMarketId === toMarketId) return;
|
|
438
|
+
const fromNeighbors = (_a = adjacencyById.get(fromMarketId)) != null ? _a : /* @__PURE__ */ new Set();
|
|
439
|
+
fromNeighbors.add(toMarketId);
|
|
440
|
+
adjacencyById.set(fromMarketId, fromNeighbors);
|
|
441
|
+
const toNeighbors = (_b = adjacencyById.get(toMarketId)) != null ? _b : /* @__PURE__ */ new Set();
|
|
442
|
+
toNeighbors.add(fromMarketId);
|
|
443
|
+
adjacencyById.set(toMarketId, toNeighbors);
|
|
444
|
+
};
|
|
445
|
+
var collectDirectRelationMarkets = (market) => {
|
|
446
|
+
var _a, _b, _c;
|
|
447
|
+
const relationMarkets = [];
|
|
448
|
+
const seenRelationIds = /* @__PURE__ */ new Set();
|
|
449
|
+
const addRelationMarket = (relationMarket) => {
|
|
450
|
+
if (!relationMarket) return;
|
|
451
|
+
if (seenRelationIds.has(relationMarket.id)) return;
|
|
452
|
+
seenRelationIds.add(relationMarket.id);
|
|
453
|
+
relationMarkets.push(relationMarket);
|
|
454
|
+
};
|
|
455
|
+
for (const matchedVenueMarket of (_a = market.matchedVenueMarkets) != null ? _a : []) {
|
|
456
|
+
addRelationMarket(matchedVenueMarket);
|
|
457
|
+
}
|
|
458
|
+
addRelationMarket((_c = (_b = market.matchEntry) == null ? void 0 : _b.targetVenueMarket) != null ? _c : void 0);
|
|
459
|
+
return relationMarkets;
|
|
460
|
+
};
|
|
461
|
+
var dedupeSourceMarketsById = (markets) => {
|
|
462
|
+
const seenMarketIds = /* @__PURE__ */ new Set();
|
|
463
|
+
return markets.filter((market) => {
|
|
464
|
+
if (seenMarketIds.has(market.id)) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
seenMarketIds.add(market.id);
|
|
468
|
+
return true;
|
|
469
|
+
});
|
|
470
|
+
};
|
|
471
|
+
var collectUniqueRelatedMarkets = (markets, sourceMarketsById) => {
|
|
472
|
+
const relatedMarketsById = /* @__PURE__ */ new Map();
|
|
473
|
+
for (const market of markets) {
|
|
474
|
+
for (const relatedMarket of collectDirectRelationMarkets(market)) {
|
|
475
|
+
if (sourceMarketsById.has(relatedMarket.id) || relatedMarketsById.has(relatedMarket.id)) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
relatedMarketsById.set(relatedMarket.id, relatedMarket);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return Array.from(relatedMarketsById.values());
|
|
482
|
+
};
|
|
483
|
+
var normalizeVenueMarketCluster = (markets, selectedMarketId) => {
|
|
484
|
+
var _a, _b;
|
|
485
|
+
if (markets.length === 0) return [];
|
|
486
|
+
const orderedSourceMarkets = dedupeSourceMarketsById(markets);
|
|
487
|
+
const sourceMarketsById = new Map(orderedSourceMarkets.map((market) => [market.id, market]));
|
|
488
|
+
const orderedRelatedMarkets = collectUniqueRelatedMarkets(
|
|
489
|
+
orderedSourceMarkets,
|
|
490
|
+
sourceMarketsById
|
|
491
|
+
);
|
|
492
|
+
const relatedMarketsById = new Map(orderedRelatedMarkets.map((market) => [market.id, market]));
|
|
493
|
+
const resolvedSelectedMarketId = toNonEmptyString(selectedMarketId);
|
|
494
|
+
if (!resolvedSelectedMarketId) {
|
|
495
|
+
return [...orderedSourceMarkets, ...orderedRelatedMarkets];
|
|
496
|
+
}
|
|
497
|
+
const adjacencyById = /* @__PURE__ */ new Map();
|
|
498
|
+
for (const market of orderedSourceMarkets) {
|
|
499
|
+
for (const relatedMarket of collectDirectRelationMarkets(market)) {
|
|
500
|
+
addGraphEdge(adjacencyById, market.id, relatedMarket.id);
|
|
501
|
+
}
|
|
502
|
+
const targetVenueMarketId = toNonEmptyString((_a = market.matchEntry) == null ? void 0 : _a.targetVenueMarketId);
|
|
503
|
+
if (targetVenueMarketId) {
|
|
504
|
+
addGraphEdge(adjacencyById, market.id, targetVenueMarketId);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (!sourceMarketsById.has(resolvedSelectedMarketId) && !relatedMarketsById.has(resolvedSelectedMarketId)) {
|
|
508
|
+
return [];
|
|
509
|
+
}
|
|
510
|
+
const visitedMarketIds = /* @__PURE__ */ new Set();
|
|
511
|
+
const queue = [resolvedSelectedMarketId];
|
|
512
|
+
while (queue.length > 0) {
|
|
513
|
+
const currentMarketId = queue.shift();
|
|
514
|
+
if (!currentMarketId || visitedMarketIds.has(currentMarketId)) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
visitedMarketIds.add(currentMarketId);
|
|
518
|
+
const neighborMarketIds = adjacencyById.get(currentMarketId);
|
|
519
|
+
for (const neighborMarketId of Array.from(neighborMarketIds != null ? neighborMarketIds : /* @__PURE__ */ new Set())) {
|
|
520
|
+
if (!visitedMarketIds.has(neighborMarketId)) {
|
|
521
|
+
queue.push(neighborMarketId);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const orderedClusterMarkets = [];
|
|
526
|
+
const appendedMarketIds = /* @__PURE__ */ new Set();
|
|
527
|
+
const appendMarket = (market) => {
|
|
528
|
+
if (!market) return;
|
|
529
|
+
if (!visitedMarketIds.has(market.id)) return;
|
|
530
|
+
if (appendedMarketIds.has(market.id)) return;
|
|
531
|
+
appendedMarketIds.add(market.id);
|
|
532
|
+
orderedClusterMarkets.push(market);
|
|
533
|
+
};
|
|
534
|
+
appendMarket(
|
|
535
|
+
(_b = sourceMarketsById.get(resolvedSelectedMarketId)) != null ? _b : relatedMarketsById.get(resolvedSelectedMarketId)
|
|
536
|
+
);
|
|
537
|
+
for (const market of orderedSourceMarkets) {
|
|
538
|
+
appendMarket(market);
|
|
539
|
+
}
|
|
540
|
+
for (const market of orderedRelatedMarkets) {
|
|
541
|
+
appendMarket(market);
|
|
542
|
+
}
|
|
543
|
+
return orderedClusterMarkets;
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// ../common/src/utils/sort-venues.ts
|
|
547
|
+
var VENUE_SORT_ORDER = [
|
|
548
|
+
"polymarket" /* polymarket */,
|
|
549
|
+
"kalshi" /* kalshi */,
|
|
550
|
+
"opinion" /* opinion */,
|
|
551
|
+
"limitless" /* limitless */,
|
|
552
|
+
"predict" /* predict */,
|
|
553
|
+
"myriad" /* myriad */,
|
|
554
|
+
"hyperliquid" /* hyperliquid */
|
|
555
|
+
];
|
|
556
|
+
var venueOrderIndex = new Map(VENUE_SORT_ORDER.map((v, i) => [v, i]));
|
|
557
|
+
var getVenueOrder = (venue) => {
|
|
558
|
+
var _a;
|
|
559
|
+
return (_a = venueOrderIndex.get(venue)) != null ? _a : VENUE_SORT_ORDER.length;
|
|
560
|
+
};
|
|
561
|
+
var sortVenues = (venues) => [...venues].sort((a, b) => getVenueOrder(a) - getVenueOrder(b));
|
|
562
|
+
|
|
563
|
+
// src/orderbook-utils.ts
|
|
564
|
+
var PRICE_KEY_SCALE = 1e9;
|
|
565
|
+
function crc32(str) {
|
|
566
|
+
let crc = 4294967295;
|
|
567
|
+
for (let i = 0; i < str.length; i++) {
|
|
568
|
+
crc ^= str.charCodeAt(i);
|
|
569
|
+
for (let j = 0; j < 8; j++) {
|
|
570
|
+
crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return (crc ^ 4294967295) >>> 0;
|
|
574
|
+
}
|
|
575
|
+
function computeChecksum(book) {
|
|
576
|
+
let checksum = 0;
|
|
577
|
+
for (const b of book.bids) checksum ^= crc32(`B${b.price}:${b.size}`);
|
|
578
|
+
for (const a of book.asks) checksum ^= crc32(`A${a.price}:${a.size}`);
|
|
579
|
+
return checksum >>> 0;
|
|
580
|
+
}
|
|
581
|
+
function attributedToLevel(wire) {
|
|
582
|
+
return { price: wire[0], size: wire[1], venues: __spreadValues({}, wire[2]) };
|
|
583
|
+
}
|
|
584
|
+
function venueLevelToLevel(wire) {
|
|
585
|
+
return { price: wire[0], size: wire[1] };
|
|
586
|
+
}
|
|
587
|
+
function convertVenueBooks(raw) {
|
|
588
|
+
const result = {};
|
|
589
|
+
for (const [venue, book] of Object.entries(raw)) {
|
|
590
|
+
result[venue] = {
|
|
591
|
+
bids: book.bids.map(venueLevelToLevel),
|
|
592
|
+
asks: book.asks.map(venueLevelToLevel)
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
return result;
|
|
596
|
+
}
|
|
597
|
+
function snapshotToOrderbook(snapshot) {
|
|
598
|
+
return {
|
|
599
|
+
marketId: snapshot.outcomeId,
|
|
600
|
+
outcomeId: snapshot.outcomeId,
|
|
601
|
+
bids: snapshot.bids.map(attributedToLevel),
|
|
602
|
+
asks: snapshot.asks.map(attributedToLevel),
|
|
603
|
+
venueOrderbooks: convertVenueBooks(snapshot.venueOrderbooks),
|
|
604
|
+
venues: __spreadValues({}, snapshot.venues),
|
|
605
|
+
midpoint: snapshot.midpoint,
|
|
606
|
+
spread: snapshot.spread,
|
|
607
|
+
seq: snapshot.seq,
|
|
608
|
+
checksum: snapshot.checksum,
|
|
609
|
+
timestamp: snapshot.timestamp
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function applyOrderbookDelta(state, delta) {
|
|
613
|
+
var _a, _b;
|
|
614
|
+
const bids = applyChanges([...state.bids], delta.bidChanges, "desc");
|
|
615
|
+
const asks = applyChanges([...state.asks], delta.askChanges, "asc");
|
|
616
|
+
const impliedBidChangesByVenue = deriveVenueChangesFromAttributedChanges(
|
|
617
|
+
state.bids,
|
|
618
|
+
delta.bidChanges
|
|
619
|
+
);
|
|
620
|
+
const impliedAskChangesByVenue = deriveVenueChangesFromAttributedChanges(
|
|
621
|
+
state.asks,
|
|
622
|
+
delta.askChanges
|
|
623
|
+
);
|
|
624
|
+
const venueOrderbooks = __spreadValues({}, state.venueOrderbooks);
|
|
625
|
+
const venuesWithDelta = /* @__PURE__ */ new Set([
|
|
626
|
+
...Object.keys(delta.venueDeltaBooks),
|
|
627
|
+
...Object.keys(impliedBidChangesByVenue),
|
|
628
|
+
...Object.keys(impliedAskChangesByVenue)
|
|
629
|
+
]);
|
|
630
|
+
for (const venue of venuesWithDelta) {
|
|
631
|
+
const explicitChanges = delta.venueDeltaBooks[venue];
|
|
632
|
+
const bidChanges = mergeVenueLevelChanges(
|
|
633
|
+
explicitChanges == null ? void 0 : explicitChanges.bidChanges,
|
|
634
|
+
impliedBidChangesByVenue[venue]
|
|
635
|
+
);
|
|
636
|
+
const askChanges = mergeVenueLevelChanges(
|
|
637
|
+
explicitChanges == null ? void 0 : explicitChanges.askChanges,
|
|
638
|
+
impliedAskChangesByVenue[venue]
|
|
639
|
+
);
|
|
640
|
+
if (bidChanges.length === 0 && askChanges.length === 0) continue;
|
|
641
|
+
const existing = (_a = venueOrderbooks[venue]) != null ? _a : { bids: [], asks: [] };
|
|
642
|
+
venueOrderbooks[venue] = {
|
|
643
|
+
bids: applyVenueChanges([...existing.bids], bidChanges, "desc"),
|
|
644
|
+
asks: applyVenueChanges([...existing.asks], askChanges, "asc")
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
marketId: state.marketId,
|
|
649
|
+
outcomeId: (_b = state.outcomeId) != null ? _b : delta.outcomeId,
|
|
650
|
+
bids,
|
|
651
|
+
asks,
|
|
652
|
+
venueOrderbooks,
|
|
653
|
+
venues: __spreadValues(__spreadValues({}, state.venues), delta.venues),
|
|
654
|
+
midpoint: delta.midpoint,
|
|
655
|
+
spread: delta.spread,
|
|
656
|
+
seq: delta.seq,
|
|
657
|
+
checksum: delta.checksum,
|
|
658
|
+
timestamp: delta.timestamp
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function toPriceKey(price) {
|
|
662
|
+
return String(Math.round(price * PRICE_KEY_SCALE));
|
|
663
|
+
}
|
|
664
|
+
function applyChanges(levels, changes, sort) {
|
|
665
|
+
const levelsByPrice = new Map(levels.map((level) => [toPriceKey(level.price), level]));
|
|
666
|
+
for (const [price, size, venues] of changes) {
|
|
667
|
+
const key = toPriceKey(price);
|
|
668
|
+
if (size === 0) {
|
|
669
|
+
levelsByPrice.delete(key);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
levelsByPrice.set(key, {
|
|
673
|
+
price,
|
|
674
|
+
size,
|
|
675
|
+
venues: __spreadValues({}, venues != null ? venues : {})
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
levels = [...levelsByPrice.values()];
|
|
679
|
+
levels.sort(sort === "desc" ? (a, b) => b.price - a.price : (a, b) => a.price - b.price);
|
|
680
|
+
return levels;
|
|
681
|
+
}
|
|
682
|
+
function applyVenueChanges(levels, changes, sort) {
|
|
683
|
+
const levelsByPrice = new Map(levels.map((level) => [toPriceKey(level.price), level]));
|
|
684
|
+
for (const [price, size] of changes) {
|
|
685
|
+
const key = toPriceKey(price);
|
|
686
|
+
if (size === 0) {
|
|
687
|
+
levelsByPrice.delete(key);
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
levelsByPrice.set(key, { price, size });
|
|
691
|
+
}
|
|
692
|
+
levels = [...levelsByPrice.values()];
|
|
693
|
+
levels.sort(sort === "desc" ? (a, b) => b.price - a.price : (a, b) => a.price - b.price);
|
|
694
|
+
return levels;
|
|
695
|
+
}
|
|
696
|
+
function mergeVenueLevelChanges(primary, fallback) {
|
|
697
|
+
if (!(primary == null ? void 0 : primary.length)) return fallback ? [...fallback] : [];
|
|
698
|
+
if (!(fallback == null ? void 0 : fallback.length)) return [...primary];
|
|
699
|
+
const changesByPrice = /* @__PURE__ */ new Map();
|
|
700
|
+
for (const change of fallback) {
|
|
701
|
+
changesByPrice.set(toPriceKey(change[0]), change);
|
|
702
|
+
}
|
|
703
|
+
for (const change of primary) {
|
|
704
|
+
changesByPrice.set(toPriceKey(change[0]), change);
|
|
705
|
+
}
|
|
706
|
+
return [...changesByPrice.values()];
|
|
707
|
+
}
|
|
708
|
+
function deriveVenueChangesFromAttributedChanges(previousLevels, changes) {
|
|
709
|
+
var _a, _b;
|
|
710
|
+
const previousLevelsByPrice = new Map(
|
|
711
|
+
previousLevels.map((level) => [toPriceKey(level.price), level])
|
|
712
|
+
);
|
|
713
|
+
const venueChanges = {};
|
|
714
|
+
for (const [price, size, nextVenuesRaw] of changes) {
|
|
715
|
+
const key = toPriceKey(price);
|
|
716
|
+
const previousLevel = previousLevelsByPrice.get(key);
|
|
717
|
+
const previousVenues = (_a = previousLevel == null ? void 0 : previousLevel.venues) != null ? _a : {};
|
|
718
|
+
const nextVenues = nextVenuesRaw != null ? nextVenuesRaw : {};
|
|
719
|
+
const affectedVenues = /* @__PURE__ */ new Set([...Object.keys(previousVenues), ...Object.keys(nextVenues)]);
|
|
720
|
+
for (const venue of affectedVenues) {
|
|
721
|
+
const nextSize = Number((_b = nextVenues[venue]) != null ? _b : 0);
|
|
722
|
+
if (!Number.isFinite(nextSize)) continue;
|
|
723
|
+
if (!venueChanges[venue]) {
|
|
724
|
+
venueChanges[venue] = [];
|
|
725
|
+
}
|
|
726
|
+
venueChanges[venue].push([price, nextSize]);
|
|
727
|
+
}
|
|
728
|
+
if (size === 0) {
|
|
729
|
+
previousLevelsByPrice.delete(key);
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
previousLevelsByPrice.set(key, {
|
|
733
|
+
price,
|
|
734
|
+
size,
|
|
735
|
+
venues: __spreadValues({}, nextVenues)
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
return venueChanges;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/agg-websocket.ts
|
|
742
|
+
var RECONNECT_BASE_MS = 1e3;
|
|
743
|
+
var RECONNECT_MAX_MS = 3e4;
|
|
744
|
+
var STALE_TIMEOUT_MS = 45e3;
|
|
745
|
+
var ORDERBOOK_DEBUG_TOP_LEVELS = 5;
|
|
746
|
+
var AggWebSocket = class {
|
|
747
|
+
constructor(options) {
|
|
748
|
+
this.ws = null;
|
|
749
|
+
// ── Subscription tracking ──
|
|
750
|
+
this.subs = /* @__PURE__ */ new Map();
|
|
751
|
+
this.pendingSubs = /* @__PURE__ */ new Set();
|
|
752
|
+
this.pendingTradeSubs = /* @__PURE__ */ new Set();
|
|
753
|
+
this.pendingResnapshots = /* @__PURE__ */ new Set();
|
|
754
|
+
// ── Post-connect batching (coalesces same-tick sub/unsub calls into one frame) ──
|
|
755
|
+
this.pendingFlushSubs = /* @__PURE__ */ new Set();
|
|
756
|
+
this.pendingFlushUnsubs = /* @__PURE__ */ new Set();
|
|
757
|
+
this.pendingFlushTradeSubs = /* @__PURE__ */ new Set();
|
|
758
|
+
this.pendingFlushTradeUnsubs = /* @__PURE__ */ new Set();
|
|
759
|
+
this.flushScheduled = false;
|
|
760
|
+
// ── Orderbook state per market ──
|
|
761
|
+
this.books = /* @__PURE__ */ new Map();
|
|
762
|
+
/** Markets currently resyncing (awaiting snapshot after seq gap / checksum mismatch). */
|
|
763
|
+
this.resyncing = /* @__PURE__ */ new Set();
|
|
764
|
+
// ── Connection state ──
|
|
765
|
+
this.connecting = false;
|
|
766
|
+
this._connected = false;
|
|
767
|
+
this.reconnectAttempts = 0;
|
|
768
|
+
this.reconnectTimer = null;
|
|
769
|
+
this.staleTimer = null;
|
|
770
|
+
this.lastMessageTime = 0;
|
|
771
|
+
this.destroyed = false;
|
|
772
|
+
var _a, _b;
|
|
773
|
+
this.url = options.url;
|
|
774
|
+
this.callbacks = (_a = options.callbacks) != null ? _a : {};
|
|
775
|
+
this.initialToken = options.initialToken;
|
|
776
|
+
this.enableLogs = (_b = options.enableLogs) != null ? _b : false;
|
|
777
|
+
}
|
|
778
|
+
// ── Public API ──
|
|
779
|
+
get connected() {
|
|
780
|
+
return this._connected;
|
|
781
|
+
}
|
|
782
|
+
/** Get the current orderbook state for an outcome (or null if no snapshot received yet). */
|
|
783
|
+
getBook(outcomeId) {
|
|
784
|
+
var _a;
|
|
785
|
+
return (_a = this.books.get(outcomeId)) != null ? _a : null;
|
|
786
|
+
}
|
|
787
|
+
/** Whether an outcome is currently resyncing (waiting for fresh snapshot). */
|
|
788
|
+
isResyncing(outcomeId) {
|
|
789
|
+
return this.resyncing.has(outcomeId);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Subscribe to a feed by outcome ID.
|
|
793
|
+
* @param outcomeId The venueMarketOutcomeId to subscribe to.
|
|
794
|
+
* @param channel "orderbook" (default) or "trades".
|
|
795
|
+
* Returns an unsubscribe function (ref-counted).
|
|
796
|
+
*/
|
|
797
|
+
subscribe(outcomeId, channel = "orderbook") {
|
|
798
|
+
if (channel === "trades") {
|
|
799
|
+
return this._subscribeTrades(outcomeId);
|
|
800
|
+
}
|
|
801
|
+
let sub = this.subs.get(outcomeId);
|
|
802
|
+
if (!sub) {
|
|
803
|
+
sub = { refCount: 0, tradeRefs: 0 };
|
|
804
|
+
this.subs.set(outcomeId, sub);
|
|
805
|
+
this.pendingSubs.add(outcomeId);
|
|
806
|
+
this.ensureConnected();
|
|
807
|
+
if (this._connected) {
|
|
808
|
+
this.pendingFlushUnsubs.delete(outcomeId);
|
|
809
|
+
this.pendingFlushSubs.add(outcomeId);
|
|
810
|
+
this.pendingSubs.delete(outcomeId);
|
|
811
|
+
this.scheduleFlush();
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
sub.refCount++;
|
|
815
|
+
return () => {
|
|
816
|
+
const s = this.subs.get(outcomeId);
|
|
817
|
+
if (!s) return;
|
|
818
|
+
s.refCount--;
|
|
819
|
+
if (s.refCount <= 0 && s.tradeRefs <= 0) {
|
|
820
|
+
this.subs.delete(outcomeId);
|
|
821
|
+
this.books.delete(outcomeId);
|
|
822
|
+
this.resyncing.delete(outcomeId);
|
|
823
|
+
if (this._connected) {
|
|
824
|
+
if (this.pendingFlushSubs.has(outcomeId)) {
|
|
825
|
+
this.pendingFlushSubs.delete(outcomeId);
|
|
826
|
+
} else {
|
|
827
|
+
this.pendingFlushUnsubs.add(outcomeId);
|
|
828
|
+
this.scheduleFlush();
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
this.pendingSubs.delete(outcomeId);
|
|
832
|
+
this.pendingResnapshots.delete(outcomeId);
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Subscribe to an outcome's trade feed.
|
|
838
|
+
* @deprecated Use `subscribe(outcomeId, "trades")` instead.
|
|
839
|
+
* Returns an unsubscribe function.
|
|
840
|
+
*/
|
|
841
|
+
subscribeTrades(outcomeId) {
|
|
842
|
+
return this._subscribeTrades(outcomeId);
|
|
843
|
+
}
|
|
844
|
+
_subscribeTrades(outcomeId) {
|
|
845
|
+
let sub = this.subs.get(outcomeId);
|
|
846
|
+
if (!sub) {
|
|
847
|
+
sub = { refCount: 0, tradeRefs: 0 };
|
|
848
|
+
this.subs.set(outcomeId, sub);
|
|
849
|
+
}
|
|
850
|
+
if (sub.tradeRefs === 0) {
|
|
851
|
+
this.pendingTradeSubs.add(outcomeId);
|
|
852
|
+
this.ensureConnected();
|
|
853
|
+
if (this._connected) {
|
|
854
|
+
this.pendingFlushTradeUnsubs.delete(outcomeId);
|
|
855
|
+
this.pendingFlushTradeSubs.add(outcomeId);
|
|
856
|
+
this.pendingTradeSubs.delete(outcomeId);
|
|
857
|
+
this.scheduleFlush();
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
sub.tradeRefs++;
|
|
861
|
+
return () => {
|
|
862
|
+
const s = this.subs.get(outcomeId);
|
|
863
|
+
if (!s) return;
|
|
864
|
+
s.tradeRefs--;
|
|
865
|
+
if (s.tradeRefs <= 0) {
|
|
866
|
+
s.tradeRefs = 0;
|
|
867
|
+
if (this._connected) {
|
|
868
|
+
if (this.pendingFlushTradeSubs.has(outcomeId)) {
|
|
869
|
+
this.pendingFlushTradeSubs.delete(outcomeId);
|
|
870
|
+
} else {
|
|
871
|
+
this.pendingFlushTradeUnsubs.add(outcomeId);
|
|
872
|
+
this.scheduleFlush();
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
this.pendingTradeSubs.delete(outcomeId);
|
|
876
|
+
this.pendingResnapshots.delete(outcomeId);
|
|
877
|
+
if (s.refCount <= 0) {
|
|
878
|
+
this.subs.delete(outcomeId);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
/** Send a JWT to authenticate (or re-authenticate) the connection. */
|
|
884
|
+
authenticate(token) {
|
|
885
|
+
if (this._connected) {
|
|
886
|
+
this.send({ action: "authenticate", token });
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/** Request a fresh snapshot for one or more subscribed outcomes. */
|
|
890
|
+
resnapshot(outcomeIdOrIds) {
|
|
891
|
+
const outcomeIds = Array.isArray(outcomeIdOrIds) ? outcomeIdOrIds : [outcomeIdOrIds];
|
|
892
|
+
outcomeIds.forEach((outcomeId) => {
|
|
893
|
+
this.requestResnapshot(outcomeId, true);
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
/** Connect (idempotent). Called automatically on first subscribe. */
|
|
897
|
+
connect() {
|
|
898
|
+
this.ensureConnected();
|
|
899
|
+
}
|
|
900
|
+
/** Disconnect and clean up. Reusable — call connect() to reconnect. */
|
|
901
|
+
disconnect() {
|
|
902
|
+
var _a, _b;
|
|
903
|
+
this.cancelReconnect();
|
|
904
|
+
this.cancelStaleTimer();
|
|
905
|
+
this._connected = false;
|
|
906
|
+
this.connecting = false;
|
|
907
|
+
if (this.ws) {
|
|
908
|
+
this.ws.onclose = null;
|
|
909
|
+
this.ws.close();
|
|
910
|
+
this.ws = null;
|
|
911
|
+
}
|
|
912
|
+
(_b = (_a = this.callbacks).onConnectionStateChange) == null ? void 0 : _b.call(_a, false);
|
|
913
|
+
}
|
|
914
|
+
/** Permanently destroy. Cannot reconnect after this. */
|
|
915
|
+
destroy() {
|
|
916
|
+
this.destroyed = true;
|
|
917
|
+
this.disconnect();
|
|
918
|
+
this.subs.clear();
|
|
919
|
+
this.books.clear();
|
|
920
|
+
this.resyncing.clear();
|
|
921
|
+
this.pendingSubs.clear();
|
|
922
|
+
this.pendingTradeSubs.clear();
|
|
923
|
+
this.pendingResnapshots.clear();
|
|
924
|
+
this.pendingFlushSubs.clear();
|
|
925
|
+
this.pendingFlushUnsubs.clear();
|
|
926
|
+
this.pendingFlushTradeSubs.clear();
|
|
927
|
+
this.pendingFlushTradeUnsubs.clear();
|
|
928
|
+
this.flushScheduled = false;
|
|
929
|
+
}
|
|
930
|
+
/** Update callbacks (e.g., after React re-render). */
|
|
931
|
+
setCallbacks(callbacks) {
|
|
932
|
+
this.callbacks = callbacks;
|
|
933
|
+
}
|
|
934
|
+
// ── Connection management ──
|
|
935
|
+
ensureConnected() {
|
|
936
|
+
var _a;
|
|
937
|
+
if (this.destroyed) return;
|
|
938
|
+
if (((_a = this.ws) == null ? void 0 : _a.readyState) === WebSocket.OPEN || this.connecting) return;
|
|
939
|
+
this.doConnect();
|
|
940
|
+
}
|
|
941
|
+
doConnect() {
|
|
942
|
+
if (this.destroyed) return;
|
|
943
|
+
this.connecting = true;
|
|
944
|
+
const ws = new WebSocket(this.url);
|
|
945
|
+
this.ws = ws;
|
|
946
|
+
ws.onopen = () => {
|
|
947
|
+
this.connecting = false;
|
|
948
|
+
this.reconnectAttempts = 0;
|
|
949
|
+
this.resetStaleTimer();
|
|
950
|
+
if (this.initialToken) {
|
|
951
|
+
this.send({ action: "authenticate", token: this.initialToken });
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
ws.onmessage = (event) => {
|
|
955
|
+
this.lastMessageTime = Date.now();
|
|
956
|
+
this.resetStaleTimer();
|
|
957
|
+
try {
|
|
958
|
+
const raw = JSON.parse(event.data);
|
|
959
|
+
this.handleMessage(raw);
|
|
960
|
+
} catch (e) {
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
ws.onclose = () => {
|
|
964
|
+
var _a, _b;
|
|
965
|
+
this.ws = null;
|
|
966
|
+
this.connecting = false;
|
|
967
|
+
const wasConnected = this._connected;
|
|
968
|
+
this._connected = false;
|
|
969
|
+
this.cancelStaleTimer();
|
|
970
|
+
if (wasConnected) {
|
|
971
|
+
(_b = (_a = this.callbacks).onConnectionStateChange) == null ? void 0 : _b.call(_a, false);
|
|
972
|
+
}
|
|
973
|
+
if (!this.destroyed && this.subs.size > 0) {
|
|
974
|
+
this.scheduleReconnect();
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
ws.onerror = () => {
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
scheduleReconnect() {
|
|
981
|
+
this.cancelReconnect();
|
|
982
|
+
const delay = Math.min(
|
|
983
|
+
RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempts),
|
|
984
|
+
RECONNECT_MAX_MS
|
|
985
|
+
);
|
|
986
|
+
this.reconnectAttempts++;
|
|
987
|
+
this.emitDiagnostic({ type: "reconnect", attempt: this.reconnectAttempts, delayMs: delay });
|
|
988
|
+
this.reconnectTimer = setTimeout(() => {
|
|
989
|
+
this.reconnectTimer = null;
|
|
990
|
+
this.doConnect();
|
|
991
|
+
}, delay);
|
|
992
|
+
}
|
|
993
|
+
cancelReconnect() {
|
|
994
|
+
if (this.reconnectTimer) {
|
|
995
|
+
clearTimeout(this.reconnectTimer);
|
|
996
|
+
this.reconnectTimer = null;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
// ── Staleness detection ──
|
|
1000
|
+
resetStaleTimer() {
|
|
1001
|
+
this.cancelStaleTimer();
|
|
1002
|
+
this.staleTimer = setTimeout(() => {
|
|
1003
|
+
const age = Date.now() - this.lastMessageTime;
|
|
1004
|
+
this.emitDiagnostic({ type: "stale_connection", lastMessageAge: age });
|
|
1005
|
+
if (this.ws) {
|
|
1006
|
+
this.ws.close();
|
|
1007
|
+
}
|
|
1008
|
+
}, STALE_TIMEOUT_MS);
|
|
1009
|
+
}
|
|
1010
|
+
cancelStaleTimer() {
|
|
1011
|
+
if (this.staleTimer) {
|
|
1012
|
+
clearTimeout(this.staleTimer);
|
|
1013
|
+
this.staleTimer = null;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
// ── Message handling ──
|
|
1017
|
+
handleMessage(raw) {
|
|
1018
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1019
|
+
const type = raw.type;
|
|
1020
|
+
switch (type) {
|
|
1021
|
+
case "orderbook_snapshot":
|
|
1022
|
+
this.handleSnapshot(raw);
|
|
1023
|
+
break;
|
|
1024
|
+
case "orderbook_delta":
|
|
1025
|
+
this.handleDelta(raw);
|
|
1026
|
+
break;
|
|
1027
|
+
case "trade":
|
|
1028
|
+
this.handleTrade(raw);
|
|
1029
|
+
break;
|
|
1030
|
+
case "heartbeat":
|
|
1031
|
+
this.handleHeartbeat(raw);
|
|
1032
|
+
break;
|
|
1033
|
+
case "subscribed":
|
|
1034
|
+
(_b = (_a = this.callbacks).onSubscribed) == null ? void 0 : _b.call(_a, raw);
|
|
1035
|
+
break;
|
|
1036
|
+
case "unsubscribed":
|
|
1037
|
+
(_d = (_c = this.callbacks).onUnsubscribed) == null ? void 0 : _d.call(_c, raw);
|
|
1038
|
+
break;
|
|
1039
|
+
case "connected":
|
|
1040
|
+
this.handleConnected(raw);
|
|
1041
|
+
break;
|
|
1042
|
+
case "authenticated":
|
|
1043
|
+
(_f = (_e = this.callbacks).onAuthenticated) == null ? void 0 : _f.call(_e, raw);
|
|
1044
|
+
break;
|
|
1045
|
+
case "market_resolved":
|
|
1046
|
+
this.handleMarketResolved(raw);
|
|
1047
|
+
break;
|
|
1048
|
+
case "error":
|
|
1049
|
+
(_h = (_g = this.callbacks).onError) == null ? void 0 : _h.call(_g, raw);
|
|
1050
|
+
break;
|
|
1051
|
+
case "order_submitted":
|
|
1052
|
+
this.handleOrderSubmitted(raw);
|
|
1053
|
+
break;
|
|
1054
|
+
case "balance_update":
|
|
1055
|
+
this.handleBalanceUpdate(raw);
|
|
1056
|
+
break;
|
|
1057
|
+
case "order_event":
|
|
1058
|
+
(_j = (_i = this.callbacks).onOrderEvent) == null ? void 0 : _j.call(_i, raw);
|
|
1059
|
+
break;
|
|
1060
|
+
case "redeem_event":
|
|
1061
|
+
this.handleRedeemEvent(raw);
|
|
1062
|
+
break;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Server confirmed the connection — gateway has validated our appId and is
|
|
1067
|
+
* ready to accept subscriptions. Now flush all pending subs.
|
|
1068
|
+
*/
|
|
1069
|
+
handleConnected(msg) {
|
|
1070
|
+
var _a, _b, _c, _d;
|
|
1071
|
+
this._connected = true;
|
|
1072
|
+
(_b = (_a = this.callbacks).onConnectionStateChange) == null ? void 0 : _b.call(_a, true);
|
|
1073
|
+
(_d = (_c = this.callbacks).onConnected) == null ? void 0 : _d.call(_c, msg);
|
|
1074
|
+
const orderbookSet = new Set(this.activeOrderbookMarkets());
|
|
1075
|
+
this.pendingSubs.forEach((id) => orderbookSet.add(id));
|
|
1076
|
+
this.pendingFlushSubs.forEach((id) => orderbookSet.add(id));
|
|
1077
|
+
const orderbookIds = Array.from(orderbookSet);
|
|
1078
|
+
if (orderbookIds.length > 0) {
|
|
1079
|
+
this.sendSubscribe(orderbookIds);
|
|
1080
|
+
}
|
|
1081
|
+
this.pendingSubs.clear();
|
|
1082
|
+
this.pendingFlushSubs.clear();
|
|
1083
|
+
this.pendingFlushUnsubs.clear();
|
|
1084
|
+
const tradeSet = new Set(this.activeTradeMarkets());
|
|
1085
|
+
this.pendingTradeSubs.forEach((id) => tradeSet.add(id));
|
|
1086
|
+
this.pendingFlushTradeSubs.forEach((id) => tradeSet.add(id));
|
|
1087
|
+
const tradeIds = Array.from(tradeSet);
|
|
1088
|
+
if (tradeIds.length > 0) {
|
|
1089
|
+
this.send({ action: "subscribe", channel: "trades", outcomeIds: tradeIds });
|
|
1090
|
+
}
|
|
1091
|
+
this.pendingTradeSubs.clear();
|
|
1092
|
+
this.pendingFlushTradeSubs.clear();
|
|
1093
|
+
this.pendingFlushTradeUnsubs.clear();
|
|
1094
|
+
const resnapshotIds = Array.from(this.pendingResnapshots);
|
|
1095
|
+
if (resnapshotIds.length > 0) {
|
|
1096
|
+
this.send({ action: "resnapshot", channel: "orderbook", outcomeIds: resnapshotIds });
|
|
1097
|
+
}
|
|
1098
|
+
this.pendingResnapshots.clear();
|
|
1099
|
+
}
|
|
1100
|
+
handleSnapshot(raw) {
|
|
1101
|
+
var _a, _b;
|
|
1102
|
+
const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
|
|
1103
|
+
const book = snapshotToOrderbook(msg);
|
|
1104
|
+
this.books.set(msg.outcomeId, book);
|
|
1105
|
+
this.resyncing.delete(msg.outcomeId);
|
|
1106
|
+
this.pendingResnapshots.delete(msg.outcomeId);
|
|
1107
|
+
this.debugOrderbook("snapshot_applied", {
|
|
1108
|
+
outcomeId: msg.outcomeId,
|
|
1109
|
+
seq: msg.seq,
|
|
1110
|
+
checksum: msg.checksum,
|
|
1111
|
+
bidsTop: this.summarizeTopLevels(book.bids),
|
|
1112
|
+
asksTop: this.summarizeTopLevels(book.asks)
|
|
1113
|
+
});
|
|
1114
|
+
(_b = (_a = this.callbacks).onSnapshot) == null ? void 0 : _b.call(_a, msg.outcomeId, book);
|
|
1115
|
+
}
|
|
1116
|
+
handleDelta(raw) {
|
|
1117
|
+
var _a, _b;
|
|
1118
|
+
const outcomeId = raw.outcomeId;
|
|
1119
|
+
const current = this.books.get(outcomeId);
|
|
1120
|
+
const deltaTop = {
|
|
1121
|
+
bids: raw.bidChanges.slice(0, ORDERBOOK_DEBUG_TOP_LEVELS).map(([price, size]) => ({
|
|
1122
|
+
price,
|
|
1123
|
+
size
|
|
1124
|
+
})),
|
|
1125
|
+
asks: raw.askChanges.slice(0, ORDERBOOK_DEBUG_TOP_LEVELS).map(([price, size]) => ({
|
|
1126
|
+
price,
|
|
1127
|
+
size
|
|
1128
|
+
}))
|
|
1129
|
+
};
|
|
1130
|
+
this.debugOrderbook("delta_received", {
|
|
1131
|
+
outcomeId,
|
|
1132
|
+
seq: raw.seq,
|
|
1133
|
+
checksum: raw.checksum,
|
|
1134
|
+
hasCurrentBook: Boolean(current),
|
|
1135
|
+
deltaTop
|
|
1136
|
+
});
|
|
1137
|
+
if (!current) {
|
|
1138
|
+
this.debugOrderbook("delta_missing_snapshot", { outcomeId, seq: raw.seq });
|
|
1139
|
+
this.requestResnapshot(outcomeId);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
if (raw.seq <= current.seq) {
|
|
1143
|
+
this.debugOrderbook("delta_stale_ignored", {
|
|
1144
|
+
outcomeId,
|
|
1145
|
+
currentSeq: current.seq,
|
|
1146
|
+
receivedSeq: raw.seq
|
|
1147
|
+
});
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (raw.seq > current.seq + 1) {
|
|
1151
|
+
this.debugOrderbook("delta_seq_gap", {
|
|
1152
|
+
outcomeId,
|
|
1153
|
+
expectedSeq: current.seq + 1,
|
|
1154
|
+
receivedSeq: raw.seq
|
|
1155
|
+
});
|
|
1156
|
+
this.emitDiagnostic({
|
|
1157
|
+
type: "seq_gap",
|
|
1158
|
+
marketId: outcomeId,
|
|
1159
|
+
outcomeId,
|
|
1160
|
+
expected: current.seq + 1,
|
|
1161
|
+
received: raw.seq
|
|
1162
|
+
});
|
|
1163
|
+
this.requestResnapshot(outcomeId);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
|
|
1167
|
+
const newBook = applyOrderbookDelta(current, msg);
|
|
1168
|
+
this.debugOrderbook("delta_applied_locally", {
|
|
1169
|
+
outcomeId,
|
|
1170
|
+
fromSeq: current.seq,
|
|
1171
|
+
toSeq: newBook.seq,
|
|
1172
|
+
beforeTop: {
|
|
1173
|
+
bids: this.summarizeTopLevels(current.bids),
|
|
1174
|
+
asks: this.summarizeTopLevels(current.asks)
|
|
1175
|
+
},
|
|
1176
|
+
afterTop: {
|
|
1177
|
+
bids: this.summarizeTopLevels(newBook.bids),
|
|
1178
|
+
asks: this.summarizeTopLevels(newBook.asks)
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
const computed = computeChecksum(newBook);
|
|
1182
|
+
if (computed !== msg.checksum) {
|
|
1183
|
+
this.debugOrderbook("delta_checksum_mismatch", {
|
|
1184
|
+
outcomeId,
|
|
1185
|
+
seq: msg.seq,
|
|
1186
|
+
expected: msg.checksum,
|
|
1187
|
+
computed
|
|
1188
|
+
});
|
|
1189
|
+
this.emitDiagnostic({
|
|
1190
|
+
type: "checksum_mismatch",
|
|
1191
|
+
marketId: outcomeId,
|
|
1192
|
+
outcomeId,
|
|
1193
|
+
expected: msg.checksum,
|
|
1194
|
+
computed
|
|
1195
|
+
});
|
|
1196
|
+
this.requestResnapshot(outcomeId);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
this.books.set(outcomeId, newBook);
|
|
1200
|
+
this.debugOrderbook("delta_committed", {
|
|
1201
|
+
outcomeId,
|
|
1202
|
+
seq: newBook.seq,
|
|
1203
|
+
checksum: newBook.checksum,
|
|
1204
|
+
bidsTop: this.summarizeTopLevels(newBook.bids),
|
|
1205
|
+
asksTop: this.summarizeTopLevels(newBook.asks)
|
|
1206
|
+
});
|
|
1207
|
+
(_b = (_a = this.callbacks).onDelta) == null ? void 0 : _b.call(_a, outcomeId, newBook);
|
|
1208
|
+
}
|
|
1209
|
+
handleTrade(raw) {
|
|
1210
|
+
var _a, _b;
|
|
1211
|
+
const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
|
|
1212
|
+
(_b = (_a = this.callbacks).onTrade) == null ? void 0 : _b.call(_a, msg);
|
|
1213
|
+
}
|
|
1214
|
+
handleHeartbeat(raw) {
|
|
1215
|
+
var _a, _b;
|
|
1216
|
+
const msg = __spreadProps(__spreadValues({}, raw), { ts: raw.ts / 1e3 });
|
|
1217
|
+
(_b = (_a = this.callbacks).onHeartbeat) == null ? void 0 : _b.call(_a, msg);
|
|
1218
|
+
}
|
|
1219
|
+
handleMarketResolved(raw) {
|
|
1220
|
+
var _a, _b;
|
|
1221
|
+
const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
|
|
1222
|
+
this.books.delete(msg.outcomeId);
|
|
1223
|
+
this.pendingResnapshots.delete(msg.outcomeId);
|
|
1224
|
+
this.resyncing.delete(msg.outcomeId);
|
|
1225
|
+
(_b = (_a = this.callbacks).onMarketResolved) == null ? void 0 : _b.call(_a, msg);
|
|
1226
|
+
}
|
|
1227
|
+
handleOrderSubmitted(raw) {
|
|
1228
|
+
var _a, _b;
|
|
1229
|
+
const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
|
|
1230
|
+
(_b = (_a = this.callbacks).onOrderSubmitted) == null ? void 0 : _b.call(_a, msg);
|
|
1231
|
+
}
|
|
1232
|
+
handleBalanceUpdate(raw) {
|
|
1233
|
+
var _a, _b;
|
|
1234
|
+
const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
|
|
1235
|
+
(_b = (_a = this.callbacks).onBalanceUpdate) == null ? void 0 : _b.call(_a, msg);
|
|
1236
|
+
}
|
|
1237
|
+
handleRedeemEvent(raw) {
|
|
1238
|
+
var _a, _b;
|
|
1239
|
+
const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
|
|
1240
|
+
(_b = (_a = this.callbacks).onRedeemEvent) == null ? void 0 : _b.call(_a, msg);
|
|
1241
|
+
}
|
|
1242
|
+
// ── Resnapshot ──
|
|
1243
|
+
requestResnapshot(outcomeId, force = false) {
|
|
1244
|
+
if (this.resyncing.has(outcomeId) && !force) return;
|
|
1245
|
+
this.resyncing.add(outcomeId);
|
|
1246
|
+
if (this._connected) {
|
|
1247
|
+
this.send({ action: "resnapshot", channel: "orderbook", outcomeIds: [outcomeId] });
|
|
1248
|
+
this.pendingResnapshots.delete(outcomeId);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
this.pendingResnapshots.add(outcomeId);
|
|
1252
|
+
this.ensureConnected();
|
|
1253
|
+
}
|
|
1254
|
+
// ── Helpers ──
|
|
1255
|
+
/**
|
|
1256
|
+
* Schedule a microtask that flushes queued subscribe/unsubscribe frames.
|
|
1257
|
+
* Coalesces multiple same-tick sub/unsub calls into at most one frame per
|
|
1258
|
+
* channel+action pair.
|
|
1259
|
+
*/
|
|
1260
|
+
scheduleFlush() {
|
|
1261
|
+
if (this.flushScheduled) return;
|
|
1262
|
+
this.flushScheduled = true;
|
|
1263
|
+
queueMicrotask(() => {
|
|
1264
|
+
this.flushScheduled = false;
|
|
1265
|
+
if (!this._connected) {
|
|
1266
|
+
this.pendingFlushSubs.clear();
|
|
1267
|
+
this.pendingFlushUnsubs.clear();
|
|
1268
|
+
this.pendingFlushTradeSubs.clear();
|
|
1269
|
+
this.pendingFlushTradeUnsubs.clear();
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
this.flushPending();
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
flushPending() {
|
|
1276
|
+
for (const id of [...this.pendingFlushSubs]) {
|
|
1277
|
+
if (this.pendingFlushUnsubs.has(id)) {
|
|
1278
|
+
this.pendingFlushSubs.delete(id);
|
|
1279
|
+
this.pendingFlushUnsubs.delete(id);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
if (this.pendingFlushSubs.size > 0) {
|
|
1283
|
+
this.sendSubscribe(Array.from(this.pendingFlushSubs));
|
|
1284
|
+
this.pendingFlushSubs.clear();
|
|
1285
|
+
}
|
|
1286
|
+
if (this.pendingFlushUnsubs.size > 0) {
|
|
1287
|
+
this.send({
|
|
1288
|
+
action: "unsubscribe",
|
|
1289
|
+
channel: "orderbook",
|
|
1290
|
+
outcomeIds: Array.from(this.pendingFlushUnsubs)
|
|
1291
|
+
});
|
|
1292
|
+
this.pendingFlushUnsubs.clear();
|
|
1293
|
+
}
|
|
1294
|
+
for (const id of [...this.pendingFlushTradeSubs]) {
|
|
1295
|
+
if (this.pendingFlushTradeUnsubs.has(id)) {
|
|
1296
|
+
this.pendingFlushTradeSubs.delete(id);
|
|
1297
|
+
this.pendingFlushTradeUnsubs.delete(id);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (this.pendingFlushTradeSubs.size > 0) {
|
|
1301
|
+
this.send({
|
|
1302
|
+
action: "subscribe",
|
|
1303
|
+
channel: "trades",
|
|
1304
|
+
outcomeIds: Array.from(this.pendingFlushTradeSubs)
|
|
1305
|
+
});
|
|
1306
|
+
this.pendingFlushTradeSubs.clear();
|
|
1307
|
+
}
|
|
1308
|
+
if (this.pendingFlushTradeUnsubs.size > 0) {
|
|
1309
|
+
this.send({
|
|
1310
|
+
action: "unsubscribe",
|
|
1311
|
+
channel: "trades",
|
|
1312
|
+
outcomeIds: Array.from(this.pendingFlushTradeUnsubs)
|
|
1313
|
+
});
|
|
1314
|
+
this.pendingFlushTradeUnsubs.clear();
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
sendSubscribe(outcomeIds) {
|
|
1318
|
+
this.send({ action: "subscribe", channel: "orderbook", outcomeIds });
|
|
1319
|
+
}
|
|
1320
|
+
send(msg) {
|
|
1321
|
+
var _a;
|
|
1322
|
+
if (((_a = this.ws) == null ? void 0 : _a.readyState) === WebSocket.OPEN) {
|
|
1323
|
+
this.ws.send(JSON.stringify(msg));
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
activeOrderbookMarkets() {
|
|
1327
|
+
const markets = [];
|
|
1328
|
+
this.subs.forEach((sub, id) => {
|
|
1329
|
+
if (sub.refCount > 0) markets.push(id);
|
|
1330
|
+
});
|
|
1331
|
+
return markets;
|
|
1332
|
+
}
|
|
1333
|
+
activeTradeMarkets() {
|
|
1334
|
+
const markets = [];
|
|
1335
|
+
this.subs.forEach((sub, id) => {
|
|
1336
|
+
if (sub.tradeRefs > 0) markets.push(id);
|
|
1337
|
+
});
|
|
1338
|
+
return markets;
|
|
1339
|
+
}
|
|
1340
|
+
emitDiagnostic(event) {
|
|
1341
|
+
var _a, _b;
|
|
1342
|
+
(_b = (_a = this.callbacks).onDiagnostics) == null ? void 0 : _b.call(_a, event);
|
|
1343
|
+
}
|
|
1344
|
+
summarizeTopLevels(levels) {
|
|
1345
|
+
return levels.slice(0, ORDERBOOK_DEBUG_TOP_LEVELS).map((level) => ({ price: level.price, size: level.size }));
|
|
1346
|
+
}
|
|
1347
|
+
debugOrderbook(event, payload) {
|
|
1348
|
+
if (!this.isOrderbookDebugEnabled()) return;
|
|
1349
|
+
console.debug(`[AggWebSocket][orderbook] ${event}`, payload);
|
|
1350
|
+
}
|
|
1351
|
+
isOrderbookDebugEnabled() {
|
|
1352
|
+
return this.enableLogs;
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
// src/client.ts
|
|
1357
|
+
var COOKIE_REFRESH_DELIVERY = "cookie-refresh";
|
|
1358
|
+
var isUserProfile = (user) => {
|
|
1359
|
+
return "accounts" in user && "wallets" in user && "avatarUrl" in user;
|
|
1360
|
+
};
|
|
1361
|
+
var createPersistedHintUserProfile = (input) => {
|
|
1362
|
+
const normalizedWalletAddress = typeof input.walletAddress === "string" && input.walletAddress.trim().length > 0 ? input.walletAddress : null;
|
|
1363
|
+
return {
|
|
1364
|
+
id: input.id,
|
|
1365
|
+
username: input.username,
|
|
1366
|
+
avatarUrl: input.avatarUrl,
|
|
1367
|
+
externalId: null,
|
|
1368
|
+
isLocationBlocked: false,
|
|
1369
|
+
accounts: normalizedWalletAddress ? [
|
|
1370
|
+
{
|
|
1371
|
+
type: "siwe" /* siwe */,
|
|
1372
|
+
provider: "wallet" /* wallet */,
|
|
1373
|
+
providerAccountId: normalizedWalletAddress
|
|
1374
|
+
}
|
|
1375
|
+
] : [],
|
|
1376
|
+
wallets: [],
|
|
1377
|
+
venueAccounts: []
|
|
1378
|
+
};
|
|
1379
|
+
};
|
|
1380
|
+
var normalizeSessionUser = (user, currentUser) => {
|
|
1381
|
+
if (!user) return void 0;
|
|
1382
|
+
if (isUserProfile(user)) return user;
|
|
1383
|
+
if ((currentUser == null ? void 0 : currentUser.id) === user.id) return currentUser;
|
|
1384
|
+
return createPersistedHintUserProfile({
|
|
1385
|
+
id: user.id,
|
|
1386
|
+
username: null,
|
|
1387
|
+
avatarUrl: null,
|
|
1388
|
+
walletAddress: null
|
|
1389
|
+
});
|
|
1390
|
+
};
|
|
1391
|
+
var mapChartResolution = (resolution) => {
|
|
1392
|
+
switch (resolution) {
|
|
1393
|
+
case void 0:
|
|
1394
|
+
return void 0;
|
|
1395
|
+
case "1m":
|
|
1396
|
+
return "1";
|
|
1397
|
+
case "5m":
|
|
1398
|
+
return "5";
|
|
1399
|
+
case "1h":
|
|
1400
|
+
return "60";
|
|
1401
|
+
case "1d":
|
|
1402
|
+
return "1D";
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
var AUTH_PATHS = /* @__PURE__ */ new Set([
|
|
1406
|
+
"/auth/start",
|
|
1407
|
+
"/auth/verify",
|
|
1408
|
+
"/auth/token/refresh",
|
|
1409
|
+
"/auth/token/exchange",
|
|
1410
|
+
"/auth/sign-out"
|
|
1411
|
+
]);
|
|
1412
|
+
var AggClient = class {
|
|
1413
|
+
constructor(options) {
|
|
1414
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1415
|
+
this._authStatus = "unauthenticated";
|
|
1416
|
+
this.authChannel = null;
|
|
1417
|
+
/** In-flight refresh promise — serializes concurrent 401 retries so only one refresh runs. */
|
|
1418
|
+
this.pendingRefresh = null;
|
|
1419
|
+
var _a, _b;
|
|
1420
|
+
if (!options.appId && !options.adminKey) {
|
|
1421
|
+
throw new Error("AggClient requires at least one of appId or adminKey");
|
|
1422
|
+
}
|
|
1423
|
+
this.appId = options.appId;
|
|
1424
|
+
this.adminKey = options.adminKey;
|
|
1425
|
+
this.apiKey = options.apiKey;
|
|
1426
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
1427
|
+
this.wsUrl = options.wsUrl;
|
|
1428
|
+
this.authOptions = options.auth;
|
|
1429
|
+
this.storageKey = `agg_auth_${(_a = this.appId) != null ? _a : "default"}`;
|
|
1430
|
+
this.authDelivery = (_b = options.authDelivery) != null ? _b : "body";
|
|
1431
|
+
this.shouldPersistSession = options.persistSession !== false;
|
|
1432
|
+
this.restoreSession();
|
|
1433
|
+
this.initAuthChannel();
|
|
1434
|
+
}
|
|
1435
|
+
withAuthPayload(payload) {
|
|
1436
|
+
var _a, _b, _c;
|
|
1437
|
+
if (typeof payload.earlyAccessCode === "string" && payload.earlyAccessCode.trim().length > 0) {
|
|
1438
|
+
return payload;
|
|
1439
|
+
}
|
|
1440
|
+
const earlyAccessCode = (_c = (_b = (_a = this.authOptions) == null ? void 0 : _a.getEarlyAccessCode) == null ? void 0 : _b.call(_a)) == null ? void 0 : _c.trim();
|
|
1441
|
+
if (!earlyAccessCode) {
|
|
1442
|
+
return payload;
|
|
1443
|
+
}
|
|
1444
|
+
return __spreadProps(__spreadValues({}, payload), {
|
|
1445
|
+
earlyAccessCode
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
// --- Session persistence (tokens are NEVER persisted — only a sanitized UI hint) ---
|
|
1449
|
+
restoreSession() {
|
|
1450
|
+
var _a, _b, _c;
|
|
1451
|
+
if (!this.shouldPersistSession || typeof globalThis.localStorage === "undefined") return;
|
|
1452
|
+
try {
|
|
1453
|
+
const raw = localStorage.getItem(this.storageKey);
|
|
1454
|
+
if (!raw) return;
|
|
1455
|
+
const data = JSON.parse(raw);
|
|
1456
|
+
if ("accessToken" in data) {
|
|
1457
|
+
localStorage.removeItem(this.storageKey);
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
const snapshot = data;
|
|
1461
|
+
if (((_a = snapshot.authHint) == null ? void 0 : _a.wasAuthenticated) && ((_b = snapshot.user) == null ? void 0 : _b.id)) {
|
|
1462
|
+
this.currentUser = createPersistedHintUserProfile({
|
|
1463
|
+
id: snapshot.user.id,
|
|
1464
|
+
username: snapshot.user.username,
|
|
1465
|
+
avatarUrl: snapshot.user.avatarUrl,
|
|
1466
|
+
walletAddress: (_c = snapshot.user.walletAddress) != null ? _c : null
|
|
1467
|
+
});
|
|
1468
|
+
this._authStatus = "unknown";
|
|
1469
|
+
}
|
|
1470
|
+
} catch (e) {
|
|
1471
|
+
localStorage.removeItem(this.storageKey);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
persistSession() {
|
|
1475
|
+
var _a;
|
|
1476
|
+
if (!this.shouldPersistSession || typeof globalThis.localStorage === "undefined") return;
|
|
1477
|
+
if (this.accessToken && this.currentUser) {
|
|
1478
|
+
const snapshot = {
|
|
1479
|
+
user: {
|
|
1480
|
+
id: this.currentUser.id,
|
|
1481
|
+
username: this.currentUser.username,
|
|
1482
|
+
avatarUrl: this.currentUser.avatarUrl,
|
|
1483
|
+
walletAddress: (_a = getWalletAddressFromUserProfile(this.currentUser)) != null ? _a : null
|
|
1484
|
+
},
|
|
1485
|
+
authHint: { wasAuthenticated: true }
|
|
1486
|
+
};
|
|
1487
|
+
localStorage.setItem(this.storageKey, JSON.stringify(snapshot));
|
|
1488
|
+
} else {
|
|
1489
|
+
localStorage.removeItem(this.storageKey);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
// --- Cross-tab auth sync via BroadcastChannel ---
|
|
1493
|
+
initAuthChannel() {
|
|
1494
|
+
var _a;
|
|
1495
|
+
if (typeof globalThis.BroadcastChannel === "undefined") return;
|
|
1496
|
+
try {
|
|
1497
|
+
this.authChannel = new BroadcastChannel(`agg_auth_${(_a = this.appId) != null ? _a : "default"}`);
|
|
1498
|
+
this.authChannel.onmessage = (event) => {
|
|
1499
|
+
var _a2;
|
|
1500
|
+
const { type } = (_a2 = event.data) != null ? _a2 : {};
|
|
1501
|
+
if (type === "logout") {
|
|
1502
|
+
this.accessToken = void 0;
|
|
1503
|
+
this.refreshTokenValue = void 0;
|
|
1504
|
+
this.currentUser = void 0;
|
|
1505
|
+
this._authStatus = "unauthenticated";
|
|
1506
|
+
this.persistSession();
|
|
1507
|
+
this.notifyListeners();
|
|
1508
|
+
} else if (type === "login") {
|
|
1509
|
+
this.refreshAccessToken().then(() => this.getCurrentUser()).catch(() => {
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
} catch (e) {
|
|
1514
|
+
this.authChannel = null;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
/** Release resources (BroadcastChannel, etc.). Safe to call multiple times. */
|
|
1518
|
+
destroy() {
|
|
1519
|
+
var _a;
|
|
1520
|
+
(_a = this.authChannel) == null ? void 0 : _a.close();
|
|
1521
|
+
this.authChannel = null;
|
|
1522
|
+
}
|
|
1523
|
+
// --- Core HTTP helper ---
|
|
1524
|
+
/**
|
|
1525
|
+
* Generic HTTP method with automatic header injection and 401 retry.
|
|
1526
|
+
* Attaches `x-app-id`, `Authorization: Bearer`, and `x-admin-key` headers as appropriate.
|
|
1527
|
+
* On 401, attempts a single token refresh and retries the request.
|
|
1528
|
+
* @param path - API path (e.g. `/venue-events`)
|
|
1529
|
+
* @param init - Fetch init options, plus optional `query` object for URL search params
|
|
1530
|
+
*/
|
|
1531
|
+
request(path, init) {
|
|
1532
|
+
return __async(this, null, function* () {
|
|
1533
|
+
const response = yield this.rawFetch(path, init);
|
|
1534
|
+
if (response.status === 401 && this.accessToken && !AUTH_PATHS.has(path)) {
|
|
1535
|
+
try {
|
|
1536
|
+
yield this.refreshWithDedup();
|
|
1537
|
+
const retryResponse = yield this.rawFetch(path, init);
|
|
1538
|
+
if (!retryResponse.ok) {
|
|
1539
|
+
return this.throwResponseError(retryResponse);
|
|
1540
|
+
}
|
|
1541
|
+
return retryResponse.json();
|
|
1542
|
+
} catch (e) {
|
|
1543
|
+
this.clearAccessToken();
|
|
1544
|
+
return this.throwResponseError(response);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (!response.ok) {
|
|
1548
|
+
return this.throwResponseError(response);
|
|
1549
|
+
}
|
|
1550
|
+
return response.json();
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
rawFetch(path, init) {
|
|
1554
|
+
return __async(this, null, function* () {
|
|
1555
|
+
const headers = {
|
|
1556
|
+
"Content-Type": "application/json"
|
|
1557
|
+
};
|
|
1558
|
+
if (this.appId) {
|
|
1559
|
+
headers["x-app-id"] = this.appId;
|
|
1560
|
+
}
|
|
1561
|
+
if (this.adminKey) {
|
|
1562
|
+
headers["x-admin-key"] = this.adminKey;
|
|
1563
|
+
}
|
|
1564
|
+
if (this.apiKey) {
|
|
1565
|
+
headers["x-app-api-key"] = this.apiKey;
|
|
1566
|
+
}
|
|
1567
|
+
if (this.accessToken) {
|
|
1568
|
+
headers["authorization"] = `Bearer ${this.accessToken}`;
|
|
1569
|
+
}
|
|
1570
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
1571
|
+
if (init == null ? void 0 : init.query) {
|
|
1572
|
+
for (const [key, value] of Object.entries(init.query)) {
|
|
1573
|
+
if (Array.isArray(value)) {
|
|
1574
|
+
for (const v of value) {
|
|
1575
|
+
v !== void 0 && url.searchParams.append(key, v == null ? void 0 : v.toString());
|
|
1576
|
+
}
|
|
1577
|
+
} else {
|
|
1578
|
+
value !== void 0 && url.searchParams.set(key, value == null ? void 0 : value.toString());
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
return fetch(url, __spreadProps(__spreadValues({}, init), {
|
|
1583
|
+
headers: __spreadValues(__spreadValues({}, headers), init == null ? void 0 : init.headers)
|
|
1584
|
+
}));
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
throwResponseError(response) {
|
|
1588
|
+
return __async(this, null, function* () {
|
|
1589
|
+
let message = response.status === 404 ? "Market not found" : `AGG API error: ${response.status} ${response.statusText}`;
|
|
1590
|
+
let code;
|
|
1591
|
+
let retryable;
|
|
1592
|
+
try {
|
|
1593
|
+
const body = yield response.json();
|
|
1594
|
+
if (typeof body.message === "string" && body.message.length > 0) {
|
|
1595
|
+
message = body.message;
|
|
1596
|
+
}
|
|
1597
|
+
if (typeof body.code === "string" && body.code.length > 0) {
|
|
1598
|
+
code = body.code;
|
|
1599
|
+
}
|
|
1600
|
+
if (typeof body.retryable === "boolean") {
|
|
1601
|
+
retryable = body.retryable;
|
|
1602
|
+
}
|
|
1603
|
+
} catch (e) {
|
|
1604
|
+
}
|
|
1605
|
+
const error = new Error(message);
|
|
1606
|
+
error.status = response.status;
|
|
1607
|
+
if (code) error.code = code;
|
|
1608
|
+
if (retryable !== void 0) error.retryable = retryable;
|
|
1609
|
+
throw error;
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Deduplicates concurrent refresh attempts — if multiple requests hit 401 at the same time,
|
|
1614
|
+
* only one refresh call is made and the rest wait for it.
|
|
1615
|
+
*/
|
|
1616
|
+
refreshWithDedup() {
|
|
1617
|
+
if (!this.pendingRefresh) {
|
|
1618
|
+
this.pendingRefresh = this.refreshAccessToken().finally(() => {
|
|
1619
|
+
this.pendingRefresh = null;
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
return this.pendingRefresh;
|
|
1623
|
+
}
|
|
1624
|
+
buildAuthRequestInit(init, options) {
|
|
1625
|
+
if (this.authDelivery !== COOKIE_REFRESH_DELIVERY) {
|
|
1626
|
+
return init;
|
|
1627
|
+
}
|
|
1628
|
+
const headers = __spreadValues({}, init.headers);
|
|
1629
|
+
if ((options == null ? void 0 : options.includeDeliveryHeader) !== false) {
|
|
1630
|
+
headers["x-auth-delivery"] = COOKIE_REFRESH_DELIVERY;
|
|
1631
|
+
}
|
|
1632
|
+
return __spreadProps(__spreadValues({}, init), {
|
|
1633
|
+
credentials: "include",
|
|
1634
|
+
headers
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
buildUserAuthResult(response, user) {
|
|
1638
|
+
return response.refreshToken === void 0 ? { accessToken: response.accessToken, user } : {
|
|
1639
|
+
accessToken: response.accessToken,
|
|
1640
|
+
refreshToken: response.refreshToken,
|
|
1641
|
+
user
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
performSignOutRequest() {
|
|
1645
|
+
return __async(this, null, function* () {
|
|
1646
|
+
yield this.request(
|
|
1647
|
+
"/auth/sign-out",
|
|
1648
|
+
this.buildAuthRequestInit({ method: "POST", body: "{}" }, { includeDeliveryHeader: false })
|
|
1649
|
+
);
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
// --- Auth & session state ---
|
|
1653
|
+
/** Set the user access token (for future user-tier auth). */
|
|
1654
|
+
setAccessToken(token) {
|
|
1655
|
+
this.accessToken = token;
|
|
1656
|
+
this._authStatus = "authenticated";
|
|
1657
|
+
this.persistSession();
|
|
1658
|
+
this.notifyListeners();
|
|
1659
|
+
}
|
|
1660
|
+
/** Set the current auth session tokens and optionally seed the user profile. */
|
|
1661
|
+
setSession(session) {
|
|
1662
|
+
const wasAuthenticated = this.isAuthenticated;
|
|
1663
|
+
this.accessToken = session.accessToken;
|
|
1664
|
+
if ("refreshToken" in session) {
|
|
1665
|
+
this.refreshTokenValue = session.refreshToken;
|
|
1666
|
+
}
|
|
1667
|
+
if (session.user !== void 0) {
|
|
1668
|
+
this.currentUser = normalizeSessionUser(session.user, this.currentUser);
|
|
1669
|
+
}
|
|
1670
|
+
this._authStatus = "authenticated";
|
|
1671
|
+
this.persistSession();
|
|
1672
|
+
this.notifyListeners();
|
|
1673
|
+
if (!wasAuthenticated) {
|
|
1674
|
+
this.broadcastAuth("login");
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
/** Clear the user access token, refresh token, and user info. Broadcasts cross-tab logout. */
|
|
1678
|
+
clearAccessToken() {
|
|
1679
|
+
this.accessToken = void 0;
|
|
1680
|
+
this.refreshTokenValue = void 0;
|
|
1681
|
+
this.currentUser = void 0;
|
|
1682
|
+
this._authStatus = "unauthenticated";
|
|
1683
|
+
this.persistSession();
|
|
1684
|
+
this.notifyListeners();
|
|
1685
|
+
this.broadcastAuth("logout");
|
|
1686
|
+
}
|
|
1687
|
+
broadcastAuth(type) {
|
|
1688
|
+
var _a;
|
|
1689
|
+
try {
|
|
1690
|
+
(_a = this.authChannel) == null ? void 0 : _a.postMessage({ type });
|
|
1691
|
+
} catch (e) {
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
/** Current auth lifecycle status. */
|
|
1695
|
+
get authStatus() {
|
|
1696
|
+
return this._authStatus;
|
|
1697
|
+
}
|
|
1698
|
+
/** Whether a user access token is currently set. */
|
|
1699
|
+
get isAuthenticated() {
|
|
1700
|
+
return !!this.accessToken;
|
|
1701
|
+
}
|
|
1702
|
+
/** Primary wallet address derived from the current user profile. */
|
|
1703
|
+
get address() {
|
|
1704
|
+
return getWalletAddressFromUserProfile(this.currentUser);
|
|
1705
|
+
}
|
|
1706
|
+
/** Subscribe to auth state changes. Returns an unsubscribe function. */
|
|
1707
|
+
onAuthStateChange(listener) {
|
|
1708
|
+
this.listeners.add(listener);
|
|
1709
|
+
return () => this.listeners.delete(listener);
|
|
1710
|
+
}
|
|
1711
|
+
notifyListeners() {
|
|
1712
|
+
const authenticated = this.isAuthenticated;
|
|
1713
|
+
for (const listener of Array.from(this.listeners)) {
|
|
1714
|
+
listener(authenticated);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
/** Get the currently authenticated user, if any. */
|
|
1718
|
+
get user() {
|
|
1719
|
+
return this.currentUser;
|
|
1720
|
+
}
|
|
1721
|
+
// --- SIWE auth ---
|
|
1722
|
+
/** Build an EIP-4361 SIWE message string (pure formatting, no crypto). */
|
|
1723
|
+
buildSiweMessage(params) {
|
|
1724
|
+
var _a, _b;
|
|
1725
|
+
const chainId = (_a = params.chainId) != null ? _a : 1;
|
|
1726
|
+
const issuedAt = (_b = params.issuedAt) != null ? _b : (/* @__PURE__ */ new Date()).toISOString();
|
|
1727
|
+
let message = `${params.domain} wants you to sign in with your Ethereum account:
|
|
1728
|
+
${params.address}`;
|
|
1729
|
+
if (params.statement) {
|
|
1730
|
+
message += `
|
|
1731
|
+
|
|
1732
|
+
${params.statement}`;
|
|
1733
|
+
}
|
|
1734
|
+
message += `
|
|
1735
|
+
|
|
1736
|
+
URI: ${params.uri}
|
|
1737
|
+
Version: 1
|
|
1738
|
+
Chain ID: ${chainId}
|
|
1739
|
+
Nonce: ${params.nonce}
|
|
1740
|
+
Issued At: ${issuedAt}`;
|
|
1741
|
+
return message;
|
|
1742
|
+
}
|
|
1743
|
+
/** Build a SIWS message string for Solana wallet sign-in (pure formatting, no crypto). */
|
|
1744
|
+
buildSiwsMessage(params) {
|
|
1745
|
+
var _a, _b;
|
|
1746
|
+
const chainId = (_a = params.chainId) != null ? _a : "mainnet";
|
|
1747
|
+
const issuedAt = (_b = params.issuedAt) != null ? _b : (/* @__PURE__ */ new Date()).toISOString();
|
|
1748
|
+
let message = `${params.domain} wants you to sign in with your Solana account:
|
|
1749
|
+
${params.address}`;
|
|
1750
|
+
if (params.statement) {
|
|
1751
|
+
message += `
|
|
1752
|
+
|
|
1753
|
+
${params.statement}`;
|
|
1754
|
+
}
|
|
1755
|
+
message += `
|
|
1756
|
+
|
|
1757
|
+
URI: ${params.uri}
|
|
1758
|
+
Version: 1
|
|
1759
|
+
Chain ID: ${chainId}
|
|
1760
|
+
Nonce: ${params.nonce}
|
|
1761
|
+
Issued At: ${issuedAt}`;
|
|
1762
|
+
return message;
|
|
1763
|
+
}
|
|
1764
|
+
/** Verify a SIWE/SIWS signature with the API, store the token + user. */
|
|
1765
|
+
verify(args) {
|
|
1766
|
+
return __async(this, null, function* () {
|
|
1767
|
+
const res = yield this.request(
|
|
1768
|
+
"/auth/verify",
|
|
1769
|
+
this.buildAuthRequestInit({
|
|
1770
|
+
method: "POST",
|
|
1771
|
+
body: JSON.stringify(this.withAuthPayload(args))
|
|
1772
|
+
})
|
|
1773
|
+
);
|
|
1774
|
+
this.setSession({
|
|
1775
|
+
accessToken: res.accessToken,
|
|
1776
|
+
refreshToken: res.refreshToken,
|
|
1777
|
+
user: res.user
|
|
1778
|
+
});
|
|
1779
|
+
try {
|
|
1780
|
+
const user = yield this.getCurrentUser();
|
|
1781
|
+
return this.buildUserAuthResult(res, user);
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
this.clearAccessToken();
|
|
1784
|
+
throw error;
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
/** Backwards-compatible alias for `verify()`. */
|
|
1789
|
+
verifySignIn(args) {
|
|
1790
|
+
return __async(this, null, function* () {
|
|
1791
|
+
return this.verify(args);
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
/** Initiate an auth flow (SIWE nonce, OAuth redirect URL, or magic link). */
|
|
1795
|
+
authStart(body) {
|
|
1796
|
+
return __async(this, null, function* () {
|
|
1797
|
+
return this.request("/auth/start", {
|
|
1798
|
+
method: "POST",
|
|
1799
|
+
body: JSON.stringify(this.withAuthPayload(body)),
|
|
1800
|
+
credentials: "include"
|
|
1801
|
+
});
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1804
|
+
/** Backwards-compatible alias for `authStart()`. */
|
|
1805
|
+
startAuth(body) {
|
|
1806
|
+
return __async(this, null, function* () {
|
|
1807
|
+
return this.authStart(body);
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Start an account-link flow. Requires the caller to already be signed in — sends
|
|
1812
|
+
* the bearer automatically. Response shape:
|
|
1813
|
+
* - OAuth providers → `{ type: "redirect", url }`; redirect the browser to `url`.
|
|
1814
|
+
* - Email → `{ type: "magic_link", success: true }`; check the destination inbox.
|
|
1815
|
+
*
|
|
1816
|
+
* After the provider callback runs, the browser lands back on the app's `redirectUrl`
|
|
1817
|
+
* with a `link_confirm_token` query param. Feed that token to `linkAccountConfirm()`
|
|
1818
|
+
* to persist the Account row.
|
|
1819
|
+
*
|
|
1820
|
+
* Wallet linking (siwe/siws) is not supported yet — it requires a different message
|
|
1821
|
+
* binding protocol than sign-in to be safe against signature phishing.
|
|
1822
|
+
*/
|
|
1823
|
+
linkAccount(body) {
|
|
1824
|
+
return __async(this, null, function* () {
|
|
1825
|
+
return this.request(
|
|
1826
|
+
"/users/me/link-account/start",
|
|
1827
|
+
this.buildAuthRequestInit({ method: "POST", body: JSON.stringify(body) })
|
|
1828
|
+
);
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Exchange a `link_confirm_token` for a linked `Account` row. The server matches
|
|
1833
|
+
* the current bearer's `principalId` to the token's `principalId` before writing —
|
|
1834
|
+
* this is the final ownership check.
|
|
1835
|
+
*/
|
|
1836
|
+
linkAccountConfirm(token) {
|
|
1837
|
+
return __async(this, null, function* () {
|
|
1838
|
+
return this.request(
|
|
1839
|
+
"/users/me/link-account/confirm",
|
|
1840
|
+
this.buildAuthRequestInit({ method: "POST", body: JSON.stringify({ token }) })
|
|
1841
|
+
);
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
/** Exchange a one-time auth code (from OAuth/magic-link callback) for tokens. */
|
|
1845
|
+
exchangeAuthCode(code) {
|
|
1846
|
+
return __async(this, null, function* () {
|
|
1847
|
+
const res = yield this.request(
|
|
1848
|
+
"/auth/token/exchange",
|
|
1849
|
+
this.buildAuthRequestInit({
|
|
1850
|
+
method: "POST",
|
|
1851
|
+
body: JSON.stringify({ code })
|
|
1852
|
+
})
|
|
1853
|
+
);
|
|
1854
|
+
this.setSession({
|
|
1855
|
+
accessToken: res.accessToken,
|
|
1856
|
+
refreshToken: res.refreshToken,
|
|
1857
|
+
user: res.user
|
|
1858
|
+
});
|
|
1859
|
+
try {
|
|
1860
|
+
const user = yield this.getCurrentUser();
|
|
1861
|
+
return this.buildUserAuthResult(res, user);
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
this.clearAccessToken();
|
|
1864
|
+
throw error;
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
/** Refresh the access token using the stored refresh token. */
|
|
1869
|
+
refreshAccessToken() {
|
|
1870
|
+
return __async(this, null, function* () {
|
|
1871
|
+
if (this.authDelivery !== COOKIE_REFRESH_DELIVERY && !this.refreshTokenValue) {
|
|
1872
|
+
throw new Error("No refresh token available");
|
|
1873
|
+
}
|
|
1874
|
+
const res = yield this.request(
|
|
1875
|
+
"/auth/token/refresh",
|
|
1876
|
+
this.buildAuthRequestInit({
|
|
1877
|
+
method: "POST",
|
|
1878
|
+
body: JSON.stringify(
|
|
1879
|
+
this.authDelivery === COOKIE_REFRESH_DELIVERY ? {} : { refreshToken: this.refreshTokenValue }
|
|
1880
|
+
)
|
|
1881
|
+
})
|
|
1882
|
+
);
|
|
1883
|
+
this.setSession({
|
|
1884
|
+
accessToken: res.accessToken,
|
|
1885
|
+
refreshToken: res.refreshToken,
|
|
1886
|
+
user: res.user
|
|
1887
|
+
});
|
|
1888
|
+
return res.accessToken;
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
/** Get the current user's profile. */
|
|
1892
|
+
getCurrentUser() {
|
|
1893
|
+
return __async(this, null, function* () {
|
|
1894
|
+
const user = yield this.request("/users/me");
|
|
1895
|
+
this.currentUser = user;
|
|
1896
|
+
this.persistSession();
|
|
1897
|
+
this.notifyListeners();
|
|
1898
|
+
return user;
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
/** Update the current user (e.g. username). Requires currentUser.id; call getCurrentUser() first if needed. */
|
|
1902
|
+
updateUser(data) {
|
|
1903
|
+
return __async(this, null, function* () {
|
|
1904
|
+
var _a;
|
|
1905
|
+
if (!((_a = this.currentUser) == null ? void 0 : _a.id)) {
|
|
1906
|
+
const profile2 = yield this.getCurrentUser();
|
|
1907
|
+
if (!(profile2 == null ? void 0 : profile2.id)) throw new Error("Not authenticated");
|
|
1908
|
+
}
|
|
1909
|
+
const profile = yield this.request("/users/me", {
|
|
1910
|
+
method: "PATCH",
|
|
1911
|
+
body: JSON.stringify(data)
|
|
1912
|
+
});
|
|
1913
|
+
this.currentUser = profile;
|
|
1914
|
+
this.persistSession();
|
|
1915
|
+
this.notifyListeners();
|
|
1916
|
+
return profile;
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
/** Link a partner-provided external user ID assertion to the current user. */
|
|
1920
|
+
linkExternalId(params) {
|
|
1921
|
+
return __async(this, null, function* () {
|
|
1922
|
+
const profile = yield this.request("/users/me/external-id", {
|
|
1923
|
+
method: "POST",
|
|
1924
|
+
body: JSON.stringify(params)
|
|
1925
|
+
});
|
|
1926
|
+
this.currentUser = profile;
|
|
1927
|
+
this.persistSession();
|
|
1928
|
+
this.notifyListeners();
|
|
1929
|
+
return profile;
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
/** Create a presigned avatar upload URL for the current user. */
|
|
1933
|
+
createAvatarUploadUrl(contentType) {
|
|
1934
|
+
return __async(this, null, function* () {
|
|
1935
|
+
return this.request(
|
|
1936
|
+
"/users/me/avatar",
|
|
1937
|
+
{
|
|
1938
|
+
method: "POST",
|
|
1939
|
+
body: JSON.stringify({ contentType })
|
|
1940
|
+
}
|
|
1941
|
+
);
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
/** Disconnect a linked auth provider from the current user. */
|
|
1945
|
+
disconnectAccount(provider) {
|
|
1946
|
+
return __async(this, null, function* () {
|
|
1947
|
+
return this.request(`/users/me/accounts/${provider}`, {
|
|
1948
|
+
method: "DELETE",
|
|
1949
|
+
body: JSON.stringify({})
|
|
1950
|
+
});
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
/** Sign out — revoke all tokens server-side and clear local session. */
|
|
1954
|
+
signOut() {
|
|
1955
|
+
return __async(this, null, function* () {
|
|
1956
|
+
if (this.accessToken) {
|
|
1957
|
+
try {
|
|
1958
|
+
yield this.performSignOutRequest();
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
if (this.authDelivery === COOKIE_REFRESH_DELIVERY && error.status === 401) {
|
|
1961
|
+
try {
|
|
1962
|
+
yield this.refreshAccessToken();
|
|
1963
|
+
yield this.performSignOutRequest();
|
|
1964
|
+
} catch (e) {
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
this.refreshTokenValue = void 0;
|
|
1970
|
+
this.clearAccessToken();
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
/** Get user holdings (positions) for a venue. Uses backend to resolve venue-specific identifiers. */
|
|
1974
|
+
getUserHoldings(params) {
|
|
1975
|
+
return __async(this, null, function* () {
|
|
1976
|
+
const { venue, venueMarketId, venueEventId, cursor } = params;
|
|
1977
|
+
const query = { venue };
|
|
1978
|
+
if (venueMarketId != null) query.venueMarketId = venueMarketId;
|
|
1979
|
+
if (venueEventId != null) query.venueEventId = venueEventId;
|
|
1980
|
+
if (cursor != null) query.cursor = cursor;
|
|
1981
|
+
return this.request("/trading/holdings", {
|
|
1982
|
+
query
|
|
1983
|
+
});
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
/** List trade-executor orders for the authenticated user. */
|
|
1987
|
+
getOrders() {
|
|
1988
|
+
return __async(this, arguments, function* (params = {}) {
|
|
1989
|
+
const query = {};
|
|
1990
|
+
if (params.userId) query.userId = params.userId;
|
|
1991
|
+
if (params.status) query.status = params.status;
|
|
1992
|
+
if (params.limit != null) query.limit = String(params.limit);
|
|
1993
|
+
if (params.offset != null) query.offset = String(params.offset);
|
|
1994
|
+
return this.request("/orders", {
|
|
1995
|
+
query: Object.keys(query).length ? query : void 0
|
|
1996
|
+
});
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
/** List execution orders for the authenticated user (cursor pagination). */
|
|
2000
|
+
getExecutionOrders() {
|
|
2001
|
+
return __async(this, arguments, function* (params = {}) {
|
|
2002
|
+
const query = {};
|
|
2003
|
+
if (params.orderId) query.orderId = params.orderId;
|
|
2004
|
+
if (params.status) query.status = params.status;
|
|
2005
|
+
if (params.cursor) query.cursor = params.cursor;
|
|
2006
|
+
if (params.limit != null) query.limit = String(params.limit);
|
|
2007
|
+
return this.request("/execution/orders", {
|
|
2008
|
+
query: Object.keys(query).length ? query : void 0
|
|
2009
|
+
});
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
/** Unified user activity feed (trades, deposits, withdrawals, bridges, wallet ops). */
|
|
2013
|
+
getUserActivity() {
|
|
2014
|
+
return __async(this, arguments, function* (params = {}) {
|
|
2015
|
+
const query = {};
|
|
2016
|
+
if (params.type) query.type = params.type;
|
|
2017
|
+
if (params.cursor) query.cursor = params.cursor;
|
|
2018
|
+
if (params.limit != null) query.limit = String(params.limit);
|
|
2019
|
+
return this.request("/users/activity", {
|
|
2020
|
+
query: Object.keys(query).length ? query : void 0
|
|
2021
|
+
});
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
/** List execution positions for the authenticated user (cursor pagination). */
|
|
2025
|
+
getExecutionPositions() {
|
|
2026
|
+
return __async(this, arguments, function* (params = {}) {
|
|
2027
|
+
const query = {};
|
|
2028
|
+
if (params.cursor) query.cursor = params.cursor;
|
|
2029
|
+
if (params.limit != null) query.limit = String(params.limit);
|
|
2030
|
+
if (params.status) query.status = params.status;
|
|
2031
|
+
return this.request("/execution/positions", {
|
|
2032
|
+
query: Object.keys(query).length ? query : void 0
|
|
2033
|
+
});
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
/** Get managed wallet balances, including per-chain cash balances and per-venue position balances. */
|
|
2037
|
+
getManagedBalances() {
|
|
2038
|
+
return __async(this, null, function* () {
|
|
2039
|
+
return this.request("/execution/balances");
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
/** Cancel a pending managed execution by order id. */
|
|
2043
|
+
cancelManagedOrder(orderId, options) {
|
|
2044
|
+
return __async(this, null, function* () {
|
|
2045
|
+
return this.request(
|
|
2046
|
+
`/execution/orders/${encodeURIComponent(orderId)}/cancel`,
|
|
2047
|
+
{
|
|
2048
|
+
method: "POST",
|
|
2049
|
+
body: JSON.stringify({}),
|
|
2050
|
+
signal: options == null ? void 0 : options.signal
|
|
2051
|
+
}
|
|
2052
|
+
);
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
// --- Venue events & venue markets (primary) ---
|
|
2056
|
+
/** List venue events with optional filters. Requires appId or admin auth. Supports cursor-based pagination. */
|
|
2057
|
+
getVenueEvents(options) {
|
|
2058
|
+
return __async(this, null, function* () {
|
|
2059
|
+
var _a;
|
|
2060
|
+
const query = {};
|
|
2061
|
+
if ((options == null ? void 0 : options.venues) && options.venues.length > 0) query.venues = options.venues;
|
|
2062
|
+
if ((_a = options == null ? void 0 : options.search) == null ? void 0 : _a.trim()) query.search = options.search.trim();
|
|
2063
|
+
if ((options == null ? void 0 : options.categoryIds) && options.categoryIds.length > 0)
|
|
2064
|
+
query.categoryIds = options.categoryIds;
|
|
2065
|
+
if ((options == null ? void 0 : options.matchStatus) && options.matchStatus.length > 0)
|
|
2066
|
+
query.matchStatus = options.matchStatus;
|
|
2067
|
+
if ((options == null ? void 0 : options.status) && options.status.length > 0) query.status = options.status;
|
|
2068
|
+
if (options == null ? void 0 : options.sortBy) query.sortBy = options.sortBy;
|
|
2069
|
+
if (options == null ? void 0 : options.sortDir) query.sortDir = options.sortDir;
|
|
2070
|
+
if ((options == null ? void 0 : options.limit) != null) query.limit = String(options.limit);
|
|
2071
|
+
if (options == null ? void 0 : options.cursor) query.cursor = options.cursor;
|
|
2072
|
+
if ((options == null ? void 0 : options.minYesPrice) != null) query.minYesPrice = String(options.minYesPrice);
|
|
2073
|
+
if ((options == null ? void 0 : options.maxYesPrice) != null) query.maxYesPrice = String(options.maxYesPrice);
|
|
2074
|
+
if (options == null ? void 0 : options.endDateFrom) query.endDateFrom = options.endDateFrom;
|
|
2075
|
+
return this.request("/venue-events", {
|
|
2076
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
2077
|
+
});
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
/** Get a single venue event by ID. Requires appId or admin auth. */
|
|
2081
|
+
getVenueEventById(id, options) {
|
|
2082
|
+
return __async(this, null, function* () {
|
|
2083
|
+
return this.request(`/venue-events/${encodeURIComponent(id)}`, {
|
|
2084
|
+
signal: options == null ? void 0 : options.signal
|
|
2085
|
+
});
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
/** List venue markets with optional filters. Requires appId or admin auth. Supports cursor-based pagination. */
|
|
2089
|
+
getVenueMarkets(options) {
|
|
2090
|
+
return __async(this, null, function* () {
|
|
2091
|
+
var _a;
|
|
2092
|
+
const query = {};
|
|
2093
|
+
if (options == null ? void 0 : options.venue) query.venue = options.venue;
|
|
2094
|
+
if (options == null ? void 0 : options.venueEventId) query.venueEventId = options.venueEventId;
|
|
2095
|
+
if ((_a = options == null ? void 0 : options.search) == null ? void 0 : _a.trim()) query.search = options.search.trim();
|
|
2096
|
+
if (options == null ? void 0 : options.matchStatus) query.matchStatus = options.matchStatus;
|
|
2097
|
+
if (options == null ? void 0 : options.status) query.status = options.status;
|
|
2098
|
+
if ((options == null ? void 0 : options.categoryIds) && options.categoryIds.length > 0)
|
|
2099
|
+
query.categoryIds = options.categoryIds;
|
|
2100
|
+
if ((options == null ? void 0 : options.limit) != null) query.limit = String(options.limit);
|
|
2101
|
+
if (options == null ? void 0 : options.cursor) query.cursor = options.cursor;
|
|
2102
|
+
if (options == null ? void 0 : options.sortBy) query.sortBy = options.sortBy;
|
|
2103
|
+
if (options == null ? void 0 : options.sortDir) query.sortDir = options.sortDir;
|
|
2104
|
+
return this.request("/venue-markets", {
|
|
2105
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
2106
|
+
});
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
/** Get categories with cursor-based pagination. Requires appId. */
|
|
2110
|
+
getCategories(options) {
|
|
2111
|
+
return __async(this, null, function* () {
|
|
2112
|
+
const query = {};
|
|
2113
|
+
if ((options == null ? void 0 : options.limit) != null) query.limit = String(options.limit);
|
|
2114
|
+
if (options == null ? void 0 : options.cursor) query.cursor = options.cursor;
|
|
2115
|
+
return this.request("/categories", {
|
|
2116
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
2117
|
+
});
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
/** Get per-app UI config (disabled venues + category presets). Requires appId. */
|
|
2121
|
+
getAppConfig(init) {
|
|
2122
|
+
return __async(this, null, function* () {
|
|
2123
|
+
return this.request("/app/config", init);
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
/** Search events or markets by query string. Supports cursor-based pagination. */
|
|
2127
|
+
search(params) {
|
|
2128
|
+
return __async(this, null, function* () {
|
|
2129
|
+
const query = {
|
|
2130
|
+
q: params.q,
|
|
2131
|
+
type: params.type
|
|
2132
|
+
};
|
|
2133
|
+
if (params.categoryIds && params.categoryIds.length > 0) query.categoryIds = params.categoryIds;
|
|
2134
|
+
if (params.limit != null) query.limit = String(params.limit);
|
|
2135
|
+
if (params.cursor) query.cursor = params.cursor;
|
|
2136
|
+
return this.request("/search", {
|
|
2137
|
+
query
|
|
2138
|
+
});
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
// --- Chart data ---
|
|
2142
|
+
/** Get the canonical TradingView-style bar series for a single venue market outcome. */
|
|
2143
|
+
getChartBars(params, options) {
|
|
2144
|
+
return __async(this, null, function* () {
|
|
2145
|
+
const resolution = mapChartResolution(params.resolution);
|
|
2146
|
+
const query = {
|
|
2147
|
+
venueMarketOutcomeId: params.venueMarketOutcomeId,
|
|
2148
|
+
to: String(params.to)
|
|
2149
|
+
};
|
|
2150
|
+
if (resolution) query.resolution = resolution;
|
|
2151
|
+
if (params.from != null) query.from = String(params.from);
|
|
2152
|
+
if (params.countBack != null) query.countBack = String(params.countBack);
|
|
2153
|
+
return this.request("/charts/bars", {
|
|
2154
|
+
query,
|
|
2155
|
+
signal: options == null ? void 0 : options.signal
|
|
2156
|
+
});
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
// --- Batch orderbooks ---
|
|
2160
|
+
/** Get live per-venue orderbooks for the explicit venueMarketIds requested. */
|
|
2161
|
+
getOrderbooks(params, options) {
|
|
2162
|
+
return __async(this, null, function* () {
|
|
2163
|
+
const query = {
|
|
2164
|
+
venueMarketIds: params.venueMarketIds
|
|
2165
|
+
};
|
|
2166
|
+
if (params.depth != null) query.depth = String(params.depth);
|
|
2167
|
+
return this.request("/orderbooks", {
|
|
2168
|
+
query,
|
|
2169
|
+
signal: options == null ? void 0 : options.signal
|
|
2170
|
+
});
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
getMidpoints(paramsOrVenueMarketIds, options) {
|
|
2174
|
+
return __async(this, null, function* () {
|
|
2175
|
+
const venueMarketIds = Array.isArray(paramsOrVenueMarketIds) ? paramsOrVenueMarketIds : paramsOrVenueMarketIds.venueMarketIds;
|
|
2176
|
+
const query = { venueMarketIds };
|
|
2177
|
+
return this.request("/midpoints", {
|
|
2178
|
+
query,
|
|
2179
|
+
signal: options == null ? void 0 : options.signal
|
|
2180
|
+
});
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
/** Get a single outcome-level orderbook from the engine. */
|
|
2184
|
+
getOutcomeOrderbook(outcomeId, options) {
|
|
2185
|
+
return __async(this, null, function* () {
|
|
2186
|
+
const query = {};
|
|
2187
|
+
if ((options == null ? void 0 : options.depth) != null) query.depth = String(options.depth);
|
|
2188
|
+
try {
|
|
2189
|
+
return yield this.request(`/orderbook/outcome/${outcomeId}`, {
|
|
2190
|
+
query,
|
|
2191
|
+
signal: options == null ? void 0 : options.signal
|
|
2192
|
+
});
|
|
2193
|
+
} catch (err) {
|
|
2194
|
+
if ((err == null ? void 0 : err.status) === 404) return null;
|
|
2195
|
+
throw err;
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
// --- Market data / orderbook ---
|
|
2200
|
+
/**
|
|
2201
|
+
* Compute optimal order routing across venues via the MILP solver.
|
|
2202
|
+
* Returns a user-scoped quote with per-venue fills and verification timing.
|
|
2203
|
+
* Deprecated aliases are accepted for source compatibility during the
|
|
2204
|
+
* outcome-level migration window.
|
|
2205
|
+
*/
|
|
2206
|
+
getSmartRoute(params, options) {
|
|
2207
|
+
return __async(this, null, function* () {
|
|
2208
|
+
var _a, _b;
|
|
2209
|
+
const venueMarketOutcomeId = (_b = (_a = params.venueMarketOutcomeId) != null ? _a : params.venueMarketId) != null ? _b : params.outcomeId;
|
|
2210
|
+
if (!venueMarketOutcomeId) {
|
|
2211
|
+
throw new Error("getSmartRoute requires venueMarketOutcomeId");
|
|
2212
|
+
}
|
|
2213
|
+
const query = {};
|
|
2214
|
+
if (params.maxSpend != null) query.maxSpend = String(params.maxSpend);
|
|
2215
|
+
if (params.sellShares != null) query.sellShares = String(params.sellShares);
|
|
2216
|
+
if (params.chainBalances) query.chainBalances = JSON.stringify(params.chainBalances);
|
|
2217
|
+
if (params.slipCapBps != null) query.slipCapBps = String(params.slipCapBps);
|
|
2218
|
+
if (params.compareVenues) query.compareVenues = "true";
|
|
2219
|
+
if (params.tradeSide) query.side = params.tradeSide;
|
|
2220
|
+
const response = yield this.request(
|
|
2221
|
+
`/orderbook/${encodeURIComponent(venueMarketOutcomeId)}/route`,
|
|
2222
|
+
{ query, signal: options == null ? void 0 : options.signal }
|
|
2223
|
+
);
|
|
2224
|
+
return params.side ? __spreadProps(__spreadValues({}, response), { side: params.side }) : response;
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Create an AggWebSocket instance configured with this client's appId and optional auth token.
|
|
2229
|
+
* Requires `wsUrl` to be set in the client options.
|
|
2230
|
+
*/
|
|
2231
|
+
createWebSocket(callbacks, options) {
|
|
2232
|
+
var _a;
|
|
2233
|
+
if (!this.wsUrl) {
|
|
2234
|
+
throw new Error("AggClient.createWebSocket() requires wsUrl to be set in client options");
|
|
2235
|
+
}
|
|
2236
|
+
const url = new URL(this.wsUrl);
|
|
2237
|
+
if (this.appId) url.searchParams.set("appId", this.appId);
|
|
2238
|
+
return new AggWebSocket({
|
|
2239
|
+
url: url.toString(),
|
|
2240
|
+
callbacks,
|
|
2241
|
+
initialToken: this.accessToken,
|
|
2242
|
+
enableLogs: (_a = options == null ? void 0 : options.enableLogs) != null ? _a : false
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
/** Request a quote for a managed trade. Returns quoteId (2-min TTL), execution steps, and splits. */
|
|
2246
|
+
quoteManaged(params) {
|
|
2247
|
+
return __async(this, null, function* () {
|
|
2248
|
+
return this.request("/execution/quote", {
|
|
2249
|
+
method: "POST",
|
|
2250
|
+
body: JSON.stringify(params)
|
|
2251
|
+
});
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
/** Execute a previously quoted managed trade. Returns pending order IDs. */
|
|
2255
|
+
executeManaged(params) {
|
|
2256
|
+
return __async(this, null, function* () {
|
|
2257
|
+
return this.request("/execution/fill", {
|
|
2258
|
+
method: "POST",
|
|
2259
|
+
body: JSON.stringify(params)
|
|
2260
|
+
});
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
/** Redeem resolved winning positions by venue market outcome ids. */
|
|
2264
|
+
redeem(body) {
|
|
2265
|
+
return __async(this, null, function* () {
|
|
2266
|
+
return this.request("/execution/redeem", {
|
|
2267
|
+
method: "POST",
|
|
2268
|
+
body: JSON.stringify(body)
|
|
2269
|
+
});
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
/** Withdraw funds from managed wallets to an external address. */
|
|
2273
|
+
withdrawManaged(params) {
|
|
2274
|
+
return __async(this, null, function* () {
|
|
2275
|
+
return this.request("/execution/withdraw", {
|
|
2276
|
+
method: "POST",
|
|
2277
|
+
body: JSON.stringify(params)
|
|
2278
|
+
});
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
/** Trigger an on-chain balance sync for all tracked tokens across all chains. */
|
|
2282
|
+
syncManagedBalances() {
|
|
2283
|
+
return __async(this, null, function* () {
|
|
2284
|
+
return this.request("/execution/sync-balances", {
|
|
2285
|
+
method: "POST",
|
|
2286
|
+
body: JSON.stringify({})
|
|
2287
|
+
});
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
/** Get managed wallet deposit addresses (EVM + Solana). Returns 202 shape while provisioning. */
|
|
2291
|
+
getDepositAddresses() {
|
|
2292
|
+
return __async(this, null, function* () {
|
|
2293
|
+
return this.request("/execution/deposit-addresses");
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
/** Get open positions grouped by matched market with per-venue breakdown. */
|
|
2297
|
+
getPositions(params) {
|
|
2298
|
+
return __async(this, null, function* () {
|
|
2299
|
+
const query = {};
|
|
2300
|
+
if (params == null ? void 0 : params.cursor) query.cursor = params.cursor;
|
|
2301
|
+
if ((params == null ? void 0 : params.limit) != null) query.limit = String(params.limit);
|
|
2302
|
+
if (params == null ? void 0 : params.status) query.status = params.status;
|
|
2303
|
+
return this.request("/execution/positions", {
|
|
2304
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
2305
|
+
});
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
// --- Ramp ---
|
|
2309
|
+
/** Get crypto purchase quotes. */
|
|
2310
|
+
getRampQuotes(params) {
|
|
2311
|
+
return __async(this, null, function* () {
|
|
2312
|
+
return this.request("/ramp/quotes", {
|
|
2313
|
+
method: "POST",
|
|
2314
|
+
body: JSON.stringify(params)
|
|
2315
|
+
});
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
/** Create a ramp widget session for checkout. */
|
|
2319
|
+
createRampSession(params) {
|
|
2320
|
+
return __async(this, null, function* () {
|
|
2321
|
+
return this.request("/ramp/session", {
|
|
2322
|
+
method: "POST",
|
|
2323
|
+
body: JSON.stringify(params)
|
|
2324
|
+
});
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
/** Initiate KYC verification for a venue. Returns a KYC URL to redirect the user to. */
|
|
2328
|
+
initiateKyc(venue, redirectUri) {
|
|
2329
|
+
return __async(this, null, function* () {
|
|
2330
|
+
return this.request(`/kyc/${venue}`, {
|
|
2331
|
+
method: "POST",
|
|
2332
|
+
body: JSON.stringify({ redirectUri })
|
|
2333
|
+
});
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
};
|
|
2337
|
+
|
|
2338
|
+
// src/best-split.ts
|
|
2339
|
+
var computeBestSplitsByAmount2 = computeBestSplitsByAmount;
|
|
2340
|
+
|
|
2341
|
+
// src/errors.ts
|
|
2342
|
+
var TurnstileChallengeError = class extends Error {
|
|
2343
|
+
constructor(siteKey) {
|
|
2344
|
+
super("Bot protection challenge required. Render Turnstile widget and retry.");
|
|
2345
|
+
this.name = "TurnstileChallengeError";
|
|
2346
|
+
this.siteKey = siteKey;
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
|
|
2350
|
+
// src/candle-builder.ts
|
|
2351
|
+
var INTERVAL_SECS = {
|
|
2352
|
+
"1m": 60,
|
|
2353
|
+
"5m": 300,
|
|
2354
|
+
"1h": 3600,
|
|
2355
|
+
"1d": 86400
|
|
2356
|
+
};
|
|
2357
|
+
var ALL_INTERVALS = ["1m", "5m", "1h", "1d"];
|
|
2358
|
+
var MAX_CLOSED = 500;
|
|
2359
|
+
var CandleBuilder = class {
|
|
2360
|
+
constructor() {
|
|
2361
|
+
this.forming = /* @__PURE__ */ new Map();
|
|
2362
|
+
this.closedMap = /* @__PURE__ */ new Map();
|
|
2363
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
2364
|
+
this._midpoint = null;
|
|
2365
|
+
this.dirty = false;
|
|
2366
|
+
this.scheduled = false;
|
|
2367
|
+
for (const interval of ALL_INTERVALS) {
|
|
2368
|
+
this.closedMap.set(interval, []);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
/** Feed a midpoint (from orderbook delta). Updates O/H/L/C for all intervals. */
|
|
2372
|
+
addMidpoint(midpoint, timestampSecs) {
|
|
2373
|
+
if (!Number.isFinite(midpoint) || !Number.isFinite(timestampSecs)) return;
|
|
2374
|
+
this._midpoint = midpoint;
|
|
2375
|
+
for (const interval of ALL_INTERVALS) {
|
|
2376
|
+
this.updateCandle(interval, midpoint, timestampSecs);
|
|
2377
|
+
}
|
|
2378
|
+
this.markDirty();
|
|
2379
|
+
}
|
|
2380
|
+
/** Feed a trade. Updates volume + refines H/L/C from trade price. */
|
|
2381
|
+
addTrade(price, size, timestampSecs) {
|
|
2382
|
+
if (!Number.isFinite(price) || !Number.isFinite(size) || !Number.isFinite(timestampSecs))
|
|
2383
|
+
return;
|
|
2384
|
+
for (const interval of ALL_INTERVALS) {
|
|
2385
|
+
this.updateCandle(interval, price, timestampSecs, size);
|
|
2386
|
+
}
|
|
2387
|
+
this.markDirty();
|
|
2388
|
+
}
|
|
2389
|
+
/** Current forming candle for an interval. */
|
|
2390
|
+
getForming(interval) {
|
|
2391
|
+
var _a;
|
|
2392
|
+
return (_a = this.forming.get(interval)) != null ? _a : null;
|
|
2393
|
+
}
|
|
2394
|
+
/** Closed candles for an interval (ring buffer, max 500, newest last). */
|
|
2395
|
+
getClosed(interval) {
|
|
2396
|
+
var _a;
|
|
2397
|
+
return (_a = this.closedMap.get(interval)) != null ? _a : [];
|
|
2398
|
+
}
|
|
2399
|
+
/** Current midpoint (latest value fed). */
|
|
2400
|
+
get midpoint() {
|
|
2401
|
+
return this._midpoint;
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Subscribe to state changes. Returns unsubscribe function.
|
|
2405
|
+
* BATCHED: callbacks fire at most once per rAF frame (~16ms).
|
|
2406
|
+
* In non-browser environments, falls back to 16ms setTimeout.
|
|
2407
|
+
*/
|
|
2408
|
+
onChange(listener) {
|
|
2409
|
+
this.listeners.add(listener);
|
|
2410
|
+
return () => {
|
|
2411
|
+
this.listeners.delete(listener);
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
/** Clear all state (on reconnect). */
|
|
2415
|
+
reset() {
|
|
2416
|
+
this.forming.clear();
|
|
2417
|
+
for (const interval of ALL_INTERVALS) {
|
|
2418
|
+
this.closedMap.set(interval, []);
|
|
2419
|
+
}
|
|
2420
|
+
this._midpoint = null;
|
|
2421
|
+
this.dirty = false;
|
|
2422
|
+
}
|
|
2423
|
+
/** Clear all state AND unsubscribe all listeners. Called on store eviction. */
|
|
2424
|
+
dispose() {
|
|
2425
|
+
this.reset();
|
|
2426
|
+
this.listeners.clear();
|
|
2427
|
+
}
|
|
2428
|
+
updateCandle(interval, price, timestampSecs, tradeSize) {
|
|
2429
|
+
const secs = INTERVAL_SECS[interval];
|
|
2430
|
+
const openTime = Math.floor(timestampSecs / secs) * secs;
|
|
2431
|
+
const current = this.forming.get(interval);
|
|
2432
|
+
if (current && current.openTime !== openTime) {
|
|
2433
|
+
const closed = this.closedMap.get(interval);
|
|
2434
|
+
closed.push(__spreadProps(__spreadValues({}, current), { isFinal: true }));
|
|
2435
|
+
if (closed.length > MAX_CLOSED) closed.shift();
|
|
2436
|
+
this.forming.set(interval, {
|
|
2437
|
+
openTime,
|
|
2438
|
+
o: price,
|
|
2439
|
+
h: price,
|
|
2440
|
+
l: price,
|
|
2441
|
+
c: price,
|
|
2442
|
+
v: tradeSize != null ? tradeSize : 0,
|
|
2443
|
+
tradeCount: tradeSize != null ? 1 : 0,
|
|
2444
|
+
isFinal: false
|
|
2445
|
+
});
|
|
2446
|
+
} else if (!current) {
|
|
2447
|
+
this.forming.set(interval, {
|
|
2448
|
+
openTime,
|
|
2449
|
+
o: price,
|
|
2450
|
+
h: price,
|
|
2451
|
+
l: price,
|
|
2452
|
+
c: price,
|
|
2453
|
+
v: tradeSize != null ? tradeSize : 0,
|
|
2454
|
+
tradeCount: tradeSize != null ? 1 : 0,
|
|
2455
|
+
isFinal: false
|
|
2456
|
+
});
|
|
2457
|
+
} else {
|
|
2458
|
+
current.h = Math.max(current.h, price);
|
|
2459
|
+
current.l = Math.min(current.l, price);
|
|
2460
|
+
current.c = price;
|
|
2461
|
+
if (tradeSize != null) {
|
|
2462
|
+
current.v += tradeSize;
|
|
2463
|
+
current.tradeCount += 1;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
markDirty() {
|
|
2468
|
+
this.dirty = true;
|
|
2469
|
+
if (this.scheduled) return;
|
|
2470
|
+
this.scheduled = true;
|
|
2471
|
+
const tick = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 16);
|
|
2472
|
+
tick(() => {
|
|
2473
|
+
this.scheduled = false;
|
|
2474
|
+
if (this.dirty) {
|
|
2475
|
+
this.dirty = false;
|
|
2476
|
+
this.listeners.forEach((l) => l());
|
|
2477
|
+
}
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
|
|
2482
|
+
// src/market-aggregator.ts
|
|
2483
|
+
function mergeCandles(candles) {
|
|
2484
|
+
const valid = candles.filter((c2) => c2 != null);
|
|
2485
|
+
if (valid.length === 0) return null;
|
|
2486
|
+
if (valid.length === 1) return __spreadValues({}, valid[0]);
|
|
2487
|
+
const totalVolume = valid.reduce((sum, c2) => sum + c2.v, 0);
|
|
2488
|
+
let o;
|
|
2489
|
+
let c;
|
|
2490
|
+
if (totalVolume > 0) {
|
|
2491
|
+
o = valid.reduce((sum, candle) => sum + candle.o * candle.v, 0) / totalVolume;
|
|
2492
|
+
c = valid.reduce((sum, candle) => sum + candle.c * candle.v, 0) / totalVolume;
|
|
2493
|
+
} else {
|
|
2494
|
+
o = valid.reduce((sum, candle) => sum + candle.o, 0) / valid.length;
|
|
2495
|
+
c = valid.reduce((sum, candle) => sum + candle.c, 0) / valid.length;
|
|
2496
|
+
}
|
|
2497
|
+
return {
|
|
2498
|
+
openTime: valid[0].openTime,
|
|
2499
|
+
o,
|
|
2500
|
+
h: Math.max(...valid.map((candle) => candle.h)),
|
|
2501
|
+
l: Math.min(...valid.map((candle) => candle.l)),
|
|
2502
|
+
c,
|
|
2503
|
+
v: totalVolume,
|
|
2504
|
+
tradeCount: valid.reduce((sum, candle) => sum + candle.tradeCount, 0),
|
|
2505
|
+
isFinal: valid.every((candle) => candle.isFinal)
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
function mergeClosedCandles(candleArrays) {
|
|
2509
|
+
const byTime = /* @__PURE__ */ new Map();
|
|
2510
|
+
for (const arr of candleArrays) {
|
|
2511
|
+
for (const candle of arr) {
|
|
2512
|
+
const group = byTime.get(candle.openTime);
|
|
2513
|
+
if (group) {
|
|
2514
|
+
group.push(candle);
|
|
2515
|
+
} else {
|
|
2516
|
+
byTime.set(candle.openTime, [candle]);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
const merged = [];
|
|
2521
|
+
for (const [, group] of byTime) {
|
|
2522
|
+
const result = mergeCandles(group);
|
|
2523
|
+
if (result) merged.push(result);
|
|
2524
|
+
}
|
|
2525
|
+
merged.sort((a, b) => a.openTime - b.openTime);
|
|
2526
|
+
return merged;
|
|
2527
|
+
}
|
|
2528
|
+
function aggregateMidpoint(midpoints) {
|
|
2529
|
+
const valid = midpoints.filter((m) => m != null && Number.isFinite(m));
|
|
2530
|
+
if (valid.length === 0) return null;
|
|
2531
|
+
return valid.reduce((sum, m) => sum + m, 0) / valid.length;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// src/index.ts
|
|
2535
|
+
function createAggClient(options) {
|
|
2536
|
+
return new AggClient(options);
|
|
2537
|
+
}
|
|
2538
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2539
|
+
0 && (module.exports = {
|
|
2540
|
+
AccountProvider,
|
|
2541
|
+
AccountType,
|
|
2542
|
+
AggClient,
|
|
2543
|
+
AggWebSocket,
|
|
2544
|
+
CONFIRMED_MATCH_STATUSES,
|
|
2545
|
+
CandleBuilder,
|
|
2546
|
+
Chain,
|
|
2547
|
+
IMAGE_SIZES,
|
|
2548
|
+
ImageSize,
|
|
2549
|
+
MarketStatus,
|
|
2550
|
+
MatchStatus,
|
|
2551
|
+
MatchType,
|
|
2552
|
+
OrderStatus,
|
|
2553
|
+
TradeSide,
|
|
2554
|
+
TurnstileChallengeError,
|
|
2555
|
+
VENUES,
|
|
2556
|
+
Venue,
|
|
2557
|
+
aggregateMidpoint,
|
|
2558
|
+
applyOrderbookDelta,
|
|
2559
|
+
computeBestSplitsByAmount,
|
|
2560
|
+
computeChecksum,
|
|
2561
|
+
createAggClient,
|
|
2562
|
+
enumGuard,
|
|
2563
|
+
formatMarketQuestion,
|
|
2564
|
+
formatOutcomeLabel,
|
|
2565
|
+
getWalletAddressFromUserProfile,
|
|
2566
|
+
hasShape,
|
|
2567
|
+
isEmail,
|
|
2568
|
+
isEnum,
|
|
2569
|
+
isFiniteNonNeg,
|
|
2570
|
+
isNonEmptyString,
|
|
2571
|
+
mergeCandles,
|
|
2572
|
+
mergeClosedCandles,
|
|
2573
|
+
normalizeVenueMarketCluster,
|
|
2574
|
+
optimizedImageUrl,
|
|
2575
|
+
parse,
|
|
2576
|
+
parseEmail,
|
|
2577
|
+
parseEmailStrict,
|
|
2578
|
+
safeParse,
|
|
2579
|
+
snapshotToOrderbook,
|
|
2580
|
+
sortVenues
|
|
2581
|
+
});
|