@agg-build/sdk 4.31.0 → 4.33.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-4EAdUldv.d.mts → agg-websocket-CvYdVJPc.d.mts} +3832 -3532
- package/dist/agg-websocket-CvYdVJPc.d.mts.map +1 -0
- package/dist/{agg-websocket-4EAdUldv.d.ts → agg-websocket-CvYdVJPc.d.ts} +3832 -3532
- package/dist/agg-websocket-CvYdVJPc.d.ts.map +1 -0
- package/dist/{agg-websocket-CVZ7laJ4.js → agg-websocket-MYZiMRXn.js} +116 -3
- 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 +568 -100
- package/dist/index.mjs +567 -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-4EAdUldv.d.mts.map +0 -1
- package/dist/agg-websocket-4EAdUldv.d.ts.map +0 -1
- package/dist/agg-websocket-ClGhhROZ.mjs.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. */
|
|
@@ -2034,14 +2419,24 @@ var AggClient = class {
|
|
|
2034
2419
|
});
|
|
2035
2420
|
}
|
|
2036
2421
|
async getVenueEventById(id, options) {
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2422
|
+
const query = {
|
|
2423
|
+
expand: "",
|
|
2424
|
+
...options?.cluster === false ? { cluster: "false" } : {},
|
|
2425
|
+
...options?.minVolume != null && options.minVolume > 0 ? { minVolume: String(options.minVolume) } : {}
|
|
2426
|
+
};
|
|
2427
|
+
const session = this.eventReadSession;
|
|
2428
|
+
const key = JSON.stringify([
|
|
2429
|
+
id,
|
|
2430
|
+
query,
|
|
2431
|
+
session
|
|
2432
|
+
]);
|
|
2433
|
+
return this.venueEventReads.read(key, (signal) => {
|
|
2434
|
+
if (this.eventReadSession !== session) throw Object.assign(/* @__PURE__ */ new Error("Authentication changed before event read"), { name: "AbortError" });
|
|
2435
|
+
return this.request(`/venue-events/${encodeURIComponent(id)}`, {
|
|
2436
|
+
query,
|
|
2437
|
+
signal
|
|
2438
|
+
});
|
|
2439
|
+
}, options?.signal);
|
|
2045
2440
|
}
|
|
2046
2441
|
/** Get normalized live score state for sports events. Requires appId or admin auth. */
|
|
2047
2442
|
async getSportsLive(eventIds, options) {
|
|
@@ -2411,27 +2806,77 @@ var AggClient = class {
|
|
|
2411
2806
|
});
|
|
2412
2807
|
}
|
|
2413
2808
|
/**
|
|
2809
|
+
* Catalog changes (events and markets created, changed or deleted) after
|
|
2810
|
+
* `since`, oldest first, one item per entity with its current state. Page
|
|
2811
|
+
* with `nextCursor` until `hasMore` is false. Positions older than 24h
|
|
2812
|
+
* throw an {@link AggApiError} with status 410 and code `resync_required`
|
|
2813
|
+
* (see `isCatalogResyncRequired`). Most callers want {@link watchCatalog}.
|
|
2814
|
+
*/
|
|
2815
|
+
async getCatalogChanges(params, options) {
|
|
2816
|
+
const query = {};
|
|
2817
|
+
if (params.since !== void 0) query.since = params.since instanceof Date ? params.since.toISOString() : params.since;
|
|
2818
|
+
if (params.cursor) query.cursor = params.cursor;
|
|
2819
|
+
if (params.limit != null) query.limit = String(params.limit);
|
|
2820
|
+
if (params.venues?.length) query.venues = params.venues;
|
|
2821
|
+
if (params.entities?.length) query.entities = params.entities;
|
|
2822
|
+
return this.request("/catalog/changes", {
|
|
2823
|
+
query,
|
|
2824
|
+
signal: options?.signal
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* Keep a copy of the catalog in sync from `since`: subscribes to the live
|
|
2829
|
+
* `catalog` channel, catches up over `GET /catalog/changes` (from the last
|
|
2830
|
+
* applied `changedAt` minus `overlapMs`) on every (re)subscribe and stream
|
|
2831
|
+
* interruption, and delivers each entity's newer state once to `onChange`.
|
|
2832
|
+
*
|
|
2833
|
+
* Persist `handle.lastChangedAt` and pass it back as `since` to resume
|
|
2834
|
+
* after a restart. When that is older than 24h, `onResyncRequired` fires:
|
|
2835
|
+
* re-list with the list endpoints, then call `handle.resync(startOfReList)`.
|
|
2836
|
+
*
|
|
2837
|
+
* Without `socket`, opens a dedicated app-level connection (requires
|
|
2838
|
+
* `wsUrl`) that `stop()` closes.
|
|
2839
|
+
*/
|
|
2840
|
+
watchCatalog(options) {
|
|
2841
|
+
return watchCatalog({
|
|
2842
|
+
getChanges: (params, signal) => this.getCatalogChanges(params, { signal }),
|
|
2843
|
+
createSocket: () => new require_agg_websocket.AggWebSocket(this.webSocketOptions(options.webSocketImpl))
|
|
2844
|
+
}, options);
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* URL and handshake headers for this client's app. Server-side clients with
|
|
2848
|
+
* an API key authenticate the handshake with it (the gateway then skips the
|
|
2849
|
+
* browser Origin check). Never in a browser: browsers cannot set handshake
|
|
2850
|
+
* headers, and keys must not ship there.
|
|
2851
|
+
*/
|
|
2852
|
+
webSocketOptions(webSocketImpl) {
|
|
2853
|
+
if (!this.wsUrl) throw new Error("AggClient WebSockets require wsUrl to be set in client options");
|
|
2854
|
+
const url = new URL(this.wsUrl);
|
|
2855
|
+
if (this._appId) url.searchParams.set("appId", this._appId);
|
|
2856
|
+
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
2857
|
+
const headers = this.apiKey && !isBrowser ? { "x-app-api-key": this.apiKey } : void 0;
|
|
2858
|
+
return {
|
|
2859
|
+
url: url.toString(),
|
|
2860
|
+
...headers ? { headers } : {},
|
|
2861
|
+
...webSocketImpl ? { webSocketImpl } : {}
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2414
2865
|
* Create an AggWebSocket instance configured with this client's appId and current auth session.
|
|
2415
2866
|
* Session changes update this socket until it or the client is destroyed.
|
|
2416
2867
|
* Requires `wsUrl` to be set in the client options.
|
|
2417
2868
|
*/
|
|
2418
2869
|
createWebSocket(callbacks, options) {
|
|
2419
2870
|
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
2871
|
const createSocket = this.webSocketFactory ?? ((socketOptions) => new require_agg_websocket.AggWebSocket(socketOptions));
|
|
2423
2872
|
const watchdogHeaders = {};
|
|
2424
2873
|
if (this._appId) watchdogHeaders["x-app-id"] = this._appId;
|
|
2425
2874
|
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
2875
|
const socket = createSocket({
|
|
2429
|
-
|
|
2876
|
+
...this.webSocketOptions(options?.webSocketImpl),
|
|
2430
2877
|
callbacks,
|
|
2431
2878
|
initialToken: this.accessToken,
|
|
2432
2879
|
enableLogs: options?.enableLogs ?? false,
|
|
2433
|
-
...headers ? { headers } : {},
|
|
2434
|
-
...options?.webSocketImpl ? { webSocketImpl: options.webSocketImpl } : {},
|
|
2435
2880
|
bookWatchdog: options?.bookWatchdog === false ? void 0 : {
|
|
2436
2881
|
baseUrl: this.baseUrl,
|
|
2437
2882
|
headers: watchdogHeaders,
|
|
@@ -2492,7 +2937,9 @@ var AggClient = class {
|
|
|
2492
2937
|
handlers.onAuthenticated?.(message);
|
|
2493
2938
|
},
|
|
2494
2939
|
onOrderEvent: (message) => {
|
|
2495
|
-
if (canReceiveUserEvent(message.userId))
|
|
2940
|
+
if (!canReceiveUserEvent(message.userId)) return;
|
|
2941
|
+
if (message.event === "step_waiting" && message.waitingFor?.topic === "agg.user.signature") for (const listener of this.signatureWaitListeners) listener(message.orderId);
|
|
2942
|
+
handlers.onOrderEvent?.(message);
|
|
2496
2943
|
},
|
|
2497
2944
|
onOrderEventReceipt: (message) => {
|
|
2498
2945
|
if (canReceiveUserEvent()) handlers.onOrderEventReceipt?.(message);
|
|
@@ -2545,7 +2992,7 @@ var AggClient = class {
|
|
|
2545
2992
|
userId = nextUserId;
|
|
2546
2993
|
if (resetConnection) {
|
|
2547
2994
|
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);
|
|
2995
|
+
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
2996
|
authenticatedUserId = void 0;
|
|
2550
2997
|
awaitingDisconnect = true;
|
|
2551
2998
|
this.tradeSocketAuthenticatedUserId = void 0;
|
|
@@ -2620,69 +3067,80 @@ var AggClient = class {
|
|
|
2620
3067
|
let ordersFilled = false;
|
|
2621
3068
|
const isPostFillFee = (req) => isPostFillFeeRequest(req, ordersFilled);
|
|
2622
3069
|
let orderSubmitted = false;
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
if (
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
};
|
|
3070
|
+
const wakeup = new ExecutionPollWakeup();
|
|
3071
|
+
const orderIds = new Set(first.orderIds ?? []);
|
|
3072
|
+
const onSignatureWait = (orderId) => {
|
|
3073
|
+
if (orderIds.has(orderId)) wakeup.notify();
|
|
3074
|
+
};
|
|
3075
|
+
this.signatureWaitListeners.add(onSignatureWait);
|
|
3076
|
+
try {
|
|
3077
|
+
for (;;) {
|
|
3078
|
+
pending = pending.filter((p) => !declinedOptional.has(p.stepId) && (orderSubmitted || !isOptionalFeeRequest(p)));
|
|
3079
|
+
if (Date.now() > deadline) throw new Error(`Fill for quote ${quoteId} did not finish within the timeout.`);
|
|
3080
|
+
if (pending.length === 0) {
|
|
3081
|
+
await wakeup.wait(nextPollMs);
|
|
3082
|
+
const polled = await this.getExecutionStatus({ quoteId });
|
|
3083
|
+
nextPollMs = Number.isFinite(polled.pollAfterMs) ? Math.min(Math.max(polled.pollAfterMs, MIN_POLL_INTERVAL_MS), MAX_POLL_INTERVAL_MS) : SELF_CUSTODY_POLL_INTERVAL_MS;
|
|
3084
|
+
if (TERMINAL_EXECUTION_STATES.has(polled.overallState)) {
|
|
3085
|
+
if (polled.overallState !== "filled" && polled.overallState !== "partially_filled") throw new Error(`Fill for quote ${quoteId} ended as ${polled.overallState}.`);
|
|
3086
|
+
return {
|
|
3087
|
+
...first,
|
|
3088
|
+
pendingSignatures: []
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
ordersFilled = areOrdersFilled(polled.orders);
|
|
3092
|
+
pending = polled.pendingSignatures ?? [];
|
|
3093
|
+
continue;
|
|
2636
3094
|
}
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
3095
|
+
const key = pending.map((p) => p.stepId).sort().join(",");
|
|
3096
|
+
if (key === lastKey) throw new Error(`Fill for quote ${quoteId} did not advance — still awaiting steps [${key}] after signing them.`);
|
|
3097
|
+
lastKey = key;
|
|
3098
|
+
const signatures = [];
|
|
3099
|
+
const ordered = orderSignatureRound(pending, { orderSubmitted });
|
|
3100
|
+
const userSigners = userSignerAddresses(pending, params.signingAddress);
|
|
3101
|
+
for (const req of ordered) {
|
|
3102
|
+
if (Date.now() > new Date(req.expiresAt).getTime()) {
|
|
3103
|
+
if (req.optional) {
|
|
3104
|
+
declinedOptional.add(req.stepId);
|
|
3105
|
+
continue;
|
|
3106
|
+
}
|
|
3107
|
+
if (isPostFillFee(req)) return {
|
|
3108
|
+
...first,
|
|
3109
|
+
pendingSignatures: []
|
|
3110
|
+
};
|
|
3111
|
+
throw new Error(`Signature request ${req.stepId} for quote ${quoteId} expired at ${req.expiresAt}; the fill must be re-quoted.`);
|
|
3112
|
+
}
|
|
3113
|
+
let signed;
|
|
3114
|
+
try {
|
|
3115
|
+
signed = await withTimeout(signer(req), signatureTimeoutMs(req, userSigners));
|
|
3116
|
+
} catch (err) {
|
|
3117
|
+
if (isPostFillFee(req)) return {
|
|
3118
|
+
...first,
|
|
3119
|
+
pendingSignatures: []
|
|
3120
|
+
};
|
|
3121
|
+
if (!req.optional) throw err;
|
|
2650
3122
|
declinedOptional.add(req.stepId);
|
|
2651
3123
|
continue;
|
|
2652
3124
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
}
|
|
2657
|
-
|
|
3125
|
+
signatures.push(typeof signed === "string" ? {
|
|
3126
|
+
stepId: req.stepId,
|
|
3127
|
+
signature: signed
|
|
3128
|
+
} : {
|
|
3129
|
+
stepId: req.stepId,
|
|
3130
|
+
txHash: signed.txHash
|
|
3131
|
+
});
|
|
2658
3132
|
}
|
|
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);
|
|
3133
|
+
if (signatures.length === 0) {
|
|
3134
|
+
pending = [];
|
|
2669
3135
|
continue;
|
|
2670
3136
|
}
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
} : {
|
|
2675
|
-
stepId: req.stepId,
|
|
2676
|
-
txHash: signed.txHash
|
|
2677
|
-
});
|
|
2678
|
-
}
|
|
2679
|
-
if (signatures.length === 0) {
|
|
2680
|
-
pending = [];
|
|
2681
|
-
continue;
|
|
3137
|
+
const next = await this.submitFillSignatures(quoteId, signatures);
|
|
3138
|
+
if (signatures.some((s) => isOrderSignatureStep(s.stepId))) orderSubmitted = true;
|
|
3139
|
+
pending = next.pendingSignatures ?? [];
|
|
2682
3140
|
}
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
3141
|
+
} finally {
|
|
3142
|
+
this.signatureWaitListeners.delete(onSignatureWait);
|
|
3143
|
+
wakeup.dispose();
|
|
2686
3144
|
}
|
|
2687
3145
|
}
|
|
2688
3146
|
/**
|
|
@@ -2766,6 +3224,14 @@ var AggClient = class {
|
|
|
2766
3224
|
body: JSON.stringify(params)
|
|
2767
3225
|
});
|
|
2768
3226
|
}
|
|
3227
|
+
/** Submit signatures for a parked self-custody withdrawal. Returns whatever is still outstanding. */
|
|
3228
|
+
async submitWithdrawalSignatures(withdrawalId, signatures) {
|
|
3229
|
+
if (!withdrawalId) throw new Error("withdrawalId is required");
|
|
3230
|
+
return this.request(`/execution/withdrawals/${encodeURIComponent(withdrawalId)}/signatures`, {
|
|
3231
|
+
method: "POST",
|
|
3232
|
+
body: JSON.stringify({ signatures })
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
2769
3235
|
/**
|
|
2770
3236
|
* Preview a withdrawal — real fee + receive estimate without creating one.
|
|
2771
3237
|
* Optional: `withdrawManaged` does NOT require a prior preview, so partners
|
|
@@ -3093,6 +3559,7 @@ exports.AccountType = AccountType;
|
|
|
3093
3559
|
exports.AggApiError = AggApiError;
|
|
3094
3560
|
exports.AggClient = AggClient;
|
|
3095
3561
|
exports.AggWebSocket = require_agg_websocket.AggWebSocket;
|
|
3562
|
+
exports.CATALOG_DEFAULT_OVERLAP_MS = CATALOG_DEFAULT_OVERLAP_MS;
|
|
3096
3563
|
exports.CONFIRMED_MATCH_STATUSES = CONFIRMED_MATCH_STATUSES;
|
|
3097
3564
|
exports.CandleBuilder = require_candle_builder.CandleBuilder;
|
|
3098
3565
|
exports.Chain = Chain;
|
|
@@ -3138,6 +3605,7 @@ exports.getWalletAddressFromUserProfile = getWalletAddressFromUserProfile;
|
|
|
3138
3605
|
exports.hasShape = hasShape;
|
|
3139
3606
|
exports.isAggApiError = isAggApiError;
|
|
3140
3607
|
exports.isBalanceRefillWithinDailyCap = isBalanceRefillWithinDailyCap;
|
|
3608
|
+
exports.isCatalogResyncRequired = isCatalogResyncRequired;
|
|
3141
3609
|
exports.isEmail = isEmail;
|
|
3142
3610
|
exports.isEnum = isEnum;
|
|
3143
3611
|
exports.isFiniteNonNeg = isFiniteNonNeg;
|