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