@agg-build/sdk 4.32.0 → 4.34.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 +15 -0
- package/dist/{agg-websocket-ClGhhROZ.mjs → agg-websocket-C2neMzGT.mjs} +117 -4
- package/dist/agg-websocket-C2neMzGT.mjs.map +1 -0
- package/dist/{agg-websocket-CVZ7laJ4.js → agg-websocket-MYZiMRXn.js} +116 -3
- package/dist/{agg-websocket-DsEtBkLk.d.ts → agg-websocket-yR0BZ4yb.d.mts} +3820 -3533
- package/dist/agg-websocket-yR0BZ4yb.d.mts.map +1 -0
- package/dist/{agg-websocket-DsEtBkLk.d.mts → agg-websocket-yR0BZ4yb.d.ts} +3820 -3533
- package/dist/agg-websocket-yR0BZ4yb.d.ts.map +1 -0
- package/dist/browser.d.mts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/browser.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +569 -100
- package/dist/index.mjs +568 -101
- package/dist/index.mjs.map +1 -1
- package/dist/market-data.d.mts +1 -1
- package/dist/market-data.d.ts +1 -1
- package/dist/market-data.js +1 -1
- package/dist/market-data.mjs +1 -1
- package/package.json +1 -1
- package/dist/agg-websocket-ClGhhROZ.mjs.map +0 -1
- package/dist/agg-websocket-DsEtBkLk.d.mts.map +0 -1
- package/dist/agg-websocket-DsEtBkLk.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_agg_websocket = require("./agg-websocket-
|
|
2
|
+
const require_agg_websocket = require("./agg-websocket-MYZiMRXn.js");
|
|
3
3
|
const require_candle_builder = require("./candle-builder-BULYdxB4.js");
|
|
4
4
|
//#region ../common/src/enums/venue.ts
|
|
5
5
|
/** Use these constants instead of hardcoding "kalshi" or "polymarket". */
|
|
@@ -782,24 +782,6 @@ const isBalanceRefillWithinDailyCap = (minimumRaw, refillAmountRaw, dailyCapRaw,
|
|
|
782
782
|
return BigInt(minimumRaw) <= maximum && BigInt(refillAmountRaw) <= maximum;
|
|
783
783
|
};
|
|
784
784
|
//#endregion
|
|
785
|
-
//#region src/retry-after.ts
|
|
786
|
-
/** Convert explicit retry hints to milliseconds without treating arbitrary strings as dates. */
|
|
787
|
-
function parseRetryAfterSeconds(value) {
|
|
788
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
789
|
-
const milliseconds = value * 1e3;
|
|
790
|
-
return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
|
|
791
|
-
}
|
|
792
|
-
function parseRetryAfterHeader(value, now = Date.now()) {
|
|
793
|
-
if (!value) return void 0;
|
|
794
|
-
const text = value.trim();
|
|
795
|
-
if (/^\d+$/.test(text)) return parseRetryAfterSeconds(Number(text));
|
|
796
|
-
if (!/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(text)) return void 0;
|
|
797
|
-
const timestamp = Date.parse(text);
|
|
798
|
-
if (!Number.isFinite(timestamp) || new Date(timestamp).toUTCString() !== text) return void 0;
|
|
799
|
-
const milliseconds = timestamp - now;
|
|
800
|
-
return Number.isSafeInteger(milliseconds) && milliseconds >= 0 ? milliseconds : void 0;
|
|
801
|
-
}
|
|
802
|
-
//#endregion
|
|
803
785
|
//#region src/errors.ts
|
|
804
786
|
/**
|
|
805
787
|
* Thrown when the server returns `challenge_required` during auth start.
|
|
@@ -846,6 +828,163 @@ function isAggApiError(value) {
|
|
|
846
828
|
return value instanceof AggApiError;
|
|
847
829
|
}
|
|
848
830
|
//#endregion
|
|
831
|
+
//#region src/venue-event-read-scheduler.ts
|
|
832
|
+
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
833
|
+
const abortError = () => Object.assign(/* @__PURE__ */ new Error("The request was aborted"), { name: "AbortError" });
|
|
834
|
+
/** Instance-local admission for event reads only. Never retries or retains successful data. */
|
|
835
|
+
var VenueEventReadScheduler = class {
|
|
836
|
+
constructor(concurrency = 4, queueLimit = 128) {
|
|
837
|
+
this.concurrency = concurrency;
|
|
838
|
+
this.queueLimit = queueLimit;
|
|
839
|
+
this.jobs = /* @__PURE__ */ new Map();
|
|
840
|
+
this.queue = [];
|
|
841
|
+
this.active = 0;
|
|
842
|
+
this.cooldownUntil = 0;
|
|
843
|
+
}
|
|
844
|
+
read(key, run, signal) {
|
|
845
|
+
if (signal?.aborted) return Promise.reject(abortError());
|
|
846
|
+
let job = this.jobs.get(key);
|
|
847
|
+
if (!job) {
|
|
848
|
+
this.queue = this.queue.filter((entry) => entry.subscribers > 0);
|
|
849
|
+
if (this.queue.length >= this.queueLimit) return Promise.reject(new AggApiError("Too many pending event reads", 429, {
|
|
850
|
+
code: "client_read_queue_full",
|
|
851
|
+
retryable: true,
|
|
852
|
+
retryAfterMs: Math.max(1e3, this.cooldownUntil - Date.now())
|
|
853
|
+
}));
|
|
854
|
+
let resolve;
|
|
855
|
+
let reject;
|
|
856
|
+
job = {
|
|
857
|
+
key,
|
|
858
|
+
run,
|
|
859
|
+
promise: new Promise((res, rej) => {
|
|
860
|
+
resolve = res;
|
|
861
|
+
reject = rej;
|
|
862
|
+
}),
|
|
863
|
+
resolve,
|
|
864
|
+
reject,
|
|
865
|
+
controller: new AbortController(),
|
|
866
|
+
subscribers: 0
|
|
867
|
+
};
|
|
868
|
+
this.jobs.set(key, job);
|
|
869
|
+
this.queue.push(job);
|
|
870
|
+
}
|
|
871
|
+
const entry = job;
|
|
872
|
+
entry.subscribers += 1;
|
|
873
|
+
const result = new Promise((resolve, reject) => {
|
|
874
|
+
let settled = false;
|
|
875
|
+
const finish = (callback) => {
|
|
876
|
+
if (settled) return;
|
|
877
|
+
settled = true;
|
|
878
|
+
signal?.removeEventListener("abort", onAbort);
|
|
879
|
+
entry.subscribers -= 1;
|
|
880
|
+
if (entry.subscribers === 0) {
|
|
881
|
+
if (this.jobs.get(key) === entry) this.jobs.delete(key);
|
|
882
|
+
entry.controller.abort();
|
|
883
|
+
this.pump();
|
|
884
|
+
}
|
|
885
|
+
callback();
|
|
886
|
+
};
|
|
887
|
+
const onAbort = () => finish(() => reject(abortError()));
|
|
888
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
889
|
+
entry.promise.then((value) => finish(() => resolve(value)), (error) => finish(() => reject(error)));
|
|
890
|
+
});
|
|
891
|
+
this.pump();
|
|
892
|
+
return result;
|
|
893
|
+
}
|
|
894
|
+
reset() {
|
|
895
|
+
for (const job of this.jobs.values()) {
|
|
896
|
+
job.controller.abort();
|
|
897
|
+
job.reject(abortError());
|
|
898
|
+
}
|
|
899
|
+
this.jobs.clear();
|
|
900
|
+
this.queue = [];
|
|
901
|
+
this.cooldownUntil = 0;
|
|
902
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
903
|
+
this.timer = void 0;
|
|
904
|
+
}
|
|
905
|
+
pump() {
|
|
906
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
907
|
+
this.timer = void 0;
|
|
908
|
+
this.queue = this.queue.filter((job) => job.subscribers > 0);
|
|
909
|
+
if (this.queue.length === 0 || this.active >= this.concurrency) return;
|
|
910
|
+
const delay = this.cooldownUntil - Date.now();
|
|
911
|
+
if (delay > 0) {
|
|
912
|
+
this.timer = setTimeout(() => this.pump(), Math.min(delay, MAX_TIMER_DELAY_MS));
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
while (this.active < this.concurrency && this.queue.length > 0) {
|
|
916
|
+
const job = this.queue.shift();
|
|
917
|
+
this.active += 1;
|
|
918
|
+
this.execute(job);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
async execute(job) {
|
|
922
|
+
try {
|
|
923
|
+
job.resolve(await job.run(job.controller.signal));
|
|
924
|
+
} catch (error) {
|
|
925
|
+
if (!job.controller.signal.aborted && isAggApiError(error) && error.status === 429) {
|
|
926
|
+
const hint = error.retryAfterMs;
|
|
927
|
+
const delay = typeof hint === "number" && Number.isSafeInteger(hint) && hint >= 0 ? hint : 1e3;
|
|
928
|
+
this.cooldownUntil = Math.max(this.cooldownUntil, Date.now() + delay + 1 + Math.floor(Math.random() * 250));
|
|
929
|
+
}
|
|
930
|
+
job.reject(error);
|
|
931
|
+
} finally {
|
|
932
|
+
if (this.jobs.get(job.key) === job) this.jobs.delete(job.key);
|
|
933
|
+
this.active -= 1;
|
|
934
|
+
this.pump();
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
//#endregion
|
|
939
|
+
//#region src/execution-poll-wakeup.ts
|
|
940
|
+
/** A coalesced socket hint can interrupt one poll delay or precede it. */
|
|
941
|
+
var ExecutionPollWakeup = class {
|
|
942
|
+
constructor() {
|
|
943
|
+
this.pending = false;
|
|
944
|
+
}
|
|
945
|
+
notify() {
|
|
946
|
+
if (this.resume) this.resume();
|
|
947
|
+
else this.pending = true;
|
|
948
|
+
}
|
|
949
|
+
wait(ms) {
|
|
950
|
+
if (this.pending) {
|
|
951
|
+
this.pending = false;
|
|
952
|
+
return Promise.resolve();
|
|
953
|
+
}
|
|
954
|
+
return new Promise((resolve) => {
|
|
955
|
+
const finish = () => {
|
|
956
|
+
clearTimeout(timer);
|
|
957
|
+
this.resume = void 0;
|
|
958
|
+
resolve();
|
|
959
|
+
};
|
|
960
|
+
const timer = setTimeout(finish, ms);
|
|
961
|
+
this.resume = finish;
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
dispose() {
|
|
965
|
+
this.resume?.();
|
|
966
|
+
this.pending = false;
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
//#endregion
|
|
970
|
+
//#region src/retry-after.ts
|
|
971
|
+
/** Convert explicit retry hints to milliseconds without treating arbitrary strings as dates. */
|
|
972
|
+
function parseRetryAfterSeconds(value) {
|
|
973
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
974
|
+
const milliseconds = value * 1e3;
|
|
975
|
+
return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
|
|
976
|
+
}
|
|
977
|
+
function parseRetryAfterHeader(value, now = Date.now()) {
|
|
978
|
+
if (!value) return void 0;
|
|
979
|
+
const text = value.trim();
|
|
980
|
+
if (/^\d+$/.test(text)) return parseRetryAfterSeconds(Number(text));
|
|
981
|
+
if (!/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(text)) return void 0;
|
|
982
|
+
const timestamp = Date.parse(text);
|
|
983
|
+
if (!Number.isFinite(timestamp) || new Date(timestamp).toUTCString() !== text) return void 0;
|
|
984
|
+
const milliseconds = timestamp - now;
|
|
985
|
+
return Number.isSafeInteger(milliseconds) && milliseconds >= 0 ? milliseconds : void 0;
|
|
986
|
+
}
|
|
987
|
+
//#endregion
|
|
849
988
|
//#region src/eip55.ts
|
|
850
989
|
/**
|
|
851
990
|
* EIP-55 address checksumming, self-contained.
|
|
@@ -1132,6 +1271,227 @@ function withTimeout(promise, ms) {
|
|
|
1132
1271
|
});
|
|
1133
1272
|
}
|
|
1134
1273
|
//#endregion
|
|
1274
|
+
//#region src/catalog-feed.ts
|
|
1275
|
+
/** Recommended REST/WS overlap: re-read this much before the resume point. */
|
|
1276
|
+
const CATALOG_DEFAULT_OVERLAP_MS = 6e4;
|
|
1277
|
+
/** Live frames held during a catch-up. Past this the buffer is dropped and the catch-up re-reads. */
|
|
1278
|
+
const LIVE_BUFFER_MAX = 1e4;
|
|
1279
|
+
const RETRY_BASE_MS = 1e3;
|
|
1280
|
+
const RETRY_MAX_MS = 3e4;
|
|
1281
|
+
/** Live applies between dedupe-map prunes. */
|
|
1282
|
+
const PRUNE_EVERY = 1e3;
|
|
1283
|
+
/**
|
|
1284
|
+
* `GET /catalog/changes` answers 410 `resync_required` when the position is
|
|
1285
|
+
* older than the 24h retention window: re-list with the list endpoints, then
|
|
1286
|
+
* resume from the time that re-list started.
|
|
1287
|
+
*/
|
|
1288
|
+
function isCatalogResyncRequired(err) {
|
|
1289
|
+
return isAggApiError(err) && (err.code === "resync_required" || err.status === 410);
|
|
1290
|
+
}
|
|
1291
|
+
/** See {@link AggClient.watchCatalog}. */
|
|
1292
|
+
function watchCatalog(deps, options) {
|
|
1293
|
+
if (Number.isNaN(toMs(options.since))) throw new Error("watchCatalog: `since` must be a valid date or ISO string");
|
|
1294
|
+
const overlapMs = options.overlapMs ?? 6e4;
|
|
1295
|
+
const filter = {
|
|
1296
|
+
...options.venues?.length ? { venues: options.venues } : {},
|
|
1297
|
+
...options.entities?.length ? { entities: options.entities } : {}
|
|
1298
|
+
};
|
|
1299
|
+
let sinceMs = toMs(options.since);
|
|
1300
|
+
let lastChangedAtMs;
|
|
1301
|
+
let lastChangedAt;
|
|
1302
|
+
/**
|
|
1303
|
+
* Dedupe state per `${entity}:${id}`. Pruned below `lastChangedAt - 2 *
|
|
1304
|
+
* overlap`: catch-ups re-read from `lastChangedAt - overlap` and REST
|
|
1305
|
+
* serves each entity's latest state, so nothing older can come back.
|
|
1306
|
+
*/
|
|
1307
|
+
const held = /* @__PURE__ */ new Map();
|
|
1308
|
+
/** matchedOnly: ids the caller holds, so a leave or delete still reaches them. Not pruned. */
|
|
1309
|
+
const delivered = /* @__PURE__ */ new Set();
|
|
1310
|
+
let appliedSincePrune = 0;
|
|
1311
|
+
let buffering = true;
|
|
1312
|
+
let buffer = [];
|
|
1313
|
+
let bufferOverflowed = false;
|
|
1314
|
+
let running = false;
|
|
1315
|
+
let rerun = false;
|
|
1316
|
+
let resyncRequired = false;
|
|
1317
|
+
let stopped = false;
|
|
1318
|
+
let retryTimer = null;
|
|
1319
|
+
let wakeRetry = null;
|
|
1320
|
+
const abort = new AbortController();
|
|
1321
|
+
let resolveReady;
|
|
1322
|
+
const ready = new Promise((resolve) => {
|
|
1323
|
+
resolveReady = resolve;
|
|
1324
|
+
});
|
|
1325
|
+
const report = (err) => {
|
|
1326
|
+
if (stopped) return;
|
|
1327
|
+
try {
|
|
1328
|
+
options.onError?.(err);
|
|
1329
|
+
} catch {}
|
|
1330
|
+
};
|
|
1331
|
+
const prune = () => {
|
|
1332
|
+
appliedSincePrune = 0;
|
|
1333
|
+
if (lastChangedAtMs === void 0) return;
|
|
1334
|
+
const floor = lastChangedAtMs - 2 * overlapMs;
|
|
1335
|
+
for (const [key, h] of held) if (h.changedAtMs < floor) held.delete(key);
|
|
1336
|
+
};
|
|
1337
|
+
const apply = (change) => {
|
|
1338
|
+
const key = `${change.entity}:${change.id}`;
|
|
1339
|
+
const changedAtMs = toMs(change.changedAt);
|
|
1340
|
+
if (Number.isNaN(changedAtMs)) return;
|
|
1341
|
+
const prev = held.get(key);
|
|
1342
|
+
if (prev && (change.version < prev.version || change.version === prev.version && changedAtMs <= prev.changedAtMs)) return;
|
|
1343
|
+
held.set(key, {
|
|
1344
|
+
version: change.version,
|
|
1345
|
+
changedAtMs
|
|
1346
|
+
});
|
|
1347
|
+
if (lastChangedAtMs === void 0 || changedAtMs > lastChangedAtMs) {
|
|
1348
|
+
lastChangedAtMs = changedAtMs;
|
|
1349
|
+
lastChangedAt = change.changedAt;
|
|
1350
|
+
}
|
|
1351
|
+
if (++appliedSincePrune >= PRUNE_EVERY) prune();
|
|
1352
|
+
if (options.matchedOnly) {
|
|
1353
|
+
if ((change.event ?? change.market)?.matched === true && change.op !== "deleted") delivered.add(key);
|
|
1354
|
+
else if (delivered.has(key)) delivered.delete(key);
|
|
1355
|
+
else return;
|
|
1356
|
+
}
|
|
1357
|
+
const { type: _type, ...item } = change;
|
|
1358
|
+
try {
|
|
1359
|
+
options.onChange(item);
|
|
1360
|
+
} catch (err) {
|
|
1361
|
+
report(err);
|
|
1362
|
+
}
|
|
1363
|
+
};
|
|
1364
|
+
const onLive = (frame) => {
|
|
1365
|
+
if (stopped) return;
|
|
1366
|
+
if (!buffering) {
|
|
1367
|
+
apply(frame);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
if (buffer.length >= LIVE_BUFFER_MAX) {
|
|
1371
|
+
buffer = [];
|
|
1372
|
+
bufferOverflowed = true;
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
buffer.push(frame);
|
|
1376
|
+
};
|
|
1377
|
+
const readAll = async () => {
|
|
1378
|
+
const from = (lastChangedAtMs ?? sinceMs) - overlapMs;
|
|
1379
|
+
let cursor;
|
|
1380
|
+
for (;;) {
|
|
1381
|
+
const page = await deps.getChanges({
|
|
1382
|
+
...cursor ? { cursor } : { since: new Date(from).toISOString() },
|
|
1383
|
+
...options.pageLimit ? { limit: options.pageLimit } : {},
|
|
1384
|
+
...filter
|
|
1385
|
+
}, abort.signal);
|
|
1386
|
+
if (stopped) return;
|
|
1387
|
+
for (const change of page.data) apply(change);
|
|
1388
|
+
if (!page.hasMore) return;
|
|
1389
|
+
cursor = page.nextCursor;
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
1393
|
+
wakeRetry = resolve;
|
|
1394
|
+
retryTimer = setTimeout(() => {
|
|
1395
|
+
retryTimer = null;
|
|
1396
|
+
wakeRetry = null;
|
|
1397
|
+
resolve();
|
|
1398
|
+
}, ms);
|
|
1399
|
+
});
|
|
1400
|
+
const catchUp = async () => {
|
|
1401
|
+
running = true;
|
|
1402
|
+
buffering = true;
|
|
1403
|
+
let attempt = 0;
|
|
1404
|
+
try {
|
|
1405
|
+
do {
|
|
1406
|
+
rerun = false;
|
|
1407
|
+
bufferOverflowed = false;
|
|
1408
|
+
try {
|
|
1409
|
+
await readAll();
|
|
1410
|
+
} catch (err) {
|
|
1411
|
+
if (stopped) return;
|
|
1412
|
+
if (isCatalogResyncRequired(err)) {
|
|
1413
|
+
resyncRequired = true;
|
|
1414
|
+
if (options.onResyncRequired) options.onResyncRequired();
|
|
1415
|
+
else report(err);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
report(err);
|
|
1419
|
+
const step = Math.min(RETRY_BASE_MS * 2 ** attempt++, RETRY_MAX_MS);
|
|
1420
|
+
await sleep(Math.round(step * (.5 + Math.random() * .5)));
|
|
1421
|
+
if (stopped) return;
|
|
1422
|
+
rerun = true;
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
if (stopped) return;
|
|
1426
|
+
attempt = 0;
|
|
1427
|
+
const drained = buffer;
|
|
1428
|
+
buffer = [];
|
|
1429
|
+
for (const frame of drained) apply(frame);
|
|
1430
|
+
if (bufferOverflowed) rerun = true;
|
|
1431
|
+
} while (rerun && !stopped && !resyncRequired);
|
|
1432
|
+
if (stopped || resyncRequired) return;
|
|
1433
|
+
prune();
|
|
1434
|
+
buffering = false;
|
|
1435
|
+
options.onSynced?.({ lastChangedAt });
|
|
1436
|
+
resolveReady();
|
|
1437
|
+
} finally {
|
|
1438
|
+
running = false;
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
const requestCatchUp = () => {
|
|
1442
|
+
if (stopped || resyncRequired) return;
|
|
1443
|
+
if (running) {
|
|
1444
|
+
rerun = true;
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
catchUp().catch(report);
|
|
1448
|
+
};
|
|
1449
|
+
const owned = !options.socket;
|
|
1450
|
+
let socket = null;
|
|
1451
|
+
let unsubscribe = null;
|
|
1452
|
+
try {
|
|
1453
|
+
socket = options.socket ?? deps.createSocket();
|
|
1454
|
+
unsubscribe = socket.subscribeCatalog(filter, onLive, {
|
|
1455
|
+
onSubscribed: requestCatchUp,
|
|
1456
|
+
onInterrupted: requestCatchUp,
|
|
1457
|
+
onError: report
|
|
1458
|
+
});
|
|
1459
|
+
if (owned) socket.connect();
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
report(err);
|
|
1462
|
+
}
|
|
1463
|
+
return {
|
|
1464
|
+
stop() {
|
|
1465
|
+
if (stopped) return;
|
|
1466
|
+
stopped = true;
|
|
1467
|
+
abort.abort();
|
|
1468
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
1469
|
+
retryTimer = null;
|
|
1470
|
+
wakeRetry?.();
|
|
1471
|
+
wakeRetry = null;
|
|
1472
|
+
buffer = [];
|
|
1473
|
+
unsubscribe?.();
|
|
1474
|
+
if (owned) socket?.destroy();
|
|
1475
|
+
},
|
|
1476
|
+
get lastChangedAt() {
|
|
1477
|
+
return lastChangedAt;
|
|
1478
|
+
},
|
|
1479
|
+
resync(since) {
|
|
1480
|
+
if (stopped) return;
|
|
1481
|
+
if (Number.isNaN(toMs(since))) throw new Error("resync: `since` must be a valid date or ISO string");
|
|
1482
|
+
sinceMs = toMs(since);
|
|
1483
|
+
lastChangedAtMs = void 0;
|
|
1484
|
+
lastChangedAt = void 0;
|
|
1485
|
+
resyncRequired = false;
|
|
1486
|
+
requestCatchUp();
|
|
1487
|
+
},
|
|
1488
|
+
ready
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
function toMs(value) {
|
|
1492
|
+
return value instanceof Date ? value.getTime() : Date.parse(value);
|
|
1493
|
+
}
|
|
1494
|
+
//#endregion
|
|
1135
1495
|
//#region src/client.ts
|
|
1136
1496
|
const COOKIE_REFRESH_DELIVERY = "cookie-refresh";
|
|
1137
1497
|
const DEFAULT_MIDPOINT_IDS_PER_REQUEST = 75;
|
|
@@ -1222,9 +1582,12 @@ const AUTH_PATHS = /* @__PURE__ */ new Set([
|
|
|
1222
1582
|
]);
|
|
1223
1583
|
var AggClient = class {
|
|
1224
1584
|
constructor(options) {
|
|
1585
|
+
this.venueEventReads = new VenueEventReadScheduler();
|
|
1586
|
+
this.eventReadSession = 0;
|
|
1225
1587
|
this.listeners = /* @__PURE__ */ new Set();
|
|
1226
1588
|
this.webSocketAuthCleanups = /* @__PURE__ */ new Set();
|
|
1227
1589
|
this.tradeSockets = /* @__PURE__ */ new Set();
|
|
1590
|
+
this.signatureWaitListeners = /* @__PURE__ */ new Set();
|
|
1228
1591
|
this._authStatus = "unauthenticated";
|
|
1229
1592
|
this.authChannel = null;
|
|
1230
1593
|
this.pendingRefresh = null;
|
|
@@ -1321,6 +1684,7 @@ var AggClient = class {
|
|
|
1321
1684
|
this.authChannel.onmessage = (event) => {
|
|
1322
1685
|
const { type } = event.data ?? {};
|
|
1323
1686
|
if (type === "logout") {
|
|
1687
|
+
this.invalidateEventReadSession();
|
|
1324
1688
|
this.accessToken = void 0;
|
|
1325
1689
|
this.accessTokenUserId = void 0;
|
|
1326
1690
|
this.refreshTokenValue = void 0;
|
|
@@ -1336,6 +1700,7 @@ var AggClient = class {
|
|
|
1336
1700
|
}
|
|
1337
1701
|
/** Release resources (BroadcastChannel, etc.). Safe to call multiple times. */
|
|
1338
1702
|
destroy() {
|
|
1703
|
+
this.venueEventReads.reset();
|
|
1339
1704
|
this.authChannel?.close();
|
|
1340
1705
|
this.authChannel = null;
|
|
1341
1706
|
for (const cleanup of this.webSocketAuthCleanups) cleanup();
|
|
@@ -1348,15 +1713,17 @@ var AggClient = class {
|
|
|
1348
1713
|
* @param init - Fetch init options, plus optional `query` object for URL search params
|
|
1349
1714
|
*/
|
|
1350
1715
|
async request(path, init, decode) {
|
|
1716
|
+
const requestSession = this.eventReadSession;
|
|
1351
1717
|
const response = await this.rawFetch(path, init);
|
|
1352
|
-
if (response.status === 401 && this.accessToken && !AUTH_PATHS.has(path)) try {
|
|
1718
|
+
if (response.status === 401 && this.accessToken && !AUTH_PATHS.has(path) && this.eventReadSession === requestSession) try {
|
|
1353
1719
|
await this.refreshWithDedup();
|
|
1720
|
+
if (this.eventReadSession !== requestSession) throw Object.assign(/* @__PURE__ */ new Error("Authentication changed during refresh"), { name: "AbortError" });
|
|
1354
1721
|
const retryResponse = await this.rawFetch(path, init);
|
|
1355
1722
|
if (!retryResponse.ok) return this.throwResponseError(retryResponse);
|
|
1356
1723
|
if (retryResponse.status === 204) return void 0;
|
|
1357
1724
|
return decode ? decode(retryResponse) : retryResponse.json();
|
|
1358
1725
|
} catch {
|
|
1359
|
-
this.clearAccessToken();
|
|
1726
|
+
if (!init?.signal?.aborted && this.eventReadSession === requestSession) this.clearAccessToken();
|
|
1360
1727
|
return this.throwResponseError(response);
|
|
1361
1728
|
}
|
|
1362
1729
|
if (!response.ok) return this.throwResponseError(response);
|
|
@@ -1412,9 +1779,12 @@ var AggClient = class {
|
|
|
1412
1779
|
* only one refresh call is made and the rest wait for it.
|
|
1413
1780
|
*/
|
|
1414
1781
|
refreshWithDedup() {
|
|
1415
|
-
if (!this.pendingRefresh)
|
|
1416
|
-
this.
|
|
1417
|
-
|
|
1782
|
+
if (!this.pendingRefresh) {
|
|
1783
|
+
const pending = this.refreshAccessToken().finally(() => {
|
|
1784
|
+
if (this.pendingRefresh === pending) this.pendingRefresh = null;
|
|
1785
|
+
});
|
|
1786
|
+
this.pendingRefresh = pending;
|
|
1787
|
+
}
|
|
1418
1788
|
return this.pendingRefresh;
|
|
1419
1789
|
}
|
|
1420
1790
|
buildAuthRequestInit(init, options) {
|
|
@@ -1445,7 +1815,10 @@ var AggClient = class {
|
|
|
1445
1815
|
}
|
|
1446
1816
|
/** Set the user access token (for future user-tier auth). */
|
|
1447
1817
|
setAccessToken(token) {
|
|
1448
|
-
if (this.accessToken !== token)
|
|
1818
|
+
if (this.accessToken !== token) {
|
|
1819
|
+
this.invalidateEventReadSession();
|
|
1820
|
+
this.accessTokenUserId = void 0;
|
|
1821
|
+
}
|
|
1449
1822
|
this.accessToken = token;
|
|
1450
1823
|
this._authStatus = "authenticated";
|
|
1451
1824
|
this.persistSession();
|
|
@@ -1453,8 +1826,12 @@ var AggClient = class {
|
|
|
1453
1826
|
}
|
|
1454
1827
|
/** Set the current auth session tokens and optionally seed the user profile. */
|
|
1455
1828
|
setSession(session) {
|
|
1829
|
+
this.applySession(session);
|
|
1830
|
+
}
|
|
1831
|
+
applySession(session, tokenRenewal = false) {
|
|
1456
1832
|
const wasAuthenticated = this.isAuthenticated;
|
|
1457
|
-
this.accessTokenUserId
|
|
1833
|
+
if (session.user !== void 0 && this.accessTokenUserId !== void 0 && session.user.id !== this.accessTokenUserId || !tokenRenewal && (this.accessToken !== session.accessToken || session.user && session.user.id !== this.accessTokenUserId)) this.invalidateEventReadSession();
|
|
1834
|
+
this.accessTokenUserId = session.user?.id ?? (tokenRenewal || this.accessToken === session.accessToken ? this.accessTokenUserId : void 0);
|
|
1458
1835
|
this.accessToken = session.accessToken;
|
|
1459
1836
|
if ("refreshToken" in session) this.refreshTokenValue = session.refreshToken;
|
|
1460
1837
|
if (session.user !== void 0) this.currentUser = normalizeSessionUser(session.user, this.currentUser);
|
|
@@ -1465,6 +1842,7 @@ var AggClient = class {
|
|
|
1465
1842
|
}
|
|
1466
1843
|
/** Clear the user access token, refresh token, and user info. Broadcasts cross-tab logout. */
|
|
1467
1844
|
clearAccessToken() {
|
|
1845
|
+
this.invalidateEventReadSession();
|
|
1468
1846
|
this.accessToken = void 0;
|
|
1469
1847
|
this.accessTokenUserId = void 0;
|
|
1470
1848
|
this.refreshTokenValue = void 0;
|
|
@@ -1474,6 +1852,11 @@ var AggClient = class {
|
|
|
1474
1852
|
this.notifyListeners();
|
|
1475
1853
|
this.broadcastAuth("logout");
|
|
1476
1854
|
}
|
|
1855
|
+
invalidateEventReadSession() {
|
|
1856
|
+
this.eventReadSession += 1;
|
|
1857
|
+
this.pendingRefresh = null;
|
|
1858
|
+
this.venueEventReads.reset();
|
|
1859
|
+
}
|
|
1477
1860
|
broadcastAuth(type) {
|
|
1478
1861
|
try {
|
|
1479
1862
|
this.authChannel?.postMessage({ type });
|
|
@@ -1637,16 +2020,18 @@ var AggClient = class {
|
|
|
1637
2020
|
}
|
|
1638
2021
|
/** Refresh the access token using the stored refresh token. */
|
|
1639
2022
|
async refreshAccessToken() {
|
|
2023
|
+
const refreshSession = this.eventReadSession;
|
|
1640
2024
|
if (this.authDelivery !== COOKIE_REFRESH_DELIVERY && !this.refreshTokenValue) throw new Error("No refresh token available");
|
|
1641
2025
|
const res = await this.request("/auth/token/refresh", this.buildAuthRequestInit({
|
|
1642
2026
|
method: "POST",
|
|
1643
2027
|
body: JSON.stringify(this.authDelivery === COOKIE_REFRESH_DELIVERY ? {} : { refreshToken: this.refreshTokenValue })
|
|
1644
2028
|
}));
|
|
1645
|
-
this.
|
|
2029
|
+
if (this.eventReadSession !== refreshSession) throw Object.assign(/* @__PURE__ */ new Error("Authentication changed during refresh"), { name: "AbortError" });
|
|
2030
|
+
this.applySession({
|
|
1646
2031
|
accessToken: res.accessToken,
|
|
1647
2032
|
refreshToken: res.refreshToken,
|
|
1648
2033
|
user: res.user
|
|
1649
|
-
});
|
|
2034
|
+
}, true);
|
|
1650
2035
|
return res.accessToken;
|
|
1651
2036
|
}
|
|
1652
2037
|
/** Get the current user's profile. */
|
|
@@ -2018,6 +2403,7 @@ var AggClient = class {
|
|
|
2018
2403
|
if (options?.minVolume != null && options.minVolume > 0) query.minVolume = String(options.minVolume);
|
|
2019
2404
|
if (options?.endDateFrom) query.endDateFrom = options.endDateFrom;
|
|
2020
2405
|
if (options?.grouped) query.grouped = "true";
|
|
2406
|
+
if (options?.live) query.live = "true";
|
|
2021
2407
|
return this.request("/venue-events", {
|
|
2022
2408
|
query: Object.keys(query).length > 0 ? query : void 0,
|
|
2023
2409
|
signal: options?.signal
|
|
@@ -2034,14 +2420,24 @@ var AggClient = class {
|
|
|
2034
2420
|
});
|
|
2035
2421
|
}
|
|
2036
2422
|
async getVenueEventById(id, options) {
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2423
|
+
const query = {
|
|
2424
|
+
expand: "",
|
|
2425
|
+
...options?.cluster === false ? { cluster: "false" } : {},
|
|
2426
|
+
...options?.minVolume != null && options.minVolume > 0 ? { minVolume: String(options.minVolume) } : {}
|
|
2427
|
+
};
|
|
2428
|
+
const session = this.eventReadSession;
|
|
2429
|
+
const key = JSON.stringify([
|
|
2430
|
+
id,
|
|
2431
|
+
query,
|
|
2432
|
+
session
|
|
2433
|
+
]);
|
|
2434
|
+
return this.venueEventReads.read(key, (signal) => {
|
|
2435
|
+
if (this.eventReadSession !== session) throw Object.assign(/* @__PURE__ */ new Error("Authentication changed before event read"), { name: "AbortError" });
|
|
2436
|
+
return this.request(`/venue-events/${encodeURIComponent(id)}`, {
|
|
2437
|
+
query,
|
|
2438
|
+
signal
|
|
2439
|
+
});
|
|
2440
|
+
}, options?.signal);
|
|
2045
2441
|
}
|
|
2046
2442
|
/** Get normalized live score state for sports events. Requires appId or admin auth. */
|
|
2047
2443
|
async getSportsLive(eventIds, options) {
|
|
@@ -2411,27 +2807,77 @@ var AggClient = class {
|
|
|
2411
2807
|
});
|
|
2412
2808
|
}
|
|
2413
2809
|
/**
|
|
2810
|
+
* Catalog changes (events and markets created, changed or deleted) after
|
|
2811
|
+
* `since`, oldest first, one item per entity with its current state. Page
|
|
2812
|
+
* with `nextCursor` until `hasMore` is false. Positions older than 24h
|
|
2813
|
+
* throw an {@link AggApiError} with status 410 and code `resync_required`
|
|
2814
|
+
* (see `isCatalogResyncRequired`). Most callers want {@link watchCatalog}.
|
|
2815
|
+
*/
|
|
2816
|
+
async getCatalogChanges(params, options) {
|
|
2817
|
+
const query = {};
|
|
2818
|
+
if (params.since !== void 0) query.since = params.since instanceof Date ? params.since.toISOString() : params.since;
|
|
2819
|
+
if (params.cursor) query.cursor = params.cursor;
|
|
2820
|
+
if (params.limit != null) query.limit = String(params.limit);
|
|
2821
|
+
if (params.venues?.length) query.venues = params.venues;
|
|
2822
|
+
if (params.entities?.length) query.entities = params.entities;
|
|
2823
|
+
return this.request("/catalog/changes", {
|
|
2824
|
+
query,
|
|
2825
|
+
signal: options?.signal
|
|
2826
|
+
});
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Keep a copy of the catalog in sync from `since`: subscribes to the live
|
|
2830
|
+
* `catalog` channel, catches up over `GET /catalog/changes` (from the last
|
|
2831
|
+
* applied `changedAt` minus `overlapMs`) on every (re)subscribe and stream
|
|
2832
|
+
* interruption, and delivers each entity's newer state once to `onChange`.
|
|
2833
|
+
*
|
|
2834
|
+
* Persist `handle.lastChangedAt` and pass it back as `since` to resume
|
|
2835
|
+
* after a restart. When that is older than 24h, `onResyncRequired` fires:
|
|
2836
|
+
* re-list with the list endpoints, then call `handle.resync(startOfReList)`.
|
|
2837
|
+
*
|
|
2838
|
+
* Without `socket`, opens a dedicated app-level connection (requires
|
|
2839
|
+
* `wsUrl`) that `stop()` closes.
|
|
2840
|
+
*/
|
|
2841
|
+
watchCatalog(options) {
|
|
2842
|
+
return watchCatalog({
|
|
2843
|
+
getChanges: (params, signal) => this.getCatalogChanges(params, { signal }),
|
|
2844
|
+
createSocket: () => new require_agg_websocket.AggWebSocket(this.webSocketOptions(options.webSocketImpl))
|
|
2845
|
+
}, options);
|
|
2846
|
+
}
|
|
2847
|
+
/**
|
|
2848
|
+
* URL and handshake headers for this client's app. Server-side clients with
|
|
2849
|
+
* an API key authenticate the handshake with it (the gateway then skips the
|
|
2850
|
+
* browser Origin check). Never in a browser: browsers cannot set handshake
|
|
2851
|
+
* headers, and keys must not ship there.
|
|
2852
|
+
*/
|
|
2853
|
+
webSocketOptions(webSocketImpl) {
|
|
2854
|
+
if (!this.wsUrl) throw new Error("AggClient WebSockets require wsUrl to be set in client options");
|
|
2855
|
+
const url = new URL(this.wsUrl);
|
|
2856
|
+
if (this._appId) url.searchParams.set("appId", this._appId);
|
|
2857
|
+
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
2858
|
+
const headers = this.apiKey && !isBrowser ? { "x-app-api-key": this.apiKey } : void 0;
|
|
2859
|
+
return {
|
|
2860
|
+
url: url.toString(),
|
|
2861
|
+
...headers ? { headers } : {},
|
|
2862
|
+
...webSocketImpl ? { webSocketImpl } : {}
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
/**
|
|
2414
2866
|
* Create an AggWebSocket instance configured with this client's appId and current auth session.
|
|
2415
2867
|
* Session changes update this socket until it or the client is destroyed.
|
|
2416
2868
|
* Requires `wsUrl` to be set in the client options.
|
|
2417
2869
|
*/
|
|
2418
2870
|
createWebSocket(callbacks, options) {
|
|
2419
2871
|
if (!this.wsUrl) throw new Error("AggClient.createWebSocket() requires wsUrl to be set in client options");
|
|
2420
|
-
const url = new URL(this.wsUrl);
|
|
2421
|
-
if (this._appId) url.searchParams.set("appId", this._appId);
|
|
2422
2872
|
const createSocket = this.webSocketFactory ?? ((socketOptions) => new require_agg_websocket.AggWebSocket(socketOptions));
|
|
2423
2873
|
const watchdogHeaders = {};
|
|
2424
2874
|
if (this._appId) watchdogHeaders["x-app-id"] = this._appId;
|
|
2425
2875
|
if (this.apiKey) watchdogHeaders["x-app-api-key"] = this.apiKey;
|
|
2426
|
-
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
2427
|
-
const headers = this.apiKey && !isBrowser ? { "x-app-api-key": this.apiKey } : void 0;
|
|
2428
2876
|
const socket = createSocket({
|
|
2429
|
-
|
|
2877
|
+
...this.webSocketOptions(options?.webSocketImpl),
|
|
2430
2878
|
callbacks,
|
|
2431
2879
|
initialToken: this.accessToken,
|
|
2432
2880
|
enableLogs: options?.enableLogs ?? false,
|
|
2433
|
-
...headers ? { headers } : {},
|
|
2434
|
-
...options?.webSocketImpl ? { webSocketImpl: options.webSocketImpl } : {},
|
|
2435
2881
|
bookWatchdog: options?.bookWatchdog === false ? void 0 : {
|
|
2436
2882
|
baseUrl: this.baseUrl,
|
|
2437
2883
|
headers: watchdogHeaders,
|
|
@@ -2492,7 +2938,9 @@ var AggClient = class {
|
|
|
2492
2938
|
handlers.onAuthenticated?.(message);
|
|
2493
2939
|
},
|
|
2494
2940
|
onOrderEvent: (message) => {
|
|
2495
|
-
if (canReceiveUserEvent(message.userId))
|
|
2941
|
+
if (!canReceiveUserEvent(message.userId)) return;
|
|
2942
|
+
if (message.event === "step_waiting" && message.waitingFor?.topic === "agg.user.signature") for (const listener of this.signatureWaitListeners) listener(message.orderId);
|
|
2943
|
+
handlers.onOrderEvent?.(message);
|
|
2496
2944
|
},
|
|
2497
2945
|
onOrderEventReceipt: (message) => {
|
|
2498
2946
|
if (canReceiveUserEvent()) handlers.onOrderEventReceipt?.(message);
|
|
@@ -2545,7 +2993,7 @@ var AggClient = class {
|
|
|
2545
2993
|
userId = nextUserId;
|
|
2546
2994
|
if (resetConnection) {
|
|
2547
2995
|
const diagnostics = socket.getDiagnostics();
|
|
2548
|
-
const reconnect = connectRequested || socket.connected || !manuallyDisconnected && (diagnostics.orderbookOutcomeIds.length > 0 || diagnostics.tradeOutcomeIds.length > 0 || diagnostics.arbMarketIds.length > 0 || diagnostics.arbFeedSubscribers > 0);
|
|
2996
|
+
const reconnect = connectRequested || socket.connected || !manuallyDisconnected && (diagnostics.orderbookOutcomeIds.length > 0 || diagnostics.tradeOutcomeIds.length > 0 || diagnostics.arbMarketIds.length > 0 || diagnostics.arbFeedSubscribers > 0 || (diagnostics.catalogSubscribers ?? 0) > 0);
|
|
2549
2997
|
authenticatedUserId = void 0;
|
|
2550
2998
|
awaitingDisconnect = true;
|
|
2551
2999
|
this.tradeSocketAuthenticatedUserId = void 0;
|
|
@@ -2620,69 +3068,80 @@ var AggClient = class {
|
|
|
2620
3068
|
let ordersFilled = false;
|
|
2621
3069
|
const isPostFillFee = (req) => isPostFillFeeRequest(req, ordersFilled);
|
|
2622
3070
|
let orderSubmitted = false;
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
if (
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
};
|
|
3071
|
+
const wakeup = new ExecutionPollWakeup();
|
|
3072
|
+
const orderIds = new Set(first.orderIds ?? []);
|
|
3073
|
+
const onSignatureWait = (orderId) => {
|
|
3074
|
+
if (orderIds.has(orderId)) wakeup.notify();
|
|
3075
|
+
};
|
|
3076
|
+
this.signatureWaitListeners.add(onSignatureWait);
|
|
3077
|
+
try {
|
|
3078
|
+
for (;;) {
|
|
3079
|
+
pending = pending.filter((p) => !declinedOptional.has(p.stepId) && (orderSubmitted || !isOptionalFeeRequest(p)));
|
|
3080
|
+
if (Date.now() > deadline) throw new Error(`Fill for quote ${quoteId} did not finish within the timeout.`);
|
|
3081
|
+
if (pending.length === 0) {
|
|
3082
|
+
await wakeup.wait(nextPollMs);
|
|
3083
|
+
const polled = await this.getExecutionStatus({ quoteId });
|
|
3084
|
+
nextPollMs = Number.isFinite(polled.pollAfterMs) ? Math.min(Math.max(polled.pollAfterMs, MIN_POLL_INTERVAL_MS), MAX_POLL_INTERVAL_MS) : SELF_CUSTODY_POLL_INTERVAL_MS;
|
|
3085
|
+
if (TERMINAL_EXECUTION_STATES.has(polled.overallState)) {
|
|
3086
|
+
if (polled.overallState !== "filled" && polled.overallState !== "partially_filled") throw new Error(`Fill for quote ${quoteId} ended as ${polled.overallState}.`);
|
|
3087
|
+
return {
|
|
3088
|
+
...first,
|
|
3089
|
+
pendingSignatures: []
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
ordersFilled = areOrdersFilled(polled.orders);
|
|
3093
|
+
pending = polled.pendingSignatures ?? [];
|
|
3094
|
+
continue;
|
|
2636
3095
|
}
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
3096
|
+
const key = pending.map((p) => p.stepId).sort().join(",");
|
|
3097
|
+
if (key === lastKey) throw new Error(`Fill for quote ${quoteId} did not advance — still awaiting steps [${key}] after signing them.`);
|
|
3098
|
+
lastKey = key;
|
|
3099
|
+
const signatures = [];
|
|
3100
|
+
const ordered = orderSignatureRound(pending, { orderSubmitted });
|
|
3101
|
+
const userSigners = userSignerAddresses(pending, params.signingAddress);
|
|
3102
|
+
for (const req of ordered) {
|
|
3103
|
+
if (Date.now() > new Date(req.expiresAt).getTime()) {
|
|
3104
|
+
if (req.optional) {
|
|
3105
|
+
declinedOptional.add(req.stepId);
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
if (isPostFillFee(req)) return {
|
|
3109
|
+
...first,
|
|
3110
|
+
pendingSignatures: []
|
|
3111
|
+
};
|
|
3112
|
+
throw new Error(`Signature request ${req.stepId} for quote ${quoteId} expired at ${req.expiresAt}; the fill must be re-quoted.`);
|
|
3113
|
+
}
|
|
3114
|
+
let signed;
|
|
3115
|
+
try {
|
|
3116
|
+
signed = await withTimeout(signer(req), signatureTimeoutMs(req, userSigners));
|
|
3117
|
+
} catch (err) {
|
|
3118
|
+
if (isPostFillFee(req)) return {
|
|
3119
|
+
...first,
|
|
3120
|
+
pendingSignatures: []
|
|
3121
|
+
};
|
|
3122
|
+
if (!req.optional) throw err;
|
|
2650
3123
|
declinedOptional.add(req.stepId);
|
|
2651
3124
|
continue;
|
|
2652
3125
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
}
|
|
2657
|
-
|
|
3126
|
+
signatures.push(typeof signed === "string" ? {
|
|
3127
|
+
stepId: req.stepId,
|
|
3128
|
+
signature: signed
|
|
3129
|
+
} : {
|
|
3130
|
+
stepId: req.stepId,
|
|
3131
|
+
txHash: signed.txHash
|
|
3132
|
+
});
|
|
2658
3133
|
}
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
signed = await withTimeout(signer(req), signatureTimeoutMs(req, userSigners));
|
|
2662
|
-
} catch (err) {
|
|
2663
|
-
if (isPostFillFee(req)) return {
|
|
2664
|
-
...first,
|
|
2665
|
-
pendingSignatures: []
|
|
2666
|
-
};
|
|
2667
|
-
if (!req.optional) throw err;
|
|
2668
|
-
declinedOptional.add(req.stepId);
|
|
3134
|
+
if (signatures.length === 0) {
|
|
3135
|
+
pending = [];
|
|
2669
3136
|
continue;
|
|
2670
3137
|
}
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
} : {
|
|
2675
|
-
stepId: req.stepId,
|
|
2676
|
-
txHash: signed.txHash
|
|
2677
|
-
});
|
|
2678
|
-
}
|
|
2679
|
-
if (signatures.length === 0) {
|
|
2680
|
-
pending = [];
|
|
2681
|
-
continue;
|
|
3138
|
+
const next = await this.submitFillSignatures(quoteId, signatures);
|
|
3139
|
+
if (signatures.some((s) => isOrderSignatureStep(s.stepId))) orderSubmitted = true;
|
|
3140
|
+
pending = next.pendingSignatures ?? [];
|
|
2682
3141
|
}
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
3142
|
+
} finally {
|
|
3143
|
+
this.signatureWaitListeners.delete(onSignatureWait);
|
|
3144
|
+
wakeup.dispose();
|
|
2686
3145
|
}
|
|
2687
3146
|
}
|
|
2688
3147
|
/**
|
|
@@ -2766,6 +3225,14 @@ var AggClient = class {
|
|
|
2766
3225
|
body: JSON.stringify(params)
|
|
2767
3226
|
});
|
|
2768
3227
|
}
|
|
3228
|
+
/** Submit signatures for a parked self-custody withdrawal. Returns whatever is still outstanding. */
|
|
3229
|
+
async submitWithdrawalSignatures(withdrawalId, signatures) {
|
|
3230
|
+
if (!withdrawalId) throw new Error("withdrawalId is required");
|
|
3231
|
+
return this.request(`/execution/withdrawals/${encodeURIComponent(withdrawalId)}/signatures`, {
|
|
3232
|
+
method: "POST",
|
|
3233
|
+
body: JSON.stringify({ signatures })
|
|
3234
|
+
});
|
|
3235
|
+
}
|
|
2769
3236
|
/**
|
|
2770
3237
|
* Preview a withdrawal — real fee + receive estimate without creating one.
|
|
2771
3238
|
* Optional: `withdrawManaged` does NOT require a prior preview, so partners
|
|
@@ -3093,6 +3560,7 @@ exports.AccountType = AccountType;
|
|
|
3093
3560
|
exports.AggApiError = AggApiError;
|
|
3094
3561
|
exports.AggClient = AggClient;
|
|
3095
3562
|
exports.AggWebSocket = require_agg_websocket.AggWebSocket;
|
|
3563
|
+
exports.CATALOG_DEFAULT_OVERLAP_MS = CATALOG_DEFAULT_OVERLAP_MS;
|
|
3096
3564
|
exports.CONFIRMED_MATCH_STATUSES = CONFIRMED_MATCH_STATUSES;
|
|
3097
3565
|
exports.CandleBuilder = require_candle_builder.CandleBuilder;
|
|
3098
3566
|
exports.Chain = Chain;
|
|
@@ -3138,6 +3606,7 @@ exports.getWalletAddressFromUserProfile = getWalletAddressFromUserProfile;
|
|
|
3138
3606
|
exports.hasShape = hasShape;
|
|
3139
3607
|
exports.isAggApiError = isAggApiError;
|
|
3140
3608
|
exports.isBalanceRefillWithinDailyCap = isBalanceRefillWithinDailyCap;
|
|
3609
|
+
exports.isCatalogResyncRequired = isCatalogResyncRequired;
|
|
3141
3610
|
exports.isEmail = isEmail;
|
|
3142
3611
|
exports.isEnum = isEnum;
|
|
3143
3612
|
exports.isFiniteNonNeg = isFiniteNonNeg;
|