@unicitylabs/sphere-sdk 0.3.0 → 0.3.2

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