@unicitylabs/sphere-sdk 0.3.1 → 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.
@@ -1454,6 +1454,7 @@ async function sendAlpha(wallet, toAddress, amountAlpha, fromAddress) {
1454
1454
  // modules/payments/L1PaymentsModule.ts
1455
1455
  var L1PaymentsModule = class {
1456
1456
  _initialized = false;
1457
+ _disabled = false;
1457
1458
  _config;
1458
1459
  _identity;
1459
1460
  _addresses = [];
@@ -1501,6 +1502,9 @@ var L1PaymentsModule = class {
1501
1502
  * (e.g. by the address scanner), this is a no-op.
1502
1503
  */
1503
1504
  async ensureConnected() {
1505
+ if (this._disabled) {
1506
+ throw new Error("L1 provider is disabled");
1507
+ }
1504
1508
  if (!isWebSocketConnected() && this._config.electrumUrl) {
1505
1509
  await connect(this._config.electrumUrl);
1506
1510
  }
@@ -1514,6 +1518,24 @@ var L1PaymentsModule = class {
1514
1518
  this._addresses = [];
1515
1519
  this._wallet = void 0;
1516
1520
  }
1521
+ /**
1522
+ * Disable this module — disconnect WebSocket and block operations until re-enabled.
1523
+ */
1524
+ disable() {
1525
+ this._disabled = true;
1526
+ if (isWebSocketConnected()) {
1527
+ disconnect();
1528
+ }
1529
+ }
1530
+ /**
1531
+ * Re-enable this module. Connection will be established lazily on next operation.
1532
+ */
1533
+ enable() {
1534
+ this._disabled = false;
1535
+ }
1536
+ get disabled() {
1537
+ return this._disabled;
1538
+ }
1517
1539
  /**
1518
1540
  * Check if a string looks like an L1 address (alpha1... or alphat1...)
1519
1541
  */
@@ -5233,7 +5255,7 @@ var PaymentsModule = class _PaymentsModule {
5233
5255
  */
5234
5256
  async getFiatBalance() {
5235
5257
  const assets = await this.getAssets();
5236
- if (!this.priceProvider) {
5258
+ if (!this.priceProvider || this.isPriceDisabled()) {
5237
5259
  return null;
5238
5260
  }
5239
5261
  let total = 0;
@@ -5268,7 +5290,7 @@ var PaymentsModule = class _PaymentsModule {
5268
5290
  */
5269
5291
  async getAssets(coinId) {
5270
5292
  const rawAssets = this.aggregateTokens(coinId);
5271
- if (!this.priceProvider || rawAssets.length === 0) {
5293
+ if (!this.priceProvider || this.isPriceDisabled() || rawAssets.length === 0) {
5272
5294
  return rawAssets;
5273
5295
  }
5274
5296
  try {
@@ -6362,18 +6384,38 @@ var PaymentsModule = class _PaymentsModule {
6362
6384
  }, _PaymentsModule.SYNC_DEBOUNCE_MS);
6363
6385
  }
6364
6386
  /**
6365
- * Get all active token storage providers
6387
+ * Get all active (non-disabled) token storage providers
6366
6388
  */
6367
6389
  getTokenStorageProviders() {
6390
+ let providers;
6368
6391
  if (this.deps.tokenStorageProviders && this.deps.tokenStorageProviders.size > 0) {
6369
- return this.deps.tokenStorageProviders;
6370
- }
6371
- if (this.deps.tokenStorage) {
6372
- const map = /* @__PURE__ */ new Map();
6373
- map.set(this.deps.tokenStorage.id, this.deps.tokenStorage);
6374
- return map;
6392
+ providers = this.deps.tokenStorageProviders;
6393
+ } else if (this.deps.tokenStorage) {
6394
+ providers = /* @__PURE__ */ new Map();
6395
+ providers.set(this.deps.tokenStorage.id, this.deps.tokenStorage);
6396
+ } else {
6397
+ return /* @__PURE__ */ new Map();
6398
+ }
6399
+ const disabled = this.deps.disabledProviderIds;
6400
+ if (disabled && disabled.size > 0) {
6401
+ const filtered = /* @__PURE__ */ new Map();
6402
+ for (const [id, provider] of providers) {
6403
+ if (!disabled.has(id)) {
6404
+ filtered.set(id, provider);
6405
+ }
6406
+ }
6407
+ return filtered;
6375
6408
  }
6376
- return /* @__PURE__ */ new Map();
6409
+ return providers;
6410
+ }
6411
+ /**
6412
+ * Check if the price provider is disabled via the disabled providers set.
6413
+ */
6414
+ isPriceDisabled() {
6415
+ const disabled = this.deps?.disabledProviderIds;
6416
+ if (!disabled || disabled.size === 0) return false;
6417
+ const priceId = this.priceProvider?.id ?? "price";
6418
+ return disabled.has(priceId);
6377
6419
  }
6378
6420
  /**
6379
6421
  * Replace the set of token storage providers at runtime.
@@ -8965,6 +9007,9 @@ async function scanAddressesImpl(deriveAddress, options = {}) {
8965
9007
  };
8966
9008
  }
8967
9009
 
9010
+ // core/Sphere.ts
9011
+ init_network();
9012
+
8968
9013
  // serialization/wallet-text.ts
8969
9014
  import CryptoJS7 from "crypto-js";
8970
9015
 
@@ -9693,6 +9738,10 @@ var Sphere = class _Sphere {
9693
9738
  _groupChat = null;
9694
9739
  // Events
9695
9740
  eventHandlers = /* @__PURE__ */ new Map();
9741
+ // Provider management
9742
+ _disabledProviders = /* @__PURE__ */ new Set();
9743
+ _providerEventCleanups = [];
9744
+ _lastProviderConnected = /* @__PURE__ */ new Map();
9696
9745
  // ===========================================================================
9697
9746
  // Constructor (private)
9698
9747
  // ===========================================================================
@@ -9899,9 +9948,14 @@ var Sphere = class _Sphere {
9899
9948
  throw new Error("Either mnemonic or masterKey is required");
9900
9949
  }
9901
9950
  console.log("[Sphere.import] Starting import...");
9902
- console.log("[Sphere.import] Clearing existing wallet data...");
9903
- await _Sphere.clear({ storage: options.storage, tokenStorage: options.tokenStorage });
9904
- console.log("[Sphere.import] Clear done");
9951
+ const needsClear = _Sphere.instance !== null || await _Sphere.exists(options.storage);
9952
+ if (needsClear) {
9953
+ console.log("[Sphere.import] Clearing existing wallet data...");
9954
+ await _Sphere.clear({ storage: options.storage, tokenStorage: options.tokenStorage });
9955
+ console.log("[Sphere.import] Clear done");
9956
+ } else {
9957
+ console.log("[Sphere.import] No existing wallet \u2014 skipping clear");
9958
+ }
9905
9959
  if (!options.storage.isConnected()) {
9906
9960
  console.log("[Sphere.import] Reconnecting storage...");
9907
9961
  await options.storage.connect();
@@ -11128,20 +11182,193 @@ var Sphere = class _Sphere {
11128
11182
  // ===========================================================================
11129
11183
  // Public Methods - Status
11130
11184
  // ===========================================================================
11185
+ /**
11186
+ * Get aggregated status of all providers, grouped by role.
11187
+ *
11188
+ * @example
11189
+ * ```ts
11190
+ * const status = sphere.getStatus();
11191
+ * // status.transport[0].connected // true/false
11192
+ * // status.transport[0].metadata?.relays // { total: 3, connected: 2 }
11193
+ * // status.tokenStorage // all registered token storage providers
11194
+ * ```
11195
+ */
11131
11196
  getStatus() {
11197
+ const mkInfo = (provider, role, metadata) => ({
11198
+ id: provider.id,
11199
+ name: provider.name,
11200
+ role,
11201
+ status: provider.getStatus(),
11202
+ connected: provider.isConnected(),
11203
+ enabled: !this._disabledProviders.has(provider.id),
11204
+ ...metadata ? { metadata } : {}
11205
+ });
11206
+ let transportMeta;
11207
+ const transport = this._transport;
11208
+ if (typeof transport.getRelays === "function") {
11209
+ const total = transport.getRelays().length;
11210
+ const connected = typeof transport.getConnectedRelays === "function" ? transport.getConnectedRelays().length : 0;
11211
+ transportMeta = { relays: { total, connected } };
11212
+ }
11213
+ const l1Module = this._payments.l1;
11214
+ const l1Providers = [];
11215
+ if (l1Module) {
11216
+ const wsConnected = isWebSocketConnected();
11217
+ l1Providers.push({
11218
+ id: "l1-alpha",
11219
+ name: "ALPHA L1",
11220
+ role: "l1",
11221
+ status: wsConnected ? "connected" : "disconnected",
11222
+ connected: wsConnected,
11223
+ enabled: !this._disabledProviders.has("l1-alpha")
11224
+ });
11225
+ }
11226
+ const priceProviders = [];
11227
+ if (this._priceProvider) {
11228
+ priceProviders.push({
11229
+ id: this._priceProviderId,
11230
+ name: this._priceProvider.platform ?? "Price",
11231
+ role: "price",
11232
+ status: "connected",
11233
+ connected: true,
11234
+ enabled: !this._disabledProviders.has(this._priceProviderId)
11235
+ });
11236
+ }
11132
11237
  return {
11133
- storage: { connected: this._storage.isConnected() },
11134
- transport: { connected: this._transport.isConnected() },
11135
- oracle: { connected: this._oracle.isConnected() }
11238
+ storage: [mkInfo(this._storage, "storage")],
11239
+ tokenStorage: Array.from(this._tokenStorageProviders.values()).map(
11240
+ (p) => mkInfo(p, "token-storage")
11241
+ ),
11242
+ transport: [mkInfo(this._transport, "transport", transportMeta)],
11243
+ oracle: [mkInfo(this._oracle, "oracle")],
11244
+ l1: l1Providers,
11245
+ price: priceProviders
11136
11246
  };
11137
11247
  }
11138
11248
  async reconnect() {
11139
11249
  await this._transport.disconnect();
11140
11250
  await this._transport.connect();
11251
+ }
11252
+ // ===========================================================================
11253
+ // Public Methods - Provider Management
11254
+ // ===========================================================================
11255
+ /**
11256
+ * Disable a provider at runtime. The provider stays registered but is disconnected
11257
+ * and skipped during operations (e.g., sync).
11258
+ *
11259
+ * Main storage provider cannot be disabled.
11260
+ *
11261
+ * @returns true if successfully disabled, false if provider not found
11262
+ */
11263
+ async disableProvider(providerId) {
11264
+ if (providerId === this._storage.id) {
11265
+ throw new Error("Cannot disable the main storage provider");
11266
+ }
11267
+ const provider = this.findProviderById(providerId);
11268
+ if (!provider) return false;
11269
+ this._disabledProviders.add(providerId);
11270
+ try {
11271
+ if ("disable" in provider && typeof provider.disable === "function") {
11272
+ provider.disable();
11273
+ } else if ("shutdown" in provider && typeof provider.shutdown === "function") {
11274
+ await provider.shutdown();
11275
+ } else if ("disconnect" in provider && typeof provider.disconnect === "function") {
11276
+ await provider.disconnect();
11277
+ } else if ("clearCache" in provider && typeof provider.clearCache === "function") {
11278
+ provider.clearCache();
11279
+ }
11280
+ } catch {
11281
+ }
11282
+ this.emitEvent("connection:changed", {
11283
+ provider: providerId,
11284
+ connected: false,
11285
+ status: "disconnected",
11286
+ enabled: false
11287
+ });
11288
+ return true;
11289
+ }
11290
+ /**
11291
+ * Re-enable a previously disabled provider. Reconnects and resumes operations.
11292
+ *
11293
+ * @returns true if successfully enabled, false if provider not found
11294
+ */
11295
+ async enableProvider(providerId) {
11296
+ const provider = this.findProviderById(providerId);
11297
+ if (!provider) return false;
11298
+ this._disabledProviders.delete(providerId);
11299
+ if ("enable" in provider && typeof provider.enable === "function") {
11300
+ provider.enable();
11301
+ this.emitEvent("connection:changed", {
11302
+ provider: providerId,
11303
+ connected: false,
11304
+ status: "disconnected",
11305
+ enabled: true
11306
+ });
11307
+ return true;
11308
+ }
11309
+ const hasLifecycle = "connect" in provider && typeof provider.connect === "function" || "initialize" in provider && typeof provider.initialize === "function";
11310
+ if (hasLifecycle) {
11311
+ try {
11312
+ if ("connect" in provider && typeof provider.connect === "function") {
11313
+ await provider.connect();
11314
+ } else if ("initialize" in provider && typeof provider.initialize === "function") {
11315
+ await provider.initialize();
11316
+ }
11317
+ } catch (err) {
11318
+ this.emitEvent("connection:changed", {
11319
+ provider: providerId,
11320
+ connected: false,
11321
+ status: "error",
11322
+ enabled: true,
11323
+ error: err instanceof Error ? err.message : String(err)
11324
+ });
11325
+ return false;
11326
+ }
11327
+ }
11141
11328
  this.emitEvent("connection:changed", {
11142
- provider: "transport",
11143
- connected: true
11329
+ provider: providerId,
11330
+ connected: true,
11331
+ status: "connected",
11332
+ enabled: true
11144
11333
  });
11334
+ return true;
11335
+ }
11336
+ /**
11337
+ * Check if a provider is currently enabled
11338
+ */
11339
+ isProviderEnabled(providerId) {
11340
+ return !this._disabledProviders.has(providerId);
11341
+ }
11342
+ /**
11343
+ * Get the set of disabled provider IDs (for passing to modules)
11344
+ */
11345
+ getDisabledProviderIds() {
11346
+ return this._disabledProviders;
11347
+ }
11348
+ /** Get the price provider's ID (implementation detail — not on PriceProvider interface) */
11349
+ get _priceProviderId() {
11350
+ if (!this._priceProvider) return "price";
11351
+ const p = this._priceProvider;
11352
+ return typeof p.id === "string" ? p.id : "price";
11353
+ }
11354
+ /**
11355
+ * Find a provider by ID across all provider collections
11356
+ */
11357
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11358
+ findProviderById(providerId) {
11359
+ if (this._storage.id === providerId) return this._storage;
11360
+ if (this._transport.id === providerId) return this._transport;
11361
+ if (this._oracle.id === providerId) return this._oracle;
11362
+ if (this._tokenStorageProviders.has(providerId)) {
11363
+ return this._tokenStorageProviders.get(providerId);
11364
+ }
11365
+ if (this._priceProvider && this._priceProviderId === providerId) {
11366
+ return this._priceProvider;
11367
+ }
11368
+ if (providerId === "l1-alpha" && this._payments.l1) {
11369
+ return this._payments.l1;
11370
+ }
11371
+ return null;
11145
11372
  }
11146
11373
  // ===========================================================================
11147
11374
  // Public Methods - Events
@@ -11484,6 +11711,33 @@ var Sphere = class _Sphere {
11484
11711
  return;
11485
11712
  }
11486
11713
  try {
11714
+ if (this._identity?.directAddress && this._transport.resolve) {
11715
+ try {
11716
+ const existing = await this._transport.resolve(this._identity.directAddress);
11717
+ if (existing) {
11718
+ if (existing.nametag && !this._identity.nametag) {
11719
+ this._identity.nametag = existing.nametag;
11720
+ await this._updateCachedProxyAddress();
11721
+ const entry = await this.ensureAddressTracked(this._currentAddressIndex);
11722
+ let nametags = this._addressNametags.get(entry.addressId);
11723
+ if (!nametags) {
11724
+ nametags = /* @__PURE__ */ new Map();
11725
+ this._addressNametags.set(entry.addressId, nametags);
11726
+ }
11727
+ if (!nametags.has(0)) {
11728
+ nametags.set(0, existing.nametag);
11729
+ await this.persistAddressNametags();
11730
+ }
11731
+ this.emitEvent("nametag:recovered", { nametag: existing.nametag });
11732
+ }
11733
+ console.log("[Sphere] Existing binding found, skipping re-publish");
11734
+ return;
11735
+ }
11736
+ } catch (e) {
11737
+ console.warn("[Sphere] resolve() failed, skipping publish to avoid overwrite", e);
11738
+ return;
11739
+ }
11740
+ }
11487
11741
  const nametag = this._identity?.nametag;
11488
11742
  const success = await this._transport.publishIdentityBinding(
11489
11743
  this._identity.chainPubkey,
@@ -11557,17 +11811,26 @@ var Sphere = class _Sphere {
11557
11811
  // Public Methods - Lifecycle
11558
11812
  // ===========================================================================
11559
11813
  async destroy() {
11814
+ this.cleanupProviderEventSubscriptions();
11560
11815
  this._payments.destroy();
11561
11816
  this._communications.destroy();
11562
11817
  this._groupChat?.destroy();
11563
11818
  await this._transport.disconnect();
11564
11819
  await this._storage.disconnect();
11565
11820
  await this._oracle.disconnect();
11821
+ for (const provider of this._tokenStorageProviders.values()) {
11822
+ try {
11823
+ await provider.shutdown();
11824
+ } catch {
11825
+ }
11826
+ }
11827
+ this._tokenStorageProviders.clear();
11566
11828
  this._initialized = false;
11567
11829
  this._identity = null;
11568
11830
  this._trackedAddresses.clear();
11569
11831
  this._addressIdToIndex.clear();
11570
11832
  this._addressNametags.clear();
11833
+ this._disabledProviders.clear();
11571
11834
  this.eventHandlers.clear();
11572
11835
  if (_Sphere.instance === this) {
11573
11836
  _Sphere.instance = null;
@@ -11760,6 +12023,79 @@ var Sphere = class _Sphere {
11760
12023
  for (const provider of this._tokenStorageProviders.values()) {
11761
12024
  await provider.initialize();
11762
12025
  }
12026
+ this.subscribeToProviderEvents();
12027
+ }
12028
+ /**
12029
+ * Subscribe to provider-level events and bridge them to Sphere connection:changed events.
12030
+ * Uses deduplication to avoid emitting duplicate events.
12031
+ */
12032
+ subscribeToProviderEvents() {
12033
+ this.cleanupProviderEventSubscriptions();
12034
+ const transportAny = this._transport;
12035
+ if (typeof transportAny.onEvent === "function") {
12036
+ const unsub = transportAny.onEvent((event) => {
12037
+ const type = event?.type;
12038
+ if (type === "transport:connected") {
12039
+ this.emitConnectionChanged(this._transport.id, true, "connected");
12040
+ } else if (type === "transport:disconnected") {
12041
+ this.emitConnectionChanged(this._transport.id, false, "disconnected");
12042
+ } else if (type === "transport:reconnecting") {
12043
+ this.emitConnectionChanged(this._transport.id, false, "connecting");
12044
+ } else if (type === "transport:error") {
12045
+ this.emitConnectionChanged(this._transport.id, false, "error", event?.error);
12046
+ }
12047
+ });
12048
+ if (unsub) this._providerEventCleanups.push(unsub);
12049
+ }
12050
+ const oracleAny = this._oracle;
12051
+ if (typeof oracleAny.onEvent === "function") {
12052
+ const unsub = oracleAny.onEvent((event) => {
12053
+ const type = event?.type;
12054
+ if (type === "oracle:connected") {
12055
+ this.emitConnectionChanged(this._oracle.id, true, "connected");
12056
+ } else if (type === "oracle:disconnected") {
12057
+ this.emitConnectionChanged(this._oracle.id, false, "disconnected");
12058
+ } else if (type === "oracle:error") {
12059
+ this.emitConnectionChanged(this._oracle.id, false, "error", event?.error);
12060
+ }
12061
+ });
12062
+ if (unsub) this._providerEventCleanups.push(unsub);
12063
+ }
12064
+ for (const [providerId, provider] of this._tokenStorageProviders) {
12065
+ if (typeof provider.onEvent === "function") {
12066
+ const unsub = provider.onEvent((event) => {
12067
+ if (event.type === "storage:error" || event.type === "sync:error") {
12068
+ this.emitConnectionChanged(providerId, provider.isConnected(), provider.getStatus(), event.error);
12069
+ }
12070
+ });
12071
+ if (unsub) this._providerEventCleanups.push(unsub);
12072
+ }
12073
+ }
12074
+ }
12075
+ /**
12076
+ * Emit connection:changed with deduplication — only emits if status actually changed.
12077
+ */
12078
+ emitConnectionChanged(providerId, connected, status, error) {
12079
+ const lastConnected = this._lastProviderConnected.get(providerId);
12080
+ if (lastConnected === connected) return;
12081
+ this._lastProviderConnected.set(providerId, connected);
12082
+ this.emitEvent("connection:changed", {
12083
+ provider: providerId,
12084
+ connected,
12085
+ status,
12086
+ enabled: !this._disabledProviders.has(providerId),
12087
+ ...error ? { error } : {}
12088
+ });
12089
+ }
12090
+ cleanupProviderEventSubscriptions() {
12091
+ for (const cleanup of this._providerEventCleanups) {
12092
+ try {
12093
+ cleanup();
12094
+ } catch {
12095
+ }
12096
+ }
12097
+ this._providerEventCleanups = [];
12098
+ this._lastProviderConnected.clear();
11763
12099
  }
11764
12100
  async initializeModules() {
11765
12101
  const emitEvent = this.emitEvent.bind(this);
@@ -11772,7 +12108,8 @@ var Sphere = class _Sphere {
11772
12108
  emitEvent,
11773
12109
  // Pass chain code for L1 HD derivation
11774
12110
  chainCode: this._masterKey?.chainCode || void 0,
11775
- price: this._priceProvider ?? void 0
12111
+ price: this._priceProvider ?? void 0,
12112
+ disabledProviderIds: this._disabledProviders
11776
12113
  });
11777
12114
  this._communications.initialize({
11778
12115
  identity: this._identity,
@@ -11867,6 +12204,192 @@ var CurrencyUtils = {
11867
12204
 
11868
12205
  // core/index.ts
11869
12206
  init_bech32();
12207
+
12208
+ // core/network-health.ts
12209
+ var DEFAULT_TIMEOUT_MS = 5e3;
12210
+ async function checkNetworkHealth(network = "testnet", options) {
12211
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
12212
+ const servicesToCheck = options?.services ?? ["relay", "oracle", "l1"];
12213
+ const networkConfig = NETWORKS[network];
12214
+ const customUrls = options?.urls;
12215
+ const startTime = Date.now();
12216
+ const allChecks = [];
12217
+ if (servicesToCheck.includes("relay")) {
12218
+ const relayUrl = customUrls?.relay ?? networkConfig.nostrRelays[0];
12219
+ allChecks.push(checkWebSocket(relayUrl, timeoutMs).then((r) => ["relay", r]));
12220
+ }
12221
+ if (servicesToCheck.includes("oracle")) {
12222
+ const oracleUrl = customUrls?.oracle ?? networkConfig.aggregatorUrl;
12223
+ allChecks.push(checkOracle(oracleUrl, timeoutMs).then((r) => ["oracle", r]));
12224
+ }
12225
+ if (servicesToCheck.includes("l1")) {
12226
+ const l1Url = customUrls?.l1 ?? networkConfig.electrumUrl;
12227
+ allChecks.push(checkWebSocket(l1Url, timeoutMs).then((r) => ["l1", r]));
12228
+ }
12229
+ if (options?.checks) {
12230
+ for (const [name, checkFn] of Object.entries(options.checks)) {
12231
+ allChecks.push(
12232
+ runCustomCheck(name, checkFn, timeoutMs).then((r) => [name, r])
12233
+ );
12234
+ }
12235
+ }
12236
+ const results = await Promise.allSettled(allChecks);
12237
+ const services = {};
12238
+ let allHealthy = true;
12239
+ for (const result of results) {
12240
+ if (result.status === "fulfilled") {
12241
+ const [name, healthResult] = result.value;
12242
+ services[name] = healthResult;
12243
+ if (!healthResult.healthy) allHealthy = false;
12244
+ } else {
12245
+ allHealthy = false;
12246
+ }
12247
+ }
12248
+ return {
12249
+ healthy: allHealthy,
12250
+ services,
12251
+ totalTimeMs: Date.now() - startTime
12252
+ };
12253
+ }
12254
+ async function checkWebSocket(url, timeoutMs) {
12255
+ const startTime = Date.now();
12256
+ const WS = typeof globalThis !== "undefined" && globalThis.WebSocket;
12257
+ if (!WS) {
12258
+ return {
12259
+ healthy: false,
12260
+ url,
12261
+ responseTimeMs: null,
12262
+ error: "WebSocket not available in this environment"
12263
+ };
12264
+ }
12265
+ return new Promise((resolve) => {
12266
+ let resolved = false;
12267
+ const timer = setTimeout(() => {
12268
+ if (!resolved) {
12269
+ resolved = true;
12270
+ try {
12271
+ ws2.close();
12272
+ } catch {
12273
+ }
12274
+ resolve({
12275
+ healthy: false,
12276
+ url,
12277
+ responseTimeMs: null,
12278
+ error: `Connection timeout after ${timeoutMs}ms`
12279
+ });
12280
+ }
12281
+ }, timeoutMs);
12282
+ let ws2;
12283
+ try {
12284
+ ws2 = new WS(url);
12285
+ } catch (err) {
12286
+ clearTimeout(timer);
12287
+ return resolve({
12288
+ healthy: false,
12289
+ url,
12290
+ responseTimeMs: null,
12291
+ error: `Failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`
12292
+ });
12293
+ }
12294
+ ws2.onopen = () => {
12295
+ if (!resolved) {
12296
+ resolved = true;
12297
+ clearTimeout(timer);
12298
+ const responseTimeMs = Date.now() - startTime;
12299
+ try {
12300
+ ws2.close();
12301
+ } catch {
12302
+ }
12303
+ resolve({ healthy: true, url, responseTimeMs });
12304
+ }
12305
+ };
12306
+ ws2.onerror = (event) => {
12307
+ if (!resolved) {
12308
+ resolved = true;
12309
+ clearTimeout(timer);
12310
+ try {
12311
+ ws2.close();
12312
+ } catch {
12313
+ }
12314
+ resolve({
12315
+ healthy: false,
12316
+ url,
12317
+ responseTimeMs: null,
12318
+ error: "WebSocket connection error"
12319
+ });
12320
+ }
12321
+ };
12322
+ ws2.onclose = (event) => {
12323
+ if (!resolved) {
12324
+ resolved = true;
12325
+ clearTimeout(timer);
12326
+ resolve({
12327
+ healthy: false,
12328
+ url,
12329
+ responseTimeMs: null,
12330
+ error: `WebSocket closed: ${event.reason || `code ${event.code}`}`
12331
+ });
12332
+ }
12333
+ };
12334
+ });
12335
+ }
12336
+ async function checkOracle(url, timeoutMs) {
12337
+ const startTime = Date.now();
12338
+ try {
12339
+ const controller = new AbortController();
12340
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
12341
+ const response = await fetch(url, {
12342
+ method: "POST",
12343
+ headers: { "Content-Type": "application/json" },
12344
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "get_round_number", params: {} }),
12345
+ signal: controller.signal
12346
+ });
12347
+ clearTimeout(timer);
12348
+ const responseTimeMs = Date.now() - startTime;
12349
+ if (response.ok) {
12350
+ return { healthy: true, url, responseTimeMs };
12351
+ }
12352
+ return {
12353
+ healthy: false,
12354
+ url,
12355
+ responseTimeMs,
12356
+ error: `HTTP ${response.status} ${response.statusText}`
12357
+ };
12358
+ } catch (err) {
12359
+ return {
12360
+ healthy: false,
12361
+ url,
12362
+ responseTimeMs: null,
12363
+ error: err instanceof Error ? err.name === "AbortError" ? `Connection timeout after ${timeoutMs}ms` : err.message : String(err)
12364
+ };
12365
+ }
12366
+ }
12367
+ async function runCustomCheck(name, checkFn, timeoutMs) {
12368
+ try {
12369
+ const result = await Promise.race([
12370
+ checkFn(timeoutMs),
12371
+ new Promise(
12372
+ (resolve) => setTimeout(
12373
+ () => resolve({
12374
+ healthy: false,
12375
+ url: name,
12376
+ responseTimeMs: null,
12377
+ error: `Custom check '${name}' timeout after ${timeoutMs}ms`
12378
+ }),
12379
+ timeoutMs
12380
+ )
12381
+ )
12382
+ ]);
12383
+ return result;
12384
+ } catch (err) {
12385
+ return {
12386
+ healthy: false,
12387
+ url: name,
12388
+ responseTimeMs: null,
12389
+ error: err instanceof Error ? err.message : String(err)
12390
+ };
12391
+ }
12392
+ }
11870
12393
  export {
11871
12394
  CHARSET,
11872
12395
  CurrencyUtils,
@@ -11876,6 +12399,7 @@ export {
11876
12399
  base58Decode,
11877
12400
  base58Encode,
11878
12401
  bytesToHex2 as bytesToHex,
12402
+ checkNetworkHealth,
11879
12403
  computeHash160,
11880
12404
  convertBits,
11881
12405
  createAddress,