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