@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.
@@ -970,6 +970,7 @@ function toWalletNetwork(chain) {
970
970
  switch (chain) {
971
971
  case "main": return "mainnet";
972
972
  case "test":
973
+ case "stn":
973
974
  case "ttn":
974
975
  case "tstn":
975
976
  case "mock": return "testnet";
@@ -983,6 +984,7 @@ function toLookupNetworkPreset(chain) {
983
984
  switch (chain) {
984
985
  case "main": return "mainnet";
985
986
  case "test": return "testnet";
987
+ case "stn":
986
988
  case "ttn":
987
989
  case "tstn":
988
990
  case "mock": return "local";
@@ -11732,9 +11734,7 @@ function genesisHeader(chain) {
11732
11734
  height: 0,
11733
11735
  hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
11734
11736
  };
11735
- case "test":
11736
- case "ttn":
11737
- case "tstn": return {
11737
+ case "test": return {
11738
11738
  version: 1,
11739
11739
  previousHash: "0000000000000000000000000000000000000000000000000000000000000000",
11740
11740
  merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
@@ -11744,6 +11744,36 @@ function genesisHeader(chain) {
11744
11744
  height: 0,
11745
11745
  hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
11746
11746
  };
11747
+ case "stn": return {
11748
+ version: 1,
11749
+ previousHash: "0000000000000000000000000000000000000000000000000000000000000000",
11750
+ merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
11751
+ time: 1296688602,
11752
+ bits: 486604799,
11753
+ nonce: 173779992,
11754
+ height: 0,
11755
+ hash: "6b38bdbcd73a19f7889d23e1fa6166a9de71affceca60ca3bb1b28af8135c594"
11756
+ };
11757
+ case "ttn": return {
11758
+ version: 1,
11759
+ previousHash: "0000000000000000000000000000000000000000000000000000000000000000",
11760
+ merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
11761
+ time: 1755606836,
11762
+ bits: 486604799,
11763
+ nonce: 1092578460,
11764
+ height: 0,
11765
+ hash: "000000000499eabba0a88f5b3747231c74b9191c1a4a04b2c2ea817976b7776d"
11766
+ };
11767
+ case "tstn": return {
11768
+ version: 1,
11769
+ previousHash: "0000000000000000000000000000000000000000000000000000000000000000",
11770
+ merkleRoot: "64452e5b25c65e492ad6a4f5ce9f427ca986626c28315d88de920d66e28cc98f",
11771
+ time: 1782864e3,
11772
+ bits: 486604799,
11773
+ nonce: 1780488216,
11774
+ height: 0,
11775
+ hash: "000000005d221c0e023cb56b5682cf094f32cd959958b40bc931e5797cae706c"
11776
+ };
11747
11777
  case "mock": throw new Error("genesisHeader does not support 'mock' chain. Mock chain generates its own genesis block.");
11748
11778
  }
11749
11779
  }
@@ -19271,10 +19301,11 @@ var Chaintracks = class {
19271
19301
  startupError = null;
19272
19302
  subscriberCallbacksEnabled = false;
19273
19303
  stopMainThread = true;
19274
- lastPresentHeight = 0;
19304
+ lastPresentHeight = -1;
19275
19305
  lastPresentHeightMsecs = 0;
19276
19306
  lastPresentHeightMaxAge = 60 * 1e3;
19277
19307
  lock = new SingleWriterMultiReaderLock();
19308
+ sourceStatus = /* @__PURE__ */ new Map();
19278
19309
  constructor(options) {
19279
19310
  this.options = options;
19280
19311
  if (options.storage == null) throw new Error("storage is required.");
@@ -19285,6 +19316,22 @@ var Chaintracks = class {
19285
19316
  this.storage = options.storage;
19286
19317
  this.bulkIngestors = options.bulkIngestors;
19287
19318
  this.liveIngestors = options.liveIngestors;
19319
+ for (const [index, source] of this.bulkIngestors.entries()) {
19320
+ const name = this.sourceName("bulk", index, source);
19321
+ this.sourceStatus.set(name, {
19322
+ name,
19323
+ role: "bulk",
19324
+ state: "unknown"
19325
+ });
19326
+ }
19327
+ for (const [index, source] of this.liveIngestors.entries()) {
19328
+ const name = this.sourceName("live", index, source);
19329
+ this.sourceStatus.set(name, {
19330
+ name,
19331
+ role: "live",
19332
+ state: "unknown"
19333
+ });
19334
+ }
19288
19335
  this.addLiveRecursionLimit = options.addLiveRecursionLimit;
19289
19336
  if (options.logging != null) this.log = options.logging;
19290
19337
  this.storage.log = this.log;
@@ -19299,19 +19346,36 @@ var Chaintracks = class {
19299
19346
  */
19300
19347
  async getPresentHeight() {
19301
19348
  const now = Date.now();
19302
- if (this.lastPresentHeight && now - this.lastPresentHeightMsecs < this.lastPresentHeightMaxAge) return this.lastPresentHeight;
19303
- const presentHeights = [];
19304
- for (const bulk of this.bulkIngestors) try {
19305
- const presentHeight = await bulk.getPresentHeight();
19306
- if (presentHeight) presentHeights.push(presentHeight);
19307
- } catch (uerr) {
19308
- console.error(uerr);
19309
- }
19310
- const presentHeight = presentHeights.length > 0 ? Math.max(...presentHeights) : void 0;
19311
- if (!presentHeight) throw new Error("At least one bulk ingestor must implement getPresentHeight.");
19312
- this.lastPresentHeight = presentHeight;
19313
- this.lastPresentHeightMsecs = now;
19314
- return presentHeight;
19349
+ if (this.lastPresentHeight >= 0 && now - this.lastPresentHeightMsecs < this.lastPresentHeightMaxAge) return this.lastPresentHeight;
19350
+ for (const [index, bulk] of this.bulkIngestors.entries()) {
19351
+ const source = this.sourceName("bulk", index, bulk);
19352
+ try {
19353
+ const presentHeight = await bulk.getPresentHeight();
19354
+ if (presentHeight != null && Number.isInteger(presentHeight) && presentHeight >= 0) {
19355
+ this.markSourceSuccess(source, "bulk");
19356
+ this.lastPresentHeight = presentHeight;
19357
+ this.lastPresentHeightMsecs = now;
19358
+ return presentHeight;
19359
+ }
19360
+ } catch (uerr) {
19361
+ const error = WalletError.fromUnknown(uerr);
19362
+ this.markSourceFailure(source, "bulk", error);
19363
+ this.log(`Present-height source ${source} failed: ${error.message}`);
19364
+ }
19365
+ }
19366
+ if (this.lastPresentHeight >= 0) return this.lastPresentHeight;
19367
+ try {
19368
+ const ranges = await this.storage.getAvailableHeightRanges();
19369
+ const localHeight = Math.max(ranges.bulk.maxHeight, ranges.live.maxHeight);
19370
+ if (localHeight >= 0) {
19371
+ this.lastPresentHeight = localHeight;
19372
+ this.lastPresentHeightMsecs = now;
19373
+ return localHeight;
19374
+ }
19375
+ } catch (error) {
19376
+ this.log(`Unable to read the locally validated ChainTracks height: ${WalletError.fromUnknown(error).message}`);
19377
+ }
19378
+ throw new Error("No present-height source or locally validated headers are available.");
19315
19379
  }
19316
19380
  async currentHeight() {
19317
19381
  return await this.getPresentHeight();
@@ -19362,7 +19426,7 @@ var Chaintracks = class {
19362
19426
  for (const bulkIn of this.bulkIngestors) await bulkIn.setStorage(this.storage, this.log);
19363
19427
  for (const liveIn of this.liveIngestors) await liveIn.setStorage(this.storage, this.log);
19364
19428
  this.stopMainThread = false;
19365
- for (const liveIngestor of this.liveIngestors) this.promises.push(this.runLiveIngestor(liveIngestor));
19429
+ for (const [index, liveIngestor] of this.liveIngestors.entries()) this.promises.push(this.runLiveIngestor(liveIngestor, index));
19366
19430
  this.promises.push(this.mainThreadShiftLiveHeaders());
19367
19431
  while (!this.available && this.startupError == null) await wait(100);
19368
19432
  if (this.startupError != null) throw this.startupError;
@@ -19389,10 +19453,12 @@ var Chaintracks = class {
19389
19453
  async listening() {
19390
19454
  return await this.makeAvailable();
19391
19455
  }
19392
- async runLiveIngestor(liveIngestor) {
19456
+ async runLiveIngestor(liveIngestor, index) {
19393
19457
  let restartCount = 0;
19394
19458
  const name = liveIngestor.constructor.name;
19459
+ const source = this.sourceName("live", index, liveIngestor);
19395
19460
  while (!this.stopMainThread) try {
19461
+ this.markSourceSuccess(source, "live");
19396
19462
  await liveIngestor.startListening(this.liveHeaders);
19397
19463
  if (this.stopMainThread) return;
19398
19464
  restartCount++;
@@ -19403,6 +19469,7 @@ var Chaintracks = class {
19403
19469
  if (this.stopMainThread) return;
19404
19470
  restartCount++;
19405
19471
  const e = WalletError.fromUnknown(error_);
19472
+ this.markSourceFailure(source, "live", e);
19406
19473
  const waitMsecs = this.liveIngestorRestartWaitMsecs(restartCount);
19407
19474
  this.log(`Live ingestor ${name} failed restart=${restartCount} retryMsecs=${waitMsecs}: ${e.stack ?? e.message}`);
19408
19475
  await wait(waitMsecs);
@@ -19450,7 +19517,8 @@ var Chaintracks = class {
19450
19517
  storage: this.storage.constructor.name,
19451
19518
  bulkIngestors: this.bulkIngestors.map((bulkIngestor) => bulkIngestor.constructor.name),
19452
19519
  liveIngestors: this.liveIngestors.map((liveIngestor) => liveIngestor.constructor.name),
19453
- packages: []
19520
+ packages: [],
19521
+ sources: Array.from(this.sourceStatus.values()).map((status) => ({ ...status }))
19454
19522
  };
19455
19523
  }
19456
19524
  async getHeaders(height, count) {
@@ -19533,26 +19601,30 @@ var Chaintracks = class {
19533
19601
  let madeProgress = false;
19534
19602
  let hadSuccess = false;
19535
19603
  let done = false;
19536
- for (const bulk of this.bulkIngestors) try {
19537
- const beforeBulkMax = before.bulk.maxHeight;
19538
- const beforeLiveRange = HeightRange.from(newLiveHeaders);
19539
- const r = await bulk.synchronize(presentHeight, before, newLiveHeaders);
19540
- hadSuccess = true;
19541
- newLiveHeaders = r.liveHeaders;
19542
- after = await this.storage.getAvailableHeightRanges();
19543
- const added = after.bulk.above(before.bulk);
19544
- const afterLiveRange = HeightRange.from(newLiveHeaders);
19545
- if (after.bulk.maxHeight > beforeBulkMax || afterLiveRange.maxHeight > beforeLiveRange.maxHeight) madeProgress = true;
19546
- before = after;
19547
- this.log(`Bulk Ingestor: ${added.length} added with ${newLiveHeaders.length} live headers from ${bulk.constructor.name}`);
19548
- if (r.done) {
19549
- done = true;
19550
- break;
19604
+ for (const [index, bulk] of this.bulkIngestors.entries()) {
19605
+ const source = this.sourceName("bulk", index, bulk);
19606
+ try {
19607
+ const beforeBulkMax = before.bulk.maxHeight;
19608
+ const beforeLiveRange = HeightRange.from(newLiveHeaders);
19609
+ const r = await bulk.synchronize(presentHeight, before, newLiveHeaders);
19610
+ hadSuccess = true;
19611
+ this.markSourceSuccess(source, "bulk");
19612
+ newLiveHeaders = r.liveHeaders;
19613
+ after = await this.storage.getAvailableHeightRanges();
19614
+ const added = after.bulk.above(before.bulk);
19615
+ const afterLiveRange = HeightRange.from(newLiveHeaders);
19616
+ if (after.bulk.maxHeight > beforeBulkMax || afterLiveRange.maxHeight > beforeLiveRange.maxHeight) madeProgress = true;
19617
+ before = after;
19618
+ this.log(`Bulk Ingestor: ${added.length} added with ${newLiveHeaders.length} live headers from ${bulk.constructor.name}`);
19619
+ if (r.done) {
19620
+ done = true;
19621
+ break;
19622
+ }
19623
+ } catch (error_) {
19624
+ const e = bulkSyncError = WalletError.fromUnknown(error_);
19625
+ this.markSourceFailure(source, "bulk", e);
19626
+ this.log(`bulk sync error: ${e.message}`);
19551
19627
  }
19552
- } catch (error_) {
19553
- const e = bulkSyncError = WalletError.fromUnknown(error_);
19554
- this.log(`bulk sync error: ${e.message}`);
19555
- if (!this.available) break;
19556
19628
  }
19557
19629
  if (!this.available && bulkSyncError != null && !hadSuccess) this.startupError = bulkSyncError;
19558
19630
  return {
@@ -19562,10 +19634,41 @@ var Chaintracks = class {
19562
19634
  madeProgress
19563
19635
  };
19564
19636
  }
19637
+ sourceName(role, index, source) {
19638
+ return `${role}[${index}]:${source.constructor.name}`;
19639
+ }
19640
+ markSourceSuccess(name, role) {
19641
+ this.sourceStatus.set(name, {
19642
+ ...this.sourceStatus.get(name),
19643
+ name,
19644
+ role,
19645
+ state: "healthy",
19646
+ lastSuccess: (/* @__PURE__ */ new Date()).toISOString(),
19647
+ error: void 0
19648
+ });
19649
+ }
19650
+ markSourceFailure(name, role, error) {
19651
+ this.sourceStatus.set(name, {
19652
+ ...this.sourceStatus.get(name),
19653
+ name,
19654
+ role,
19655
+ state: "degraded",
19656
+ lastFailure: (/* @__PURE__ */ new Date()).toISOString(),
19657
+ error: error.message
19658
+ });
19659
+ }
19565
19660
  async getMissingBlockHeader(hash) {
19566
- for (const live of this.liveIngestors) {
19567
- const header = await live.getHeaderByHash(hash);
19568
- if (header != null) return header;
19661
+ for (const [index, live] of this.liveIngestors.entries()) {
19662
+ const source = this.sourceName("live", index, live);
19663
+ try {
19664
+ const header = await live.getHeaderByHash(hash);
19665
+ this.markSourceSuccess(source, "live");
19666
+ if (header != null) return header;
19667
+ } catch (error) {
19668
+ const resolved = WalletError.fromUnknown(error);
19669
+ this.markSourceFailure(source, "live", resolved);
19670
+ this.log(`Header lookup source ${source} failed: ${resolved.message}`);
19671
+ }
19569
19672
  }
19570
19673
  }
19571
19674
  invalidInsertHeaderResult(ihr) {
@@ -19898,6 +20001,9 @@ var GoChaintracksServiceClient = class {
19898
20001
  chain;
19899
20002
  baseUrl;
19900
20003
  fetcher;
20004
+ requestTimeoutMsecs;
20005
+ reconnectWaitMsecs;
20006
+ reconnectWaitMaxMsecs;
19901
20007
  subscriptions = /* @__PURE__ */ new Map();
19902
20008
  nextSubscriptionId = 1;
19903
20009
  constructor(chain, serviceUrl, options = {}) {
@@ -19911,6 +20017,15 @@ var GoChaintracksServiceClient = class {
19911
20017
  }
19912
20018
  this.baseUrl = `${base}${prefix}`;
19913
20019
  this.fetcher = options.fetch ?? fetch;
20020
+ this.requestTimeoutMsecs = options.requestTimeoutMsecs ?? 3e4;
20021
+ this.reconnectWaitMsecs = options.reconnectWaitMsecs ?? 1e3;
20022
+ this.reconnectWaitMaxMsecs = options.reconnectWaitMaxMsecs ?? 6e4;
20023
+ for (const [name, value] of [
20024
+ ["requestTimeoutMsecs", this.requestTimeoutMsecs],
20025
+ ["reconnectWaitMsecs", this.reconnectWaitMsecs],
20026
+ ["reconnectWaitMaxMsecs", this.reconnectWaitMaxMsecs]
20027
+ ]) if (!Number.isSafeInteger(value) || value < 1) throw new Error(`${name} must be a positive integer.`);
20028
+ if (this.reconnectWaitMaxMsecs < this.reconnectWaitMsecs) throw new Error("reconnectWaitMaxMsecs must be greater than or equal to reconnectWaitMsecs.");
19914
20029
  }
19915
20030
  async currentHeight() {
19916
20031
  return await this.getPresentHeight();
@@ -19920,12 +20035,8 @@ var GoChaintracksServiceClient = class {
19920
20035
  return h != null && root === asString(h.merkleRoot);
19921
20036
  }
19922
20037
  async getChain() {
19923
- try {
19924
- const r = await this.getJson("/network");
19925
- return this.normalizeChain(r.network);
19926
- } catch {
19927
- return this.chain;
19928
- }
20038
+ const r = await this.getJson("/network");
20039
+ return this.normalizeChain(typeof r === "string" ? r : r.network);
19929
20040
  }
19930
20041
  async getInfo() {
19931
20042
  const tip = await this.findChainTipHeader();
@@ -19940,11 +20051,11 @@ var GoChaintracksServiceClient = class {
19940
20051
  };
19941
20052
  }
19942
20053
  async getPresentHeight() {
19943
- return (await this.getJson("/height")).height;
20054
+ const result = await this.getJson("/height");
20055
+ return typeof result === "number" ? result : result.height;
19944
20056
  }
19945
20057
  async getHeaders(height, count) {
19946
- const bytes = await this.getBinary(`/headers.bin?height=${height}&count=${count}`);
19947
- return Buffer.from(bytes).toString("hex");
20058
+ return asString(await this.getBinary(`/headers.bin?height=${height}&count=${count}`));
19948
20059
  }
19949
20060
  async findChainTipHeader() {
19950
20061
  return await this.getJson("/tip");
@@ -20000,7 +20111,7 @@ var GoChaintracksServiceClient = class {
20000
20111
  async subscribe(type, path, onPayload) {
20001
20112
  const id = `${type}-${this.nextSubscriptionId++}`;
20002
20113
  const abort = new AbortController();
20003
- const done = this.runSse(path, abort.signal, onPayload);
20114
+ const done = this.runSseWithReconnect(path, abort.signal, onPayload);
20004
20115
  this.subscriptions.set(id, {
20005
20116
  id,
20006
20117
  type,
@@ -20012,30 +20123,75 @@ var GoChaintracksServiceClient = class {
20012
20123
  });
20013
20124
  return id;
20014
20125
  }
20015
- async runSse(path, signal, onPayload) {
20016
- const response = await this.fetcher(this.url(path), {
20017
- headers: { Accept: "text/event-stream" },
20018
- signal
20126
+ async runSseWithReconnect(path, signal, onPayload) {
20127
+ let failures = 0;
20128
+ while (!signal.aborted) {
20129
+ try {
20130
+ failures = await this.runSse(path, signal, onPayload) ? 0 : failures + 1;
20131
+ } catch {
20132
+ if (signal.aborted) return;
20133
+ failures++;
20134
+ }
20135
+ const multiplier = Math.min(2 ** Math.max(0, failures - 1), 64);
20136
+ const delay = Math.min(this.reconnectWaitMsecs * multiplier, this.reconnectWaitMaxMsecs);
20137
+ await this.waitForReconnect(delay, signal);
20138
+ }
20139
+ }
20140
+ async waitForReconnect(msecs, signal) {
20141
+ if (signal.aborted || msecs <= 0) return;
20142
+ await new Promise((resolve) => {
20143
+ let timeout;
20144
+ const onAbort = () => done();
20145
+ const done = () => {
20146
+ clearTimeout(timeout);
20147
+ signal.removeEventListener("abort", onAbort);
20148
+ resolve();
20149
+ };
20150
+ timeout = setTimeout(done, msecs);
20151
+ signal.addEventListener("abort", onAbort, { once: true });
20019
20152
  });
20020
- if (!response.ok) throw new Error(`GET ${this.url(path)} failed ${response.status} ${response.statusText}`);
20021
- if (response.body == null) throw new Error(`GET ${this.url(path)} returned no response body`);
20022
- const reader = response.body.getReader();
20023
- const decoder = new TextDecoder();
20024
- let buffer = "";
20153
+ }
20154
+ async runSse(path, signal, onPayload) {
20155
+ const controller = new AbortController();
20156
+ const onAbort = () => controller.abort();
20157
+ signal.addEventListener("abort", onAbort, { once: true });
20158
+ const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMsecs);
20159
+ let receivedEvent = false;
20160
+ const observePayload = (payload) => {
20161
+ receivedEvent = true;
20162
+ onPayload(payload);
20163
+ };
20025
20164
  try {
20026
- for (;;) {
20027
- const { done, value } = await reader.read();
20028
- if (done) break;
20029
- buffer += decoder.decode(value, { stream: true });
20030
- buffer = this.processSseBuffer(buffer, onPayload);
20165
+ const response = await this.fetcher(this.url(path), {
20166
+ headers: { Accept: "text/event-stream" },
20167
+ signal: controller.signal
20168
+ });
20169
+ clearTimeout(timeout);
20170
+ if (!response.ok) throw new Error(`GET ${this.url(path)} failed ${response.status} ${response.statusText}`);
20171
+ if (response.body == null) throw new Error(`GET ${this.url(path)} returned no response body`);
20172
+ const reader = response.body.getReader();
20173
+ const decoder = new TextDecoder();
20174
+ let buffer = "";
20175
+ try {
20176
+ for (;;) {
20177
+ const { done, value } = await reader.read();
20178
+ if (done) break;
20179
+ buffer += decoder.decode(value, { stream: true });
20180
+ buffer = this.processSseBuffer(buffer, observePayload);
20181
+ }
20182
+ buffer += decoder.decode();
20183
+ this.processSseBuffer(`${buffer}\n\n`, observePayload);
20184
+ } finally {
20185
+ reader.releaseLock();
20031
20186
  }
20032
- buffer += decoder.decode();
20033
- this.processSseBuffer(`${buffer}\n\n`, onPayload);
20034
20187
  } finally {
20035
- reader.releaseLock();
20188
+ clearTimeout(timeout);
20189
+ signal.removeEventListener("abort", onAbort);
20036
20190
  }
20191
+ return receivedEvent;
20037
20192
  }
20038
20193
  processSseBuffer(buffer, onPayload) {
20194
+ buffer = buffer.replaceAll("\r\n", "\n");
20039
20195
  for (;;) {
20040
20196
  const boundary = buffer.indexOf("\n\n");
20041
20197
  if (boundary < 0) return buffer;
@@ -20054,32 +20210,51 @@ var GoChaintracksServiceClient = class {
20054
20210
  return r;
20055
20211
  }
20056
20212
  async getJsonOrUndefined(path) {
20057
- const response = await this.fetcher(this.url(path), { headers: { Accept: "application/json" } });
20213
+ const response = await this.fetchWithTimeout(this.url(path), { headers: { Accept: "application/json" } });
20058
20214
  if (response.status === 404) return void 0;
20059
20215
  if (!response.ok) throw new Error(`GET ${this.url(path)} failed ${response.status} ${response.statusText}`);
20060
- return await response.json();
20216
+ const value = await response.json();
20217
+ if (value != null && typeof value === "object" && "status" in value) {
20218
+ const envelope = value;
20219
+ if (envelope.status === "success") return envelope.value;
20220
+ if (envelope.status === "error") throw new Error(envelope.description ?? `GET ${this.url(path)} failed`);
20221
+ }
20222
+ return value;
20061
20223
  }
20062
20224
  async getBinary(path) {
20063
- const response = await this.fetcher(this.url(path), { headers: { Accept: "application/octet-stream" } });
20225
+ const response = await this.fetchWithTimeout(this.url(path), { headers: { Accept: "application/octet-stream" } });
20064
20226
  if (!response.ok) throw new Error(`GET ${this.url(path)} failed ${response.status} ${response.statusText}`);
20065
20227
  return new Uint8Array(await response.arrayBuffer());
20066
20228
  }
20229
+ async fetchWithTimeout(url, init) {
20230
+ const controller = new AbortController();
20231
+ const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMsecs);
20232
+ try {
20233
+ return await this.fetcher(url, {
20234
+ ...init,
20235
+ signal: controller.signal
20236
+ });
20237
+ } finally {
20238
+ clearTimeout(timeout);
20239
+ }
20240
+ }
20067
20241
  url(path) {
20068
20242
  return `${this.baseUrl}${path}`;
20069
20243
  }
20070
20244
  normalizeChain(network) {
20071
- switch (network) {
20245
+ switch (network.trim().toLowerCase()) {
20072
20246
  case "main":
20073
20247
  case "mainnet": return "main";
20074
20248
  case "test":
20075
20249
  case "testnet": return "test";
20250
+ case "stn":
20251
+ case "scalingtestnet": return "stn";
20076
20252
  case "ttn":
20077
20253
  case "teratest":
20078
20254
  case "teratestnet": return "ttn";
20079
20255
  case "tstn":
20080
- case "teranodescalingtestnet":
20081
- case "scalingtestnet": return "tstn";
20082
- default: return this.chain;
20256
+ case "teranodescalingtestnet": return "tstn";
20257
+ default: throw new Error(`Unsupported ChainTracks upstream network '${network}'.`);
20083
20258
  }
20084
20259
  }
20085
20260
  };
@@ -21412,31 +21587,45 @@ var ServiceCollection = class ServiceCollection {
21412
21587
  //#endregion
21413
21588
  //#region ../src/services/networkConfig.ts
21414
21589
  /**
21415
- * Runtime service-endpoint configuration for the `tstn` (Teranode Scaling Test Net) network.
21590
+ * Runtime service-endpoint configuration for Teranode networks that do not
21591
+ * have a public, operator-independent service endpoint.
21416
21592
  *
21417
- * Unlike `main`, `test`, and `ttn`, the tstn service endpoints are not public and must not be
21593
+ * Unlike `main`, `test`, and `ttn`, the stn/tstn service endpoints are not public and must not be
21418
21594
  * hardcoded in this (public) source tree. They are supplied at runtime through environment
21419
21595
  * variables:
21420
21596
  *
21597
+ * STN_ARCADE_URL STN Arcade broadcaster / ARC endpoint base.
21598
+ * STN_CHAINTRACKS_URL STN ChainTracks service URL.
21421
21599
  * TSTN_ARCADE_URL Arcade broadcaster / ARC endpoint base. Also the fallback host for
21422
21600
  * ChainTracks when TSTN_CHAINTRACKS_URL is unset
21423
21601
  * (`${TSTN_ARCADE_URL}/chaintracks/v1`, mirroring the ttn layout).
21424
21602
  * TSTN_CHAINTRACKS_URL ChainTracks service URL.
21425
21603
  *
21426
- * tstn runs only Arcade (broadcast + merkle proofs) and ChainTracks (headers); there is no
21427
- * WhatsOnChain / block-explorer service for tstn, so no WhatsOnChain endpoint is configured and
21604
+ * stn/tstn run only operator-configured Arcade and ChainTracks services; there is no
21605
+ * documented WhatsOnChain service for them, so no WhatsOnChain endpoint is configured and
21428
21606
  * the WhatsOnChain-only lookups (raw tx, utxo status, txid status, script-hash history) are not
21429
- * available on tstn.
21607
+ * available on stn/tstn.
21430
21608
  *
21431
- * `process` is accessed defensively so importing this module remains safe in browser bundles;
21432
- * tstn is a server-side network and these variables are only read when the selected chain is
21433
- * tstn.
21609
+ * `process` is accessed defensively so importing this module remains safe in
21610
+ * browser bundles. Browser applications can still supply an explicit
21611
+ * ChaintracksClientApi without relying on environment variables.
21434
21612
  */
21435
21613
  function readEnv(name) {
21436
21614
  const value = (typeof process !== "undefined" ? process.env : void 0)?.[name];
21437
21615
  return value != null && value.trim() !== "" ? value.trim() : void 0;
21438
21616
  }
21439
- const stripTrailingSlash = (url) => {
21617
+ /** Credential-free public Arcade host for supported networks. */
21618
+ function publicArcadeUrl(chain) {
21619
+ switch (chain) {
21620
+ case "main": return "https://arcade-v2-us-1.bsvblockchain.tech";
21621
+ case "test": return "https://arcade-v2-testnet-us-1.bsvblockchain.tech";
21622
+ case "ttn": return "https://arcade-v2-ttn-us-1.bsvblockchain.tech";
21623
+ case "stn":
21624
+ case "tstn":
21625
+ case "mock": return;
21626
+ }
21627
+ }
21628
+ const stripTrailingSlash$1 = (url) => {
21440
21629
  let end = url.length;
21441
21630
  while (end > 0 && url[end - 1] === "/") end--;
21442
21631
  return url.slice(0, end);
@@ -21445,6 +21634,10 @@ const stripTrailingSlash = (url) => {
21445
21634
  function tstnArcadeUrl() {
21446
21635
  return readEnv("TSTN_ARCADE_URL");
21447
21636
  }
21637
+ /** Arcade broadcaster / ARC endpoint for stn, or `undefined` when unset. */
21638
+ function stnArcadeUrl() {
21639
+ return readEnv("STN_ARCADE_URL");
21640
+ }
21448
21641
  /**
21449
21642
  * ChainTracks service URL for tstn. Falls back to `${TSTN_ARCADE_URL}/chaintracks/v1` when
21450
21643
  * `TSTN_CHAINTRACKS_URL` is unset (mirrors the ttn layout). Throws when neither is configured.
@@ -21453,20 +21646,53 @@ function tstnChaintracksUrl() {
21453
21646
  const explicit = readEnv("TSTN_CHAINTRACKS_URL");
21454
21647
  if (explicit != null) return explicit;
21455
21648
  const arcade = tstnArcadeUrl();
21456
- if (arcade != null) return `${stripTrailingSlash(arcade)}/chaintracks/v1`;
21649
+ if (arcade != null) return `${stripTrailingSlash$1(arcade)}/chaintracks/v1`;
21457
21650
  throw new Error("tstn chain requires a ChainTracks URL: set TSTN_CHAINTRACKS_URL (or TSTN_ARCADE_URL) in the environment.");
21458
21651
  }
21652
+ /**
21653
+ * ChainTracks service URL for stn. Falls back to the configured Arcade host's
21654
+ * legacy-compatible path when STN_CHAINTRACKS_URL is unset.
21655
+ */
21656
+ function stnChaintracksUrl() {
21657
+ const explicit = readEnv("STN_CHAINTRACKS_URL");
21658
+ if (explicit != null) return explicit;
21659
+ const arcade = stnArcadeUrl();
21660
+ if (arcade != null) return `${stripTrailingSlash$1(arcade)}/chaintracks/v1`;
21661
+ throw new Error("stn chain requires a ChainTracks URL: set STN_CHAINTRACKS_URL (or STN_ARCADE_URL) in the environment.");
21662
+ }
21459
21663
  //#endregion
21460
21664
  //#region ../src/services/createDefaultWalletServicesOptions.ts
21665
+ function stripTrailingSlash(value) {
21666
+ let end = value.length;
21667
+ while (end > 0 && value[end - 1] === "/") end--;
21668
+ return value.slice(0, end);
21669
+ }
21670
+ function configuredChaintracksClient(chain, serviceUrl) {
21671
+ let path = "";
21672
+ try {
21673
+ path = stripTrailingSlash(new URL(serviceUrl).pathname);
21674
+ } catch {}
21675
+ if (path.endsWith("/v2")) return new GoChaintracksServiceClient(chain, serviceUrl);
21676
+ return new ChaintracksServiceClient(chain, serviceUrl);
21677
+ }
21678
+ /**
21679
+ * Returns the credential-free default ChainTracks client for a supported
21680
+ * public network, or an operator-configured client for stn/tstn.
21681
+ */
21682
+ function createDefaultChaintracksClient(chain) {
21683
+ switch (chain) {
21684
+ case "main":
21685
+ case "test":
21686
+ case "ttn": return new GoChaintracksServiceClient(chain, arcadeDefaultUrl(chain), { apiPrefix: "/chaintracks/v2" });
21687
+ case "stn": return configuredChaintracksClient(chain, stnChaintracksUrl());
21688
+ case "tstn": return configuredChaintracksClient(chain, tstnChaintracksUrl());
21689
+ }
21690
+ }
21461
21691
  function createDefaultWalletServicesOptions(...[chain, arcCallbackUrl, arcCallbackToken, taalArcApiKey, gorillaPoolArcApiKey, bitailsApiKey, deploymentId, chaintracks, arcadeUrl, arcadeApiKey, arcadeCallbackToken]) {
21462
21692
  if (chain === "mock") throw new Error("createDefaultWalletServicesOptions does not support 'mock' chain. Use MockServices directly.");
21463
21693
  deploymentId ||= `wallet-toolbox-${randomBytesHex(16)}`;
21464
- let chaintracksUrl;
21465
- if (chain === "ttn") chaintracksUrl = "https://arcade-v2-ttn-us-1.bsvblockchain.tech/chaintracks/v1";
21466
- else if (chain === "tstn") chaintracksUrl = tstnChaintracksUrl();
21467
- else chaintracksUrl = `https://${chain}net-chaintracks.babbage.systems`;
21468
21694
  const chaintracksFiatExchangeRatesUrl = "https://mainnet-chaintracks.babbage.systems/getFiatExchangeRates";
21469
- chaintracks ||= new ChaintracksServiceClient(chain, chaintracksUrl);
21695
+ chaintracks ||= createDefaultChaintracksClient(chain);
21470
21696
  const o = {
21471
21697
  chain,
21472
21698
  taalApiKey: void 0,
@@ -21524,14 +21750,15 @@ function createDefaultWalletServicesOptions(...[chain, arcCallbackUrl, arcCallba
21524
21750
  }
21525
21751
  /**
21526
21752
  * Default Arcade (bsv-blockchain/arcade) endpoint per chain.
21527
- * Returns undefined when no public default is known for the chain (e.g. testnet not yet deployed).
21753
+ * Returns undefined when no public default is known for the chain.
21528
21754
  */
21529
21755
  function arcadeDefaultUrl(chain) {
21530
21756
  switch (chain) {
21531
- case "main": return "https://arcade-v2-us-1.bsvblockchain.tech";
21532
- case "ttn": return "https://arcade-v2-ttn-us-1.bsvblockchain.tech";
21757
+ case "main":
21758
+ case "test":
21759
+ case "ttn": return publicArcadeUrl(chain);
21760
+ case "stn": return stnArcadeUrl();
21533
21761
  case "tstn": return tstnArcadeUrl();
21534
- case "test": return;
21535
21762
  case "mock": return;
21536
21763
  }
21537
21764
  }
@@ -21539,6 +21766,7 @@ function arcDefaultUrl(chain) {
21539
21766
  switch (chain) {
21540
21767
  case "main": return "https://arc.taal.com";
21541
21768
  case "test": return "https://arc-test.taal.com";
21769
+ case "stn": return stnArcadeUrl() ?? "";
21542
21770
  case "ttn": return "https://arcade-v2-ttn-us-1.bsvblockchain.tech/";
21543
21771
  case "tstn": return tstnArcadeUrl() ?? "";
21544
21772
  case "mock": return "";
@@ -22920,7 +23148,7 @@ var Services = class Services {
22920
23148
  telemetry;
22921
23149
  constructor(optionsOrChain) {
22922
23150
  this.chain = typeof optionsOrChain === "string" ? optionsOrChain : optionsOrChain.chain;
22923
- if (this.chain === "mock") throw new WERR_INVALID_PARAMETER("chain", "'main', 'test', 'ttn', or 'tstn'. Use MockServices for 'mock' chain.");
23151
+ if (this.chain === "mock") throw new WERR_INVALID_PARAMETER("chain", "'main', 'test', 'stn', 'ttn', or 'tstn'. Use MockServices for 'mock' chain.");
22924
23152
  this.options = typeof optionsOrChain === "string" ? Services.createDefaultOptions(this.chain) : optionsOrChain;
22925
23153
  this.telemetry = new Telemetry(this.options.telemetry);
22926
23154
  this.whatsonchain = new WhatsOnChain(this.chain, { apiKey: this.options.whatsOnChainApiKey }, this);
@@ -22934,7 +23162,7 @@ var Services = class Services {
22934
23162
  if (this.options.arcGorillaPoolUrl != null && this.options.arcGorillaPoolUrl !== "") this.arcGorillaPool = new ARC(this.options.arcGorillaPoolUrl, this.options.arcGorillaPoolConfig, "arcGorillaPool");
22935
23163
  if (this.options.arcadeUrl != null && this.options.arcadeUrl !== "") this.arcade = new Arcade(this.options.arcadeUrl, this.options.arcadeConfig, "arcade");
22936
23164
  const hasBitails = this.chain === "main" || this.chain === "test";
22937
- const hasWhatsOnChain = this.chain !== "tstn";
23165
+ const hasWhatsOnChain = this.chain === "main" || this.chain === "test";
22938
23166
  if (hasBitails) this.bitails = new Bitails(this.chain, { apiKey: this.options.bitailsApiKey });
22939
23167
  return {
22940
23168
  hasBitails,
@@ -23648,9 +23876,22 @@ function classifyMerklePathResponse(status, statusText, retry) {
23648
23876
  //#endregion
23649
23877
  //#region ../src/services/providers/WhatsOnChain.ts
23650
23878
  var WhatsOnChainNoServices = class extends SdkWhatsOnChain {
23879
+ requestGate;
23651
23880
  constructor(chain = "main", config = {}) {
23652
23881
  if (chain === "mock") throw new Error("WhatsOnChain does not support 'mock' chain. Use MockServices directly.");
23653
23882
  super(chain, config);
23883
+ this.requestGate = config.requestGate;
23884
+ }
23885
+ async requestWithAnonymousAuthFallback(url, requestOptions) {
23886
+ await this.requestGate?.();
23887
+ const response = await this.httpClient.request(url, requestOptions);
23888
+ if (response.status !== 401 && response.status !== 403 || this.apiKey.trim() === "") return response;
23889
+ if (this.requestGate != null) await this.requestGate();
23890
+ else await wait(350);
23891
+ return await this.httpClient.request(url, {
23892
+ method: "GET",
23893
+ headers: { Accept: "application/json" }
23894
+ });
23654
23895
  }
23655
23896
  /**
23656
23897
  * POST
@@ -24059,7 +24300,7 @@ var WhatsOnChainNoServices = class extends SdkWhatsOnChain {
24059
24300
  };
24060
24301
  const url = `${this.URL}/block/${hash}/header`;
24061
24302
  for (let retry = 0; retry < 2; retry++) {
24062
- const response = await this.httpClient.request(url, requestOptions);
24303
+ const response = await this.requestWithAnonymousAuthFallback(url, requestOptions);
24063
24304
  if (response.statusText === "Too Many Requests" && retry < 2) {
24064
24305
  await wait(2e3);
24065
24306
  continue;
@@ -24077,7 +24318,7 @@ var WhatsOnChainNoServices = class extends SdkWhatsOnChain {
24077
24318
  };
24078
24319
  const url = `${this.URL}/chain/info`;
24079
24320
  for (let retry = 0; retry < 2; retry++) {
24080
- const response = await this.httpClient.request(url, requestOptions);
24321
+ const response = await this.requestWithAnonymousAuthFallback(url, requestOptions);
24081
24322
  if (response.statusText === "Too Many Requests" && retry < 2) {
24082
24323
  await wait(2e3);
24083
24324
  continue;
@@ -24263,12 +24504,16 @@ var WhatsOnChainServices = class WhatsOnChainServices {
24263
24504
  timeout: 3e4,
24264
24505
  userAgent: "BabbageWhatsOnChainServices",
24265
24506
  enableCache: true,
24266
- chainInfoMsecs: 5e3
24507
+ chainInfoMsecs: 5e3,
24508
+ minRequestIntervalMsecs: 350
24267
24509
  };
24268
24510
  }
24269
24511
  static chainInfo = [];
24270
24512
  static chainInfoTime = [];
24271
24513
  static chainInfoMsecs = [];
24514
+ static chainInfoPromise = {};
24515
+ static requestTail = Promise.resolve();
24516
+ static nextRequestMsecs = 0;
24272
24517
  chain;
24273
24518
  woc;
24274
24519
  constructor(options) {
@@ -24277,7 +24522,8 @@ var WhatsOnChainServices = class WhatsOnChainServices {
24277
24522
  apiKey: this.options.apiKey,
24278
24523
  timeout: this.options.timeout,
24279
24524
  userAgent: this.options.userAgent,
24280
- enableCache: this.options.enableCache
24525
+ enableCache: this.options.enableCache,
24526
+ requestGate: async () => await this.waitForRateLimit()
24281
24527
  };
24282
24528
  this.chain = options.chain;
24283
24529
  const chainInfoMsecs = WhatsOnChainServices.chainInfoMsecs;
@@ -24295,7 +24541,16 @@ var WhatsOnChainServices = class WhatsOnChainServices {
24295
24541
  let update = chainInfo[this.chain] === void 0;
24296
24542
  if (!update && chainInfoTime[this.chain] !== void 0) update = now.getTime() - chainInfoTime[this.chain].getTime() > chainInfoMsecs[this.chain];
24297
24543
  if (update) {
24298
- chainInfo[this.chain] = await this.woc.getChainInfo();
24544
+ let pending = WhatsOnChainServices.chainInfoPromise[this.chain];
24545
+ if (pending == null) {
24546
+ pending = this.woc.getChainInfo();
24547
+ WhatsOnChainServices.chainInfoPromise[this.chain] = pending;
24548
+ }
24549
+ try {
24550
+ chainInfo[this.chain] = await pending;
24551
+ } finally {
24552
+ if (WhatsOnChainServices.chainInfoPromise[this.chain] === pending) delete WhatsOnChainServices.chainInfoPromise[this.chain];
24553
+ }
24299
24554
  chainInfoTime[this.chain] = now;
24300
24555
  }
24301
24556
  if (!chainInfo[this.chain]) throw new Error("Unexpected failure to update chainInfo.");
@@ -24313,10 +24568,12 @@ var WhatsOnChainServices = class WhatsOnChainServices {
24313
24568
  */
24314
24569
  async getHeaders(fetch) {
24315
24570
  fetch ||= new ChaintracksFetch();
24571
+ await this.waitForRateLimit();
24316
24572
  return await fetch.fetchJson(`https://api.whatsonchain.com/v1/bsv/${this.chain}/block/headers`);
24317
24573
  }
24318
24574
  async getHeaderByteFileLinks(neededRange, fetch) {
24319
24575
  fetch ||= new ChaintracksFetch();
24576
+ await this.waitForRateLimit();
24320
24577
  const files = await fetch.fetchJson(`https://api.whatsonchain.com/v1/bsv/${this.chain}/block/headers/resources`);
24321
24578
  const r = [];
24322
24579
  let range;
@@ -24329,6 +24586,21 @@ var WhatsOnChainServices = class WhatsOnChainServices {
24329
24586
  }
24330
24587
  return r;
24331
24588
  }
24589
+ async waitForRateLimit() {
24590
+ let release;
24591
+ const previous = WhatsOnChainServices.requestTail;
24592
+ WhatsOnChainServices.requestTail = new Promise((resolve) => {
24593
+ release = resolve;
24594
+ });
24595
+ await previous;
24596
+ try {
24597
+ const delay = Math.max(0, WhatsOnChainServices.nextRequestMsecs - Date.now());
24598
+ if (delay > 0) await wait(delay);
24599
+ WhatsOnChainServices.nextRequestMsecs = Date.now() + (this.options.minRequestIntervalMsecs ?? 350);
24600
+ } finally {
24601
+ release();
24602
+ }
24603
+ }
24332
24604
  };
24333
24605
  function wocGetHeadersHeaderToBlockHeader(h) {
24334
24606
  const bits = typeof h.bits === "string" ? Number.parseInt(h.bits, 16) : h.bits;
@@ -24389,6 +24661,51 @@ var BulkIngestorWhatsOnChainCdn = class extends BulkIngestorBase {
24389
24661
  }
24390
24662
  };
24391
24663
  //#endregion
24664
+ //#region ../src/services/chaintracker/chaintracks/Ingest/BulkIngestorChaintracks.ts
24665
+ /**
24666
+ * Uses a go-chaintracks/Arcade-compatible service as a validated bulk source.
24667
+ * Retrieved bytes still pass through ChainTracks' local serialization, hash,
24668
+ * continuity, and genesis checks before storage.
24669
+ */
24670
+ var BulkIngestorChaintracks = class extends BulkIngestorBase {
24671
+ chaintracks;
24672
+ maxHeadersPerRequest;
24673
+ networkChecked = false;
24674
+ constructor(options) {
24675
+ super(options);
24676
+ this.chaintracks = options.chaintracks;
24677
+ this.maxHeadersPerRequest = options.maxHeadersPerRequest ?? 1e3;
24678
+ if (!Number.isInteger(this.maxHeadersPerRequest) || this.maxHeadersPerRequest < 1) throw new Error("maxHeadersPerRequest must be a positive integer.");
24679
+ }
24680
+ async getPresentHeight() {
24681
+ await this.ensureNetwork();
24682
+ return await this.chaintracks.getPresentHeight();
24683
+ }
24684
+ async fetchHeaders(_before, fetchRange, bulkRange, priorLiveHeaders) {
24685
+ if (fetchRange.isEmpty) return priorLiveHeaders;
24686
+ await this.ensureNetwork();
24687
+ let liveHeaders = priorLiveHeaders;
24688
+ let height = fetchRange.minHeight;
24689
+ while (height <= fetchRange.maxHeight) {
24690
+ const requested = Math.min(this.maxHeadersPerRequest, fetchRange.maxHeight - height + 1);
24691
+ const bytes = asUint8Array(await this.chaintracks.getHeaders(height, requested));
24692
+ if (bytes.length === 0) throw new Error(`ChainTracks upstream returned no headers at height ${height}.`);
24693
+ if (bytes.length % 80 !== 0 || bytes.length > requested * 80) throw new Error(`ChainTracks upstream returned ${bytes.length} bytes for ${requested} headers at height ${height}.`);
24694
+ const headers = deserializeBlockHeaders(height, bytes);
24695
+ liveHeaders = await this.storage().addBulkHeaders(headers, bulkRange, liveHeaders);
24696
+ height += headers.length;
24697
+ if (headers.length < requested && height <= fetchRange.maxHeight) throw new Error(`ChainTracks upstream returned ${headers.length} of ${requested} headers at height ${height - headers.length}.`);
24698
+ }
24699
+ return liveHeaders;
24700
+ }
24701
+ async ensureNetwork() {
24702
+ if (this.networkChecked) return;
24703
+ const actual = await this.chaintracks.getChain();
24704
+ if (actual !== this.chain) throw new Error(`ChainTracks upstream network '${actual}' does not match configured chain '${this.chain}'.`);
24705
+ this.networkChecked = true;
24706
+ }
24707
+ };
24708
+ //#endregion
24392
24709
  //#region ../src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.ts
24393
24710
  /**
24394
24711
  * Reports new headers by polling periodically.
@@ -24495,9 +24812,17 @@ var LiveIngestorChaintracksSSE = class extends LiveIngestorBase {
24495
24812
  }
24496
24813
  async startListening(liveHeaders) {
24497
24814
  this.stopped = false;
24498
- this.subscriptionId = await this.options.chaintracks.subscribeHeaders((header) => {
24815
+ const actual = await this.options.chaintracks.getChain();
24816
+ if (actual !== this.chain) throw new Error(`ChainTracks upstream network '${actual}' does not match configured chain '${this.chain}'.`);
24817
+ if (this.stopped) return;
24818
+ const subscriptionId = await this.options.chaintracks.subscribeHeaders((header) => {
24499
24819
  if (!this.stopped) liveHeaders.push(header);
24500
24820
  });
24821
+ if (this.stopped) {
24822
+ await this.options.chaintracks.unsubscribe(subscriptionId);
24823
+ return;
24824
+ }
24825
+ this.subscriptionId = subscriptionId;
24501
24826
  await new Promise((resolve) => {
24502
24827
  this.resolveStopped = resolve;
24503
24828
  if (this.stopped) resolve();
@@ -24510,7 +24835,9 @@ var LiveIngestorChaintracksSSE = class extends LiveIngestorBase {
24510
24835
  if (subscriptionId != null) this.options.chaintracks.unsubscribe(subscriptionId).catch((e) => {
24511
24836
  this.log(`LiveIngestorChaintracksSSE unsubscribe failed: ${e}`);
24512
24837
  });
24513
- this.resolveStopped?.();
24838
+ const resolveStopped = this.resolveStopped;
24839
+ this.resolveStopped = void 0;
24840
+ resolveStopped?.();
24514
24841
  }
24515
24842
  async shutdown() {
24516
24843
  this.stopListening();
@@ -25206,6 +25533,27 @@ var ChaintracksStorageNoDb = class ChaintracksStorageNoDb extends ChaintracksSto
25206
25533
  tipHeaderId: 0,
25207
25534
  hashToHeaderId: /* @__PURE__ */ new Map()
25208
25535
  };
25536
+ static stnData = {
25537
+ chain: "stn",
25538
+ liveHeaders: /* @__PURE__ */ new Map(),
25539
+ maxHeaderId: 0,
25540
+ tipHeaderId: 0,
25541
+ hashToHeaderId: /* @__PURE__ */ new Map()
25542
+ };
25543
+ static ttnData = {
25544
+ chain: "ttn",
25545
+ liveHeaders: /* @__PURE__ */ new Map(),
25546
+ maxHeaderId: 0,
25547
+ tipHeaderId: 0,
25548
+ hashToHeaderId: /* @__PURE__ */ new Map()
25549
+ };
25550
+ static tstnData = {
25551
+ chain: "tstn",
25552
+ liveHeaders: /* @__PURE__ */ new Map(),
25553
+ maxHeaderId: 0,
25554
+ tipHeaderId: 0,
25555
+ hashToHeaderId: /* @__PURE__ */ new Map()
25556
+ };
25209
25557
  constructor(options) {
25210
25558
  super(options);
25211
25559
  }
@@ -25213,10 +25561,11 @@ var ChaintracksStorageNoDb = class ChaintracksStorageNoDb extends ChaintracksSto
25213
25561
  async getData() {
25214
25562
  switch (this.chain) {
25215
25563
  case "main": return ChaintracksStorageNoDb.mainData;
25216
- case "test":
25217
- case "ttn":
25218
- case "tstn": return ChaintracksStorageNoDb.testData;
25219
- default: throw new WERR_INVALID_PARAMETER("chain", `'main', 'test', 'ttn', or 'tstn'. '${this.chain}' is unsupported.`);
25564
+ case "test": return ChaintracksStorageNoDb.testData;
25565
+ case "stn": return ChaintracksStorageNoDb.stnData;
25566
+ case "ttn": return ChaintracksStorageNoDb.ttnData;
25567
+ case "tstn": return ChaintracksStorageNoDb.tstnData;
25568
+ default: throw new WERR_INVALID_PARAMETER("chain", `'main', 'test', 'stn', 'ttn', or 'tstn'. '${this.chain}' is unsupported.`);
25220
25569
  }
25221
25570
  }
25222
25571
  async deleteLiveBlockHeaders() {
@@ -25805,7 +26154,7 @@ var ChaintracksStorageIdb = class extends ChaintracksStorageBase {
25805
26154
  //#endregion
25806
26155
  //#region ../src/services/chaintracker/chaintracks/configureChaintracksIngestors.ts
25807
26156
  function resolveDefaultChaintracksArguments(args) {
25808
- 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;
26157
+ 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;
25809
26158
  return {
25810
26159
  chain,
25811
26160
  whatsonchainApiKey,
@@ -25817,11 +26166,12 @@ function resolveDefaultChaintracksArguments(args) {
25817
26166
  reorgHeightThreshold,
25818
26167
  bulkMigrationChunkSize,
25819
26168
  batchInsertLimit,
25820
- addLiveRecursionLimit
26169
+ addLiveRecursionLimit,
26170
+ sources
25821
26171
  };
25822
26172
  }
25823
26173
  function toDefaultChaintracksArguments(params) {
25824
- return [
26174
+ const args = [
25825
26175
  params.chain,
25826
26176
  params.whatsonchainApiKey,
25827
26177
  params.maxPerFile,
@@ -25834,6 +26184,8 @@ function toDefaultChaintracksArguments(params) {
25834
26184
  params.batchInsertLimit,
25835
26185
  params.addLiveRecursionLimit
25836
26186
  ];
26187
+ if (Object.keys(params.sources).length > 0) args.push(params.sources);
26188
+ return args;
25837
26189
  }
25838
26190
  function createDefaultBulkFileDataManager(params) {
25839
26191
  return new BulkFileDataManager({
@@ -25876,7 +26228,7 @@ function createAndStartDefaultChaintracks(args, createOptions) {
25876
26228
  * The caller is responsible for providing the storage implementation.
25877
26229
  */
25878
26230
  function buildChaintracksOptionsWithIngestors(params, storage) {
25879
- const { chain, whatsonchainApiKey, maxPerFile, fetch, cdnUrl, addLiveRecursionLimit } = params;
26231
+ const { chain, whatsonchainApiKey, maxPerFile, fetch, cdnUrl, addLiveRecursionLimit, sources } = params;
25880
26232
  const co = {
25881
26233
  chain,
25882
26234
  storage,
@@ -25887,35 +26239,58 @@ function buildChaintracksOptionsWithIngestors(params, storage) {
25887
26239
  readonly: false
25888
26240
  };
25889
26241
  const jsonResource = `${chain}NetBlockHeaders.json`;
25890
- const bulkCdnOptions = {
25891
- chain,
25892
- jsonResource,
25893
- fetch,
25894
- cdnUrl,
25895
- maxPerFile
25896
- };
25897
- co.bulkIngestors.push(new BulkIngestorCDNBabbage(bulkCdnOptions));
25898
- const wocOptions = {
25899
- chain,
25900
- apiKey: whatsonchainApiKey,
25901
- timeout: 3e4,
25902
- userAgent: "BabbageWhatsOnChainServices",
25903
- enableCache: true,
25904
- chainInfoMsecs: 5e3
25905
- };
25906
- const bulkOptions = {
25907
- ...wocOptions,
25908
- jsonResource,
25909
- idleWait: 5e3
25910
- };
25911
- co.bulkIngestors.push(new BulkIngestorWhatsOnChainCdn(bulkOptions));
25912
- const liveOptions = {
25913
- ...wocOptions,
25914
- idleWait: 1e5
25915
- };
25916
- co.liveIngestors.push(new LiveIngestorWhatsOnChainPoll(liveOptions));
26242
+ if (!sources.disableCdn && cdnUrl !== "") {
26243
+ const bulkCdnOptions = {
26244
+ chain,
26245
+ jsonResource,
26246
+ fetch,
26247
+ cdnUrl,
26248
+ maxPerFile
26249
+ };
26250
+ co.bulkIngestors.push(new BulkIngestorCDNBabbage(bulkCdnOptions));
26251
+ }
26252
+ const chaintracksSource = sources.chaintracks ?? (sources.disableChaintracks ? void 0 : createPublicChaintracksSource(chain));
26253
+ if (chaintracksSource != null) {
26254
+ co.bulkIngestors.push(new BulkIngestorChaintracks({
26255
+ chain,
26256
+ jsonResource,
26257
+ chaintracks: chaintracksSource,
26258
+ maxHeadersPerRequest: sources.remoteMaxHeadersPerRequest
26259
+ }));
26260
+ co.liveIngestors.push(new LiveIngestorChaintracksSSE({
26261
+ chain,
26262
+ chaintracks: chaintracksSource
26263
+ }));
26264
+ }
26265
+ if ((chain === "main" || chain === "test") && !sources.disableWhatsOnChain) {
26266
+ const wocOptions = {
26267
+ chain,
26268
+ apiKey: whatsonchainApiKey,
26269
+ timeout: 3e4,
26270
+ userAgent: "BabbageWhatsOnChainServices",
26271
+ enableCache: true,
26272
+ chainInfoMsecs: 5e3
26273
+ };
26274
+ const bulkOptions = {
26275
+ ...wocOptions,
26276
+ jsonResource,
26277
+ idleWait: 5e3
26278
+ };
26279
+ co.bulkIngestors.push(new BulkIngestorWhatsOnChainCdn(bulkOptions));
26280
+ const liveOptions = {
26281
+ ...wocOptions,
26282
+ idleWait: 1e5
26283
+ };
26284
+ co.liveIngestors.push(new LiveIngestorWhatsOnChainPoll(liveOptions));
26285
+ }
26286
+ 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.`);
25917
26287
  return co;
25918
26288
  }
26289
+ function createPublicChaintracksSource(chain) {
26290
+ const serviceUrl = publicArcadeUrl(chain);
26291
+ if (serviceUrl == null) return void 0;
26292
+ return new GoChaintracksServiceClient(chain, serviceUrl, { apiPrefix: "/chaintracks/v2" });
26293
+ }
25919
26294
  //#endregion
25920
26295
  //#region ../src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.ts
25921
26296
  function createDefaultNoDbChaintracksOptions(...args) {
@@ -34494,6 +34869,6 @@ var WalletPermissionsManager = class WalletPermissionsManager {
34494
34869
  }
34495
34870
  };
34496
34871
  //#endregion
34497
- export { ARGON2ID_DEFAULT_HASH_LENGTH, ARGON2ID_DEFAULT_ITERATIONS, ARGON2ID_DEFAULT_MEMORY_KIB, ARGON2ID_DEFAULT_PARALLELISM, ARGON2ID_MAX_ITERATIONS, ARGON2ID_MAX_MEMORY_KIB, ARGON2ID_MAX_PARALLELISM, AuthMethodInteractor, BHServiceClient, BulkFileDataManager, BulkFileDataReader, BulkFilesReader, BulkFilesReaderFs, BulkFilesReaderStorage, BulkIngestorBase, BulkIngestorCDN, BulkIngestorCDNBabbage, BulkIngestorWhatsOnChainCdn, BulkStorageBase, CWIStyleWalletManager, Chaintracks, ChaintracksChainTracker, ChaintracksFetch, ChaintracksFetchError, ChaintracksServiceClient, ChaintracksStorageBase, ChaintracksStorageIdb, ChaintracksStorageNoDb, DEFAULT_PROFILE_ID, DEFAULT_SETTINGS, DevConsoleInteractor, EntityBase, EntityCertificate, EntityCertificateField, EntityCommission, EntityOutput, EntityOutputBasket, EntityOutputTag, EntityOutputTagMap, EntityProvenTx, EntityProvenTxReq, EntitySyncState, EntityTransaction, EntityTxLabel, EntityTxLabelMap, EntityUser, GoChaintracksServiceClient, HeightRange, KDF_MAX_HASH_LENGTH, LiveIngestorBase, LiveIngestorChaintracksSSE, LiveIngestorWhatsOnChainPoll, MAX_STATE_SNAPSHOT_BYTES, MergeEntity, Monitor, OverlayUMPTokenInteractor, PBKDF2_MAX_ITERATIONS, PBKDF2_NUM_ROUNDS, PersonaIDInteractor, PrivilegedKeyManager, ScriptTemplateBRC29, Services, SetupClient, SimpleWalletManager, StorageClient, StorageIdb, StorageProvider, StorageSyncReader, TESTNET_DEFAULT_SETTINGS, TwilioPhoneInteractor, UMPTokenLookupError, WABAccountContinuityError, WABClient, WABClientError, WABTransport, Wallet, WalletAuthenticationManager, WalletLogger, WalletPermissionsManager, WalletSettingsManager, WalletSigner, WalletStorageManager, WhatsOnChainServices, arcDefaultUrl, arcGorillaPoolUrl, arcadeDefaultUrl, arraysEqual, asArray, asBsvSdkPrivateKey, asBsvSdkPublickKey, asBsvSdkScript, asBsvSdkTx, asString, asUint8Array, brc29ProtocolID, convertProofToMerklePath, createDefaultIdbChaintracksOptions, createDefaultNoDbChaintracksOptions, createDefaultWalletServicesOptions, createIdbChaintracks, createNoDbChaintracks, createSyncMap, decryptBRC39, doubleSha256BE, doubleSha256LE, encryptBRC39, exportBRC38, exportBRC38Json, exportBRC39, getIdentityKey, getLabelToSpecOp, getListOutputsSpecOp, importBRC38, importBRC39, isAutoSpendableChangeOutput, isBaseBlockHeader, isBlockHeader, isLive, isLiveBlockHeader, isManagedChangeOutput, logCreateActionArgs, logWalletError, logger, makeAtomicBeef, makeBrc114ActionTimeLabel, managedChangeOutputFields, maxDate, optionalArraysEqual, outputColumnsWithoutLockingScript, parseBRC38Json, parseBrc114ActionTimeLabels, parseFileLink, parseTxScriptOffsets, partitionActionLabels, randomBytes, randomBytesBase64, randomBytesHex, sdk_exports as sdk, selectBulkHeaderFiles, sha256Hash, stampLog, stampLogFormat, tableAuthSessionToPeerSession, throwDummyReviewActions, toBinaryBaseBlockHeader, toLookupNetworkPreset, toWalletNetwork, transactionColumnsWithoutRawTx, blockHeaderUtilities_exports as utils, validateScriptHash, validateSecondsSinceEpoch, validateStorageFeeModel, verifyHexString, verifyId, verifyInteger, verifyNumber, verifyOne, verifyOneOrNone, verifyOptionalHexString, verifyTruthy, wait, wocGetHeadersHeaderToBlockHeader };
34872
+ export { ARGON2ID_DEFAULT_HASH_LENGTH, ARGON2ID_DEFAULT_ITERATIONS, ARGON2ID_DEFAULT_MEMORY_KIB, ARGON2ID_DEFAULT_PARALLELISM, ARGON2ID_MAX_ITERATIONS, ARGON2ID_MAX_MEMORY_KIB, ARGON2ID_MAX_PARALLELISM, AuthMethodInteractor, BHServiceClient, BulkFileDataManager, BulkFileDataReader, BulkFilesReader, BulkFilesReaderFs, BulkFilesReaderStorage, BulkIngestorBase, BulkIngestorCDN, BulkIngestorCDNBabbage, BulkIngestorChaintracks, BulkIngestorWhatsOnChainCdn, BulkStorageBase, CWIStyleWalletManager, Chaintracks, ChaintracksChainTracker, ChaintracksFetch, ChaintracksFetchError, ChaintracksServiceClient, ChaintracksStorageBase, ChaintracksStorageIdb, ChaintracksStorageNoDb, DEFAULT_PROFILE_ID, DEFAULT_SETTINGS, DevConsoleInteractor, EntityBase, EntityCertificate, EntityCertificateField, EntityCommission, EntityOutput, EntityOutputBasket, EntityOutputTag, EntityOutputTagMap, EntityProvenTx, EntityProvenTxReq, EntitySyncState, EntityTransaction, EntityTxLabel, EntityTxLabelMap, EntityUser, GoChaintracksServiceClient, HeightRange, KDF_MAX_HASH_LENGTH, LiveIngestorBase, LiveIngestorChaintracksSSE, LiveIngestorWhatsOnChainPoll, MAX_STATE_SNAPSHOT_BYTES, MergeEntity, Monitor, OverlayUMPTokenInteractor, PBKDF2_MAX_ITERATIONS, PBKDF2_NUM_ROUNDS, PersonaIDInteractor, PrivilegedKeyManager, ScriptTemplateBRC29, Services, SetupClient, SimpleWalletManager, StorageClient, StorageIdb, StorageProvider, StorageSyncReader, TESTNET_DEFAULT_SETTINGS, TwilioPhoneInteractor, UMPTokenLookupError, WABAccountContinuityError, WABClient, WABClientError, WABTransport, Wallet, WalletAuthenticationManager, WalletLogger, WalletPermissionsManager, WalletSettingsManager, WalletSigner, WalletStorageManager, WhatsOnChainServices, arcDefaultUrl, arcGorillaPoolUrl, arcadeDefaultUrl, arraysEqual, asArray, asBsvSdkPrivateKey, asBsvSdkPublickKey, asBsvSdkScript, asBsvSdkTx, asString, asUint8Array, brc29ProtocolID, buildChaintracksOptionsWithIngestors, convertProofToMerklePath, createAndStartDefaultChaintracks, createDefaultBulkFileDataManager, createDefaultChaintracksClient, createDefaultChaintracksStorageOptions, createDefaultIdbChaintracksOptions, createDefaultNoDbChaintracksOptions, createDefaultWalletServicesOptions, createIdbChaintracks, createNoDbChaintracks, createSyncMap, decryptBRC39, doubleSha256BE, doubleSha256LE, encryptBRC39, exportBRC38, exportBRC38Json, exportBRC39, getIdentityKey, getLabelToSpecOp, getListOutputsSpecOp, importBRC38, importBRC39, isAutoSpendableChangeOutput, isBaseBlockHeader, isBlockHeader, isLive, isLiveBlockHeader, isManagedChangeOutput, logCreateActionArgs, logWalletError, logger, makeAtomicBeef, makeBrc114ActionTimeLabel, managedChangeOutputFields, maxDate, optionalArraysEqual, outputColumnsWithoutLockingScript, parseBRC38Json, parseBrc114ActionTimeLabels, parseFileLink, parseTxScriptOffsets, partitionActionLabels, randomBytes, randomBytesBase64, randomBytesHex, resolveDefaultChaintracksArguments, sdk_exports as sdk, selectBulkHeaderFiles, sha256Hash, stampLog, stampLogFormat, startChaintracks, tableAuthSessionToPeerSession, throwDummyReviewActions, toBinaryBaseBlockHeader, toDefaultChaintracksArguments, toLookupNetworkPreset, toWalletNetwork, transactionColumnsWithoutRawTx, blockHeaderUtilities_exports as utils, validateScriptHash, validateSecondsSinceEpoch, validateStorageFeeModel, verifyHexString, verifyId, verifyInteger, verifyNumber, verifyOne, verifyOneOrNone, verifyOptionalHexString, verifyTruthy, wait, wocGetHeadersHeaderToBlockHeader };
34498
34873
 
34499
34874
  //# sourceMappingURL=index.client.mjs.map