@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.js CHANGED
@@ -1605,6 +1605,7 @@ async function sendAlpha(wallet, toAddress, amountAlpha, fromAddress) {
1605
1605
  // modules/payments/L1PaymentsModule.ts
1606
1606
  var L1PaymentsModule = class {
1607
1607
  _initialized = false;
1608
+ _disabled = false;
1608
1609
  _config;
1609
1610
  _identity;
1610
1611
  _addresses = [];
@@ -1652,6 +1653,9 @@ var L1PaymentsModule = class {
1652
1653
  * (e.g. by the address scanner), this is a no-op.
1653
1654
  */
1654
1655
  async ensureConnected() {
1656
+ if (this._disabled) {
1657
+ throw new Error("L1 provider is disabled");
1658
+ }
1655
1659
  if (!isWebSocketConnected() && this._config.electrumUrl) {
1656
1660
  await connect(this._config.electrumUrl);
1657
1661
  }
@@ -1665,6 +1669,24 @@ var L1PaymentsModule = class {
1665
1669
  this._addresses = [];
1666
1670
  this._wallet = void 0;
1667
1671
  }
1672
+ /**
1673
+ * Disable this module — disconnect WebSocket and block operations until re-enabled.
1674
+ */
1675
+ disable() {
1676
+ this._disabled = true;
1677
+ if (isWebSocketConnected()) {
1678
+ disconnect();
1679
+ }
1680
+ }
1681
+ /**
1682
+ * Re-enable this module. Connection will be established lazily on next operation.
1683
+ */
1684
+ enable() {
1685
+ this._disabled = false;
1686
+ }
1687
+ get disabled() {
1688
+ return this._disabled;
1689
+ }
1668
1690
  /**
1669
1691
  * Check if a string looks like an L1 address (alpha1... or alphat1...)
1670
1692
  */
@@ -5525,7 +5547,7 @@ var PaymentsModule = class _PaymentsModule {
5525
5547
  */
5526
5548
  async getFiatBalance() {
5527
5549
  const assets = await this.getAssets();
5528
- if (!this.priceProvider) {
5550
+ if (!this.priceProvider || this.isPriceDisabled()) {
5529
5551
  return null;
5530
5552
  }
5531
5553
  let total = 0;
@@ -5560,7 +5582,7 @@ var PaymentsModule = class _PaymentsModule {
5560
5582
  */
5561
5583
  async getAssets(coinId) {
5562
5584
  const rawAssets = this.aggregateTokens(coinId);
5563
- if (!this.priceProvider || rawAssets.length === 0) {
5585
+ if (!this.priceProvider || this.isPriceDisabled() || rawAssets.length === 0) {
5564
5586
  return rawAssets;
5565
5587
  }
5566
5588
  try {
@@ -6654,18 +6676,38 @@ var PaymentsModule = class _PaymentsModule {
6654
6676
  }, _PaymentsModule.SYNC_DEBOUNCE_MS);
6655
6677
  }
6656
6678
  /**
6657
- * Get all active token storage providers
6679
+ * Get all active (non-disabled) token storage providers
6658
6680
  */
6659
6681
  getTokenStorageProviders() {
6682
+ let providers;
6660
6683
  if (this.deps.tokenStorageProviders && this.deps.tokenStorageProviders.size > 0) {
6661
- return this.deps.tokenStorageProviders;
6684
+ providers = this.deps.tokenStorageProviders;
6685
+ } else if (this.deps.tokenStorage) {
6686
+ providers = /* @__PURE__ */ new Map();
6687
+ providers.set(this.deps.tokenStorage.id, this.deps.tokenStorage);
6688
+ } else {
6689
+ return /* @__PURE__ */ new Map();
6662
6690
  }
6663
- if (this.deps.tokenStorage) {
6664
- const map = /* @__PURE__ */ new Map();
6665
- map.set(this.deps.tokenStorage.id, this.deps.tokenStorage);
6666
- return map;
6691
+ const disabled = this.deps.disabledProviderIds;
6692
+ if (disabled && disabled.size > 0) {
6693
+ const filtered = /* @__PURE__ */ new Map();
6694
+ for (const [id, provider] of providers) {
6695
+ if (!disabled.has(id)) {
6696
+ filtered.set(id, provider);
6697
+ }
6698
+ }
6699
+ return filtered;
6667
6700
  }
6668
- return /* @__PURE__ */ new Map();
6701
+ return providers;
6702
+ }
6703
+ /**
6704
+ * Check if the price provider is disabled via the disabled providers set.
6705
+ */
6706
+ isPriceDisabled() {
6707
+ const disabled = this.deps?.disabledProviderIds;
6708
+ if (!disabled || disabled.size === 0) return false;
6709
+ const priceId = this.priceProvider?.id ?? "price";
6710
+ return disabled.has(priceId);
6669
6711
  }
6670
6712
  /**
6671
6713
  * Replace the set of token storage providers at runtime.
@@ -9172,6 +9214,9 @@ async function scanAddressesImpl(deriveAddress, options = {}) {
9172
9214
  };
9173
9215
  }
9174
9216
 
9217
+ // core/Sphere.ts
9218
+ init_network();
9219
+
9175
9220
  // serialization/wallet-text.ts
9176
9221
  import CryptoJS7 from "crypto-js";
9177
9222
 
@@ -9900,6 +9945,10 @@ var Sphere = class _Sphere {
9900
9945
  _groupChat = null;
9901
9946
  // Events
9902
9947
  eventHandlers = /* @__PURE__ */ new Map();
9948
+ // Provider management
9949
+ _disabledProviders = /* @__PURE__ */ new Set();
9950
+ _providerEventCleanups = [];
9951
+ _lastProviderConnected = /* @__PURE__ */ new Map();
9903
9952
  // ===========================================================================
9904
9953
  // Constructor (private)
9905
9954
  // ===========================================================================
@@ -10106,9 +10155,14 @@ var Sphere = class _Sphere {
10106
10155
  throw new Error("Either mnemonic or masterKey is required");
10107
10156
  }
10108
10157
  console.log("[Sphere.import] Starting import...");
10109
- console.log("[Sphere.import] Clearing existing wallet data...");
10110
- await _Sphere.clear({ storage: options.storage, tokenStorage: options.tokenStorage });
10111
- console.log("[Sphere.import] Clear done");
10158
+ const needsClear = _Sphere.instance !== null || await _Sphere.exists(options.storage);
10159
+ if (needsClear) {
10160
+ console.log("[Sphere.import] Clearing existing wallet data...");
10161
+ await _Sphere.clear({ storage: options.storage, tokenStorage: options.tokenStorage });
10162
+ console.log("[Sphere.import] Clear done");
10163
+ } else {
10164
+ console.log("[Sphere.import] No existing wallet \u2014 skipping clear");
10165
+ }
10112
10166
  if (!options.storage.isConnected()) {
10113
10167
  console.log("[Sphere.import] Reconnecting storage...");
10114
10168
  await options.storage.connect();
@@ -11335,20 +11389,193 @@ var Sphere = class _Sphere {
11335
11389
  // ===========================================================================
11336
11390
  // Public Methods - Status
11337
11391
  // ===========================================================================
11392
+ /**
11393
+ * Get aggregated status of all providers, grouped by role.
11394
+ *
11395
+ * @example
11396
+ * ```ts
11397
+ * const status = sphere.getStatus();
11398
+ * // status.transport[0].connected // true/false
11399
+ * // status.transport[0].metadata?.relays // { total: 3, connected: 2 }
11400
+ * // status.tokenStorage // all registered token storage providers
11401
+ * ```
11402
+ */
11338
11403
  getStatus() {
11404
+ const mkInfo = (provider, role, metadata) => ({
11405
+ id: provider.id,
11406
+ name: provider.name,
11407
+ role,
11408
+ status: provider.getStatus(),
11409
+ connected: provider.isConnected(),
11410
+ enabled: !this._disabledProviders.has(provider.id),
11411
+ ...metadata ? { metadata } : {}
11412
+ });
11413
+ let transportMeta;
11414
+ const transport = this._transport;
11415
+ if (typeof transport.getRelays === "function") {
11416
+ const total = transport.getRelays().length;
11417
+ const connected = typeof transport.getConnectedRelays === "function" ? transport.getConnectedRelays().length : 0;
11418
+ transportMeta = { relays: { total, connected } };
11419
+ }
11420
+ const l1Module = this._payments.l1;
11421
+ const l1Providers = [];
11422
+ if (l1Module) {
11423
+ const wsConnected = isWebSocketConnected();
11424
+ l1Providers.push({
11425
+ id: "l1-alpha",
11426
+ name: "ALPHA L1",
11427
+ role: "l1",
11428
+ status: wsConnected ? "connected" : "disconnected",
11429
+ connected: wsConnected,
11430
+ enabled: !this._disabledProviders.has("l1-alpha")
11431
+ });
11432
+ }
11433
+ const priceProviders = [];
11434
+ if (this._priceProvider) {
11435
+ priceProviders.push({
11436
+ id: this._priceProviderId,
11437
+ name: this._priceProvider.platform ?? "Price",
11438
+ role: "price",
11439
+ status: "connected",
11440
+ connected: true,
11441
+ enabled: !this._disabledProviders.has(this._priceProviderId)
11442
+ });
11443
+ }
11339
11444
  return {
11340
- storage: { connected: this._storage.isConnected() },
11341
- transport: { connected: this._transport.isConnected() },
11342
- oracle: { connected: this._oracle.isConnected() }
11445
+ storage: [mkInfo(this._storage, "storage")],
11446
+ tokenStorage: Array.from(this._tokenStorageProviders.values()).map(
11447
+ (p) => mkInfo(p, "token-storage")
11448
+ ),
11449
+ transport: [mkInfo(this._transport, "transport", transportMeta)],
11450
+ oracle: [mkInfo(this._oracle, "oracle")],
11451
+ l1: l1Providers,
11452
+ price: priceProviders
11343
11453
  };
11344
11454
  }
11345
11455
  async reconnect() {
11346
11456
  await this._transport.disconnect();
11347
11457
  await this._transport.connect();
11458
+ }
11459
+ // ===========================================================================
11460
+ // Public Methods - Provider Management
11461
+ // ===========================================================================
11462
+ /**
11463
+ * Disable a provider at runtime. The provider stays registered but is disconnected
11464
+ * and skipped during operations (e.g., sync).
11465
+ *
11466
+ * Main storage provider cannot be disabled.
11467
+ *
11468
+ * @returns true if successfully disabled, false if provider not found
11469
+ */
11470
+ async disableProvider(providerId) {
11471
+ if (providerId === this._storage.id) {
11472
+ throw new Error("Cannot disable the main storage provider");
11473
+ }
11474
+ const provider = this.findProviderById(providerId);
11475
+ if (!provider) return false;
11476
+ this._disabledProviders.add(providerId);
11477
+ try {
11478
+ if ("disable" in provider && typeof provider.disable === "function") {
11479
+ provider.disable();
11480
+ } else if ("shutdown" in provider && typeof provider.shutdown === "function") {
11481
+ await provider.shutdown();
11482
+ } else if ("disconnect" in provider && typeof provider.disconnect === "function") {
11483
+ await provider.disconnect();
11484
+ } else if ("clearCache" in provider && typeof provider.clearCache === "function") {
11485
+ provider.clearCache();
11486
+ }
11487
+ } catch {
11488
+ }
11489
+ this.emitEvent("connection:changed", {
11490
+ provider: providerId,
11491
+ connected: false,
11492
+ status: "disconnected",
11493
+ enabled: false
11494
+ });
11495
+ return true;
11496
+ }
11497
+ /**
11498
+ * Re-enable a previously disabled provider. Reconnects and resumes operations.
11499
+ *
11500
+ * @returns true if successfully enabled, false if provider not found
11501
+ */
11502
+ async enableProvider(providerId) {
11503
+ const provider = this.findProviderById(providerId);
11504
+ if (!provider) return false;
11505
+ this._disabledProviders.delete(providerId);
11506
+ if ("enable" in provider && typeof provider.enable === "function") {
11507
+ provider.enable();
11508
+ this.emitEvent("connection:changed", {
11509
+ provider: providerId,
11510
+ connected: false,
11511
+ status: "disconnected",
11512
+ enabled: true
11513
+ });
11514
+ return true;
11515
+ }
11516
+ const hasLifecycle = "connect" in provider && typeof provider.connect === "function" || "initialize" in provider && typeof provider.initialize === "function";
11517
+ if (hasLifecycle) {
11518
+ try {
11519
+ if ("connect" in provider && typeof provider.connect === "function") {
11520
+ await provider.connect();
11521
+ } else if ("initialize" in provider && typeof provider.initialize === "function") {
11522
+ await provider.initialize();
11523
+ }
11524
+ } catch (err) {
11525
+ this.emitEvent("connection:changed", {
11526
+ provider: providerId,
11527
+ connected: false,
11528
+ status: "error",
11529
+ enabled: true,
11530
+ error: err instanceof Error ? err.message : String(err)
11531
+ });
11532
+ return false;
11533
+ }
11534
+ }
11348
11535
  this.emitEvent("connection:changed", {
11349
- provider: "transport",
11350
- connected: true
11536
+ provider: providerId,
11537
+ connected: true,
11538
+ status: "connected",
11539
+ enabled: true
11351
11540
  });
11541
+ return true;
11542
+ }
11543
+ /**
11544
+ * Check if a provider is currently enabled
11545
+ */
11546
+ isProviderEnabled(providerId) {
11547
+ return !this._disabledProviders.has(providerId);
11548
+ }
11549
+ /**
11550
+ * Get the set of disabled provider IDs (for passing to modules)
11551
+ */
11552
+ getDisabledProviderIds() {
11553
+ return this._disabledProviders;
11554
+ }
11555
+ /** Get the price provider's ID (implementation detail — not on PriceProvider interface) */
11556
+ get _priceProviderId() {
11557
+ if (!this._priceProvider) return "price";
11558
+ const p = this._priceProvider;
11559
+ return typeof p.id === "string" ? p.id : "price";
11560
+ }
11561
+ /**
11562
+ * Find a provider by ID across all provider collections
11563
+ */
11564
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11565
+ findProviderById(providerId) {
11566
+ if (this._storage.id === providerId) return this._storage;
11567
+ if (this._transport.id === providerId) return this._transport;
11568
+ if (this._oracle.id === providerId) return this._oracle;
11569
+ if (this._tokenStorageProviders.has(providerId)) {
11570
+ return this._tokenStorageProviders.get(providerId);
11571
+ }
11572
+ if (this._priceProvider && this._priceProviderId === providerId) {
11573
+ return this._priceProvider;
11574
+ }
11575
+ if (providerId === "l1-alpha" && this._payments.l1) {
11576
+ return this._payments.l1;
11577
+ }
11578
+ return null;
11352
11579
  }
11353
11580
  // ===========================================================================
11354
11581
  // Public Methods - Events
@@ -11691,6 +11918,33 @@ var Sphere = class _Sphere {
11691
11918
  return;
11692
11919
  }
11693
11920
  try {
11921
+ if (this._identity?.directAddress && this._transport.resolve) {
11922
+ try {
11923
+ const existing = await this._transport.resolve(this._identity.directAddress);
11924
+ if (existing) {
11925
+ if (existing.nametag && !this._identity.nametag) {
11926
+ this._identity.nametag = existing.nametag;
11927
+ await this._updateCachedProxyAddress();
11928
+ const entry = await this.ensureAddressTracked(this._currentAddressIndex);
11929
+ let nametags = this._addressNametags.get(entry.addressId);
11930
+ if (!nametags) {
11931
+ nametags = /* @__PURE__ */ new Map();
11932
+ this._addressNametags.set(entry.addressId, nametags);
11933
+ }
11934
+ if (!nametags.has(0)) {
11935
+ nametags.set(0, existing.nametag);
11936
+ await this.persistAddressNametags();
11937
+ }
11938
+ this.emitEvent("nametag:recovered", { nametag: existing.nametag });
11939
+ }
11940
+ console.log("[Sphere] Existing binding found, skipping re-publish");
11941
+ return;
11942
+ }
11943
+ } catch (e) {
11944
+ console.warn("[Sphere] resolve() failed, skipping publish to avoid overwrite", e);
11945
+ return;
11946
+ }
11947
+ }
11694
11948
  const nametag = this._identity?.nametag;
11695
11949
  const success = await this._transport.publishIdentityBinding(
11696
11950
  this._identity.chainPubkey,
@@ -11764,17 +12018,26 @@ var Sphere = class _Sphere {
11764
12018
  // Public Methods - Lifecycle
11765
12019
  // ===========================================================================
11766
12020
  async destroy() {
12021
+ this.cleanupProviderEventSubscriptions();
11767
12022
  this._payments.destroy();
11768
12023
  this._communications.destroy();
11769
12024
  this._groupChat?.destroy();
11770
12025
  await this._transport.disconnect();
11771
12026
  await this._storage.disconnect();
11772
12027
  await this._oracle.disconnect();
12028
+ for (const provider of this._tokenStorageProviders.values()) {
12029
+ try {
12030
+ await provider.shutdown();
12031
+ } catch {
12032
+ }
12033
+ }
12034
+ this._tokenStorageProviders.clear();
11773
12035
  this._initialized = false;
11774
12036
  this._identity = null;
11775
12037
  this._trackedAddresses.clear();
11776
12038
  this._addressIdToIndex.clear();
11777
12039
  this._addressNametags.clear();
12040
+ this._disabledProviders.clear();
11778
12041
  this.eventHandlers.clear();
11779
12042
  if (_Sphere.instance === this) {
11780
12043
  _Sphere.instance = null;
@@ -11967,6 +12230,79 @@ var Sphere = class _Sphere {
11967
12230
  for (const provider of this._tokenStorageProviders.values()) {
11968
12231
  await provider.initialize();
11969
12232
  }
12233
+ this.subscribeToProviderEvents();
12234
+ }
12235
+ /**
12236
+ * Subscribe to provider-level events and bridge them to Sphere connection:changed events.
12237
+ * Uses deduplication to avoid emitting duplicate events.
12238
+ */
12239
+ subscribeToProviderEvents() {
12240
+ this.cleanupProviderEventSubscriptions();
12241
+ const transportAny = this._transport;
12242
+ if (typeof transportAny.onEvent === "function") {
12243
+ const unsub = transportAny.onEvent((event) => {
12244
+ const type = event?.type;
12245
+ if (type === "transport:connected") {
12246
+ this.emitConnectionChanged(this._transport.id, true, "connected");
12247
+ } else if (type === "transport:disconnected") {
12248
+ this.emitConnectionChanged(this._transport.id, false, "disconnected");
12249
+ } else if (type === "transport:reconnecting") {
12250
+ this.emitConnectionChanged(this._transport.id, false, "connecting");
12251
+ } else if (type === "transport:error") {
12252
+ this.emitConnectionChanged(this._transport.id, false, "error", event?.error);
12253
+ }
12254
+ });
12255
+ if (unsub) this._providerEventCleanups.push(unsub);
12256
+ }
12257
+ const oracleAny = this._oracle;
12258
+ if (typeof oracleAny.onEvent === "function") {
12259
+ const unsub = oracleAny.onEvent((event) => {
12260
+ const type = event?.type;
12261
+ if (type === "oracle:connected") {
12262
+ this.emitConnectionChanged(this._oracle.id, true, "connected");
12263
+ } else if (type === "oracle:disconnected") {
12264
+ this.emitConnectionChanged(this._oracle.id, false, "disconnected");
12265
+ } else if (type === "oracle:error") {
12266
+ this.emitConnectionChanged(this._oracle.id, false, "error", event?.error);
12267
+ }
12268
+ });
12269
+ if (unsub) this._providerEventCleanups.push(unsub);
12270
+ }
12271
+ for (const [providerId, provider] of this._tokenStorageProviders) {
12272
+ if (typeof provider.onEvent === "function") {
12273
+ const unsub = provider.onEvent((event) => {
12274
+ if (event.type === "storage:error" || event.type === "sync:error") {
12275
+ this.emitConnectionChanged(providerId, provider.isConnected(), provider.getStatus(), event.error);
12276
+ }
12277
+ });
12278
+ if (unsub) this._providerEventCleanups.push(unsub);
12279
+ }
12280
+ }
12281
+ }
12282
+ /**
12283
+ * Emit connection:changed with deduplication — only emits if status actually changed.
12284
+ */
12285
+ emitConnectionChanged(providerId, connected, status, error) {
12286
+ const lastConnected = this._lastProviderConnected.get(providerId);
12287
+ if (lastConnected === connected) return;
12288
+ this._lastProviderConnected.set(providerId, connected);
12289
+ this.emitEvent("connection:changed", {
12290
+ provider: providerId,
12291
+ connected,
12292
+ status,
12293
+ enabled: !this._disabledProviders.has(providerId),
12294
+ ...error ? { error } : {}
12295
+ });
12296
+ }
12297
+ cleanupProviderEventSubscriptions() {
12298
+ for (const cleanup of this._providerEventCleanups) {
12299
+ try {
12300
+ cleanup();
12301
+ } catch {
12302
+ }
12303
+ }
12304
+ this._providerEventCleanups = [];
12305
+ this._lastProviderConnected.clear();
11970
12306
  }
11971
12307
  async initializeModules() {
11972
12308
  const emitEvent = this.emitEvent.bind(this);
@@ -11979,7 +12315,8 @@ var Sphere = class _Sphere {
11979
12315
  emitEvent,
11980
12316
  // Pass chain code for L1 HD derivation
11981
12317
  chainCode: this._masterKey?.chainCode || void 0,
11982
- price: this._priceProvider ?? void 0
12318
+ price: this._priceProvider ?? void 0,
12319
+ disabledProviderIds: this._disabledProviders
11983
12320
  });
11984
12321
  this._communications.initialize({
11985
12322
  identity: this._identity,
@@ -12070,6 +12407,192 @@ function formatAmount(amount, options = {}) {
12070
12407
  // core/index.ts
12071
12408
  init_bech32();
12072
12409
 
12410
+ // core/network-health.ts
12411
+ var DEFAULT_TIMEOUT_MS = 5e3;
12412
+ async function checkNetworkHealth(network = "testnet", options) {
12413
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
12414
+ const servicesToCheck = options?.services ?? ["relay", "oracle", "l1"];
12415
+ const networkConfig = NETWORKS[network];
12416
+ const customUrls = options?.urls;
12417
+ const startTime = Date.now();
12418
+ const allChecks = [];
12419
+ if (servicesToCheck.includes("relay")) {
12420
+ const relayUrl = customUrls?.relay ?? networkConfig.nostrRelays[0];
12421
+ allChecks.push(checkWebSocket(relayUrl, timeoutMs).then((r) => ["relay", r]));
12422
+ }
12423
+ if (servicesToCheck.includes("oracle")) {
12424
+ const oracleUrl = customUrls?.oracle ?? networkConfig.aggregatorUrl;
12425
+ allChecks.push(checkOracle(oracleUrl, timeoutMs).then((r) => ["oracle", r]));
12426
+ }
12427
+ if (servicesToCheck.includes("l1")) {
12428
+ const l1Url = customUrls?.l1 ?? networkConfig.electrumUrl;
12429
+ allChecks.push(checkWebSocket(l1Url, timeoutMs).then((r) => ["l1", r]));
12430
+ }
12431
+ if (options?.checks) {
12432
+ for (const [name, checkFn] of Object.entries(options.checks)) {
12433
+ allChecks.push(
12434
+ runCustomCheck(name, checkFn, timeoutMs).then((r) => [name, r])
12435
+ );
12436
+ }
12437
+ }
12438
+ const results = await Promise.allSettled(allChecks);
12439
+ const services = {};
12440
+ let allHealthy = true;
12441
+ for (const result of results) {
12442
+ if (result.status === "fulfilled") {
12443
+ const [name, healthResult] = result.value;
12444
+ services[name] = healthResult;
12445
+ if (!healthResult.healthy) allHealthy = false;
12446
+ } else {
12447
+ allHealthy = false;
12448
+ }
12449
+ }
12450
+ return {
12451
+ healthy: allHealthy,
12452
+ services,
12453
+ totalTimeMs: Date.now() - startTime
12454
+ };
12455
+ }
12456
+ async function checkWebSocket(url, timeoutMs) {
12457
+ const startTime = Date.now();
12458
+ const WS = typeof globalThis !== "undefined" && globalThis.WebSocket;
12459
+ if (!WS) {
12460
+ return {
12461
+ healthy: false,
12462
+ url,
12463
+ responseTimeMs: null,
12464
+ error: "WebSocket not available in this environment"
12465
+ };
12466
+ }
12467
+ return new Promise((resolve) => {
12468
+ let resolved = false;
12469
+ const timer = setTimeout(() => {
12470
+ if (!resolved) {
12471
+ resolved = true;
12472
+ try {
12473
+ ws2.close();
12474
+ } catch {
12475
+ }
12476
+ resolve({
12477
+ healthy: false,
12478
+ url,
12479
+ responseTimeMs: null,
12480
+ error: `Connection timeout after ${timeoutMs}ms`
12481
+ });
12482
+ }
12483
+ }, timeoutMs);
12484
+ let ws2;
12485
+ try {
12486
+ ws2 = new WS(url);
12487
+ } catch (err) {
12488
+ clearTimeout(timer);
12489
+ return resolve({
12490
+ healthy: false,
12491
+ url,
12492
+ responseTimeMs: null,
12493
+ error: `Failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`
12494
+ });
12495
+ }
12496
+ ws2.onopen = () => {
12497
+ if (!resolved) {
12498
+ resolved = true;
12499
+ clearTimeout(timer);
12500
+ const responseTimeMs = Date.now() - startTime;
12501
+ try {
12502
+ ws2.close();
12503
+ } catch {
12504
+ }
12505
+ resolve({ healthy: true, url, responseTimeMs });
12506
+ }
12507
+ };
12508
+ ws2.onerror = (event) => {
12509
+ if (!resolved) {
12510
+ resolved = true;
12511
+ clearTimeout(timer);
12512
+ try {
12513
+ ws2.close();
12514
+ } catch {
12515
+ }
12516
+ resolve({
12517
+ healthy: false,
12518
+ url,
12519
+ responseTimeMs: null,
12520
+ error: "WebSocket connection error"
12521
+ });
12522
+ }
12523
+ };
12524
+ ws2.onclose = (event) => {
12525
+ if (!resolved) {
12526
+ resolved = true;
12527
+ clearTimeout(timer);
12528
+ resolve({
12529
+ healthy: false,
12530
+ url,
12531
+ responseTimeMs: null,
12532
+ error: `WebSocket closed: ${event.reason || `code ${event.code}`}`
12533
+ });
12534
+ }
12535
+ };
12536
+ });
12537
+ }
12538
+ async function checkOracle(url, timeoutMs) {
12539
+ const startTime = Date.now();
12540
+ try {
12541
+ const controller = new AbortController();
12542
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
12543
+ const response = await fetch(url, {
12544
+ method: "POST",
12545
+ headers: { "Content-Type": "application/json" },
12546
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "get_round_number", params: {} }),
12547
+ signal: controller.signal
12548
+ });
12549
+ clearTimeout(timer);
12550
+ const responseTimeMs = Date.now() - startTime;
12551
+ if (response.ok) {
12552
+ return { healthy: true, url, responseTimeMs };
12553
+ }
12554
+ return {
12555
+ healthy: false,
12556
+ url,
12557
+ responseTimeMs,
12558
+ error: `HTTP ${response.status} ${response.statusText}`
12559
+ };
12560
+ } catch (err) {
12561
+ return {
12562
+ healthy: false,
12563
+ url,
12564
+ responseTimeMs: null,
12565
+ error: err instanceof Error ? err.name === "AbortError" ? `Connection timeout after ${timeoutMs}ms` : err.message : String(err)
12566
+ };
12567
+ }
12568
+ }
12569
+ async function runCustomCheck(name, checkFn, timeoutMs) {
12570
+ try {
12571
+ const result = await Promise.race([
12572
+ checkFn(timeoutMs),
12573
+ new Promise(
12574
+ (resolve) => setTimeout(
12575
+ () => resolve({
12576
+ healthy: false,
12577
+ url: name,
12578
+ responseTimeMs: null,
12579
+ error: `Custom check '${name}' timeout after ${timeoutMs}ms`
12580
+ }),
12581
+ timeoutMs
12582
+ )
12583
+ )
12584
+ ]);
12585
+ return result;
12586
+ } catch (err) {
12587
+ return {
12588
+ healthy: false,
12589
+ url: name,
12590
+ responseTimeMs: null,
12591
+ error: err instanceof Error ? err.message : String(err)
12592
+ };
12593
+ }
12594
+ }
12595
+
12073
12596
  // types/payment-session.ts
12074
12597
  function createPaymentSession(params) {
12075
12598
  const now = Date.now();
@@ -12618,6 +13141,7 @@ export {
12618
13141
  base58Encode2 as base58Encode,
12619
13142
  buildTxfStorageData,
12620
13143
  bytesToHex2 as bytesToHex,
13144
+ checkNetworkHealth,
12621
13145
  countCommittedTransactions,
12622
13146
  createAddress,
12623
13147
  createCommunicationsModule,