@unicitylabs/sphere-sdk 0.3.1 → 0.3.3

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/dist/index.cjs CHANGED
@@ -508,6 +508,7 @@ __export(index_exports, {
508
508
  base58Encode: () => base58Encode2,
509
509
  buildTxfStorageData: () => buildTxfStorageData,
510
510
  bytesToHex: () => bytesToHex2,
511
+ checkNetworkHealth: () => checkNetworkHealth,
511
512
  countCommittedTransactions: () => countCommittedTransactions,
512
513
  createAddress: () => createAddress,
513
514
  createCommunicationsModule: () => createCommunicationsModule,
@@ -1758,6 +1759,7 @@ async function sendAlpha(wallet, toAddress, amountAlpha, fromAddress) {
1758
1759
  // modules/payments/L1PaymentsModule.ts
1759
1760
  var L1PaymentsModule = class {
1760
1761
  _initialized = false;
1762
+ _disabled = false;
1761
1763
  _config;
1762
1764
  _identity;
1763
1765
  _addresses = [];
@@ -1805,6 +1807,9 @@ var L1PaymentsModule = class {
1805
1807
  * (e.g. by the address scanner), this is a no-op.
1806
1808
  */
1807
1809
  async ensureConnected() {
1810
+ if (this._disabled) {
1811
+ throw new Error("L1 provider is disabled");
1812
+ }
1808
1813
  if (!isWebSocketConnected() && this._config.electrumUrl) {
1809
1814
  await connect(this._config.electrumUrl);
1810
1815
  }
@@ -1818,6 +1823,24 @@ var L1PaymentsModule = class {
1818
1823
  this._addresses = [];
1819
1824
  this._wallet = void 0;
1820
1825
  }
1826
+ /**
1827
+ * Disable this module — disconnect WebSocket and block operations until re-enabled.
1828
+ */
1829
+ disable() {
1830
+ this._disabled = true;
1831
+ if (isWebSocketConnected()) {
1832
+ disconnect();
1833
+ }
1834
+ }
1835
+ /**
1836
+ * Re-enable this module. Connection will be established lazily on next operation.
1837
+ */
1838
+ enable() {
1839
+ this._disabled = false;
1840
+ }
1841
+ get disabled() {
1842
+ return this._disabled;
1843
+ }
1821
1844
  /**
1822
1845
  * Check if a string looks like an L1 address (alpha1... or alphat1...)
1823
1846
  */
@@ -2711,9 +2734,7 @@ var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
2711
2734
  var TEST_AGGREGATOR_URL = "https://goggregator-test.unicity.network";
2712
2735
  var DEFAULT_AGGREGATOR_TIMEOUT = 3e4;
2713
2736
  var DEFAULT_IPFS_GATEWAYS = [
2714
- "https://ipfs.unicity.network",
2715
- "https://dweb.link",
2716
- "https://ipfs.io"
2737
+ "https://unicity-ipfs1.dyndns.org"
2717
2738
  ];
2718
2739
  var DEFAULT_IPFS_BOOTSTRAP_PEERS = [
2719
2740
  "/dns4/unicity-ipfs2.dyndns.org/tcp/4001/p2p/12D3KooWLNi5NDPPHbrfJakAQqwBqymYTTwMQXQKEWuCrJNDdmfh",
@@ -5678,7 +5699,7 @@ var PaymentsModule = class _PaymentsModule {
5678
5699
  */
5679
5700
  async getFiatBalance() {
5680
5701
  const assets = await this.getAssets();
5681
- if (!this.priceProvider) {
5702
+ if (!this.priceProvider || this.isPriceDisabled()) {
5682
5703
  return null;
5683
5704
  }
5684
5705
  let total = 0;
@@ -5713,7 +5734,7 @@ var PaymentsModule = class _PaymentsModule {
5713
5734
  */
5714
5735
  async getAssets(coinId) {
5715
5736
  const rawAssets = this.aggregateTokens(coinId);
5716
- if (!this.priceProvider || rawAssets.length === 0) {
5737
+ if (!this.priceProvider || this.isPriceDisabled() || rawAssets.length === 0) {
5717
5738
  return rawAssets;
5718
5739
  }
5719
5740
  try {
@@ -6807,18 +6828,38 @@ var PaymentsModule = class _PaymentsModule {
6807
6828
  }, _PaymentsModule.SYNC_DEBOUNCE_MS);
6808
6829
  }
6809
6830
  /**
6810
- * Get all active token storage providers
6831
+ * Get all active (non-disabled) token storage providers
6811
6832
  */
6812
6833
  getTokenStorageProviders() {
6834
+ let providers;
6813
6835
  if (this.deps.tokenStorageProviders && this.deps.tokenStorageProviders.size > 0) {
6814
- return this.deps.tokenStorageProviders;
6836
+ providers = this.deps.tokenStorageProviders;
6837
+ } else if (this.deps.tokenStorage) {
6838
+ providers = /* @__PURE__ */ new Map();
6839
+ providers.set(this.deps.tokenStorage.id, this.deps.tokenStorage);
6840
+ } else {
6841
+ return /* @__PURE__ */ new Map();
6815
6842
  }
6816
- if (this.deps.tokenStorage) {
6817
- const map = /* @__PURE__ */ new Map();
6818
- map.set(this.deps.tokenStorage.id, this.deps.tokenStorage);
6819
- return map;
6843
+ const disabled = this.deps.disabledProviderIds;
6844
+ if (disabled && disabled.size > 0) {
6845
+ const filtered = /* @__PURE__ */ new Map();
6846
+ for (const [id, provider] of providers) {
6847
+ if (!disabled.has(id)) {
6848
+ filtered.set(id, provider);
6849
+ }
6850
+ }
6851
+ return filtered;
6820
6852
  }
6821
- return /* @__PURE__ */ new Map();
6853
+ return providers;
6854
+ }
6855
+ /**
6856
+ * Check if the price provider is disabled via the disabled providers set.
6857
+ */
6858
+ isPriceDisabled() {
6859
+ const disabled = this.deps?.disabledProviderIds;
6860
+ if (!disabled || disabled.size === 0) return false;
6861
+ const priceId = this.priceProvider?.id ?? "price";
6862
+ return disabled.has(priceId);
6822
6863
  }
6823
6864
  /**
6824
6865
  * Replace the set of token storage providers at runtime.
@@ -9321,6 +9362,9 @@ async function scanAddressesImpl(deriveAddress, options = {}) {
9321
9362
  };
9322
9363
  }
9323
9364
 
9365
+ // core/Sphere.ts
9366
+ init_network();
9367
+
9324
9368
  // serialization/wallet-text.ts
9325
9369
  var import_crypto_js7 = __toESM(require("crypto-js"), 1);
9326
9370
 
@@ -10049,6 +10093,10 @@ var Sphere = class _Sphere {
10049
10093
  _groupChat = null;
10050
10094
  // Events
10051
10095
  eventHandlers = /* @__PURE__ */ new Map();
10096
+ // Provider management
10097
+ _disabledProviders = /* @__PURE__ */ new Set();
10098
+ _providerEventCleanups = [];
10099
+ _lastProviderConnected = /* @__PURE__ */ new Map();
10052
10100
  // ===========================================================================
10053
10101
  // Constructor (private)
10054
10102
  // ===========================================================================
@@ -10255,9 +10303,14 @@ var Sphere = class _Sphere {
10255
10303
  throw new Error("Either mnemonic or masterKey is required");
10256
10304
  }
10257
10305
  console.log("[Sphere.import] Starting import...");
10258
- console.log("[Sphere.import] Clearing existing wallet data...");
10259
- await _Sphere.clear({ storage: options.storage, tokenStorage: options.tokenStorage });
10260
- console.log("[Sphere.import] Clear done");
10306
+ const needsClear = _Sphere.instance !== null || await _Sphere.exists(options.storage);
10307
+ if (needsClear) {
10308
+ console.log("[Sphere.import] Clearing existing wallet data...");
10309
+ await _Sphere.clear({ storage: options.storage, tokenStorage: options.tokenStorage });
10310
+ console.log("[Sphere.import] Clear done");
10311
+ } else {
10312
+ console.log("[Sphere.import] No existing wallet \u2014 skipping clear");
10313
+ }
10261
10314
  if (!options.storage.isConnected()) {
10262
10315
  console.log("[Sphere.import] Reconnecting storage...");
10263
10316
  await options.storage.connect();
@@ -11484,20 +11537,193 @@ var Sphere = class _Sphere {
11484
11537
  // ===========================================================================
11485
11538
  // Public Methods - Status
11486
11539
  // ===========================================================================
11540
+ /**
11541
+ * Get aggregated status of all providers, grouped by role.
11542
+ *
11543
+ * @example
11544
+ * ```ts
11545
+ * const status = sphere.getStatus();
11546
+ * // status.transport[0].connected // true/false
11547
+ * // status.transport[0].metadata?.relays // { total: 3, connected: 2 }
11548
+ * // status.tokenStorage // all registered token storage providers
11549
+ * ```
11550
+ */
11487
11551
  getStatus() {
11552
+ const mkInfo = (provider, role, metadata) => ({
11553
+ id: provider.id,
11554
+ name: provider.name,
11555
+ role,
11556
+ status: provider.getStatus(),
11557
+ connected: provider.isConnected(),
11558
+ enabled: !this._disabledProviders.has(provider.id),
11559
+ ...metadata ? { metadata } : {}
11560
+ });
11561
+ let transportMeta;
11562
+ const transport = this._transport;
11563
+ if (typeof transport.getRelays === "function") {
11564
+ const total = transport.getRelays().length;
11565
+ const connected = typeof transport.getConnectedRelays === "function" ? transport.getConnectedRelays().length : 0;
11566
+ transportMeta = { relays: { total, connected } };
11567
+ }
11568
+ const l1Module = this._payments.l1;
11569
+ const l1Providers = [];
11570
+ if (l1Module) {
11571
+ const wsConnected = isWebSocketConnected();
11572
+ l1Providers.push({
11573
+ id: "l1-alpha",
11574
+ name: "ALPHA L1",
11575
+ role: "l1",
11576
+ status: wsConnected ? "connected" : "disconnected",
11577
+ connected: wsConnected,
11578
+ enabled: !this._disabledProviders.has("l1-alpha")
11579
+ });
11580
+ }
11581
+ const priceProviders = [];
11582
+ if (this._priceProvider) {
11583
+ priceProviders.push({
11584
+ id: this._priceProviderId,
11585
+ name: this._priceProvider.platform ?? "Price",
11586
+ role: "price",
11587
+ status: "connected",
11588
+ connected: true,
11589
+ enabled: !this._disabledProviders.has(this._priceProviderId)
11590
+ });
11591
+ }
11488
11592
  return {
11489
- storage: { connected: this._storage.isConnected() },
11490
- transport: { connected: this._transport.isConnected() },
11491
- oracle: { connected: this._oracle.isConnected() }
11593
+ storage: [mkInfo(this._storage, "storage")],
11594
+ tokenStorage: Array.from(this._tokenStorageProviders.values()).map(
11595
+ (p) => mkInfo(p, "token-storage")
11596
+ ),
11597
+ transport: [mkInfo(this._transport, "transport", transportMeta)],
11598
+ oracle: [mkInfo(this._oracle, "oracle")],
11599
+ l1: l1Providers,
11600
+ price: priceProviders
11492
11601
  };
11493
11602
  }
11494
11603
  async reconnect() {
11495
11604
  await this._transport.disconnect();
11496
11605
  await this._transport.connect();
11606
+ }
11607
+ // ===========================================================================
11608
+ // Public Methods - Provider Management
11609
+ // ===========================================================================
11610
+ /**
11611
+ * Disable a provider at runtime. The provider stays registered but is disconnected
11612
+ * and skipped during operations (e.g., sync).
11613
+ *
11614
+ * Main storage provider cannot be disabled.
11615
+ *
11616
+ * @returns true if successfully disabled, false if provider not found
11617
+ */
11618
+ async disableProvider(providerId) {
11619
+ if (providerId === this._storage.id) {
11620
+ throw new Error("Cannot disable the main storage provider");
11621
+ }
11622
+ const provider = this.findProviderById(providerId);
11623
+ if (!provider) return false;
11624
+ this._disabledProviders.add(providerId);
11625
+ try {
11626
+ if ("disable" in provider && typeof provider.disable === "function") {
11627
+ provider.disable();
11628
+ } else if ("shutdown" in provider && typeof provider.shutdown === "function") {
11629
+ await provider.shutdown();
11630
+ } else if ("disconnect" in provider && typeof provider.disconnect === "function") {
11631
+ await provider.disconnect();
11632
+ } else if ("clearCache" in provider && typeof provider.clearCache === "function") {
11633
+ provider.clearCache();
11634
+ }
11635
+ } catch {
11636
+ }
11637
+ this.emitEvent("connection:changed", {
11638
+ provider: providerId,
11639
+ connected: false,
11640
+ status: "disconnected",
11641
+ enabled: false
11642
+ });
11643
+ return true;
11644
+ }
11645
+ /**
11646
+ * Re-enable a previously disabled provider. Reconnects and resumes operations.
11647
+ *
11648
+ * @returns true if successfully enabled, false if provider not found
11649
+ */
11650
+ async enableProvider(providerId) {
11651
+ const provider = this.findProviderById(providerId);
11652
+ if (!provider) return false;
11653
+ this._disabledProviders.delete(providerId);
11654
+ if ("enable" in provider && typeof provider.enable === "function") {
11655
+ provider.enable();
11656
+ this.emitEvent("connection:changed", {
11657
+ provider: providerId,
11658
+ connected: false,
11659
+ status: "disconnected",
11660
+ enabled: true
11661
+ });
11662
+ return true;
11663
+ }
11664
+ const hasLifecycle = "connect" in provider && typeof provider.connect === "function" || "initialize" in provider && typeof provider.initialize === "function";
11665
+ if (hasLifecycle) {
11666
+ try {
11667
+ if ("connect" in provider && typeof provider.connect === "function") {
11668
+ await provider.connect();
11669
+ } else if ("initialize" in provider && typeof provider.initialize === "function") {
11670
+ await provider.initialize();
11671
+ }
11672
+ } catch (err) {
11673
+ this.emitEvent("connection:changed", {
11674
+ provider: providerId,
11675
+ connected: false,
11676
+ status: "error",
11677
+ enabled: true,
11678
+ error: err instanceof Error ? err.message : String(err)
11679
+ });
11680
+ return false;
11681
+ }
11682
+ }
11497
11683
  this.emitEvent("connection:changed", {
11498
- provider: "transport",
11499
- connected: true
11684
+ provider: providerId,
11685
+ connected: true,
11686
+ status: "connected",
11687
+ enabled: true
11500
11688
  });
11689
+ return true;
11690
+ }
11691
+ /**
11692
+ * Check if a provider is currently enabled
11693
+ */
11694
+ isProviderEnabled(providerId) {
11695
+ return !this._disabledProviders.has(providerId);
11696
+ }
11697
+ /**
11698
+ * Get the set of disabled provider IDs (for passing to modules)
11699
+ */
11700
+ getDisabledProviderIds() {
11701
+ return this._disabledProviders;
11702
+ }
11703
+ /** Get the price provider's ID (implementation detail — not on PriceProvider interface) */
11704
+ get _priceProviderId() {
11705
+ if (!this._priceProvider) return "price";
11706
+ const p = this._priceProvider;
11707
+ return typeof p.id === "string" ? p.id : "price";
11708
+ }
11709
+ /**
11710
+ * Find a provider by ID across all provider collections
11711
+ */
11712
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11713
+ findProviderById(providerId) {
11714
+ if (this._storage.id === providerId) return this._storage;
11715
+ if (this._transport.id === providerId) return this._transport;
11716
+ if (this._oracle.id === providerId) return this._oracle;
11717
+ if (this._tokenStorageProviders.has(providerId)) {
11718
+ return this._tokenStorageProviders.get(providerId);
11719
+ }
11720
+ if (this._priceProvider && this._priceProviderId === providerId) {
11721
+ return this._priceProvider;
11722
+ }
11723
+ if (providerId === "l1-alpha" && this._payments.l1) {
11724
+ return this._payments.l1;
11725
+ }
11726
+ return null;
11501
11727
  }
11502
11728
  // ===========================================================================
11503
11729
  // Public Methods - Events
@@ -11840,6 +12066,33 @@ var Sphere = class _Sphere {
11840
12066
  return;
11841
12067
  }
11842
12068
  try {
12069
+ if (this._identity?.directAddress && this._transport.resolve) {
12070
+ try {
12071
+ const existing = await this._transport.resolve(this._identity.directAddress);
12072
+ if (existing) {
12073
+ if (existing.nametag && !this._identity.nametag) {
12074
+ this._identity.nametag = existing.nametag;
12075
+ await this._updateCachedProxyAddress();
12076
+ const entry = await this.ensureAddressTracked(this._currentAddressIndex);
12077
+ let nametags = this._addressNametags.get(entry.addressId);
12078
+ if (!nametags) {
12079
+ nametags = /* @__PURE__ */ new Map();
12080
+ this._addressNametags.set(entry.addressId, nametags);
12081
+ }
12082
+ if (!nametags.has(0)) {
12083
+ nametags.set(0, existing.nametag);
12084
+ await this.persistAddressNametags();
12085
+ }
12086
+ this.emitEvent("nametag:recovered", { nametag: existing.nametag });
12087
+ }
12088
+ console.log("[Sphere] Existing binding found, skipping re-publish");
12089
+ return;
12090
+ }
12091
+ } catch (e) {
12092
+ console.warn("[Sphere] resolve() failed, skipping publish to avoid overwrite", e);
12093
+ return;
12094
+ }
12095
+ }
11843
12096
  const nametag = this._identity?.nametag;
11844
12097
  const success = await this._transport.publishIdentityBinding(
11845
12098
  this._identity.chainPubkey,
@@ -11913,17 +12166,26 @@ var Sphere = class _Sphere {
11913
12166
  // Public Methods - Lifecycle
11914
12167
  // ===========================================================================
11915
12168
  async destroy() {
12169
+ this.cleanupProviderEventSubscriptions();
11916
12170
  this._payments.destroy();
11917
12171
  this._communications.destroy();
11918
12172
  this._groupChat?.destroy();
11919
12173
  await this._transport.disconnect();
11920
12174
  await this._storage.disconnect();
11921
12175
  await this._oracle.disconnect();
12176
+ for (const provider of this._tokenStorageProviders.values()) {
12177
+ try {
12178
+ await provider.shutdown();
12179
+ } catch {
12180
+ }
12181
+ }
12182
+ this._tokenStorageProviders.clear();
11922
12183
  this._initialized = false;
11923
12184
  this._identity = null;
11924
12185
  this._trackedAddresses.clear();
11925
12186
  this._addressIdToIndex.clear();
11926
12187
  this._addressNametags.clear();
12188
+ this._disabledProviders.clear();
11927
12189
  this.eventHandlers.clear();
11928
12190
  if (_Sphere.instance === this) {
11929
12191
  _Sphere.instance = null;
@@ -12116,6 +12378,79 @@ var Sphere = class _Sphere {
12116
12378
  for (const provider of this._tokenStorageProviders.values()) {
12117
12379
  await provider.initialize();
12118
12380
  }
12381
+ this.subscribeToProviderEvents();
12382
+ }
12383
+ /**
12384
+ * Subscribe to provider-level events and bridge them to Sphere connection:changed events.
12385
+ * Uses deduplication to avoid emitting duplicate events.
12386
+ */
12387
+ subscribeToProviderEvents() {
12388
+ this.cleanupProviderEventSubscriptions();
12389
+ const transportAny = this._transport;
12390
+ if (typeof transportAny.onEvent === "function") {
12391
+ const unsub = transportAny.onEvent((event) => {
12392
+ const type = event?.type;
12393
+ if (type === "transport:connected") {
12394
+ this.emitConnectionChanged(this._transport.id, true, "connected");
12395
+ } else if (type === "transport:disconnected") {
12396
+ this.emitConnectionChanged(this._transport.id, false, "disconnected");
12397
+ } else if (type === "transport:reconnecting") {
12398
+ this.emitConnectionChanged(this._transport.id, false, "connecting");
12399
+ } else if (type === "transport:error") {
12400
+ this.emitConnectionChanged(this._transport.id, false, "error", event?.error);
12401
+ }
12402
+ });
12403
+ if (unsub) this._providerEventCleanups.push(unsub);
12404
+ }
12405
+ const oracleAny = this._oracle;
12406
+ if (typeof oracleAny.onEvent === "function") {
12407
+ const unsub = oracleAny.onEvent((event) => {
12408
+ const type = event?.type;
12409
+ if (type === "oracle:connected") {
12410
+ this.emitConnectionChanged(this._oracle.id, true, "connected");
12411
+ } else if (type === "oracle:disconnected") {
12412
+ this.emitConnectionChanged(this._oracle.id, false, "disconnected");
12413
+ } else if (type === "oracle:error") {
12414
+ this.emitConnectionChanged(this._oracle.id, false, "error", event?.error);
12415
+ }
12416
+ });
12417
+ if (unsub) this._providerEventCleanups.push(unsub);
12418
+ }
12419
+ for (const [providerId, provider] of this._tokenStorageProviders) {
12420
+ if (typeof provider.onEvent === "function") {
12421
+ const unsub = provider.onEvent((event) => {
12422
+ if (event.type === "storage:error" || event.type === "sync:error") {
12423
+ this.emitConnectionChanged(providerId, provider.isConnected(), provider.getStatus(), event.error);
12424
+ }
12425
+ });
12426
+ if (unsub) this._providerEventCleanups.push(unsub);
12427
+ }
12428
+ }
12429
+ }
12430
+ /**
12431
+ * Emit connection:changed with deduplication — only emits if status actually changed.
12432
+ */
12433
+ emitConnectionChanged(providerId, connected, status, error) {
12434
+ const lastConnected = this._lastProviderConnected.get(providerId);
12435
+ if (lastConnected === connected) return;
12436
+ this._lastProviderConnected.set(providerId, connected);
12437
+ this.emitEvent("connection:changed", {
12438
+ provider: providerId,
12439
+ connected,
12440
+ status,
12441
+ enabled: !this._disabledProviders.has(providerId),
12442
+ ...error ? { error } : {}
12443
+ });
12444
+ }
12445
+ cleanupProviderEventSubscriptions() {
12446
+ for (const cleanup of this._providerEventCleanups) {
12447
+ try {
12448
+ cleanup();
12449
+ } catch {
12450
+ }
12451
+ }
12452
+ this._providerEventCleanups = [];
12453
+ this._lastProviderConnected.clear();
12119
12454
  }
12120
12455
  async initializeModules() {
12121
12456
  const emitEvent = this.emitEvent.bind(this);
@@ -12128,7 +12463,8 @@ var Sphere = class _Sphere {
12128
12463
  emitEvent,
12129
12464
  // Pass chain code for L1 HD derivation
12130
12465
  chainCode: this._masterKey?.chainCode || void 0,
12131
- price: this._priceProvider ?? void 0
12466
+ price: this._priceProvider ?? void 0,
12467
+ disabledProviderIds: this._disabledProviders
12132
12468
  });
12133
12469
  this._communications.initialize({
12134
12470
  identity: this._identity,
@@ -12219,6 +12555,192 @@ function formatAmount(amount, options = {}) {
12219
12555
  // core/index.ts
12220
12556
  init_bech32();
12221
12557
 
12558
+ // core/network-health.ts
12559
+ var DEFAULT_TIMEOUT_MS = 5e3;
12560
+ async function checkNetworkHealth(network = "testnet", options) {
12561
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
12562
+ const servicesToCheck = options?.services ?? ["relay", "oracle", "l1"];
12563
+ const networkConfig = NETWORKS[network];
12564
+ const customUrls = options?.urls;
12565
+ const startTime = Date.now();
12566
+ const allChecks = [];
12567
+ if (servicesToCheck.includes("relay")) {
12568
+ const relayUrl = customUrls?.relay ?? networkConfig.nostrRelays[0];
12569
+ allChecks.push(checkWebSocket(relayUrl, timeoutMs).then((r) => ["relay", r]));
12570
+ }
12571
+ if (servicesToCheck.includes("oracle")) {
12572
+ const oracleUrl = customUrls?.oracle ?? networkConfig.aggregatorUrl;
12573
+ allChecks.push(checkOracle(oracleUrl, timeoutMs).then((r) => ["oracle", r]));
12574
+ }
12575
+ if (servicesToCheck.includes("l1")) {
12576
+ const l1Url = customUrls?.l1 ?? networkConfig.electrumUrl;
12577
+ allChecks.push(checkWebSocket(l1Url, timeoutMs).then((r) => ["l1", r]));
12578
+ }
12579
+ if (options?.checks) {
12580
+ for (const [name, checkFn] of Object.entries(options.checks)) {
12581
+ allChecks.push(
12582
+ runCustomCheck(name, checkFn, timeoutMs).then((r) => [name, r])
12583
+ );
12584
+ }
12585
+ }
12586
+ const results = await Promise.allSettled(allChecks);
12587
+ const services = {};
12588
+ let allHealthy = true;
12589
+ for (const result of results) {
12590
+ if (result.status === "fulfilled") {
12591
+ const [name, healthResult] = result.value;
12592
+ services[name] = healthResult;
12593
+ if (!healthResult.healthy) allHealthy = false;
12594
+ } else {
12595
+ allHealthy = false;
12596
+ }
12597
+ }
12598
+ return {
12599
+ healthy: allHealthy,
12600
+ services,
12601
+ totalTimeMs: Date.now() - startTime
12602
+ };
12603
+ }
12604
+ async function checkWebSocket(url, timeoutMs) {
12605
+ const startTime = Date.now();
12606
+ const WS = typeof globalThis !== "undefined" && globalThis.WebSocket;
12607
+ if (!WS) {
12608
+ return {
12609
+ healthy: false,
12610
+ url,
12611
+ responseTimeMs: null,
12612
+ error: "WebSocket not available in this environment"
12613
+ };
12614
+ }
12615
+ return new Promise((resolve) => {
12616
+ let resolved = false;
12617
+ const timer = setTimeout(() => {
12618
+ if (!resolved) {
12619
+ resolved = true;
12620
+ try {
12621
+ ws2.close();
12622
+ } catch {
12623
+ }
12624
+ resolve({
12625
+ healthy: false,
12626
+ url,
12627
+ responseTimeMs: null,
12628
+ error: `Connection timeout after ${timeoutMs}ms`
12629
+ });
12630
+ }
12631
+ }, timeoutMs);
12632
+ let ws2;
12633
+ try {
12634
+ ws2 = new WS(url);
12635
+ } catch (err) {
12636
+ clearTimeout(timer);
12637
+ return resolve({
12638
+ healthy: false,
12639
+ url,
12640
+ responseTimeMs: null,
12641
+ error: `Failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`
12642
+ });
12643
+ }
12644
+ ws2.onopen = () => {
12645
+ if (!resolved) {
12646
+ resolved = true;
12647
+ clearTimeout(timer);
12648
+ const responseTimeMs = Date.now() - startTime;
12649
+ try {
12650
+ ws2.close();
12651
+ } catch {
12652
+ }
12653
+ resolve({ healthy: true, url, responseTimeMs });
12654
+ }
12655
+ };
12656
+ ws2.onerror = (event) => {
12657
+ if (!resolved) {
12658
+ resolved = true;
12659
+ clearTimeout(timer);
12660
+ try {
12661
+ ws2.close();
12662
+ } catch {
12663
+ }
12664
+ resolve({
12665
+ healthy: false,
12666
+ url,
12667
+ responseTimeMs: null,
12668
+ error: "WebSocket connection error"
12669
+ });
12670
+ }
12671
+ };
12672
+ ws2.onclose = (event) => {
12673
+ if (!resolved) {
12674
+ resolved = true;
12675
+ clearTimeout(timer);
12676
+ resolve({
12677
+ healthy: false,
12678
+ url,
12679
+ responseTimeMs: null,
12680
+ error: `WebSocket closed: ${event.reason || `code ${event.code}`}`
12681
+ });
12682
+ }
12683
+ };
12684
+ });
12685
+ }
12686
+ async function checkOracle(url, timeoutMs) {
12687
+ const startTime = Date.now();
12688
+ try {
12689
+ const controller = new AbortController();
12690
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
12691
+ const response = await fetch(url, {
12692
+ method: "POST",
12693
+ headers: { "Content-Type": "application/json" },
12694
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "get_round_number", params: {} }),
12695
+ signal: controller.signal
12696
+ });
12697
+ clearTimeout(timer);
12698
+ const responseTimeMs = Date.now() - startTime;
12699
+ if (response.ok) {
12700
+ return { healthy: true, url, responseTimeMs };
12701
+ }
12702
+ return {
12703
+ healthy: false,
12704
+ url,
12705
+ responseTimeMs,
12706
+ error: `HTTP ${response.status} ${response.statusText}`
12707
+ };
12708
+ } catch (err) {
12709
+ return {
12710
+ healthy: false,
12711
+ url,
12712
+ responseTimeMs: null,
12713
+ error: err instanceof Error ? err.name === "AbortError" ? `Connection timeout after ${timeoutMs}ms` : err.message : String(err)
12714
+ };
12715
+ }
12716
+ }
12717
+ async function runCustomCheck(name, checkFn, timeoutMs) {
12718
+ try {
12719
+ const result = await Promise.race([
12720
+ checkFn(timeoutMs),
12721
+ new Promise(
12722
+ (resolve) => setTimeout(
12723
+ () => resolve({
12724
+ healthy: false,
12725
+ url: name,
12726
+ responseTimeMs: null,
12727
+ error: `Custom check '${name}' timeout after ${timeoutMs}ms`
12728
+ }),
12729
+ timeoutMs
12730
+ )
12731
+ )
12732
+ ]);
12733
+ return result;
12734
+ } catch (err) {
12735
+ return {
12736
+ healthy: false,
12737
+ url: name,
12738
+ responseTimeMs: null,
12739
+ error: err instanceof Error ? err.message : String(err)
12740
+ };
12741
+ }
12742
+ }
12743
+
12222
12744
  // types/payment-session.ts
12223
12745
  function createPaymentSession(params) {
12224
12746
  const now = Date.now();
@@ -12763,6 +13285,7 @@ function createPriceProvider(config) {
12763
13285
  base58Encode,
12764
13286
  buildTxfStorageData,
12765
13287
  bytesToHex,
13288
+ checkNetworkHealth,
12766
13289
  countCommittedTransactions,
12767
13290
  createAddress,
12768
13291
  createCommunicationsModule,