@bsv/wallet-toolbox-client 2.5.0 → 2.6.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/out/index.client.cjs +523 -139
- package/out/index.client.cjs.map +1 -1
- package/out/index.client.d.cts +140 -12
- package/out/index.client.d.cts.map +1 -1
- package/out/index.client.d.mts +140 -12
- package/out/index.client.d.mts.map +1 -1
- package/out/index.client.mjs +515 -140
- package/out/index.client.mjs.map +1 -1
- package/package.json +1 -1
package/out/index.client.cjs
CHANGED
|
@@ -1005,6 +1005,7 @@ function toWalletNetwork(chain) {
|
|
|
1005
1005
|
switch (chain) {
|
|
1006
1006
|
case "main": return "mainnet";
|
|
1007
1007
|
case "test":
|
|
1008
|
+
case "stn":
|
|
1008
1009
|
case "ttn":
|
|
1009
1010
|
case "tstn":
|
|
1010
1011
|
case "mock": return "testnet";
|
|
@@ -1018,6 +1019,7 @@ function toLookupNetworkPreset(chain) {
|
|
|
1018
1019
|
switch (chain) {
|
|
1019
1020
|
case "main": return "mainnet";
|
|
1020
1021
|
case "test": return "testnet";
|
|
1022
|
+
case "stn":
|
|
1021
1023
|
case "ttn":
|
|
1022
1024
|
case "tstn":
|
|
1023
1025
|
case "mock": return "local";
|
|
@@ -11767,9 +11769,7 @@ function genesisHeader(chain) {
|
|
|
11767
11769
|
height: 0,
|
|
11768
11770
|
hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
|
11769
11771
|
};
|
|
11770
|
-
case "test":
|
|
11771
|
-
case "ttn":
|
|
11772
|
-
case "tstn": return {
|
|
11772
|
+
case "test": return {
|
|
11773
11773
|
version: 1,
|
|
11774
11774
|
previousHash: "0000000000000000000000000000000000000000000000000000000000000000",
|
|
11775
11775
|
merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
|
|
@@ -11779,6 +11779,36 @@ function genesisHeader(chain) {
|
|
|
11779
11779
|
height: 0,
|
|
11780
11780
|
hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
|
|
11781
11781
|
};
|
|
11782
|
+
case "stn": return {
|
|
11783
|
+
version: 1,
|
|
11784
|
+
previousHash: "0000000000000000000000000000000000000000000000000000000000000000",
|
|
11785
|
+
merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
|
|
11786
|
+
time: 1296688602,
|
|
11787
|
+
bits: 486604799,
|
|
11788
|
+
nonce: 173779992,
|
|
11789
|
+
height: 0,
|
|
11790
|
+
hash: "6b38bdbcd73a19f7889d23e1fa6166a9de71affceca60ca3bb1b28af8135c594"
|
|
11791
|
+
};
|
|
11792
|
+
case "ttn": return {
|
|
11793
|
+
version: 1,
|
|
11794
|
+
previousHash: "0000000000000000000000000000000000000000000000000000000000000000",
|
|
11795
|
+
merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
|
|
11796
|
+
time: 1755606836,
|
|
11797
|
+
bits: 486604799,
|
|
11798
|
+
nonce: 1092578460,
|
|
11799
|
+
height: 0,
|
|
11800
|
+
hash: "000000000499eabba0a88f5b3747231c74b9191c1a4a04b2c2ea817976b7776d"
|
|
11801
|
+
};
|
|
11802
|
+
case "tstn": return {
|
|
11803
|
+
version: 1,
|
|
11804
|
+
previousHash: "0000000000000000000000000000000000000000000000000000000000000000",
|
|
11805
|
+
merkleRoot: "64452e5b25c65e492ad6a4f5ce9f427ca986626c28315d88de920d66e28cc98f",
|
|
11806
|
+
time: 1782864e3,
|
|
11807
|
+
bits: 486604799,
|
|
11808
|
+
nonce: 1780488216,
|
|
11809
|
+
height: 0,
|
|
11810
|
+
hash: "000000005d221c0e023cb56b5682cf094f32cd959958b40bc931e5797cae706c"
|
|
11811
|
+
};
|
|
11782
11812
|
case "mock": throw new Error("genesisHeader does not support 'mock' chain. Mock chain generates its own genesis block.");
|
|
11783
11813
|
}
|
|
11784
11814
|
}
|
|
@@ -19306,10 +19336,11 @@ var Chaintracks = class {
|
|
|
19306
19336
|
startupError = null;
|
|
19307
19337
|
subscriberCallbacksEnabled = false;
|
|
19308
19338
|
stopMainThread = true;
|
|
19309
|
-
lastPresentHeight =
|
|
19339
|
+
lastPresentHeight = -1;
|
|
19310
19340
|
lastPresentHeightMsecs = 0;
|
|
19311
19341
|
lastPresentHeightMaxAge = 60 * 1e3;
|
|
19312
19342
|
lock = new SingleWriterMultiReaderLock();
|
|
19343
|
+
sourceStatus = /* @__PURE__ */ new Map();
|
|
19313
19344
|
constructor(options) {
|
|
19314
19345
|
this.options = options;
|
|
19315
19346
|
if (options.storage == null) throw new Error("storage is required.");
|
|
@@ -19320,6 +19351,22 @@ var Chaintracks = class {
|
|
|
19320
19351
|
this.storage = options.storage;
|
|
19321
19352
|
this.bulkIngestors = options.bulkIngestors;
|
|
19322
19353
|
this.liveIngestors = options.liveIngestors;
|
|
19354
|
+
for (const [index, source] of this.bulkIngestors.entries()) {
|
|
19355
|
+
const name = this.sourceName("bulk", index, source);
|
|
19356
|
+
this.sourceStatus.set(name, {
|
|
19357
|
+
name,
|
|
19358
|
+
role: "bulk",
|
|
19359
|
+
state: "unknown"
|
|
19360
|
+
});
|
|
19361
|
+
}
|
|
19362
|
+
for (const [index, source] of this.liveIngestors.entries()) {
|
|
19363
|
+
const name = this.sourceName("live", index, source);
|
|
19364
|
+
this.sourceStatus.set(name, {
|
|
19365
|
+
name,
|
|
19366
|
+
role: "live",
|
|
19367
|
+
state: "unknown"
|
|
19368
|
+
});
|
|
19369
|
+
}
|
|
19323
19370
|
this.addLiveRecursionLimit = options.addLiveRecursionLimit;
|
|
19324
19371
|
if (options.logging != null) this.log = options.logging;
|
|
19325
19372
|
this.storage.log = this.log;
|
|
@@ -19334,19 +19381,36 @@ var Chaintracks = class {
|
|
|
19334
19381
|
*/
|
|
19335
19382
|
async getPresentHeight() {
|
|
19336
19383
|
const now = Date.now();
|
|
19337
|
-
if (this.lastPresentHeight && now - this.lastPresentHeightMsecs < this.lastPresentHeightMaxAge) return this.lastPresentHeight;
|
|
19338
|
-
const
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
|
|
19384
|
+
if (this.lastPresentHeight >= 0 && now - this.lastPresentHeightMsecs < this.lastPresentHeightMaxAge) return this.lastPresentHeight;
|
|
19385
|
+
for (const [index, bulk] of this.bulkIngestors.entries()) {
|
|
19386
|
+
const source = this.sourceName("bulk", index, bulk);
|
|
19387
|
+
try {
|
|
19388
|
+
const presentHeight = await bulk.getPresentHeight();
|
|
19389
|
+
if (presentHeight != null && Number.isInteger(presentHeight) && presentHeight >= 0) {
|
|
19390
|
+
this.markSourceSuccess(source, "bulk");
|
|
19391
|
+
this.lastPresentHeight = presentHeight;
|
|
19392
|
+
this.lastPresentHeightMsecs = now;
|
|
19393
|
+
return presentHeight;
|
|
19394
|
+
}
|
|
19395
|
+
} catch (uerr) {
|
|
19396
|
+
const error = WalletError.fromUnknown(uerr);
|
|
19397
|
+
this.markSourceFailure(source, "bulk", error);
|
|
19398
|
+
this.log(`Present-height source ${source} failed: ${error.message}`);
|
|
19399
|
+
}
|
|
19400
|
+
}
|
|
19401
|
+
if (this.lastPresentHeight >= 0) return this.lastPresentHeight;
|
|
19402
|
+
try {
|
|
19403
|
+
const ranges = await this.storage.getAvailableHeightRanges();
|
|
19404
|
+
const localHeight = Math.max(ranges.bulk.maxHeight, ranges.live.maxHeight);
|
|
19405
|
+
if (localHeight >= 0) {
|
|
19406
|
+
this.lastPresentHeight = localHeight;
|
|
19407
|
+
this.lastPresentHeightMsecs = now;
|
|
19408
|
+
return localHeight;
|
|
19409
|
+
}
|
|
19410
|
+
} catch (error) {
|
|
19411
|
+
this.log(`Unable to read the locally validated ChainTracks height: ${WalletError.fromUnknown(error).message}`);
|
|
19412
|
+
}
|
|
19413
|
+
throw new Error("No present-height source or locally validated headers are available.");
|
|
19350
19414
|
}
|
|
19351
19415
|
async currentHeight() {
|
|
19352
19416
|
return await this.getPresentHeight();
|
|
@@ -19397,7 +19461,7 @@ var Chaintracks = class {
|
|
|
19397
19461
|
for (const bulkIn of this.bulkIngestors) await bulkIn.setStorage(this.storage, this.log);
|
|
19398
19462
|
for (const liveIn of this.liveIngestors) await liveIn.setStorage(this.storage, this.log);
|
|
19399
19463
|
this.stopMainThread = false;
|
|
19400
|
-
for (const liveIngestor of this.liveIngestors) this.promises.push(this.runLiveIngestor(liveIngestor));
|
|
19464
|
+
for (const [index, liveIngestor] of this.liveIngestors.entries()) this.promises.push(this.runLiveIngestor(liveIngestor, index));
|
|
19401
19465
|
this.promises.push(this.mainThreadShiftLiveHeaders());
|
|
19402
19466
|
while (!this.available && this.startupError == null) await wait(100);
|
|
19403
19467
|
if (this.startupError != null) throw this.startupError;
|
|
@@ -19424,10 +19488,12 @@ var Chaintracks = class {
|
|
|
19424
19488
|
async listening() {
|
|
19425
19489
|
return await this.makeAvailable();
|
|
19426
19490
|
}
|
|
19427
|
-
async runLiveIngestor(liveIngestor) {
|
|
19491
|
+
async runLiveIngestor(liveIngestor, index) {
|
|
19428
19492
|
let restartCount = 0;
|
|
19429
19493
|
const name = liveIngestor.constructor.name;
|
|
19494
|
+
const source = this.sourceName("live", index, liveIngestor);
|
|
19430
19495
|
while (!this.stopMainThread) try {
|
|
19496
|
+
this.markSourceSuccess(source, "live");
|
|
19431
19497
|
await liveIngestor.startListening(this.liveHeaders);
|
|
19432
19498
|
if (this.stopMainThread) return;
|
|
19433
19499
|
restartCount++;
|
|
@@ -19438,6 +19504,7 @@ var Chaintracks = class {
|
|
|
19438
19504
|
if (this.stopMainThread) return;
|
|
19439
19505
|
restartCount++;
|
|
19440
19506
|
const e = WalletError.fromUnknown(error_);
|
|
19507
|
+
this.markSourceFailure(source, "live", e);
|
|
19441
19508
|
const waitMsecs = this.liveIngestorRestartWaitMsecs(restartCount);
|
|
19442
19509
|
this.log(`Live ingestor ${name} failed restart=${restartCount} retryMsecs=${waitMsecs}: ${e.stack ?? e.message}`);
|
|
19443
19510
|
await wait(waitMsecs);
|
|
@@ -19485,7 +19552,8 @@ var Chaintracks = class {
|
|
|
19485
19552
|
storage: this.storage.constructor.name,
|
|
19486
19553
|
bulkIngestors: this.bulkIngestors.map((bulkIngestor) => bulkIngestor.constructor.name),
|
|
19487
19554
|
liveIngestors: this.liveIngestors.map((liveIngestor) => liveIngestor.constructor.name),
|
|
19488
|
-
packages: []
|
|
19555
|
+
packages: [],
|
|
19556
|
+
sources: Array.from(this.sourceStatus.values()).map((status) => ({ ...status }))
|
|
19489
19557
|
};
|
|
19490
19558
|
}
|
|
19491
19559
|
async getHeaders(height, count) {
|
|
@@ -19568,26 +19636,30 @@ var Chaintracks = class {
|
|
|
19568
19636
|
let madeProgress = false;
|
|
19569
19637
|
let hadSuccess = false;
|
|
19570
19638
|
let done = false;
|
|
19571
|
-
for (const bulk of this.bulkIngestors)
|
|
19572
|
-
const
|
|
19573
|
-
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
|
|
19583
|
-
|
|
19584
|
-
|
|
19585
|
-
|
|
19639
|
+
for (const [index, bulk] of this.bulkIngestors.entries()) {
|
|
19640
|
+
const source = this.sourceName("bulk", index, bulk);
|
|
19641
|
+
try {
|
|
19642
|
+
const beforeBulkMax = before.bulk.maxHeight;
|
|
19643
|
+
const beforeLiveRange = HeightRange.from(newLiveHeaders);
|
|
19644
|
+
const r = await bulk.synchronize(presentHeight, before, newLiveHeaders);
|
|
19645
|
+
hadSuccess = true;
|
|
19646
|
+
this.markSourceSuccess(source, "bulk");
|
|
19647
|
+
newLiveHeaders = r.liveHeaders;
|
|
19648
|
+
after = await this.storage.getAvailableHeightRanges();
|
|
19649
|
+
const added = after.bulk.above(before.bulk);
|
|
19650
|
+
const afterLiveRange = HeightRange.from(newLiveHeaders);
|
|
19651
|
+
if (after.bulk.maxHeight > beforeBulkMax || afterLiveRange.maxHeight > beforeLiveRange.maxHeight) madeProgress = true;
|
|
19652
|
+
before = after;
|
|
19653
|
+
this.log(`Bulk Ingestor: ${added.length} added with ${newLiveHeaders.length} live headers from ${bulk.constructor.name}`);
|
|
19654
|
+
if (r.done) {
|
|
19655
|
+
done = true;
|
|
19656
|
+
break;
|
|
19657
|
+
}
|
|
19658
|
+
} catch (error_) {
|
|
19659
|
+
const e = bulkSyncError = WalletError.fromUnknown(error_);
|
|
19660
|
+
this.markSourceFailure(source, "bulk", e);
|
|
19661
|
+
this.log(`bulk sync error: ${e.message}`);
|
|
19586
19662
|
}
|
|
19587
|
-
} catch (error_) {
|
|
19588
|
-
const e = bulkSyncError = WalletError.fromUnknown(error_);
|
|
19589
|
-
this.log(`bulk sync error: ${e.message}`);
|
|
19590
|
-
if (!this.available) break;
|
|
19591
19663
|
}
|
|
19592
19664
|
if (!this.available && bulkSyncError != null && !hadSuccess) this.startupError = bulkSyncError;
|
|
19593
19665
|
return {
|
|
@@ -19597,10 +19669,41 @@ var Chaintracks = class {
|
|
|
19597
19669
|
madeProgress
|
|
19598
19670
|
};
|
|
19599
19671
|
}
|
|
19672
|
+
sourceName(role, index, source) {
|
|
19673
|
+
return `${role}[${index}]:${source.constructor.name}`;
|
|
19674
|
+
}
|
|
19675
|
+
markSourceSuccess(name, role) {
|
|
19676
|
+
this.sourceStatus.set(name, {
|
|
19677
|
+
...this.sourceStatus.get(name),
|
|
19678
|
+
name,
|
|
19679
|
+
role,
|
|
19680
|
+
state: "healthy",
|
|
19681
|
+
lastSuccess: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19682
|
+
error: void 0
|
|
19683
|
+
});
|
|
19684
|
+
}
|
|
19685
|
+
markSourceFailure(name, role, error) {
|
|
19686
|
+
this.sourceStatus.set(name, {
|
|
19687
|
+
...this.sourceStatus.get(name),
|
|
19688
|
+
name,
|
|
19689
|
+
role,
|
|
19690
|
+
state: "degraded",
|
|
19691
|
+
lastFailure: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19692
|
+
error: error.message
|
|
19693
|
+
});
|
|
19694
|
+
}
|
|
19600
19695
|
async getMissingBlockHeader(hash) {
|
|
19601
|
-
for (const live of this.liveIngestors) {
|
|
19602
|
-
const
|
|
19603
|
-
|
|
19696
|
+
for (const [index, live] of this.liveIngestors.entries()) {
|
|
19697
|
+
const source = this.sourceName("live", index, live);
|
|
19698
|
+
try {
|
|
19699
|
+
const header = await live.getHeaderByHash(hash);
|
|
19700
|
+
this.markSourceSuccess(source, "live");
|
|
19701
|
+
if (header != null) return header;
|
|
19702
|
+
} catch (error) {
|
|
19703
|
+
const resolved = WalletError.fromUnknown(error);
|
|
19704
|
+
this.markSourceFailure(source, "live", resolved);
|
|
19705
|
+
this.log(`Header lookup source ${source} failed: ${resolved.message}`);
|
|
19706
|
+
}
|
|
19604
19707
|
}
|
|
19605
19708
|
}
|
|
19606
19709
|
invalidInsertHeaderResult(ihr) {
|
|
@@ -19933,6 +20036,9 @@ var GoChaintracksServiceClient = class {
|
|
|
19933
20036
|
chain;
|
|
19934
20037
|
baseUrl;
|
|
19935
20038
|
fetcher;
|
|
20039
|
+
requestTimeoutMsecs;
|
|
20040
|
+
reconnectWaitMsecs;
|
|
20041
|
+
reconnectWaitMaxMsecs;
|
|
19936
20042
|
subscriptions = /* @__PURE__ */ new Map();
|
|
19937
20043
|
nextSubscriptionId = 1;
|
|
19938
20044
|
constructor(chain, serviceUrl, options = {}) {
|
|
@@ -19946,6 +20052,15 @@ var GoChaintracksServiceClient = class {
|
|
|
19946
20052
|
}
|
|
19947
20053
|
this.baseUrl = `${base}${prefix}`;
|
|
19948
20054
|
this.fetcher = options.fetch ?? fetch;
|
|
20055
|
+
this.requestTimeoutMsecs = options.requestTimeoutMsecs ?? 3e4;
|
|
20056
|
+
this.reconnectWaitMsecs = options.reconnectWaitMsecs ?? 1e3;
|
|
20057
|
+
this.reconnectWaitMaxMsecs = options.reconnectWaitMaxMsecs ?? 6e4;
|
|
20058
|
+
for (const [name, value] of [
|
|
20059
|
+
["requestTimeoutMsecs", this.requestTimeoutMsecs],
|
|
20060
|
+
["reconnectWaitMsecs", this.reconnectWaitMsecs],
|
|
20061
|
+
["reconnectWaitMaxMsecs", this.reconnectWaitMaxMsecs]
|
|
20062
|
+
]) if (!Number.isSafeInteger(value) || value < 1) throw new Error(`${name} must be a positive integer.`);
|
|
20063
|
+
if (this.reconnectWaitMaxMsecs < this.reconnectWaitMsecs) throw new Error("reconnectWaitMaxMsecs must be greater than or equal to reconnectWaitMsecs.");
|
|
19949
20064
|
}
|
|
19950
20065
|
async currentHeight() {
|
|
19951
20066
|
return await this.getPresentHeight();
|
|
@@ -19955,12 +20070,8 @@ var GoChaintracksServiceClient = class {
|
|
|
19955
20070
|
return h != null && root === asString(h.merkleRoot);
|
|
19956
20071
|
}
|
|
19957
20072
|
async getChain() {
|
|
19958
|
-
|
|
19959
|
-
|
|
19960
|
-
return this.normalizeChain(r.network);
|
|
19961
|
-
} catch {
|
|
19962
|
-
return this.chain;
|
|
19963
|
-
}
|
|
20073
|
+
const r = await this.getJson("/network");
|
|
20074
|
+
return this.normalizeChain(typeof r === "string" ? r : r.network);
|
|
19964
20075
|
}
|
|
19965
20076
|
async getInfo() {
|
|
19966
20077
|
const tip = await this.findChainTipHeader();
|
|
@@ -19975,11 +20086,11 @@ var GoChaintracksServiceClient = class {
|
|
|
19975
20086
|
};
|
|
19976
20087
|
}
|
|
19977
20088
|
async getPresentHeight() {
|
|
19978
|
-
|
|
20089
|
+
const result = await this.getJson("/height");
|
|
20090
|
+
return typeof result === "number" ? result : result.height;
|
|
19979
20091
|
}
|
|
19980
20092
|
async getHeaders(height, count) {
|
|
19981
|
-
|
|
19982
|
-
return Buffer.from(bytes).toString("hex");
|
|
20093
|
+
return asString(await this.getBinary(`/headers.bin?height=${height}&count=${count}`));
|
|
19983
20094
|
}
|
|
19984
20095
|
async findChainTipHeader() {
|
|
19985
20096
|
return await this.getJson("/tip");
|
|
@@ -20035,7 +20146,7 @@ var GoChaintracksServiceClient = class {
|
|
|
20035
20146
|
async subscribe(type, path, onPayload) {
|
|
20036
20147
|
const id = `${type}-${this.nextSubscriptionId++}`;
|
|
20037
20148
|
const abort = new AbortController();
|
|
20038
|
-
const done = this.
|
|
20149
|
+
const done = this.runSseWithReconnect(path, abort.signal, onPayload);
|
|
20039
20150
|
this.subscriptions.set(id, {
|
|
20040
20151
|
id,
|
|
20041
20152
|
type,
|
|
@@ -20047,30 +20158,75 @@ var GoChaintracksServiceClient = class {
|
|
|
20047
20158
|
});
|
|
20048
20159
|
return id;
|
|
20049
20160
|
}
|
|
20050
|
-
async
|
|
20051
|
-
|
|
20052
|
-
|
|
20053
|
-
|
|
20161
|
+
async runSseWithReconnect(path, signal, onPayload) {
|
|
20162
|
+
let failures = 0;
|
|
20163
|
+
while (!signal.aborted) {
|
|
20164
|
+
try {
|
|
20165
|
+
failures = await this.runSse(path, signal, onPayload) ? 0 : failures + 1;
|
|
20166
|
+
} catch {
|
|
20167
|
+
if (signal.aborted) return;
|
|
20168
|
+
failures++;
|
|
20169
|
+
}
|
|
20170
|
+
const multiplier = Math.min(2 ** Math.max(0, failures - 1), 64);
|
|
20171
|
+
const delay = Math.min(this.reconnectWaitMsecs * multiplier, this.reconnectWaitMaxMsecs);
|
|
20172
|
+
await this.waitForReconnect(delay, signal);
|
|
20173
|
+
}
|
|
20174
|
+
}
|
|
20175
|
+
async waitForReconnect(msecs, signal) {
|
|
20176
|
+
if (signal.aborted || msecs <= 0) return;
|
|
20177
|
+
await new Promise((resolve) => {
|
|
20178
|
+
let timeout;
|
|
20179
|
+
const onAbort = () => done();
|
|
20180
|
+
const done = () => {
|
|
20181
|
+
clearTimeout(timeout);
|
|
20182
|
+
signal.removeEventListener("abort", onAbort);
|
|
20183
|
+
resolve();
|
|
20184
|
+
};
|
|
20185
|
+
timeout = setTimeout(done, msecs);
|
|
20186
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
20054
20187
|
});
|
|
20055
|
-
|
|
20056
|
-
|
|
20057
|
-
const
|
|
20058
|
-
const
|
|
20059
|
-
|
|
20188
|
+
}
|
|
20189
|
+
async runSse(path, signal, onPayload) {
|
|
20190
|
+
const controller = new AbortController();
|
|
20191
|
+
const onAbort = () => controller.abort();
|
|
20192
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
20193
|
+
const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMsecs);
|
|
20194
|
+
let receivedEvent = false;
|
|
20195
|
+
const observePayload = (payload) => {
|
|
20196
|
+
receivedEvent = true;
|
|
20197
|
+
onPayload(payload);
|
|
20198
|
+
};
|
|
20060
20199
|
try {
|
|
20061
|
-
|
|
20062
|
-
|
|
20063
|
-
|
|
20064
|
-
|
|
20065
|
-
|
|
20200
|
+
const response = await this.fetcher(this.url(path), {
|
|
20201
|
+
headers: { Accept: "text/event-stream" },
|
|
20202
|
+
signal: controller.signal
|
|
20203
|
+
});
|
|
20204
|
+
clearTimeout(timeout);
|
|
20205
|
+
if (!response.ok) throw new Error(`GET ${this.url(path)} failed ${response.status} ${response.statusText}`);
|
|
20206
|
+
if (response.body == null) throw new Error(`GET ${this.url(path)} returned no response body`);
|
|
20207
|
+
const reader = response.body.getReader();
|
|
20208
|
+
const decoder = new TextDecoder();
|
|
20209
|
+
let buffer = "";
|
|
20210
|
+
try {
|
|
20211
|
+
for (;;) {
|
|
20212
|
+
const { done, value } = await reader.read();
|
|
20213
|
+
if (done) break;
|
|
20214
|
+
buffer += decoder.decode(value, { stream: true });
|
|
20215
|
+
buffer = this.processSseBuffer(buffer, observePayload);
|
|
20216
|
+
}
|
|
20217
|
+
buffer += decoder.decode();
|
|
20218
|
+
this.processSseBuffer(`${buffer}\n\n`, observePayload);
|
|
20219
|
+
} finally {
|
|
20220
|
+
reader.releaseLock();
|
|
20066
20221
|
}
|
|
20067
|
-
buffer += decoder.decode();
|
|
20068
|
-
this.processSseBuffer(`${buffer}\n\n`, onPayload);
|
|
20069
20222
|
} finally {
|
|
20070
|
-
|
|
20223
|
+
clearTimeout(timeout);
|
|
20224
|
+
signal.removeEventListener("abort", onAbort);
|
|
20071
20225
|
}
|
|
20226
|
+
return receivedEvent;
|
|
20072
20227
|
}
|
|
20073
20228
|
processSseBuffer(buffer, onPayload) {
|
|
20229
|
+
buffer = buffer.replaceAll("\r\n", "\n");
|
|
20074
20230
|
for (;;) {
|
|
20075
20231
|
const boundary = buffer.indexOf("\n\n");
|
|
20076
20232
|
if (boundary < 0) return buffer;
|
|
@@ -20089,32 +20245,51 @@ var GoChaintracksServiceClient = class {
|
|
|
20089
20245
|
return r;
|
|
20090
20246
|
}
|
|
20091
20247
|
async getJsonOrUndefined(path) {
|
|
20092
|
-
const response = await this.
|
|
20248
|
+
const response = await this.fetchWithTimeout(this.url(path), { headers: { Accept: "application/json" } });
|
|
20093
20249
|
if (response.status === 404) return void 0;
|
|
20094
20250
|
if (!response.ok) throw new Error(`GET ${this.url(path)} failed ${response.status} ${response.statusText}`);
|
|
20095
|
-
|
|
20251
|
+
const value = await response.json();
|
|
20252
|
+
if (value != null && typeof value === "object" && "status" in value) {
|
|
20253
|
+
const envelope = value;
|
|
20254
|
+
if (envelope.status === "success") return envelope.value;
|
|
20255
|
+
if (envelope.status === "error") throw new Error(envelope.description ?? `GET ${this.url(path)} failed`);
|
|
20256
|
+
}
|
|
20257
|
+
return value;
|
|
20096
20258
|
}
|
|
20097
20259
|
async getBinary(path) {
|
|
20098
|
-
const response = await this.
|
|
20260
|
+
const response = await this.fetchWithTimeout(this.url(path), { headers: { Accept: "application/octet-stream" } });
|
|
20099
20261
|
if (!response.ok) throw new Error(`GET ${this.url(path)} failed ${response.status} ${response.statusText}`);
|
|
20100
20262
|
return new Uint8Array(await response.arrayBuffer());
|
|
20101
20263
|
}
|
|
20264
|
+
async fetchWithTimeout(url, init) {
|
|
20265
|
+
const controller = new AbortController();
|
|
20266
|
+
const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMsecs);
|
|
20267
|
+
try {
|
|
20268
|
+
return await this.fetcher(url, {
|
|
20269
|
+
...init,
|
|
20270
|
+
signal: controller.signal
|
|
20271
|
+
});
|
|
20272
|
+
} finally {
|
|
20273
|
+
clearTimeout(timeout);
|
|
20274
|
+
}
|
|
20275
|
+
}
|
|
20102
20276
|
url(path) {
|
|
20103
20277
|
return `${this.baseUrl}${path}`;
|
|
20104
20278
|
}
|
|
20105
20279
|
normalizeChain(network) {
|
|
20106
|
-
switch (network) {
|
|
20280
|
+
switch (network.trim().toLowerCase()) {
|
|
20107
20281
|
case "main":
|
|
20108
20282
|
case "mainnet": return "main";
|
|
20109
20283
|
case "test":
|
|
20110
20284
|
case "testnet": return "test";
|
|
20285
|
+
case "stn":
|
|
20286
|
+
case "scalingtestnet": return "stn";
|
|
20111
20287
|
case "ttn":
|
|
20112
20288
|
case "teratest":
|
|
20113
20289
|
case "teratestnet": return "ttn";
|
|
20114
20290
|
case "tstn":
|
|
20115
|
-
case "teranodescalingtestnet":
|
|
20116
|
-
|
|
20117
|
-
default: return this.chain;
|
|
20291
|
+
case "teranodescalingtestnet": return "tstn";
|
|
20292
|
+
default: throw new Error(`Unsupported ChainTracks upstream network '${network}'.`);
|
|
20118
20293
|
}
|
|
20119
20294
|
}
|
|
20120
20295
|
};
|
|
@@ -21447,31 +21622,45 @@ var ServiceCollection = class ServiceCollection {
|
|
|
21447
21622
|
//#endregion
|
|
21448
21623
|
//#region ../src/services/networkConfig.ts
|
|
21449
21624
|
/**
|
|
21450
|
-
* Runtime service-endpoint configuration for
|
|
21625
|
+
* Runtime service-endpoint configuration for Teranode networks that do not
|
|
21626
|
+
* have a public, operator-independent service endpoint.
|
|
21451
21627
|
*
|
|
21452
|
-
* Unlike `main`, `test`, and `ttn`, the tstn service endpoints are not public and must not be
|
|
21628
|
+
* Unlike `main`, `test`, and `ttn`, the stn/tstn service endpoints are not public and must not be
|
|
21453
21629
|
* hardcoded in this (public) source tree. They are supplied at runtime through environment
|
|
21454
21630
|
* variables:
|
|
21455
21631
|
*
|
|
21632
|
+
* STN_ARCADE_URL STN Arcade broadcaster / ARC endpoint base.
|
|
21633
|
+
* STN_CHAINTRACKS_URL STN ChainTracks service URL.
|
|
21456
21634
|
* TSTN_ARCADE_URL Arcade broadcaster / ARC endpoint base. Also the fallback host for
|
|
21457
21635
|
* ChainTracks when TSTN_CHAINTRACKS_URL is unset
|
|
21458
21636
|
* (`${TSTN_ARCADE_URL}/chaintracks/v1`, mirroring the ttn layout).
|
|
21459
21637
|
* TSTN_CHAINTRACKS_URL ChainTracks service URL.
|
|
21460
21638
|
*
|
|
21461
|
-
* tstn
|
|
21462
|
-
* WhatsOnChain
|
|
21639
|
+
* stn/tstn run only operator-configured Arcade and ChainTracks services; there is no
|
|
21640
|
+
* documented WhatsOnChain service for them, so no WhatsOnChain endpoint is configured and
|
|
21463
21641
|
* the WhatsOnChain-only lookups (raw tx, utxo status, txid status, script-hash history) are not
|
|
21464
|
-
* available on tstn.
|
|
21642
|
+
* available on stn/tstn.
|
|
21465
21643
|
*
|
|
21466
|
-
* `process` is accessed defensively so importing this module remains safe in
|
|
21467
|
-
*
|
|
21468
|
-
*
|
|
21644
|
+
* `process` is accessed defensively so importing this module remains safe in
|
|
21645
|
+
* browser bundles. Browser applications can still supply an explicit
|
|
21646
|
+
* ChaintracksClientApi without relying on environment variables.
|
|
21469
21647
|
*/
|
|
21470
21648
|
function readEnv(name) {
|
|
21471
21649
|
const value = (typeof process !== "undefined" ? process.env : void 0)?.[name];
|
|
21472
21650
|
return value != null && value.trim() !== "" ? value.trim() : void 0;
|
|
21473
21651
|
}
|
|
21474
|
-
|
|
21652
|
+
/** Credential-free public Arcade host for supported networks. */
|
|
21653
|
+
function publicArcadeUrl(chain) {
|
|
21654
|
+
switch (chain) {
|
|
21655
|
+
case "main": return "https://arcade-v2-us-1.bsvblockchain.tech";
|
|
21656
|
+
case "test": return "https://arcade-v2-testnet-us-1.bsvblockchain.tech";
|
|
21657
|
+
case "ttn": return "https://arcade-v2-ttn-us-1.bsvblockchain.tech";
|
|
21658
|
+
case "stn":
|
|
21659
|
+
case "tstn":
|
|
21660
|
+
case "mock": return;
|
|
21661
|
+
}
|
|
21662
|
+
}
|
|
21663
|
+
const stripTrailingSlash$1 = (url) => {
|
|
21475
21664
|
let end = url.length;
|
|
21476
21665
|
while (end > 0 && url[end - 1] === "/") end--;
|
|
21477
21666
|
return url.slice(0, end);
|
|
@@ -21480,6 +21669,10 @@ const stripTrailingSlash = (url) => {
|
|
|
21480
21669
|
function tstnArcadeUrl() {
|
|
21481
21670
|
return readEnv("TSTN_ARCADE_URL");
|
|
21482
21671
|
}
|
|
21672
|
+
/** Arcade broadcaster / ARC endpoint for stn, or `undefined` when unset. */
|
|
21673
|
+
function stnArcadeUrl() {
|
|
21674
|
+
return readEnv("STN_ARCADE_URL");
|
|
21675
|
+
}
|
|
21483
21676
|
/**
|
|
21484
21677
|
* ChainTracks service URL for tstn. Falls back to `${TSTN_ARCADE_URL}/chaintracks/v1` when
|
|
21485
21678
|
* `TSTN_CHAINTRACKS_URL` is unset (mirrors the ttn layout). Throws when neither is configured.
|
|
@@ -21488,20 +21681,53 @@ function tstnChaintracksUrl() {
|
|
|
21488
21681
|
const explicit = readEnv("TSTN_CHAINTRACKS_URL");
|
|
21489
21682
|
if (explicit != null) return explicit;
|
|
21490
21683
|
const arcade = tstnArcadeUrl();
|
|
21491
|
-
if (arcade != null) return `${stripTrailingSlash(arcade)}/chaintracks/v1`;
|
|
21684
|
+
if (arcade != null) return `${stripTrailingSlash$1(arcade)}/chaintracks/v1`;
|
|
21492
21685
|
throw new Error("tstn chain requires a ChainTracks URL: set TSTN_CHAINTRACKS_URL (or TSTN_ARCADE_URL) in the environment.");
|
|
21493
21686
|
}
|
|
21687
|
+
/**
|
|
21688
|
+
* ChainTracks service URL for stn. Falls back to the configured Arcade host's
|
|
21689
|
+
* legacy-compatible path when STN_CHAINTRACKS_URL is unset.
|
|
21690
|
+
*/
|
|
21691
|
+
function stnChaintracksUrl() {
|
|
21692
|
+
const explicit = readEnv("STN_CHAINTRACKS_URL");
|
|
21693
|
+
if (explicit != null) return explicit;
|
|
21694
|
+
const arcade = stnArcadeUrl();
|
|
21695
|
+
if (arcade != null) return `${stripTrailingSlash$1(arcade)}/chaintracks/v1`;
|
|
21696
|
+
throw new Error("stn chain requires a ChainTracks URL: set STN_CHAINTRACKS_URL (or STN_ARCADE_URL) in the environment.");
|
|
21697
|
+
}
|
|
21494
21698
|
//#endregion
|
|
21495
21699
|
//#region ../src/services/createDefaultWalletServicesOptions.ts
|
|
21700
|
+
function stripTrailingSlash(value) {
|
|
21701
|
+
let end = value.length;
|
|
21702
|
+
while (end > 0 && value[end - 1] === "/") end--;
|
|
21703
|
+
return value.slice(0, end);
|
|
21704
|
+
}
|
|
21705
|
+
function configuredChaintracksClient(chain, serviceUrl) {
|
|
21706
|
+
let path = "";
|
|
21707
|
+
try {
|
|
21708
|
+
path = stripTrailingSlash(new URL(serviceUrl).pathname);
|
|
21709
|
+
} catch {}
|
|
21710
|
+
if (path.endsWith("/v2")) return new GoChaintracksServiceClient(chain, serviceUrl);
|
|
21711
|
+
return new ChaintracksServiceClient(chain, serviceUrl);
|
|
21712
|
+
}
|
|
21713
|
+
/**
|
|
21714
|
+
* Returns the credential-free default ChainTracks client for a supported
|
|
21715
|
+
* public network, or an operator-configured client for stn/tstn.
|
|
21716
|
+
*/
|
|
21717
|
+
function createDefaultChaintracksClient(chain) {
|
|
21718
|
+
switch (chain) {
|
|
21719
|
+
case "main":
|
|
21720
|
+
case "test":
|
|
21721
|
+
case "ttn": return new GoChaintracksServiceClient(chain, arcadeDefaultUrl(chain), { apiPrefix: "/chaintracks/v2" });
|
|
21722
|
+
case "stn": return configuredChaintracksClient(chain, stnChaintracksUrl());
|
|
21723
|
+
case "tstn": return configuredChaintracksClient(chain, tstnChaintracksUrl());
|
|
21724
|
+
}
|
|
21725
|
+
}
|
|
21496
21726
|
function createDefaultWalletServicesOptions(...[chain, arcCallbackUrl, arcCallbackToken, taalArcApiKey, gorillaPoolArcApiKey, bitailsApiKey, deploymentId, chaintracks, arcadeUrl, arcadeApiKey, arcadeCallbackToken]) {
|
|
21497
21727
|
if (chain === "mock") throw new Error("createDefaultWalletServicesOptions does not support 'mock' chain. Use MockServices directly.");
|
|
21498
21728
|
deploymentId ||= `wallet-toolbox-${randomBytesHex(16)}`;
|
|
21499
|
-
let chaintracksUrl;
|
|
21500
|
-
if (chain === "ttn") chaintracksUrl = "https://arcade-v2-ttn-us-1.bsvblockchain.tech/chaintracks/v1";
|
|
21501
|
-
else if (chain === "tstn") chaintracksUrl = tstnChaintracksUrl();
|
|
21502
|
-
else chaintracksUrl = `https://${chain}net-chaintracks.babbage.systems`;
|
|
21503
21729
|
const chaintracksFiatExchangeRatesUrl = "https://mainnet-chaintracks.babbage.systems/getFiatExchangeRates";
|
|
21504
|
-
chaintracks ||=
|
|
21730
|
+
chaintracks ||= createDefaultChaintracksClient(chain);
|
|
21505
21731
|
const o = {
|
|
21506
21732
|
chain,
|
|
21507
21733
|
taalApiKey: void 0,
|
|
@@ -21559,14 +21785,15 @@ function createDefaultWalletServicesOptions(...[chain, arcCallbackUrl, arcCallba
|
|
|
21559
21785
|
}
|
|
21560
21786
|
/**
|
|
21561
21787
|
* Default Arcade (bsv-blockchain/arcade) endpoint per chain.
|
|
21562
|
-
* Returns undefined when no public default is known for the chain
|
|
21788
|
+
* Returns undefined when no public default is known for the chain.
|
|
21563
21789
|
*/
|
|
21564
21790
|
function arcadeDefaultUrl(chain) {
|
|
21565
21791
|
switch (chain) {
|
|
21566
|
-
case "main":
|
|
21567
|
-
case "
|
|
21792
|
+
case "main":
|
|
21793
|
+
case "test":
|
|
21794
|
+
case "ttn": return publicArcadeUrl(chain);
|
|
21795
|
+
case "stn": return stnArcadeUrl();
|
|
21568
21796
|
case "tstn": return tstnArcadeUrl();
|
|
21569
|
-
case "test": return;
|
|
21570
21797
|
case "mock": return;
|
|
21571
21798
|
}
|
|
21572
21799
|
}
|
|
@@ -21574,6 +21801,7 @@ function arcDefaultUrl(chain) {
|
|
|
21574
21801
|
switch (chain) {
|
|
21575
21802
|
case "main": return "https://arc.taal.com";
|
|
21576
21803
|
case "test": return "https://arc-test.taal.com";
|
|
21804
|
+
case "stn": return stnArcadeUrl() ?? "";
|
|
21577
21805
|
case "ttn": return "https://arcade-v2-ttn-us-1.bsvblockchain.tech/";
|
|
21578
21806
|
case "tstn": return tstnArcadeUrl() ?? "";
|
|
21579
21807
|
case "mock": return "";
|
|
@@ -22955,7 +23183,7 @@ var Services = class Services {
|
|
|
22955
23183
|
telemetry;
|
|
22956
23184
|
constructor(optionsOrChain) {
|
|
22957
23185
|
this.chain = typeof optionsOrChain === "string" ? optionsOrChain : optionsOrChain.chain;
|
|
22958
|
-
if (this.chain === "mock") throw new WERR_INVALID_PARAMETER("chain", "'main', 'test', 'ttn', or 'tstn'. Use MockServices for 'mock' chain.");
|
|
23186
|
+
if (this.chain === "mock") throw new WERR_INVALID_PARAMETER("chain", "'main', 'test', 'stn', 'ttn', or 'tstn'. Use MockServices for 'mock' chain.");
|
|
22959
23187
|
this.options = typeof optionsOrChain === "string" ? Services.createDefaultOptions(this.chain) : optionsOrChain;
|
|
22960
23188
|
this.telemetry = new _bsv_sdk.Telemetry(this.options.telemetry);
|
|
22961
23189
|
this.whatsonchain = new WhatsOnChain(this.chain, { apiKey: this.options.whatsOnChainApiKey }, this);
|
|
@@ -22969,7 +23197,7 @@ var Services = class Services {
|
|
|
22969
23197
|
if (this.options.arcGorillaPoolUrl != null && this.options.arcGorillaPoolUrl !== "") this.arcGorillaPool = new ARC(this.options.arcGorillaPoolUrl, this.options.arcGorillaPoolConfig, "arcGorillaPool");
|
|
22970
23198
|
if (this.options.arcadeUrl != null && this.options.arcadeUrl !== "") this.arcade = new Arcade(this.options.arcadeUrl, this.options.arcadeConfig, "arcade");
|
|
22971
23199
|
const hasBitails = this.chain === "main" || this.chain === "test";
|
|
22972
|
-
const hasWhatsOnChain = this.chain
|
|
23200
|
+
const hasWhatsOnChain = this.chain === "main" || this.chain === "test";
|
|
22973
23201
|
if (hasBitails) this.bitails = new Bitails(this.chain, { apiKey: this.options.bitailsApiKey });
|
|
22974
23202
|
return {
|
|
22975
23203
|
hasBitails,
|
|
@@ -23683,9 +23911,22 @@ function classifyMerklePathResponse(status, statusText, retry) {
|
|
|
23683
23911
|
//#endregion
|
|
23684
23912
|
//#region ../src/services/providers/WhatsOnChain.ts
|
|
23685
23913
|
var WhatsOnChainNoServices = class extends SdkWhatsOnChain {
|
|
23914
|
+
requestGate;
|
|
23686
23915
|
constructor(chain = "main", config = {}) {
|
|
23687
23916
|
if (chain === "mock") throw new Error("WhatsOnChain does not support 'mock' chain. Use MockServices directly.");
|
|
23688
23917
|
super(chain, config);
|
|
23918
|
+
this.requestGate = config.requestGate;
|
|
23919
|
+
}
|
|
23920
|
+
async requestWithAnonymousAuthFallback(url, requestOptions) {
|
|
23921
|
+
await this.requestGate?.();
|
|
23922
|
+
const response = await this.httpClient.request(url, requestOptions);
|
|
23923
|
+
if (response.status !== 401 && response.status !== 403 || this.apiKey.trim() === "") return response;
|
|
23924
|
+
if (this.requestGate != null) await this.requestGate();
|
|
23925
|
+
else await wait(350);
|
|
23926
|
+
return await this.httpClient.request(url, {
|
|
23927
|
+
method: "GET",
|
|
23928
|
+
headers: { Accept: "application/json" }
|
|
23929
|
+
});
|
|
23689
23930
|
}
|
|
23690
23931
|
/**
|
|
23691
23932
|
* POST
|
|
@@ -24094,7 +24335,7 @@ var WhatsOnChainNoServices = class extends SdkWhatsOnChain {
|
|
|
24094
24335
|
};
|
|
24095
24336
|
const url = `${this.URL}/block/${hash}/header`;
|
|
24096
24337
|
for (let retry = 0; retry < 2; retry++) {
|
|
24097
|
-
const response = await this.
|
|
24338
|
+
const response = await this.requestWithAnonymousAuthFallback(url, requestOptions);
|
|
24098
24339
|
if (response.statusText === "Too Many Requests" && retry < 2) {
|
|
24099
24340
|
await wait(2e3);
|
|
24100
24341
|
continue;
|
|
@@ -24112,7 +24353,7 @@ var WhatsOnChainNoServices = class extends SdkWhatsOnChain {
|
|
|
24112
24353
|
};
|
|
24113
24354
|
const url = `${this.URL}/chain/info`;
|
|
24114
24355
|
for (let retry = 0; retry < 2; retry++) {
|
|
24115
|
-
const response = await this.
|
|
24356
|
+
const response = await this.requestWithAnonymousAuthFallback(url, requestOptions);
|
|
24116
24357
|
if (response.statusText === "Too Many Requests" && retry < 2) {
|
|
24117
24358
|
await wait(2e3);
|
|
24118
24359
|
continue;
|
|
@@ -24298,12 +24539,16 @@ var WhatsOnChainServices = class WhatsOnChainServices {
|
|
|
24298
24539
|
timeout: 3e4,
|
|
24299
24540
|
userAgent: "BabbageWhatsOnChainServices",
|
|
24300
24541
|
enableCache: true,
|
|
24301
|
-
chainInfoMsecs: 5e3
|
|
24542
|
+
chainInfoMsecs: 5e3,
|
|
24543
|
+
minRequestIntervalMsecs: 350
|
|
24302
24544
|
};
|
|
24303
24545
|
}
|
|
24304
24546
|
static chainInfo = [];
|
|
24305
24547
|
static chainInfoTime = [];
|
|
24306
24548
|
static chainInfoMsecs = [];
|
|
24549
|
+
static chainInfoPromise = {};
|
|
24550
|
+
static requestTail = Promise.resolve();
|
|
24551
|
+
static nextRequestMsecs = 0;
|
|
24307
24552
|
chain;
|
|
24308
24553
|
woc;
|
|
24309
24554
|
constructor(options) {
|
|
@@ -24312,7 +24557,8 @@ var WhatsOnChainServices = class WhatsOnChainServices {
|
|
|
24312
24557
|
apiKey: this.options.apiKey,
|
|
24313
24558
|
timeout: this.options.timeout,
|
|
24314
24559
|
userAgent: this.options.userAgent,
|
|
24315
|
-
enableCache: this.options.enableCache
|
|
24560
|
+
enableCache: this.options.enableCache,
|
|
24561
|
+
requestGate: async () => await this.waitForRateLimit()
|
|
24316
24562
|
};
|
|
24317
24563
|
this.chain = options.chain;
|
|
24318
24564
|
const chainInfoMsecs = WhatsOnChainServices.chainInfoMsecs;
|
|
@@ -24330,7 +24576,16 @@ var WhatsOnChainServices = class WhatsOnChainServices {
|
|
|
24330
24576
|
let update = chainInfo[this.chain] === void 0;
|
|
24331
24577
|
if (!update && chainInfoTime[this.chain] !== void 0) update = now.getTime() - chainInfoTime[this.chain].getTime() > chainInfoMsecs[this.chain];
|
|
24332
24578
|
if (update) {
|
|
24333
|
-
|
|
24579
|
+
let pending = WhatsOnChainServices.chainInfoPromise[this.chain];
|
|
24580
|
+
if (pending == null) {
|
|
24581
|
+
pending = this.woc.getChainInfo();
|
|
24582
|
+
WhatsOnChainServices.chainInfoPromise[this.chain] = pending;
|
|
24583
|
+
}
|
|
24584
|
+
try {
|
|
24585
|
+
chainInfo[this.chain] = await pending;
|
|
24586
|
+
} finally {
|
|
24587
|
+
if (WhatsOnChainServices.chainInfoPromise[this.chain] === pending) delete WhatsOnChainServices.chainInfoPromise[this.chain];
|
|
24588
|
+
}
|
|
24334
24589
|
chainInfoTime[this.chain] = now;
|
|
24335
24590
|
}
|
|
24336
24591
|
if (!chainInfo[this.chain]) throw new Error("Unexpected failure to update chainInfo.");
|
|
@@ -24348,10 +24603,12 @@ var WhatsOnChainServices = class WhatsOnChainServices {
|
|
|
24348
24603
|
*/
|
|
24349
24604
|
async getHeaders(fetch) {
|
|
24350
24605
|
fetch ||= new ChaintracksFetch();
|
|
24606
|
+
await this.waitForRateLimit();
|
|
24351
24607
|
return await fetch.fetchJson(`https://api.whatsonchain.com/v1/bsv/${this.chain}/block/headers`);
|
|
24352
24608
|
}
|
|
24353
24609
|
async getHeaderByteFileLinks(neededRange, fetch) {
|
|
24354
24610
|
fetch ||= new ChaintracksFetch();
|
|
24611
|
+
await this.waitForRateLimit();
|
|
24355
24612
|
const files = await fetch.fetchJson(`https://api.whatsonchain.com/v1/bsv/${this.chain}/block/headers/resources`);
|
|
24356
24613
|
const r = [];
|
|
24357
24614
|
let range;
|
|
@@ -24364,6 +24621,21 @@ var WhatsOnChainServices = class WhatsOnChainServices {
|
|
|
24364
24621
|
}
|
|
24365
24622
|
return r;
|
|
24366
24623
|
}
|
|
24624
|
+
async waitForRateLimit() {
|
|
24625
|
+
let release;
|
|
24626
|
+
const previous = WhatsOnChainServices.requestTail;
|
|
24627
|
+
WhatsOnChainServices.requestTail = new Promise((resolve) => {
|
|
24628
|
+
release = resolve;
|
|
24629
|
+
});
|
|
24630
|
+
await previous;
|
|
24631
|
+
try {
|
|
24632
|
+
const delay = Math.max(0, WhatsOnChainServices.nextRequestMsecs - Date.now());
|
|
24633
|
+
if (delay > 0) await wait(delay);
|
|
24634
|
+
WhatsOnChainServices.nextRequestMsecs = Date.now() + (this.options.minRequestIntervalMsecs ?? 350);
|
|
24635
|
+
} finally {
|
|
24636
|
+
release();
|
|
24637
|
+
}
|
|
24638
|
+
}
|
|
24367
24639
|
};
|
|
24368
24640
|
function wocGetHeadersHeaderToBlockHeader(h) {
|
|
24369
24641
|
const bits = typeof h.bits === "string" ? Number.parseInt(h.bits, 16) : h.bits;
|
|
@@ -24424,6 +24696,51 @@ var BulkIngestorWhatsOnChainCdn = class extends BulkIngestorBase {
|
|
|
24424
24696
|
}
|
|
24425
24697
|
};
|
|
24426
24698
|
//#endregion
|
|
24699
|
+
//#region ../src/services/chaintracker/chaintracks/Ingest/BulkIngestorChaintracks.ts
|
|
24700
|
+
/**
|
|
24701
|
+
* Uses a go-chaintracks/Arcade-compatible service as a validated bulk source.
|
|
24702
|
+
* Retrieved bytes still pass through ChainTracks' local serialization, hash,
|
|
24703
|
+
* continuity, and genesis checks before storage.
|
|
24704
|
+
*/
|
|
24705
|
+
var BulkIngestorChaintracks = class extends BulkIngestorBase {
|
|
24706
|
+
chaintracks;
|
|
24707
|
+
maxHeadersPerRequest;
|
|
24708
|
+
networkChecked = false;
|
|
24709
|
+
constructor(options) {
|
|
24710
|
+
super(options);
|
|
24711
|
+
this.chaintracks = options.chaintracks;
|
|
24712
|
+
this.maxHeadersPerRequest = options.maxHeadersPerRequest ?? 1e3;
|
|
24713
|
+
if (!Number.isInteger(this.maxHeadersPerRequest) || this.maxHeadersPerRequest < 1) throw new Error("maxHeadersPerRequest must be a positive integer.");
|
|
24714
|
+
}
|
|
24715
|
+
async getPresentHeight() {
|
|
24716
|
+
await this.ensureNetwork();
|
|
24717
|
+
return await this.chaintracks.getPresentHeight();
|
|
24718
|
+
}
|
|
24719
|
+
async fetchHeaders(_before, fetchRange, bulkRange, priorLiveHeaders) {
|
|
24720
|
+
if (fetchRange.isEmpty) return priorLiveHeaders;
|
|
24721
|
+
await this.ensureNetwork();
|
|
24722
|
+
let liveHeaders = priorLiveHeaders;
|
|
24723
|
+
let height = fetchRange.minHeight;
|
|
24724
|
+
while (height <= fetchRange.maxHeight) {
|
|
24725
|
+
const requested = Math.min(this.maxHeadersPerRequest, fetchRange.maxHeight - height + 1);
|
|
24726
|
+
const bytes = asUint8Array(await this.chaintracks.getHeaders(height, requested));
|
|
24727
|
+
if (bytes.length === 0) throw new Error(`ChainTracks upstream returned no headers at height ${height}.`);
|
|
24728
|
+
if (bytes.length % 80 !== 0 || bytes.length > requested * 80) throw new Error(`ChainTracks upstream returned ${bytes.length} bytes for ${requested} headers at height ${height}.`);
|
|
24729
|
+
const headers = deserializeBlockHeaders(height, bytes);
|
|
24730
|
+
liveHeaders = await this.storage().addBulkHeaders(headers, bulkRange, liveHeaders);
|
|
24731
|
+
height += headers.length;
|
|
24732
|
+
if (headers.length < requested && height <= fetchRange.maxHeight) throw new Error(`ChainTracks upstream returned ${headers.length} of ${requested} headers at height ${height - headers.length}.`);
|
|
24733
|
+
}
|
|
24734
|
+
return liveHeaders;
|
|
24735
|
+
}
|
|
24736
|
+
async ensureNetwork() {
|
|
24737
|
+
if (this.networkChecked) return;
|
|
24738
|
+
const actual = await this.chaintracks.getChain();
|
|
24739
|
+
if (actual !== this.chain) throw new Error(`ChainTracks upstream network '${actual}' does not match configured chain '${this.chain}'.`);
|
|
24740
|
+
this.networkChecked = true;
|
|
24741
|
+
}
|
|
24742
|
+
};
|
|
24743
|
+
//#endregion
|
|
24427
24744
|
//#region ../src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.ts
|
|
24428
24745
|
/**
|
|
24429
24746
|
* Reports new headers by polling periodically.
|
|
@@ -24530,9 +24847,17 @@ var LiveIngestorChaintracksSSE = class extends LiveIngestorBase {
|
|
|
24530
24847
|
}
|
|
24531
24848
|
async startListening(liveHeaders) {
|
|
24532
24849
|
this.stopped = false;
|
|
24533
|
-
|
|
24850
|
+
const actual = await this.options.chaintracks.getChain();
|
|
24851
|
+
if (actual !== this.chain) throw new Error(`ChainTracks upstream network '${actual}' does not match configured chain '${this.chain}'.`);
|
|
24852
|
+
if (this.stopped) return;
|
|
24853
|
+
const subscriptionId = await this.options.chaintracks.subscribeHeaders((header) => {
|
|
24534
24854
|
if (!this.stopped) liveHeaders.push(header);
|
|
24535
24855
|
});
|
|
24856
|
+
if (this.stopped) {
|
|
24857
|
+
await this.options.chaintracks.unsubscribe(subscriptionId);
|
|
24858
|
+
return;
|
|
24859
|
+
}
|
|
24860
|
+
this.subscriptionId = subscriptionId;
|
|
24536
24861
|
await new Promise((resolve) => {
|
|
24537
24862
|
this.resolveStopped = resolve;
|
|
24538
24863
|
if (this.stopped) resolve();
|
|
@@ -24545,7 +24870,9 @@ var LiveIngestorChaintracksSSE = class extends LiveIngestorBase {
|
|
|
24545
24870
|
if (subscriptionId != null) this.options.chaintracks.unsubscribe(subscriptionId).catch((e) => {
|
|
24546
24871
|
this.log(`LiveIngestorChaintracksSSE unsubscribe failed: ${e}`);
|
|
24547
24872
|
});
|
|
24548
|
-
this.resolveStopped
|
|
24873
|
+
const resolveStopped = this.resolveStopped;
|
|
24874
|
+
this.resolveStopped = void 0;
|
|
24875
|
+
resolveStopped?.();
|
|
24549
24876
|
}
|
|
24550
24877
|
async shutdown() {
|
|
24551
24878
|
this.stopListening();
|
|
@@ -25241,6 +25568,27 @@ var ChaintracksStorageNoDb = class ChaintracksStorageNoDb extends ChaintracksSto
|
|
|
25241
25568
|
tipHeaderId: 0,
|
|
25242
25569
|
hashToHeaderId: /* @__PURE__ */ new Map()
|
|
25243
25570
|
};
|
|
25571
|
+
static stnData = {
|
|
25572
|
+
chain: "stn",
|
|
25573
|
+
liveHeaders: /* @__PURE__ */ new Map(),
|
|
25574
|
+
maxHeaderId: 0,
|
|
25575
|
+
tipHeaderId: 0,
|
|
25576
|
+
hashToHeaderId: /* @__PURE__ */ new Map()
|
|
25577
|
+
};
|
|
25578
|
+
static ttnData = {
|
|
25579
|
+
chain: "ttn",
|
|
25580
|
+
liveHeaders: /* @__PURE__ */ new Map(),
|
|
25581
|
+
maxHeaderId: 0,
|
|
25582
|
+
tipHeaderId: 0,
|
|
25583
|
+
hashToHeaderId: /* @__PURE__ */ new Map()
|
|
25584
|
+
};
|
|
25585
|
+
static tstnData = {
|
|
25586
|
+
chain: "tstn",
|
|
25587
|
+
liveHeaders: /* @__PURE__ */ new Map(),
|
|
25588
|
+
maxHeaderId: 0,
|
|
25589
|
+
tipHeaderId: 0,
|
|
25590
|
+
hashToHeaderId: /* @__PURE__ */ new Map()
|
|
25591
|
+
};
|
|
25244
25592
|
constructor(options) {
|
|
25245
25593
|
super(options);
|
|
25246
25594
|
}
|
|
@@ -25248,10 +25596,11 @@ var ChaintracksStorageNoDb = class ChaintracksStorageNoDb extends ChaintracksSto
|
|
|
25248
25596
|
async getData() {
|
|
25249
25597
|
switch (this.chain) {
|
|
25250
25598
|
case "main": return ChaintracksStorageNoDb.mainData;
|
|
25251
|
-
case "test":
|
|
25252
|
-
case "
|
|
25253
|
-
case "
|
|
25254
|
-
|
|
25599
|
+
case "test": return ChaintracksStorageNoDb.testData;
|
|
25600
|
+
case "stn": return ChaintracksStorageNoDb.stnData;
|
|
25601
|
+
case "ttn": return ChaintracksStorageNoDb.ttnData;
|
|
25602
|
+
case "tstn": return ChaintracksStorageNoDb.tstnData;
|
|
25603
|
+
default: throw new WERR_INVALID_PARAMETER("chain", `'main', 'test', 'stn', 'ttn', or 'tstn'. '${this.chain}' is unsupported.`);
|
|
25255
25604
|
}
|
|
25256
25605
|
}
|
|
25257
25606
|
async deleteLiveBlockHeaders() {
|
|
@@ -25840,7 +26189,7 @@ var ChaintracksStorageIdb = class extends ChaintracksStorageBase {
|
|
|
25840
26189
|
//#endregion
|
|
25841
26190
|
//#region ../src/services/chaintracker/chaintracks/configureChaintracksIngestors.ts
|
|
25842
26191
|
function resolveDefaultChaintracksArguments(args) {
|
|
25843
|
-
const [chain, whatsonchainApiKey = "", maxPerFile = 1e5, maxRetained = 2, fetch = new ChaintracksFetch(), cdnUrl = "https://cdn.projectbabbage.com/blockheaders/", liveHeightThreshold = 2e3, reorgHeightThreshold = 400, bulkMigrationChunkSize = 500, batchInsertLimit = 400, addLiveRecursionLimit = 36] = args;
|
|
26192
|
+
const [chain, whatsonchainApiKey = "", maxPerFile = 1e5, maxRetained = 2, fetch = new ChaintracksFetch(), cdnUrl = chain === "main" || chain === "test" ? "https://cdn.projectbabbage.com/blockheaders/" : "", liveHeightThreshold = 2e3, reorgHeightThreshold = 400, bulkMigrationChunkSize = 500, batchInsertLimit = 400, addLiveRecursionLimit = 36, sources = {}] = args;
|
|
25844
26193
|
return {
|
|
25845
26194
|
chain,
|
|
25846
26195
|
whatsonchainApiKey,
|
|
@@ -25852,11 +26201,12 @@ function resolveDefaultChaintracksArguments(args) {
|
|
|
25852
26201
|
reorgHeightThreshold,
|
|
25853
26202
|
bulkMigrationChunkSize,
|
|
25854
26203
|
batchInsertLimit,
|
|
25855
|
-
addLiveRecursionLimit
|
|
26204
|
+
addLiveRecursionLimit,
|
|
26205
|
+
sources
|
|
25856
26206
|
};
|
|
25857
26207
|
}
|
|
25858
26208
|
function toDefaultChaintracksArguments(params) {
|
|
25859
|
-
|
|
26209
|
+
const args = [
|
|
25860
26210
|
params.chain,
|
|
25861
26211
|
params.whatsonchainApiKey,
|
|
25862
26212
|
params.maxPerFile,
|
|
@@ -25869,6 +26219,8 @@ function toDefaultChaintracksArguments(params) {
|
|
|
25869
26219
|
params.batchInsertLimit,
|
|
25870
26220
|
params.addLiveRecursionLimit
|
|
25871
26221
|
];
|
|
26222
|
+
if (Object.keys(params.sources).length > 0) args.push(params.sources);
|
|
26223
|
+
return args;
|
|
25872
26224
|
}
|
|
25873
26225
|
function createDefaultBulkFileDataManager(params) {
|
|
25874
26226
|
return new BulkFileDataManager({
|
|
@@ -25911,7 +26263,7 @@ function createAndStartDefaultChaintracks(args, createOptions) {
|
|
|
25911
26263
|
* The caller is responsible for providing the storage implementation.
|
|
25912
26264
|
*/
|
|
25913
26265
|
function buildChaintracksOptionsWithIngestors(params, storage) {
|
|
25914
|
-
const { chain, whatsonchainApiKey, maxPerFile, fetch, cdnUrl, addLiveRecursionLimit } = params;
|
|
26266
|
+
const { chain, whatsonchainApiKey, maxPerFile, fetch, cdnUrl, addLiveRecursionLimit, sources } = params;
|
|
25915
26267
|
const co = {
|
|
25916
26268
|
chain,
|
|
25917
26269
|
storage,
|
|
@@ -25922,35 +26274,58 @@ function buildChaintracksOptionsWithIngestors(params, storage) {
|
|
|
25922
26274
|
readonly: false
|
|
25923
26275
|
};
|
|
25924
26276
|
const jsonResource = `${chain}NetBlockHeaders.json`;
|
|
25925
|
-
|
|
25926
|
-
|
|
25927
|
-
|
|
25928
|
-
|
|
25929
|
-
|
|
25930
|
-
|
|
25931
|
-
|
|
25932
|
-
|
|
25933
|
-
|
|
25934
|
-
|
|
25935
|
-
|
|
25936
|
-
|
|
25937
|
-
|
|
25938
|
-
|
|
25939
|
-
|
|
25940
|
-
|
|
25941
|
-
|
|
25942
|
-
|
|
25943
|
-
|
|
25944
|
-
|
|
25945
|
-
|
|
25946
|
-
|
|
25947
|
-
|
|
25948
|
-
|
|
25949
|
-
|
|
25950
|
-
|
|
25951
|
-
|
|
26277
|
+
if (!sources.disableCdn && cdnUrl !== "") {
|
|
26278
|
+
const bulkCdnOptions = {
|
|
26279
|
+
chain,
|
|
26280
|
+
jsonResource,
|
|
26281
|
+
fetch,
|
|
26282
|
+
cdnUrl,
|
|
26283
|
+
maxPerFile
|
|
26284
|
+
};
|
|
26285
|
+
co.bulkIngestors.push(new BulkIngestorCDNBabbage(bulkCdnOptions));
|
|
26286
|
+
}
|
|
26287
|
+
const chaintracksSource = sources.chaintracks ?? (sources.disableChaintracks ? void 0 : createPublicChaintracksSource(chain));
|
|
26288
|
+
if (chaintracksSource != null) {
|
|
26289
|
+
co.bulkIngestors.push(new BulkIngestorChaintracks({
|
|
26290
|
+
chain,
|
|
26291
|
+
jsonResource,
|
|
26292
|
+
chaintracks: chaintracksSource,
|
|
26293
|
+
maxHeadersPerRequest: sources.remoteMaxHeadersPerRequest
|
|
26294
|
+
}));
|
|
26295
|
+
co.liveIngestors.push(new LiveIngestorChaintracksSSE({
|
|
26296
|
+
chain,
|
|
26297
|
+
chaintracks: chaintracksSource
|
|
26298
|
+
}));
|
|
26299
|
+
}
|
|
26300
|
+
if ((chain === "main" || chain === "test") && !sources.disableWhatsOnChain) {
|
|
26301
|
+
const wocOptions = {
|
|
26302
|
+
chain,
|
|
26303
|
+
apiKey: whatsonchainApiKey,
|
|
26304
|
+
timeout: 3e4,
|
|
26305
|
+
userAgent: "BabbageWhatsOnChainServices",
|
|
26306
|
+
enableCache: true,
|
|
26307
|
+
chainInfoMsecs: 5e3
|
|
26308
|
+
};
|
|
26309
|
+
const bulkOptions = {
|
|
26310
|
+
...wocOptions,
|
|
26311
|
+
jsonResource,
|
|
26312
|
+
idleWait: 5e3
|
|
26313
|
+
};
|
|
26314
|
+
co.bulkIngestors.push(new BulkIngestorWhatsOnChainCdn(bulkOptions));
|
|
26315
|
+
const liveOptions = {
|
|
26316
|
+
...wocOptions,
|
|
26317
|
+
idleWait: 1e5
|
|
26318
|
+
};
|
|
26319
|
+
co.liveIngestors.push(new LiveIngestorWhatsOnChainPoll(liveOptions));
|
|
26320
|
+
}
|
|
26321
|
+
if (co.bulkIngestors.length === 0 || co.liveIngestors.length === 0) throw new Error(`ChainTracks ${chain} requires at least one bulk and live source. Configure sources.chaintracks for Teranode networks.`);
|
|
25952
26322
|
return co;
|
|
25953
26323
|
}
|
|
26324
|
+
function createPublicChaintracksSource(chain) {
|
|
26325
|
+
const serviceUrl = publicArcadeUrl(chain);
|
|
26326
|
+
if (serviceUrl == null) return void 0;
|
|
26327
|
+
return new GoChaintracksServiceClient(chain, serviceUrl, { apiPrefix: "/chaintracks/v2" });
|
|
26328
|
+
}
|
|
25954
26329
|
//#endregion
|
|
25955
26330
|
//#region ../src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.ts
|
|
25956
26331
|
function createDefaultNoDbChaintracksOptions(...args) {
|
|
@@ -34546,6 +34921,7 @@ exports.BulkFilesReaderStorage = BulkFilesReaderStorage;
|
|
|
34546
34921
|
exports.BulkIngestorBase = BulkIngestorBase;
|
|
34547
34922
|
exports.BulkIngestorCDN = BulkIngestorCDN;
|
|
34548
34923
|
exports.BulkIngestorCDNBabbage = BulkIngestorCDNBabbage;
|
|
34924
|
+
exports.BulkIngestorChaintracks = BulkIngestorChaintracks;
|
|
34549
34925
|
exports.BulkIngestorWhatsOnChainCdn = BulkIngestorWhatsOnChainCdn;
|
|
34550
34926
|
exports.BulkStorageBase = BulkStorageBase;
|
|
34551
34927
|
exports.CWIStyleWalletManager = CWIStyleWalletManager;
|
|
@@ -34624,7 +35000,12 @@ exports.asBsvSdkTx = asBsvSdkTx;
|
|
|
34624
35000
|
exports.asString = asString;
|
|
34625
35001
|
exports.asUint8Array = asUint8Array;
|
|
34626
35002
|
exports.brc29ProtocolID = brc29ProtocolID;
|
|
35003
|
+
exports.buildChaintracksOptionsWithIngestors = buildChaintracksOptionsWithIngestors;
|
|
34627
35004
|
exports.convertProofToMerklePath = convertProofToMerklePath;
|
|
35005
|
+
exports.createAndStartDefaultChaintracks = createAndStartDefaultChaintracks;
|
|
35006
|
+
exports.createDefaultBulkFileDataManager = createDefaultBulkFileDataManager;
|
|
35007
|
+
exports.createDefaultChaintracksClient = createDefaultChaintracksClient;
|
|
35008
|
+
exports.createDefaultChaintracksStorageOptions = createDefaultChaintracksStorageOptions;
|
|
34628
35009
|
exports.createDefaultIdbChaintracksOptions = createDefaultIdbChaintracksOptions;
|
|
34629
35010
|
exports.createDefaultNoDbChaintracksOptions = createDefaultNoDbChaintracksOptions;
|
|
34630
35011
|
exports.createDefaultWalletServicesOptions = createDefaultWalletServicesOptions;
|
|
@@ -34666,6 +35047,7 @@ exports.partitionActionLabels = partitionActionLabels;
|
|
|
34666
35047
|
exports.randomBytes = randomBytes;
|
|
34667
35048
|
exports.randomBytesBase64 = randomBytesBase64;
|
|
34668
35049
|
exports.randomBytesHex = randomBytesHex;
|
|
35050
|
+
exports.resolveDefaultChaintracksArguments = resolveDefaultChaintracksArguments;
|
|
34669
35051
|
Object.defineProperty(exports, "sdk", {
|
|
34670
35052
|
enumerable: true,
|
|
34671
35053
|
get: function() {
|
|
@@ -34676,9 +35058,11 @@ exports.selectBulkHeaderFiles = selectBulkHeaderFiles;
|
|
|
34676
35058
|
exports.sha256Hash = sha256Hash;
|
|
34677
35059
|
exports.stampLog = stampLog;
|
|
34678
35060
|
exports.stampLogFormat = stampLogFormat;
|
|
35061
|
+
exports.startChaintracks = startChaintracks;
|
|
34679
35062
|
exports.tableAuthSessionToPeerSession = tableAuthSessionToPeerSession;
|
|
34680
35063
|
exports.throwDummyReviewActions = throwDummyReviewActions;
|
|
34681
35064
|
exports.toBinaryBaseBlockHeader = toBinaryBaseBlockHeader;
|
|
35065
|
+
exports.toDefaultChaintracksArguments = toDefaultChaintracksArguments;
|
|
34682
35066
|
exports.toLookupNetworkPreset = toLookupNetworkPreset;
|
|
34683
35067
|
exports.toWalletNetwork = toWalletNetwork;
|
|
34684
35068
|
exports.transactionColumnsWithoutRawTx = transactionColumnsWithoutRawTx;
|