@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.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as loadImmutableHistory, c as orderbookCrc32, i as snapshotToOrderbook, l as projectedOrderbookChecksum, n as applyOrderbookDelta, r as computeChecksum, s as readChartHistoryText, t as AggWebSocket } from "./agg-websocket-
|
|
1
|
+
import { a as loadImmutableHistory, c as orderbookCrc32, i as snapshotToOrderbook, l as projectedOrderbookChecksum, n as applyOrderbookDelta, r as computeChecksum, s as readChartHistoryText, t as AggWebSocket } from "./agg-websocket-C2neMzGT.mjs";
|
|
2
2
|
import { t as CandleBuilder } from "./candle-builder-BNewIe9K.mjs";
|
|
3
3
|
//#region ../common/src/enums/venue.ts
|
|
4
4
|
/** Use these constants instead of hardcoding "kalshi" or "polymarket". */
|
|
@@ -781,24 +781,6 @@ const isBalanceRefillWithinDailyCap = (minimumRaw, refillAmountRaw, dailyCapRaw,
|
|
|
781
781
|
return BigInt(minimumRaw) <= maximum && BigInt(refillAmountRaw) <= maximum;
|
|
782
782
|
};
|
|
783
783
|
//#endregion
|
|
784
|
-
//#region src/retry-after.ts
|
|
785
|
-
/** Convert explicit retry hints to milliseconds without treating arbitrary strings as dates. */
|
|
786
|
-
function parseRetryAfterSeconds(value) {
|
|
787
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
788
|
-
const milliseconds = value * 1e3;
|
|
789
|
-
return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
|
|
790
|
-
}
|
|
791
|
-
function parseRetryAfterHeader(value, now = Date.now()) {
|
|
792
|
-
if (!value) return void 0;
|
|
793
|
-
const text = value.trim();
|
|
794
|
-
if (/^\d+$/.test(text)) return parseRetryAfterSeconds(Number(text));
|
|
795
|
-
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;
|
|
796
|
-
const timestamp = Date.parse(text);
|
|
797
|
-
if (!Number.isFinite(timestamp) || new Date(timestamp).toUTCString() !== text) return void 0;
|
|
798
|
-
const milliseconds = timestamp - now;
|
|
799
|
-
return Number.isSafeInteger(milliseconds) && milliseconds >= 0 ? milliseconds : void 0;
|
|
800
|
-
}
|
|
801
|
-
//#endregion
|
|
802
784
|
//#region src/errors.ts
|
|
803
785
|
/**
|
|
804
786
|
* Thrown when the server returns `challenge_required` during auth start.
|
|
@@ -845,6 +827,163 @@ function isAggApiError(value) {
|
|
|
845
827
|
return value instanceof AggApiError;
|
|
846
828
|
}
|
|
847
829
|
//#endregion
|
|
830
|
+
//#region src/venue-event-read-scheduler.ts
|
|
831
|
+
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
832
|
+
const abortError = () => Object.assign(/* @__PURE__ */ new Error("The request was aborted"), { name: "AbortError" });
|
|
833
|
+
/** Instance-local admission for event reads only. Never retries or retains successful data. */
|
|
834
|
+
var VenueEventReadScheduler = class {
|
|
835
|
+
constructor(concurrency = 4, queueLimit = 128) {
|
|
836
|
+
this.concurrency = concurrency;
|
|
837
|
+
this.queueLimit = queueLimit;
|
|
838
|
+
this.jobs = /* @__PURE__ */ new Map();
|
|
839
|
+
this.queue = [];
|
|
840
|
+
this.active = 0;
|
|
841
|
+
this.cooldownUntil = 0;
|
|
842
|
+
}
|
|
843
|
+
read(key, run, signal) {
|
|
844
|
+
if (signal?.aborted) return Promise.reject(abortError());
|
|
845
|
+
let job = this.jobs.get(key);
|
|
846
|
+
if (!job) {
|
|
847
|
+
this.queue = this.queue.filter((entry) => entry.subscribers > 0);
|
|
848
|
+
if (this.queue.length >= this.queueLimit) return Promise.reject(new AggApiError("Too many pending event reads", 429, {
|
|
849
|
+
code: "client_read_queue_full",
|
|
850
|
+
retryable: true,
|
|
851
|
+
retryAfterMs: Math.max(1e3, this.cooldownUntil - Date.now())
|
|
852
|
+
}));
|
|
853
|
+
let resolve;
|
|
854
|
+
let reject;
|
|
855
|
+
job = {
|
|
856
|
+
key,
|
|
857
|
+
run,
|
|
858
|
+
promise: new Promise((res, rej) => {
|
|
859
|
+
resolve = res;
|
|
860
|
+
reject = rej;
|
|
861
|
+
}),
|
|
862
|
+
resolve,
|
|
863
|
+
reject,
|
|
864
|
+
controller: new AbortController(),
|
|
865
|
+
subscribers: 0
|
|
866
|
+
};
|
|
867
|
+
this.jobs.set(key, job);
|
|
868
|
+
this.queue.push(job);
|
|
869
|
+
}
|
|
870
|
+
const entry = job;
|
|
871
|
+
entry.subscribers += 1;
|
|
872
|
+
const result = new Promise((resolve, reject) => {
|
|
873
|
+
let settled = false;
|
|
874
|
+
const finish = (callback) => {
|
|
875
|
+
if (settled) return;
|
|
876
|
+
settled = true;
|
|
877
|
+
signal?.removeEventListener("abort", onAbort);
|
|
878
|
+
entry.subscribers -= 1;
|
|
879
|
+
if (entry.subscribers === 0) {
|
|
880
|
+
if (this.jobs.get(key) === entry) this.jobs.delete(key);
|
|
881
|
+
entry.controller.abort();
|
|
882
|
+
this.pump();
|
|
883
|
+
}
|
|
884
|
+
callback();
|
|
885
|
+
};
|
|
886
|
+
const onAbort = () => finish(() => reject(abortError()));
|
|
887
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
888
|
+
entry.promise.then((value) => finish(() => resolve(value)), (error) => finish(() => reject(error)));
|
|
889
|
+
});
|
|
890
|
+
this.pump();
|
|
891
|
+
return result;
|
|
892
|
+
}
|
|
893
|
+
reset() {
|
|
894
|
+
for (const job of this.jobs.values()) {
|
|
895
|
+
job.controller.abort();
|
|
896
|
+
job.reject(abortError());
|
|
897
|
+
}
|
|
898
|
+
this.jobs.clear();
|
|
899
|
+
this.queue = [];
|
|
900
|
+
this.cooldownUntil = 0;
|
|
901
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
902
|
+
this.timer = void 0;
|
|
903
|
+
}
|
|
904
|
+
pump() {
|
|
905
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
906
|
+
this.timer = void 0;
|
|
907
|
+
this.queue = this.queue.filter((job) => job.subscribers > 0);
|
|
908
|
+
if (this.queue.length === 0 || this.active >= this.concurrency) return;
|
|
909
|
+
const delay = this.cooldownUntil - Date.now();
|
|
910
|
+
if (delay > 0) {
|
|
911
|
+
this.timer = setTimeout(() => this.pump(), Math.min(delay, MAX_TIMER_DELAY_MS));
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
while (this.active < this.concurrency && this.queue.length > 0) {
|
|
915
|
+
const job = this.queue.shift();
|
|
916
|
+
this.active += 1;
|
|
917
|
+
this.execute(job);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
async execute(job) {
|
|
921
|
+
try {
|
|
922
|
+
job.resolve(await job.run(job.controller.signal));
|
|
923
|
+
} catch (error) {
|
|
924
|
+
if (!job.controller.signal.aborted && isAggApiError(error) && error.status === 429) {
|
|
925
|
+
const hint = error.retryAfterMs;
|
|
926
|
+
const delay = typeof hint === "number" && Number.isSafeInteger(hint) && hint >= 0 ? hint : 1e3;
|
|
927
|
+
this.cooldownUntil = Math.max(this.cooldownUntil, Date.now() + delay + 1 + Math.floor(Math.random() * 250));
|
|
928
|
+
}
|
|
929
|
+
job.reject(error);
|
|
930
|
+
} finally {
|
|
931
|
+
if (this.jobs.get(job.key) === job) this.jobs.delete(job.key);
|
|
932
|
+
this.active -= 1;
|
|
933
|
+
this.pump();
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
//#endregion
|
|
938
|
+
//#region src/execution-poll-wakeup.ts
|
|
939
|
+
/** A coalesced socket hint can interrupt one poll delay or precede it. */
|
|
940
|
+
var ExecutionPollWakeup = class {
|
|
941
|
+
constructor() {
|
|
942
|
+
this.pending = false;
|
|
943
|
+
}
|
|
944
|
+
notify() {
|
|
945
|
+
if (this.resume) this.resume();
|
|
946
|
+
else this.pending = true;
|
|
947
|
+
}
|
|
948
|
+
wait(ms) {
|
|
949
|
+
if (this.pending) {
|
|
950
|
+
this.pending = false;
|
|
951
|
+
return Promise.resolve();
|
|
952
|
+
}
|
|
953
|
+
return new Promise((resolve) => {
|
|
954
|
+
const finish = () => {
|
|
955
|
+
clearTimeout(timer);
|
|
956
|
+
this.resume = void 0;
|
|
957
|
+
resolve();
|
|
958
|
+
};
|
|
959
|
+
const timer = setTimeout(finish, ms);
|
|
960
|
+
this.resume = finish;
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
dispose() {
|
|
964
|
+
this.resume?.();
|
|
965
|
+
this.pending = false;
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
//#endregion
|
|
969
|
+
//#region src/retry-after.ts
|
|
970
|
+
/** Convert explicit retry hints to milliseconds without treating arbitrary strings as dates. */
|
|
971
|
+
function parseRetryAfterSeconds(value) {
|
|
972
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
973
|
+
const milliseconds = value * 1e3;
|
|
974
|
+
return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
|
|
975
|
+
}
|
|
976
|
+
function parseRetryAfterHeader(value, now = Date.now()) {
|
|
977
|
+
if (!value) return void 0;
|
|
978
|
+
const text = value.trim();
|
|
979
|
+
if (/^\d+$/.test(text)) return parseRetryAfterSeconds(Number(text));
|
|
980
|
+
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;
|
|
981
|
+
const timestamp = Date.parse(text);
|
|
982
|
+
if (!Number.isFinite(timestamp) || new Date(timestamp).toUTCString() !== text) return void 0;
|
|
983
|
+
const milliseconds = timestamp - now;
|
|
984
|
+
return Number.isSafeInteger(milliseconds) && milliseconds >= 0 ? milliseconds : void 0;
|
|
985
|
+
}
|
|
986
|
+
//#endregion
|
|
848
987
|
//#region src/eip55.ts
|
|
849
988
|
/**
|
|
850
989
|
* EIP-55 address checksumming, self-contained.
|
|
@@ -1131,6 +1270,227 @@ function withTimeout(promise, ms) {
|
|
|
1131
1270
|
});
|
|
1132
1271
|
}
|
|
1133
1272
|
//#endregion
|
|
1273
|
+
//#region src/catalog-feed.ts
|
|
1274
|
+
/** Recommended REST/WS overlap: re-read this much before the resume point. */
|
|
1275
|
+
const CATALOG_DEFAULT_OVERLAP_MS = 6e4;
|
|
1276
|
+
/** Live frames held during a catch-up. Past this the buffer is dropped and the catch-up re-reads. */
|
|
1277
|
+
const LIVE_BUFFER_MAX = 1e4;
|
|
1278
|
+
const RETRY_BASE_MS = 1e3;
|
|
1279
|
+
const RETRY_MAX_MS = 3e4;
|
|
1280
|
+
/** Live applies between dedupe-map prunes. */
|
|
1281
|
+
const PRUNE_EVERY = 1e3;
|
|
1282
|
+
/**
|
|
1283
|
+
* `GET /catalog/changes` answers 410 `resync_required` when the position is
|
|
1284
|
+
* older than the 24h retention window: re-list with the list endpoints, then
|
|
1285
|
+
* resume from the time that re-list started.
|
|
1286
|
+
*/
|
|
1287
|
+
function isCatalogResyncRequired(err) {
|
|
1288
|
+
return isAggApiError(err) && (err.code === "resync_required" || err.status === 410);
|
|
1289
|
+
}
|
|
1290
|
+
/** See {@link AggClient.watchCatalog}. */
|
|
1291
|
+
function watchCatalog(deps, options) {
|
|
1292
|
+
if (Number.isNaN(toMs(options.since))) throw new Error("watchCatalog: `since` must be a valid date or ISO string");
|
|
1293
|
+
const overlapMs = options.overlapMs ?? 6e4;
|
|
1294
|
+
const filter = {
|
|
1295
|
+
...options.venues?.length ? { venues: options.venues } : {},
|
|
1296
|
+
...options.entities?.length ? { entities: options.entities } : {}
|
|
1297
|
+
};
|
|
1298
|
+
let sinceMs = toMs(options.since);
|
|
1299
|
+
let lastChangedAtMs;
|
|
1300
|
+
let lastChangedAt;
|
|
1301
|
+
/**
|
|
1302
|
+
* Dedupe state per `${entity}:${id}`. Pruned below `lastChangedAt - 2 *
|
|
1303
|
+
* overlap`: catch-ups re-read from `lastChangedAt - overlap` and REST
|
|
1304
|
+
* serves each entity's latest state, so nothing older can come back.
|
|
1305
|
+
*/
|
|
1306
|
+
const held = /* @__PURE__ */ new Map();
|
|
1307
|
+
/** matchedOnly: ids the caller holds, so a leave or delete still reaches them. Not pruned. */
|
|
1308
|
+
const delivered = /* @__PURE__ */ new Set();
|
|
1309
|
+
let appliedSincePrune = 0;
|
|
1310
|
+
let buffering = true;
|
|
1311
|
+
let buffer = [];
|
|
1312
|
+
let bufferOverflowed = false;
|
|
1313
|
+
let running = false;
|
|
1314
|
+
let rerun = false;
|
|
1315
|
+
let resyncRequired = false;
|
|
1316
|
+
let stopped = false;
|
|
1317
|
+
let retryTimer = null;
|
|
1318
|
+
let wakeRetry = null;
|
|
1319
|
+
const abort = new AbortController();
|
|
1320
|
+
let resolveReady;
|
|
1321
|
+
const ready = new Promise((resolve) => {
|
|
1322
|
+
resolveReady = resolve;
|
|
1323
|
+
});
|
|
1324
|
+
const report = (err) => {
|
|
1325
|
+
if (stopped) return;
|
|
1326
|
+
try {
|
|
1327
|
+
options.onError?.(err);
|
|
1328
|
+
} catch {}
|
|
1329
|
+
};
|
|
1330
|
+
const prune = () => {
|
|
1331
|
+
appliedSincePrune = 0;
|
|
1332
|
+
if (lastChangedAtMs === void 0) return;
|
|
1333
|
+
const floor = lastChangedAtMs - 2 * overlapMs;
|
|
1334
|
+
for (const [key, h] of held) if (h.changedAtMs < floor) held.delete(key);
|
|
1335
|
+
};
|
|
1336
|
+
const apply = (change) => {
|
|
1337
|
+
const key = `${change.entity}:${change.id}`;
|
|
1338
|
+
const changedAtMs = toMs(change.changedAt);
|
|
1339
|
+
if (Number.isNaN(changedAtMs)) return;
|
|
1340
|
+
const prev = held.get(key);
|
|
1341
|
+
if (prev && (change.version < prev.version || change.version === prev.version && changedAtMs <= prev.changedAtMs)) return;
|
|
1342
|
+
held.set(key, {
|
|
1343
|
+
version: change.version,
|
|
1344
|
+
changedAtMs
|
|
1345
|
+
});
|
|
1346
|
+
if (lastChangedAtMs === void 0 || changedAtMs > lastChangedAtMs) {
|
|
1347
|
+
lastChangedAtMs = changedAtMs;
|
|
1348
|
+
lastChangedAt = change.changedAt;
|
|
1349
|
+
}
|
|
1350
|
+
if (++appliedSincePrune >= PRUNE_EVERY) prune();
|
|
1351
|
+
if (options.matchedOnly) {
|
|
1352
|
+
if ((change.event ?? change.market)?.matched === true && change.op !== "deleted") delivered.add(key);
|
|
1353
|
+
else if (delivered.has(key)) delivered.delete(key);
|
|
1354
|
+
else return;
|
|
1355
|
+
}
|
|
1356
|
+
const { type: _type, ...item } = change;
|
|
1357
|
+
try {
|
|
1358
|
+
options.onChange(item);
|
|
1359
|
+
} catch (err) {
|
|
1360
|
+
report(err);
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
const onLive = (frame) => {
|
|
1364
|
+
if (stopped) return;
|
|
1365
|
+
if (!buffering) {
|
|
1366
|
+
apply(frame);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
if (buffer.length >= LIVE_BUFFER_MAX) {
|
|
1370
|
+
buffer = [];
|
|
1371
|
+
bufferOverflowed = true;
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
buffer.push(frame);
|
|
1375
|
+
};
|
|
1376
|
+
const readAll = async () => {
|
|
1377
|
+
const from = (lastChangedAtMs ?? sinceMs) - overlapMs;
|
|
1378
|
+
let cursor;
|
|
1379
|
+
for (;;) {
|
|
1380
|
+
const page = await deps.getChanges({
|
|
1381
|
+
...cursor ? { cursor } : { since: new Date(from).toISOString() },
|
|
1382
|
+
...options.pageLimit ? { limit: options.pageLimit } : {},
|
|
1383
|
+
...filter
|
|
1384
|
+
}, abort.signal);
|
|
1385
|
+
if (stopped) return;
|
|
1386
|
+
for (const change of page.data) apply(change);
|
|
1387
|
+
if (!page.hasMore) return;
|
|
1388
|
+
cursor = page.nextCursor;
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
1392
|
+
wakeRetry = resolve;
|
|
1393
|
+
retryTimer = setTimeout(() => {
|
|
1394
|
+
retryTimer = null;
|
|
1395
|
+
wakeRetry = null;
|
|
1396
|
+
resolve();
|
|
1397
|
+
}, ms);
|
|
1398
|
+
});
|
|
1399
|
+
const catchUp = async () => {
|
|
1400
|
+
running = true;
|
|
1401
|
+
buffering = true;
|
|
1402
|
+
let attempt = 0;
|
|
1403
|
+
try {
|
|
1404
|
+
do {
|
|
1405
|
+
rerun = false;
|
|
1406
|
+
bufferOverflowed = false;
|
|
1407
|
+
try {
|
|
1408
|
+
await readAll();
|
|
1409
|
+
} catch (err) {
|
|
1410
|
+
if (stopped) return;
|
|
1411
|
+
if (isCatalogResyncRequired(err)) {
|
|
1412
|
+
resyncRequired = true;
|
|
1413
|
+
if (options.onResyncRequired) options.onResyncRequired();
|
|
1414
|
+
else report(err);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
report(err);
|
|
1418
|
+
const step = Math.min(RETRY_BASE_MS * 2 ** attempt++, RETRY_MAX_MS);
|
|
1419
|
+
await sleep(Math.round(step * (.5 + Math.random() * .5)));
|
|
1420
|
+
if (stopped) return;
|
|
1421
|
+
rerun = true;
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
if (stopped) return;
|
|
1425
|
+
attempt = 0;
|
|
1426
|
+
const drained = buffer;
|
|
1427
|
+
buffer = [];
|
|
1428
|
+
for (const frame of drained) apply(frame);
|
|
1429
|
+
if (bufferOverflowed) rerun = true;
|
|
1430
|
+
} while (rerun && !stopped && !resyncRequired);
|
|
1431
|
+
if (stopped || resyncRequired) return;
|
|
1432
|
+
prune();
|
|
1433
|
+
buffering = false;
|
|
1434
|
+
options.onSynced?.({ lastChangedAt });
|
|
1435
|
+
resolveReady();
|
|
1436
|
+
} finally {
|
|
1437
|
+
running = false;
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
const requestCatchUp = () => {
|
|
1441
|
+
if (stopped || resyncRequired) return;
|
|
1442
|
+
if (running) {
|
|
1443
|
+
rerun = true;
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
catchUp().catch(report);
|
|
1447
|
+
};
|
|
1448
|
+
const owned = !options.socket;
|
|
1449
|
+
let socket = null;
|
|
1450
|
+
let unsubscribe = null;
|
|
1451
|
+
try {
|
|
1452
|
+
socket = options.socket ?? deps.createSocket();
|
|
1453
|
+
unsubscribe = socket.subscribeCatalog(filter, onLive, {
|
|
1454
|
+
onSubscribed: requestCatchUp,
|
|
1455
|
+
onInterrupted: requestCatchUp,
|
|
1456
|
+
onError: report
|
|
1457
|
+
});
|
|
1458
|
+
if (owned) socket.connect();
|
|
1459
|
+
} catch (err) {
|
|
1460
|
+
report(err);
|
|
1461
|
+
}
|
|
1462
|
+
return {
|
|
1463
|
+
stop() {
|
|
1464
|
+
if (stopped) return;
|
|
1465
|
+
stopped = true;
|
|
1466
|
+
abort.abort();
|
|
1467
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
1468
|
+
retryTimer = null;
|
|
1469
|
+
wakeRetry?.();
|
|
1470
|
+
wakeRetry = null;
|
|
1471
|
+
buffer = [];
|
|
1472
|
+
unsubscribe?.();
|
|
1473
|
+
if (owned) socket?.destroy();
|
|
1474
|
+
},
|
|
1475
|
+
get lastChangedAt() {
|
|
1476
|
+
return lastChangedAt;
|
|
1477
|
+
},
|
|
1478
|
+
resync(since) {
|
|
1479
|
+
if (stopped) return;
|
|
1480
|
+
if (Number.isNaN(toMs(since))) throw new Error("resync: `since` must be a valid date or ISO string");
|
|
1481
|
+
sinceMs = toMs(since);
|
|
1482
|
+
lastChangedAtMs = void 0;
|
|
1483
|
+
lastChangedAt = void 0;
|
|
1484
|
+
resyncRequired = false;
|
|
1485
|
+
requestCatchUp();
|
|
1486
|
+
},
|
|
1487
|
+
ready
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
function toMs(value) {
|
|
1491
|
+
return value instanceof Date ? value.getTime() : Date.parse(value);
|
|
1492
|
+
}
|
|
1493
|
+
//#endregion
|
|
1134
1494
|
//#region src/client.ts
|
|
1135
1495
|
const COOKIE_REFRESH_DELIVERY = "cookie-refresh";
|
|
1136
1496
|
const DEFAULT_MIDPOINT_IDS_PER_REQUEST = 75;
|
|
@@ -1221,9 +1581,12 @@ const AUTH_PATHS = /* @__PURE__ */ new Set([
|
|
|
1221
1581
|
]);
|
|
1222
1582
|
var AggClient = class {
|
|
1223
1583
|
constructor(options) {
|
|
1584
|
+
this.venueEventReads = new VenueEventReadScheduler();
|
|
1585
|
+
this.eventReadSession = 0;
|
|
1224
1586
|
this.listeners = /* @__PURE__ */ new Set();
|
|
1225
1587
|
this.webSocketAuthCleanups = /* @__PURE__ */ new Set();
|
|
1226
1588
|
this.tradeSockets = /* @__PURE__ */ new Set();
|
|
1589
|
+
this.signatureWaitListeners = /* @__PURE__ */ new Set();
|
|
1227
1590
|
this._authStatus = "unauthenticated";
|
|
1228
1591
|
this.authChannel = null;
|
|
1229
1592
|
this.pendingRefresh = null;
|
|
@@ -1320,6 +1683,7 @@ var AggClient = class {
|
|
|
1320
1683
|
this.authChannel.onmessage = (event) => {
|
|
1321
1684
|
const { type } = event.data ?? {};
|
|
1322
1685
|
if (type === "logout") {
|
|
1686
|
+
this.invalidateEventReadSession();
|
|
1323
1687
|
this.accessToken = void 0;
|
|
1324
1688
|
this.accessTokenUserId = void 0;
|
|
1325
1689
|
this.refreshTokenValue = void 0;
|
|
@@ -1335,6 +1699,7 @@ var AggClient = class {
|
|
|
1335
1699
|
}
|
|
1336
1700
|
/** Release resources (BroadcastChannel, etc.). Safe to call multiple times. */
|
|
1337
1701
|
destroy() {
|
|
1702
|
+
this.venueEventReads.reset();
|
|
1338
1703
|
this.authChannel?.close();
|
|
1339
1704
|
this.authChannel = null;
|
|
1340
1705
|
for (const cleanup of this.webSocketAuthCleanups) cleanup();
|
|
@@ -1347,15 +1712,17 @@ var AggClient = class {
|
|
|
1347
1712
|
* @param init - Fetch init options, plus optional `query` object for URL search params
|
|
1348
1713
|
*/
|
|
1349
1714
|
async request(path, init, decode) {
|
|
1715
|
+
const requestSession = this.eventReadSession;
|
|
1350
1716
|
const response = await this.rawFetch(path, init);
|
|
1351
|
-
if (response.status === 401 && this.accessToken && !AUTH_PATHS.has(path)) try {
|
|
1717
|
+
if (response.status === 401 && this.accessToken && !AUTH_PATHS.has(path) && this.eventReadSession === requestSession) try {
|
|
1352
1718
|
await this.refreshWithDedup();
|
|
1719
|
+
if (this.eventReadSession !== requestSession) throw Object.assign(/* @__PURE__ */ new Error("Authentication changed during refresh"), { name: "AbortError" });
|
|
1353
1720
|
const retryResponse = await this.rawFetch(path, init);
|
|
1354
1721
|
if (!retryResponse.ok) return this.throwResponseError(retryResponse);
|
|
1355
1722
|
if (retryResponse.status === 204) return void 0;
|
|
1356
1723
|
return decode ? decode(retryResponse) : retryResponse.json();
|
|
1357
1724
|
} catch {
|
|
1358
|
-
this.clearAccessToken();
|
|
1725
|
+
if (!init?.signal?.aborted && this.eventReadSession === requestSession) this.clearAccessToken();
|
|
1359
1726
|
return this.throwResponseError(response);
|
|
1360
1727
|
}
|
|
1361
1728
|
if (!response.ok) return this.throwResponseError(response);
|
|
@@ -1411,9 +1778,12 @@ var AggClient = class {
|
|
|
1411
1778
|
* only one refresh call is made and the rest wait for it.
|
|
1412
1779
|
*/
|
|
1413
1780
|
refreshWithDedup() {
|
|
1414
|
-
if (!this.pendingRefresh)
|
|
1415
|
-
this.
|
|
1416
|
-
|
|
1781
|
+
if (!this.pendingRefresh) {
|
|
1782
|
+
const pending = this.refreshAccessToken().finally(() => {
|
|
1783
|
+
if (this.pendingRefresh === pending) this.pendingRefresh = null;
|
|
1784
|
+
});
|
|
1785
|
+
this.pendingRefresh = pending;
|
|
1786
|
+
}
|
|
1417
1787
|
return this.pendingRefresh;
|
|
1418
1788
|
}
|
|
1419
1789
|
buildAuthRequestInit(init, options) {
|
|
@@ -1444,7 +1814,10 @@ var AggClient = class {
|
|
|
1444
1814
|
}
|
|
1445
1815
|
/** Set the user access token (for future user-tier auth). */
|
|
1446
1816
|
setAccessToken(token) {
|
|
1447
|
-
if (this.accessToken !== token)
|
|
1817
|
+
if (this.accessToken !== token) {
|
|
1818
|
+
this.invalidateEventReadSession();
|
|
1819
|
+
this.accessTokenUserId = void 0;
|
|
1820
|
+
}
|
|
1448
1821
|
this.accessToken = token;
|
|
1449
1822
|
this._authStatus = "authenticated";
|
|
1450
1823
|
this.persistSession();
|
|
@@ -1452,8 +1825,12 @@ var AggClient = class {
|
|
|
1452
1825
|
}
|
|
1453
1826
|
/** Set the current auth session tokens and optionally seed the user profile. */
|
|
1454
1827
|
setSession(session) {
|
|
1828
|
+
this.applySession(session);
|
|
1829
|
+
}
|
|
1830
|
+
applySession(session, tokenRenewal = false) {
|
|
1455
1831
|
const wasAuthenticated = this.isAuthenticated;
|
|
1456
|
-
this.accessTokenUserId
|
|
1832
|
+
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();
|
|
1833
|
+
this.accessTokenUserId = session.user?.id ?? (tokenRenewal || this.accessToken === session.accessToken ? this.accessTokenUserId : void 0);
|
|
1457
1834
|
this.accessToken = session.accessToken;
|
|
1458
1835
|
if ("refreshToken" in session) this.refreshTokenValue = session.refreshToken;
|
|
1459
1836
|
if (session.user !== void 0) this.currentUser = normalizeSessionUser(session.user, this.currentUser);
|
|
@@ -1464,6 +1841,7 @@ var AggClient = class {
|
|
|
1464
1841
|
}
|
|
1465
1842
|
/** Clear the user access token, refresh token, and user info. Broadcasts cross-tab logout. */
|
|
1466
1843
|
clearAccessToken() {
|
|
1844
|
+
this.invalidateEventReadSession();
|
|
1467
1845
|
this.accessToken = void 0;
|
|
1468
1846
|
this.accessTokenUserId = void 0;
|
|
1469
1847
|
this.refreshTokenValue = void 0;
|
|
@@ -1473,6 +1851,11 @@ var AggClient = class {
|
|
|
1473
1851
|
this.notifyListeners();
|
|
1474
1852
|
this.broadcastAuth("logout");
|
|
1475
1853
|
}
|
|
1854
|
+
invalidateEventReadSession() {
|
|
1855
|
+
this.eventReadSession += 1;
|
|
1856
|
+
this.pendingRefresh = null;
|
|
1857
|
+
this.venueEventReads.reset();
|
|
1858
|
+
}
|
|
1476
1859
|
broadcastAuth(type) {
|
|
1477
1860
|
try {
|
|
1478
1861
|
this.authChannel?.postMessage({ type });
|
|
@@ -1636,16 +2019,18 @@ var AggClient = class {
|
|
|
1636
2019
|
}
|
|
1637
2020
|
/** Refresh the access token using the stored refresh token. */
|
|
1638
2021
|
async refreshAccessToken() {
|
|
2022
|
+
const refreshSession = this.eventReadSession;
|
|
1639
2023
|
if (this.authDelivery !== COOKIE_REFRESH_DELIVERY && !this.refreshTokenValue) throw new Error("No refresh token available");
|
|
1640
2024
|
const res = await this.request("/auth/token/refresh", this.buildAuthRequestInit({
|
|
1641
2025
|
method: "POST",
|
|
1642
2026
|
body: JSON.stringify(this.authDelivery === COOKIE_REFRESH_DELIVERY ? {} : { refreshToken: this.refreshTokenValue })
|
|
1643
2027
|
}));
|
|
1644
|
-
this.
|
|
2028
|
+
if (this.eventReadSession !== refreshSession) throw Object.assign(/* @__PURE__ */ new Error("Authentication changed during refresh"), { name: "AbortError" });
|
|
2029
|
+
this.applySession({
|
|
1645
2030
|
accessToken: res.accessToken,
|
|
1646
2031
|
refreshToken: res.refreshToken,
|
|
1647
2032
|
user: res.user
|
|
1648
|
-
});
|
|
2033
|
+
}, true);
|
|
1649
2034
|
return res.accessToken;
|
|
1650
2035
|
}
|
|
1651
2036
|
/** Get the current user's profile. */
|
|
@@ -2017,6 +2402,7 @@ var AggClient = class {
|
|
|
2017
2402
|
if (options?.minVolume != null && options.minVolume > 0) query.minVolume = String(options.minVolume);
|
|
2018
2403
|
if (options?.endDateFrom) query.endDateFrom = options.endDateFrom;
|
|
2019
2404
|
if (options?.grouped) query.grouped = "true";
|
|
2405
|
+
if (options?.live) query.live = "true";
|
|
2020
2406
|
return this.request("/venue-events", {
|
|
2021
2407
|
query: Object.keys(query).length > 0 ? query : void 0,
|
|
2022
2408
|
signal: options?.signal
|
|
@@ -2033,14 +2419,24 @@ var AggClient = class {
|
|
|
2033
2419
|
});
|
|
2034
2420
|
}
|
|
2035
2421
|
async getVenueEventById(id, options) {
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
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);
|
|
2044
2440
|
}
|
|
2045
2441
|
/** Get normalized live score state for sports events. Requires appId or admin auth. */
|
|
2046
2442
|
async getSportsLive(eventIds, options) {
|
|
@@ -2410,27 +2806,77 @@ var AggClient = class {
|
|
|
2410
2806
|
});
|
|
2411
2807
|
}
|
|
2412
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 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
|
+
/**
|
|
2413
2865
|
* Create an AggWebSocket instance configured with this client's appId and current auth session.
|
|
2414
2866
|
* Session changes update this socket until it or the client is destroyed.
|
|
2415
2867
|
* Requires `wsUrl` to be set in the client options.
|
|
2416
2868
|
*/
|
|
2417
2869
|
createWebSocket(callbacks, options) {
|
|
2418
2870
|
if (!this.wsUrl) throw new Error("AggClient.createWebSocket() requires wsUrl to be set in client options");
|
|
2419
|
-
const url = new URL(this.wsUrl);
|
|
2420
|
-
if (this._appId) url.searchParams.set("appId", this._appId);
|
|
2421
2871
|
const createSocket = this.webSocketFactory ?? ((socketOptions) => new AggWebSocket(socketOptions));
|
|
2422
2872
|
const watchdogHeaders = {};
|
|
2423
2873
|
if (this._appId) watchdogHeaders["x-app-id"] = this._appId;
|
|
2424
2874
|
if (this.apiKey) watchdogHeaders["x-app-api-key"] = this.apiKey;
|
|
2425
|
-
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
2426
|
-
const headers = this.apiKey && !isBrowser ? { "x-app-api-key": this.apiKey } : void 0;
|
|
2427
2875
|
const socket = createSocket({
|
|
2428
|
-
|
|
2876
|
+
...this.webSocketOptions(options?.webSocketImpl),
|
|
2429
2877
|
callbacks,
|
|
2430
2878
|
initialToken: this.accessToken,
|
|
2431
2879
|
enableLogs: options?.enableLogs ?? false,
|
|
2432
|
-
...headers ? { headers } : {},
|
|
2433
|
-
...options?.webSocketImpl ? { webSocketImpl: options.webSocketImpl } : {},
|
|
2434
2880
|
bookWatchdog: options?.bookWatchdog === false ? void 0 : {
|
|
2435
2881
|
baseUrl: this.baseUrl,
|
|
2436
2882
|
headers: watchdogHeaders,
|
|
@@ -2491,7 +2937,9 @@ var AggClient = class {
|
|
|
2491
2937
|
handlers.onAuthenticated?.(message);
|
|
2492
2938
|
},
|
|
2493
2939
|
onOrderEvent: (message) => {
|
|
2494
|
-
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);
|
|
2495
2943
|
},
|
|
2496
2944
|
onOrderEventReceipt: (message) => {
|
|
2497
2945
|
if (canReceiveUserEvent()) handlers.onOrderEventReceipt?.(message);
|
|
@@ -2544,7 +2992,7 @@ var AggClient = class {
|
|
|
2544
2992
|
userId = nextUserId;
|
|
2545
2993
|
if (resetConnection) {
|
|
2546
2994
|
const diagnostics = socket.getDiagnostics();
|
|
2547
|
-
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);
|
|
2548
2996
|
authenticatedUserId = void 0;
|
|
2549
2997
|
awaitingDisconnect = true;
|
|
2550
2998
|
this.tradeSocketAuthenticatedUserId = void 0;
|
|
@@ -2619,69 +3067,80 @@ var AggClient = class {
|
|
|
2619
3067
|
let ordersFilled = false;
|
|
2620
3068
|
const isPostFillFee = (req) => isPostFillFeeRequest(req, ordersFilled);
|
|
2621
3069
|
let orderSubmitted = false;
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
if (
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
};
|
|
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;
|
|
2635
3094
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
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;
|
|
2649
3122
|
declinedOptional.add(req.stepId);
|
|
2650
3123
|
continue;
|
|
2651
3124
|
}
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
}
|
|
2656
|
-
|
|
3125
|
+
signatures.push(typeof signed === "string" ? {
|
|
3126
|
+
stepId: req.stepId,
|
|
3127
|
+
signature: signed
|
|
3128
|
+
} : {
|
|
3129
|
+
stepId: req.stepId,
|
|
3130
|
+
txHash: signed.txHash
|
|
3131
|
+
});
|
|
2657
3132
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
signed = await withTimeout(signer(req), signatureTimeoutMs(req, userSigners));
|
|
2661
|
-
} catch (err) {
|
|
2662
|
-
if (isPostFillFee(req)) return {
|
|
2663
|
-
...first,
|
|
2664
|
-
pendingSignatures: []
|
|
2665
|
-
};
|
|
2666
|
-
if (!req.optional) throw err;
|
|
2667
|
-
declinedOptional.add(req.stepId);
|
|
3133
|
+
if (signatures.length === 0) {
|
|
3134
|
+
pending = [];
|
|
2668
3135
|
continue;
|
|
2669
3136
|
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
} : {
|
|
2674
|
-
stepId: req.stepId,
|
|
2675
|
-
txHash: signed.txHash
|
|
2676
|
-
});
|
|
2677
|
-
}
|
|
2678
|
-
if (signatures.length === 0) {
|
|
2679
|
-
pending = [];
|
|
2680
|
-
continue;
|
|
3137
|
+
const next = await this.submitFillSignatures(quoteId, signatures);
|
|
3138
|
+
if (signatures.some((s) => isOrderSignatureStep(s.stepId))) orderSubmitted = true;
|
|
3139
|
+
pending = next.pendingSignatures ?? [];
|
|
2681
3140
|
}
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
3141
|
+
} finally {
|
|
3142
|
+
this.signatureWaitListeners.delete(onSignatureWait);
|
|
3143
|
+
wakeup.dispose();
|
|
2685
3144
|
}
|
|
2686
3145
|
}
|
|
2687
3146
|
/**
|
|
@@ -2765,6 +3224,14 @@ var AggClient = class {
|
|
|
2765
3224
|
body: JSON.stringify(params)
|
|
2766
3225
|
});
|
|
2767
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
|
+
}
|
|
2768
3235
|
/**
|
|
2769
3236
|
* Preview a withdrawal — real fee + receive estimate without creating one.
|
|
2770
3237
|
* Optional: `withdrawManaged` does NOT require a prior preview, so partners
|
|
@@ -3086,6 +3553,6 @@ function createAggClient(options) {
|
|
|
3086
3553
|
return new AggClient(options);
|
|
3087
3554
|
}
|
|
3088
3555
|
//#endregion
|
|
3089
|
-
export { ACTIVE_VENUES, AccountProvider, AccountType, AggApiError, AggClient, AggWebSocket, CONFIRMED_MATCH_STATUSES, CandleBuilder, Chain, DEFAULT_BALANCE_REFILL_DAILY_CAP_RAW, DEFAULT_BALANCE_REFILL_MAX_FEE_RAW, IMAGE_SIZES, INACTIVE_VENUES, ImageSize, LIMIT_PRICE_RAW_SCALE, MarketStatus, MatchStatus, MatchType, OPTIONAL_FEE_SIGNATURE_TIMEOUT_MS, OPTIONAL_SIGNATURE_TIMEOUT_MS, OrderStatus, OrderType, RECURRENCE_CADENCES, RETINA_IMAGE_SIZES, SELF_CUSTODY_FILL_TIMEOUT_MS, SELF_CUSTODY_POLL_INTERVAL_MS, TERMINAL_EXECUTION_STATES, TimeInForce, TradeSide, TurnstileChallengeError, VENUES, VENUE_CHAIN_IDS, Venue, adjustLimitPriceRawToTick, aggregateMidpoint, applyOrderbookDelta, areOrdersFilled, buildVenueUrl, computeBestSplitsByAmount, computeChecksum, createAggClient, enumGuard, formatMarketQuestion, formatOutcomeLabel, formatOutcomeTitle, getBalanceRefillMaximumRaw, getEffectiveDisabledVenues, getWalletAddressFromUserProfile, hasShape, isAggApiError, isBalanceRefillWithinDailyCap, isEmail, isEnum, isFiniteNonNeg, isNonEmptyString, isOptionalFeeRequest, isOrderSignatureStep, isPolymarketOrderSignatureStep, isPostFillFeeRequest, isSameWalletAddress, isVenueActive, isVenueDataVisible, isVenueInactive, loadImmutableHistory, mergeCandles, mergeClosedCandles, normalizeVenueMarketCluster, optimizedImageUrl, orderSignatureRound, orderbookCrc32, parse, parseEmail, parseEmailStrict, prepareHostedVenueFunding, prepareHostedVenueRoute, projectedOrderbookChecksum, retinaImageUrl, safeParse, signatureTimeoutMs, snapshotToOrderbook, sortVenues, toChecksumAddress, userSignerAddresses, withTimeout };
|
|
3556
|
+
export { ACTIVE_VENUES, AccountProvider, AccountType, AggApiError, AggClient, AggWebSocket, CATALOG_DEFAULT_OVERLAP_MS, CONFIRMED_MATCH_STATUSES, CandleBuilder, Chain, DEFAULT_BALANCE_REFILL_DAILY_CAP_RAW, DEFAULT_BALANCE_REFILL_MAX_FEE_RAW, IMAGE_SIZES, INACTIVE_VENUES, ImageSize, LIMIT_PRICE_RAW_SCALE, MarketStatus, MatchStatus, MatchType, OPTIONAL_FEE_SIGNATURE_TIMEOUT_MS, OPTIONAL_SIGNATURE_TIMEOUT_MS, OrderStatus, OrderType, RECURRENCE_CADENCES, RETINA_IMAGE_SIZES, SELF_CUSTODY_FILL_TIMEOUT_MS, SELF_CUSTODY_POLL_INTERVAL_MS, TERMINAL_EXECUTION_STATES, TimeInForce, TradeSide, TurnstileChallengeError, VENUES, VENUE_CHAIN_IDS, Venue, adjustLimitPriceRawToTick, aggregateMidpoint, applyOrderbookDelta, areOrdersFilled, buildVenueUrl, computeBestSplitsByAmount, computeChecksum, createAggClient, enumGuard, formatMarketQuestion, formatOutcomeLabel, formatOutcomeTitle, getBalanceRefillMaximumRaw, getEffectiveDisabledVenues, getWalletAddressFromUserProfile, hasShape, isAggApiError, isBalanceRefillWithinDailyCap, isCatalogResyncRequired, isEmail, isEnum, isFiniteNonNeg, isNonEmptyString, isOptionalFeeRequest, isOrderSignatureStep, isPolymarketOrderSignatureStep, isPostFillFeeRequest, isSameWalletAddress, isVenueActive, isVenueDataVisible, isVenueInactive, loadImmutableHistory, mergeCandles, mergeClosedCandles, normalizeVenueMarketCluster, optimizedImageUrl, orderSignatureRound, orderbookCrc32, parse, parseEmail, parseEmailStrict, prepareHostedVenueFunding, prepareHostedVenueRoute, projectedOrderbookChecksum, retinaImageUrl, safeParse, signatureTimeoutMs, snapshotToOrderbook, sortVenues, toChecksumAddress, userSignerAddresses, withTimeout };
|
|
3090
3557
|
|
|
3091
3558
|
//# sourceMappingURL=index.mjs.map
|