@unicitylabs/sphere-sdk 0.15.0 → 0.16.0-dev.1

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.
Files changed (50) hide show
  1. package/README.md +15 -5
  2. package/dist/connect/index.cjs +14 -23
  3. package/dist/connect/index.cjs.map +1 -1
  4. package/dist/connect/index.d.cts +4 -0
  5. package/dist/connect/index.d.ts +4 -0
  6. package/dist/connect/index.js +14 -23
  7. package/dist/connect/index.js.map +1 -1
  8. package/dist/core/index.cjs +726 -225
  9. package/dist/core/index.cjs.map +1 -1
  10. package/dist/core/index.d.cts +172 -21
  11. package/dist/core/index.d.ts +172 -21
  12. package/dist/core/index.js +726 -224
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/impl/browser/connect/index.cjs +14 -23
  15. package/dist/impl/browser/connect/index.cjs.map +1 -1
  16. package/dist/impl/browser/connect/index.js +14 -23
  17. package/dist/impl/browser/connect/index.js.map +1 -1
  18. package/dist/impl/browser/index.cjs +331 -504
  19. package/dist/impl/browser/index.cjs.map +1 -1
  20. package/dist/impl/browser/index.js +331 -504
  21. package/dist/impl/browser/index.js.map +1 -1
  22. package/dist/impl/nodejs/connect/index.cjs +13 -22
  23. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  24. package/dist/impl/nodejs/connect/index.js +13 -22
  25. package/dist/impl/nodejs/connect/index.js.map +1 -1
  26. package/dist/impl/nodejs/index.cjs +298 -569
  27. package/dist/impl/nodejs/index.cjs.map +1 -1
  28. package/dist/impl/nodejs/index.d.cts +90 -13
  29. package/dist/impl/nodejs/index.d.ts +90 -13
  30. package/dist/impl/nodejs/index.js +298 -569
  31. package/dist/impl/nodejs/index.js.map +1 -1
  32. package/dist/impl/shared/wallet-api/index.d.cts +54 -2
  33. package/dist/impl/shared/wallet-api/index.d.ts +54 -2
  34. package/dist/index.cjs +728 -225
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +227 -108
  37. package/dist/index.d.ts +227 -108
  38. package/dist/index.js +728 -224
  39. package/dist/index.js.map +1 -1
  40. package/dist/modules/payments-v2/index.cjs +11 -7
  41. package/dist/modules/payments-v2/index.cjs.map +1 -1
  42. package/dist/modules/payments-v2/index.d.cts +56 -3
  43. package/dist/modules/payments-v2/index.d.ts +56 -3
  44. package/dist/modules/payments-v2/index.js +11 -7
  45. package/dist/modules/payments-v2/index.js.map +1 -1
  46. package/dist/token-engine/index.cjs +46 -0
  47. package/dist/token-engine/index.cjs.map +1 -1
  48. package/dist/token-engine/index.js +46 -0
  49. package/dist/token-engine/index.js.map +1 -1
  50. package/package.json +1 -1
@@ -93,6 +93,58 @@ var logger = {
93
93
  }
94
94
  };
95
95
 
96
+ // core/global-cell.ts
97
+ var CELLS_KEY = "__sphere_sdk_cells_v1__";
98
+ var bundleLocalBag = null;
99
+ function isObject(value) {
100
+ return typeof value === "object" && value !== null;
101
+ }
102
+ function installBag(host, fresh) {
103
+ try {
104
+ Object.defineProperty(host, CELLS_KEY, {
105
+ value: fresh,
106
+ writable: true,
107
+ configurable: true,
108
+ enumerable: false
109
+ });
110
+ return host[CELLS_KEY] === fresh;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+ function cellBag() {
116
+ if (bundleLocalBag) return bundleLocalBag;
117
+ const host = globalThis;
118
+ const found = host[CELLS_KEY];
119
+ if (isObject(found)) return found;
120
+ const fresh = {};
121
+ if (!installBag(host, fresh)) bundleLocalBag = fresh;
122
+ return fresh;
123
+ }
124
+ function accepted(intact, candidate) {
125
+ try {
126
+ return intact(candidate);
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+ function put(bag, name, cell) {
132
+ try {
133
+ bag[name] = cell;
134
+ if (bag[name] === cell) return;
135
+ } catch {
136
+ }
137
+ if (bag !== bundleLocalBag) bundleLocalBag = { [name]: cell };
138
+ }
139
+ function sharedCell(name, create, intact) {
140
+ const bag = cellBag();
141
+ const found = bag[name];
142
+ if (isObject(found) && accepted(intact, found)) return found;
143
+ const fresh = create();
144
+ put(bag, name, fresh);
145
+ return fresh;
146
+ }
147
+
96
148
  // core/errors.ts
97
149
  var SphereError = class extends Error {
98
150
  code;
@@ -195,7 +247,6 @@ function isNetworkScopedAddressKey(key) {
195
247
  return false;
196
248
  }
197
249
  var DEFAULT_NOSTR_RELAYS = [
198
- "wss://relay.unicity.network",
199
250
  "wss://relay.damus.io",
200
251
  "wss://nos.lol",
201
252
  "wss://relay.nostr.band"
@@ -208,13 +259,9 @@ var NOSTR_EVENT_KINDS = {
208
259
  /** Public broadcast */
209
260
  BROADCAST: 1
210
261
  };
211
- var DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
212
- var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
213
262
  var DEFAULT_AGGREGATOR_TIMEOUT = 3e4;
214
263
  var DEFAULT_BASE_PATH = "m/44'/0'/0'";
215
264
  var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
216
- var TOKEN_REGISTRY_URL = "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
217
- var TOKEN_REGISTRY_REFRESH_INTERVAL = 36e5;
218
265
  var TEST_NOSTR_RELAYS = [
219
266
  "wss://nostr-relay.testnet.unicity.network"
220
267
  ];
@@ -224,10 +271,19 @@ var DEFAULT_GROUP_RELAYS = [
224
271
  var NETWORKS = {
225
272
  mainnet: {
226
273
  name: "Mainnet",
227
- aggregatorUrl: DEFAULT_AGGREGATOR_URL,
228
- nostrRelays: DEFAULT_NOSTR_RELAYS,
274
+ networkId: 1,
275
+ // v3 state-transition gateway (networkId 1 comes from the trust base). apiKey is env-injected;
276
+ // unlike testnet2's, a mainnet gateway key is a SECRET — never commit one.
277
+ aggregatorUrl: "https://gateway.mainnet.unicity.network",
278
+ // Mainnet has no relay of its own yet — it shares the testnet relay until one is stood up.
279
+ // Consequence while shared: nametag bindings for both networks live in ONE namespace, and
280
+ // since bindings carry no network the cross-network recipient guard can only signal (#734).
281
+ nostrRelays: TEST_NOSTR_RELAYS,
229
282
  groupRelays: DEFAULT_GROUP_RELAYS,
230
- tokenRegistryUrl: TOKEN_REGISTRY_URL
283
+ // Published, but currently only the non-fungible base token type — no fungible
284
+ // coins yet, so those still miss the registry (decimals 0, symbol falls back to
285
+ // six hex chars). Presentation only: the money path treats coinId as opaque bytes.
286
+ tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.mainnet.json"
231
287
  },
232
288
  // v1 cutover: 'testnet' now POINTS AT TESTNET2 (the v2 gateway network). The
233
289
  // old goggregator testnet spoke the removed v1 protocol — a v2 engine cannot
@@ -251,19 +307,10 @@ var NETWORKS = {
251
307
  // reuse testnet infra (shared relays/ipfs)
252
308
  groupRelays: DEFAULT_GROUP_RELAYS,
253
309
  tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet2.json"
254
- },
255
- // NOTE: mainnet/dev still point at v1-era aggregators. The v2 engine cannot
256
- // operate against them until their gateways are cut over to the v2 protocol —
257
- // wallet operations on these networks fail loudly (AGGREGATOR_ERROR) until then.
258
- dev: {
259
- name: "Development",
260
- aggregatorUrl: DEV_AGGREGATOR_URL,
261
- nostrRelays: TEST_NOSTR_RELAYS,
262
- groupRelays: DEFAULT_GROUP_RELAYS,
263
- tokenRegistryUrl: TOKEN_REGISTRY_URL
264
310
  }
265
311
  };
266
312
  var SPHERE_NETWORKS = {
313
+ mainnet: { id: NETWORKS.mainnet.networkId, name: "mainnet" },
267
314
  testnet2: { id: NETWORKS.testnet2.networkId, name: "testnet2" }
268
315
  };
269
316
  var TIMEOUTS = {
@@ -279,12 +326,106 @@ var TIMEOUTS = {
279
326
  SYNC_INTERVAL: 6e4
280
327
  };
281
328
 
329
+ // storage/tracked-addresses.ts
330
+ function isDerivableIndex(value) {
331
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 4294967295;
332
+ }
333
+ function num(value, fallback) {
334
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
335
+ }
336
+ function toEntry(value) {
337
+ if (typeof value !== "object" || value === null) return null;
338
+ const e = value;
339
+ if (!isDerivableIndex(e.index)) return null;
340
+ return {
341
+ ...e,
342
+ index: e.index,
343
+ hidden: e.hidden === true,
344
+ createdAt: num(e.createdAt, 0),
345
+ updatedAt: num(e.updatedAt, 0)
346
+ };
347
+ }
348
+ function parseTrackedAddresses(raw) {
349
+ if (!raw) return [];
350
+ let parsed;
351
+ try {
352
+ parsed = JSON.parse(raw);
353
+ } catch {
354
+ return [];
355
+ }
356
+ if (typeof parsed !== "object" || parsed === null) return [];
357
+ const addresses = parsed.addresses;
358
+ if (!Array.isArray(addresses)) return [];
359
+ const entries = [];
360
+ for (const row of addresses) {
361
+ const entry = toEntry(row);
362
+ if (entry) entries.push(entry);
363
+ }
364
+ return entries;
365
+ }
366
+ function mergeTrackedAddresses(onDisk, incoming) {
367
+ const merged = /* @__PURE__ */ new Map();
368
+ for (const entry of onDisk) {
369
+ if (isDerivableIndex(entry.index)) merged.set(entry.index, entry);
370
+ }
371
+ for (const entry of incoming) {
372
+ if (!isDerivableIndex(entry.index)) {
373
+ throw new SphereError(
374
+ `Tracked address index ${String(entry.index)} is not a BIP32 child number: it must be an integer in 0\u20260xffffffff. Refusing the write \u2014 such a row derives another address's keys (1.5 parses to 1) instead of its own.`,
375
+ "VALIDATION_ERROR"
376
+ );
377
+ }
378
+ const existing = merged.get(entry.index);
379
+ if (!existing) {
380
+ merged.set(entry.index, entry);
381
+ continue;
382
+ }
383
+ const winner = entry.updatedAt >= existing.updatedAt ? entry : existing;
384
+ merged.set(entry.index, {
385
+ ...existing,
386
+ ...winner,
387
+ index: entry.index,
388
+ createdAt: Math.min(existing.createdAt, entry.createdAt)
389
+ });
390
+ }
391
+ return Array.from(merged.values()).sort((a, b) => a.index - b.index);
392
+ }
393
+
282
394
  // impl/browser/storage/LocalStorageProvider.ts
395
+ var storeIdentity = sharedCell(
396
+ "impl.browser.localStorage.identity@1",
397
+ () => ({ tags: /* @__PURE__ */ new WeakMap(), writeChains: /* @__PURE__ */ new Map(), seq: 0 }),
398
+ (cell) => {
399
+ const c = cell;
400
+ return c.tags instanceof WeakMap && c.writeChains instanceof Map && typeof c.seq === "number";
401
+ }
402
+ );
403
+ function storageObjectTag(storage) {
404
+ let tag = storeIdentity.tags.get(storage);
405
+ if (!tag) {
406
+ tag = String(++storeIdentity.seq);
407
+ storeIdentity.tags.set(storage, tag);
408
+ }
409
+ return tag;
410
+ }
411
+ var trackedWriteChains = storeIdentity.writeChains;
412
+ function serializeTrackedWrite(storeId, task) {
413
+ const previous = trackedWriteChains.get(storeId) ?? Promise.resolve();
414
+ const run = previous.then(task);
415
+ const tail = run.then(() => void 0, () => void 0);
416
+ trackedWriteChains.set(storeId, tail);
417
+ void tail.then(() => {
418
+ if (trackedWriteChains.get(storeId) === tail) trackedWriteChains.delete(storeId);
419
+ });
420
+ return run;
421
+ }
283
422
  var LocalStorageProvider = class {
284
423
  id = "localStorage";
285
424
  name = "Local Storage";
286
425
  type = "local";
287
426
  description = "Browser localStorage for single-device persistence";
427
+ /** The `Storage` object + prefix — two providers over one pair share erasure (#766). */
428
+ backingStoreId;
288
429
  config;
289
430
  network;
290
431
  identity = null;
@@ -297,6 +438,7 @@ var LocalStorageProvider = class {
297
438
  debug: config?.debug ?? false
298
439
  };
299
440
  this.network = config?.network;
441
+ this.backingStoreId = `localstorage:${storageObjectTag(storage)}:${encodeURIComponent(this.config.prefix)}`;
300
442
  }
301
443
  // ===========================================================================
302
444
  // BaseProvider Implementation
@@ -372,18 +514,27 @@ var LocalStorageProvider = class {
372
514
  await this.remove(key);
373
515
  }
374
516
  }
517
+ /**
518
+ * Persist the tracked-address registry by MERGING, never replacing.
519
+ *
520
+ * Every Sphere over this storage holds its own snapshot and writes it in
521
+ * full, so a wholesale write drops the addresses this writer never saw
522
+ * (#766 item 5 — a lost update, reproducible on one network). localStorage
523
+ * offers no transaction, so the read-merge-write is serialized by BACKING
524
+ * STORE — see `serializeTrackedWrite`, and the realm limit stated there.
525
+ */
375
526
  async saveTrackedAddresses(entries) {
376
- await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
527
+ await serializeTrackedWrite(this.backingStoreId, async () => {
528
+ const onDisk = parseTrackedAddresses(await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES));
529
+ const file = {
530
+ version: 1,
531
+ addresses: mergeTrackedAddresses(onDisk, entries)
532
+ };
533
+ await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify(file));
534
+ });
377
535
  }
378
536
  async loadTrackedAddresses() {
379
- const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
380
- if (!data) return [];
381
- try {
382
- const parsed = JSON.parse(data);
383
- return parsed.addresses ?? [];
384
- } catch {
385
- return [];
386
- }
537
+ return parseTrackedAddresses(await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES));
387
538
  }
388
539
  // ===========================================================================
389
540
  // Helpers
@@ -470,6 +621,8 @@ var IndexedDBStorageProvider = class {
470
621
  name = "IndexedDB Storage";
471
622
  type = "local";
472
623
  description = "Browser IndexedDB for large-capacity persistence";
624
+ /** The database + prefix pair — two providers over one pair share erasure (#766). */
625
+ backingStoreId;
473
626
  prefix;
474
627
  dbName;
475
628
  network;
@@ -484,6 +637,7 @@ var IndexedDBStorageProvider = class {
484
637
  this.dbName = config?.dbName ?? DB_NAME;
485
638
  this.network = config?.network;
486
639
  this.debug = config?.debug ?? false;
640
+ this.backingStoreId = `indexeddb:${encodeURIComponent(this.dbName)}:${encodeURIComponent(this.prefix)}`;
487
641
  }
488
642
  // ===========================================================================
489
643
  // BaseProvider Implementation
@@ -613,18 +767,27 @@ var IndexedDBStorageProvider = class {
613
767
  await this.remove(key);
614
768
  }
615
769
  }
770
+ /**
771
+ * Persist the tracked-address registry by MERGING, never replacing.
772
+ *
773
+ * Every Sphere over this storage holds its own snapshot and writes it in
774
+ * full, so a wholesale write drops the addresses this writer never saw
775
+ * (#766 item 5 — a lost update, reproducible on one network). The read, the
776
+ * merge and the write share ONE transaction: a lock on this object would
777
+ * order only this object's calls, and `backingStoreId` exists precisely
778
+ * because two provider objects — or two TABS — can address one database.
779
+ * IndexedDB serializes overlapping readwrite transactions across every
780
+ * connection, so both are covered.
781
+ */
616
782
  async saveTrackedAddresses(entries) {
617
- await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
783
+ this.ensureConnected();
784
+ await this.idbMergeTrackedAddresses(
785
+ this.getFullKey(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES),
786
+ entries
787
+ );
618
788
  }
619
789
  async loadTrackedAddresses() {
620
- const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
621
- if (!data) return [];
622
- try {
623
- const parsed = JSON.parse(data);
624
- return parsed.addresses ?? [];
625
- } catch {
626
- return [];
627
- }
790
+ return parseTrackedAddresses(await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES));
628
791
  }
629
792
  // ===========================================================================
630
793
  // Helpers
@@ -741,6 +904,25 @@ var IndexedDBStorageProvider = class {
741
904
  request.onsuccess = () => resolve(request.result ?? []);
742
905
  });
743
906
  }
907
+ idbMergeTrackedAddresses(key, entries) {
908
+ return new Promise((resolve, reject) => {
909
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
910
+ const store = tx.objectStore(STORE_NAME);
911
+ const read = store.get(key);
912
+ read.onerror = () => reject(read.error);
913
+ read.onsuccess = () => {
914
+ const onDisk = parseTrackedAddresses(read.result?.v ?? null);
915
+ const file = {
916
+ version: 1,
917
+ addresses: mergeTrackedAddresses(onDisk, entries)
918
+ };
919
+ store.put({ k: key, v: JSON.stringify(file) });
920
+ };
921
+ tx.oncomplete = () => resolve();
922
+ tx.onerror = () => reject(tx.error);
923
+ tx.onabort = () => reject(tx.error ?? new Error("tracked-address write aborted"));
924
+ });
925
+ }
744
926
  idbClear() {
745
927
  return new Promise((resolve, reject) => {
746
928
  const tx = this.db.transaction(STORE_NAME, "readwrite");
@@ -1331,14 +1513,10 @@ var NostrTransportProvider = class _NostrTransportProvider {
1331
1513
  });
1332
1514
  }
1333
1515
  });
1334
- await Promise.race([
1335
- this.nostrClient.connect(...this.config.relays),
1336
- new Promise(
1337
- (_, reject) => setTimeout(() => reject(new Error(
1338
- `Transport connection timed out after ${this.config.timeout}ms`
1339
- )), this.config.timeout)
1340
- )
1341
- ]);
1516
+ await this.connectWithDeadline(
1517
+ this.nostrClient,
1518
+ `Transport connection timed out after ${this.config.timeout}ms`
1519
+ );
1342
1520
  if (!this.nostrClient.isConnected()) {
1343
1521
  throw new SphereError("Failed to connect to any relay", "TRANSPORT_ERROR");
1344
1522
  }
@@ -1353,6 +1531,27 @@ var NostrTransportProvider = class _NostrTransportProvider {
1353
1531
  throw error;
1354
1532
  }
1355
1533
  }
1534
+ /**
1535
+ * Race one client's relay connect against the configured timeout.
1536
+ *
1537
+ * The timer is cleared in a `finally` on EVERY path. `Promise.race` does not
1538
+ * cancel the loser: an un-cleared `setTimeout` keeps Node's event loop pinned
1539
+ * for the whole `config.timeout` after the call has already returned (and
1540
+ * then rejects a promise nobody is listening to any more).
1541
+ */
1542
+ async connectWithDeadline(client, timeoutMessage) {
1543
+ let timer;
1544
+ try {
1545
+ await Promise.race([
1546
+ client.connect(...this.config.relays),
1547
+ new Promise((_, reject) => {
1548
+ timer = setTimeout(() => reject(new Error(timeoutMessage)), this.config.timeout);
1549
+ })
1550
+ ]);
1551
+ } finally {
1552
+ if (timer !== void 0) clearTimeout(timer);
1553
+ }
1554
+ }
1356
1555
  async disconnect() {
1357
1556
  if (this.nostrClient) {
1358
1557
  this.nostrClient.disconnect();
@@ -1471,19 +1670,22 @@ var NostrTransportProvider = class _NostrTransportProvider {
1471
1670
  // TransportProvider Implementation
1472
1671
  // ===========================================================================
1473
1672
  async setIdentity(identity) {
1474
- this.identity = identity;
1475
- this.processedEventIds.clear();
1476
- this.lastEventTs = 0;
1477
- this.lastDmEventTs = 0;
1478
- this.fallbackDmSince = null;
1479
1673
  const secretKey = Buffer.from(identity.privateKey, "hex");
1480
- this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
1481
- const nostrPubkey = this.keyManager.getPublicKeyHex();
1482
- logger.debug("Nostr", "Identity set, Nostr pubkey:", nostrPubkey.slice(0, 16) + "...");
1674
+ const nextKeyManager = NostrKeyManager.fromPrivateKey(secretKey);
1675
+ const nostrPubkey = nextKeyManager.getPublicKeyHex();
1676
+ logger.debug("Nostr", "Identity staged, Nostr pubkey:", nostrPubkey.slice(0, 16) + "...");
1677
+ const applyStagedIdentity = () => {
1678
+ this.identity = identity;
1679
+ this.keyManager = nextKeyManager;
1680
+ this.processedEventIds.clear();
1681
+ this.lastEventTs = 0;
1682
+ this.lastDmEventTs = 0;
1683
+ this.fallbackDmSince = null;
1684
+ };
1483
1685
  if (this.nostrClient && this.status === "connected") {
1484
1686
  logger.debug("Nostr", "Identity changed while connected - recreating NostrClient");
1485
1687
  const oldClient = this.nostrClient;
1486
- this.nostrClient = new NostrClient(this.keyManager, {
1688
+ const nextClient = new NostrClient(nextKeyManager, {
1487
1689
  autoReconnect: this.config.autoReconnect,
1488
1690
  reconnectIntervalMs: this.config.reconnectDelay,
1489
1691
  maxReconnectIntervalMs: this.config.reconnectDelay * 16,
@@ -1492,7 +1694,7 @@ var NostrTransportProvider = class _NostrTransportProvider {
1492
1694
  queryTimeoutMs: 2e4
1493
1695
  // see same option above for rationale
1494
1696
  });
1495
- this.nostrClient.addConnectionListener({
1697
+ nextClient.addConnectionListener({
1496
1698
  onConnect: (url) => {
1497
1699
  logger.debug("Nostr", "NostrClient connected to relay:", url);
1498
1700
  },
@@ -1506,18 +1708,43 @@ var NostrTransportProvider = class _NostrTransportProvider {
1506
1708
  logger.debug("Nostr", "NostrClient reconnected to relay:", url);
1507
1709
  }
1508
1710
  });
1509
- await Promise.race([
1510
- this.nostrClient.connect(...this.config.relays),
1511
- new Promise(
1512
- (_, reject) => setTimeout(() => reject(new Error(
1513
- `Transport reconnection timed out after ${this.config.timeout}ms`
1514
- )), this.config.timeout)
1515
- )
1516
- ]);
1517
- await this.subscribeToEvents();
1518
- oldClient.disconnect();
1711
+ try {
1712
+ await this.connectWithDeadline(
1713
+ nextClient,
1714
+ `Transport reconnection timed out after ${this.config.timeout}ms`
1715
+ );
1716
+ } catch (error) {
1717
+ try {
1718
+ nextClient.disconnect();
1719
+ } catch {
1720
+ }
1721
+ throw error;
1722
+ }
1723
+ if (this.nostrClient !== oldClient || this.status !== "connected") {
1724
+ try {
1725
+ nextClient.disconnect();
1726
+ } catch {
1727
+ }
1728
+ throw new SphereError(
1729
+ "Transport client changed or left the connected state during setIdentity - identity not applied",
1730
+ "TRANSPORT_ERROR"
1731
+ );
1732
+ }
1733
+ this.nostrClient = nextClient;
1734
+ applyStagedIdentity();
1735
+ try {
1736
+ await this.subscribeToEvents();
1737
+ } finally {
1738
+ try {
1739
+ oldClient.disconnect();
1740
+ } catch {
1741
+ }
1742
+ }
1519
1743
  } else if (this.isConnected()) {
1744
+ applyStagedIdentity();
1520
1745
  await this.subscribeToEvents();
1746
+ } else {
1747
+ applyStagedIdentity();
1521
1748
  }
1522
1749
  }
1523
1750
  setFallbackSince(sinceSeconds) {
@@ -2812,26 +3039,6 @@ var UnicityAggregatorProvider = class {
2812
3039
  var UnicityOracleProvider = UnicityAggregatorProvider;
2813
3040
 
2814
3041
  // assets/trustbase.ts
2815
- var TRUSTBASE_TESTNET = {
2816
- version: 1,
2817
- networkId: 3,
2818
- epoch: 1,
2819
- epochStartRound: 1,
2820
- rootNodes: [
2821
- {
2822
- nodeId: "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU",
2823
- sigKey: "0x039afb2acb65f5fbc272d8907f763d0a5d189aadc9b97afdcc5897ea4dd112e68b",
2824
- stake: 1
2825
- }
2826
- ],
2827
- quorumThreshold: 1,
2828
- stateHash: "",
2829
- changeRecordHash: "",
2830
- previousEntryHash: "",
2831
- signatures: {
2832
- "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU": "0xf157c9fdd8a378e3ca70d354ccc4475ab2cd8de360127bc46b0aeab4b453a80f07fd9136a5843b60a8babaff23e20acc8879861f7651440a5e2829f7541b31f100"
2833
- }
2834
- };
2835
3042
  var TRUSTBASE_TESTNET2 = {
2836
3043
  version: 1,
2837
3044
  networkId: 4,
@@ -2870,8 +3077,44 @@ var TRUSTBASE_TESTNET2 = {
2870
3077
  "16Uiu2HAmU5T1TqKyKkUB3sitWb431hVFfp9nqvaLgVagQF6ywLFF": "0x9f28f3e3c50d4bfdaf54d7290fbbe311fffb3be2281a1d211be56818fa43d06a2e9a81925e9d365e552c8b772c99285a686de1d9fb581920be7524c7a5d467a301"
2871
3078
  }
2872
3079
  };
2873
- var TRUSTBASE_MAINNET = null;
2874
- var TRUSTBASE_DEV = TRUSTBASE_TESTNET;
3080
+ var TRUSTBASE_MAINNET = {
3081
+ version: 1,
3082
+ networkId: 1,
3083
+ epoch: 1,
3084
+ epochStartRound: 1,
3085
+ rootNodes: [
3086
+ {
3087
+ nodeId: "16Uiu2HAkvk4EpXoyh7SVLTX3TcsZkjbni5jY1sBgJ7qCEWfx1Gma",
3088
+ sigKey: "0x03059e30b5c980ce92e2c6d4e14775838be91a6f0e993f3a4899ab459c4b5225f2",
3089
+ stake: 1
3090
+ },
3091
+ {
3092
+ nodeId: "16Uiu2HAmF9Mq9pH9yUTdao3TpJeFZY9oX9gyMqfxggVA4fNmzmXr",
3093
+ sigKey: "0x03a514db8b17b1c79b4a6affeb5d88a129c4d5ff0f92c8f86c311971b0beb94335",
3094
+ stake: 1
3095
+ },
3096
+ {
3097
+ nodeId: "16Uiu2HAmLAHTSMvZ2apRRtBN9piYVhaK2i7sTMaQeSWSxsLh75TG",
3098
+ sigKey: "0x02e5669b2daa5d1afccfe66603f7d3fe171ecbd7ed891487ab3683cdebc3c3cd8b",
3099
+ stake: 1
3100
+ },
3101
+ {
3102
+ nodeId: "16Uiu2HAmNNaWfK4RTTAq7Ap8w2yZUryrEejqmmGf44ogMaYEnkJ4",
3103
+ sigKey: "0x03ffee964da8ca276d4fc1cf391929e6759db106ef1d0aea998154b51e5a7fb1b7",
3104
+ stake: 1
3105
+ }
3106
+ ],
3107
+ quorumThreshold: 3,
3108
+ stateHash: "",
3109
+ changeRecordHash: "",
3110
+ previousEntryHash: "",
3111
+ signatures: {
3112
+ "16Uiu2HAkvk4EpXoyh7SVLTX3TcsZkjbni5jY1sBgJ7qCEWfx1Gma": "0xc2f1d67dbb47fe4dcf502301d8cd5021b8903c0d3385781653d09c703d566a3a3a4e59c75792b36f929e32564ddc14c40ef728c0bde5859774d08d802de2318c00",
3113
+ "16Uiu2HAmF9Mq9pH9yUTdao3TpJeFZY9oX9gyMqfxggVA4fNmzmXr": "0x3e3ad3a7540daa0eded457e31d2ebcacfb354036798f49f63433d26c44857e76157e4abd36cbb1d39de8cdf6e1d33f88b250077d4347c3e5bf0f35a64f2d815300",
3114
+ "16Uiu2HAmLAHTSMvZ2apRRtBN9piYVhaK2i7sTMaQeSWSxsLh75TG": "0x6101bd9df8ab87439d90b52cd74727bc8bf31dd74173c8f731ec3ea501d3d86556ba7c0e84643337959f3be0e448637c301e8e0b28196e780d4f2ff930d9062100",
3115
+ "16Uiu2HAmNNaWfK4RTTAq7Ap8w2yZUryrEejqmmGf44ogMaYEnkJ4": "0xda13c777b832425e5cbd8d6e49eb08ddbde35c1e4a9bee8bd495c3883f039e434b64560865c3bc52b68d65780932b1f2bf8971f245d2244face822af5984945c00"
3116
+ }
3117
+ };
2875
3118
 
2876
3119
  // impl/shared/trustbase-loader.ts
2877
3120
  function getEmbeddedTrustBase(network) {
@@ -2884,8 +3127,6 @@ function getEmbeddedTrustBase(network) {
2884
3127
  case "testnet":
2885
3128
  case "testnet2":
2886
3129
  return TRUSTBASE_TESTNET2;
2887
- case "dev":
2888
- return TRUSTBASE_DEV;
2889
3130
  default:
2890
3131
  throw new SphereError(`getEmbeddedTrustBase: unknown network "${network}"`, "INVALID_CONFIG");
2891
3132
  }
@@ -3042,6 +3283,7 @@ async function readFileAsUint8Array(file) {
3042
3283
 
3043
3284
  // impl/shared/network.ts
3044
3285
  var EXPECTED_NETWORK_ID = {
3286
+ mainnet: 1,
3045
3287
  // v1 cutover: 'testnet' is an alias of testnet2 (the v2 gateway network),
3046
3288
  // so both expect the testnet2 trust base (networkId 4).
3047
3289
  testnet: 4,
@@ -3062,7 +3304,7 @@ function assertNetworkConsistency(network) {
3062
3304
  );
3063
3305
  }
3064
3306
  const expected = EXPECTED_NETWORK_ID[network];
3065
- if (expected !== void 0 && networkId !== expected) {
3307
+ if (networkId !== expected) {
3066
3308
  throw new SphereError(
3067
3309
  `Network "${network}" trust base networkId ${networkId} does not match expected ${expected} (wrong trust base wired to this network).`,
3068
3310
  "INVALID_CONFIG"
@@ -3327,419 +3569,6 @@ function createPriceProvider(config) {
3327
3569
  }
3328
3570
  }
3329
3571
 
3330
- // registry/TokenRegistry.ts
3331
- var FETCH_TIMEOUT_MS = 1e4;
3332
- var TokenRegistry = class _TokenRegistry {
3333
- static instance = null;
3334
- definitionsById;
3335
- definitionsBySymbol;
3336
- definitionsByName;
3337
- // Remote refresh state
3338
- remoteUrl = null;
3339
- storage = null;
3340
- refreshIntervalMs = TOKEN_REGISTRY_REFRESH_INTERVAL;
3341
- refreshTimer = null;
3342
- lastRefreshAt = 0;
3343
- refreshPromise = null;
3344
- initialLoadPromise = null;
3345
- constructor() {
3346
- this.definitionsById = /* @__PURE__ */ new Map();
3347
- this.definitionsBySymbol = /* @__PURE__ */ new Map();
3348
- this.definitionsByName = /* @__PURE__ */ new Map();
3349
- }
3350
- /**
3351
- * Get singleton instance of TokenRegistry
3352
- */
3353
- static getInstance() {
3354
- if (!_TokenRegistry.instance) {
3355
- _TokenRegistry.instance = new _TokenRegistry();
3356
- }
3357
- return _TokenRegistry.instance;
3358
- }
3359
- /**
3360
- * Configure remote registry refresh with persistent caching.
3361
- *
3362
- * On first call:
3363
- * 1. Loads cached data from StorageProvider (if available and fresh)
3364
- * 2. Starts periodic remote fetch (if autoRefresh is true, which is default)
3365
- *
3366
- * @param options - Configuration options
3367
- * @param options.remoteUrl - Remote URL to fetch definitions from
3368
- * @param options.storage - StorageProvider for persistent caching
3369
- * @param options.refreshIntervalMs - Refresh interval in ms (default: 1 hour)
3370
- * @param options.autoRefresh - Start auto-refresh immediately (default: true)
3371
- */
3372
- static configure(options) {
3373
- const instance = _TokenRegistry.getInstance();
3374
- if (options.remoteUrl !== void 0) {
3375
- instance.remoteUrl = options.remoteUrl;
3376
- }
3377
- if (options.storage !== void 0) {
3378
- instance.storage = options.storage;
3379
- }
3380
- if (options.refreshIntervalMs !== void 0) {
3381
- instance.refreshIntervalMs = options.refreshIntervalMs;
3382
- }
3383
- const autoRefresh = options.autoRefresh ?? true;
3384
- instance.initialLoadPromise = instance.performInitialLoad(autoRefresh);
3385
- }
3386
- /**
3387
- * Reset the singleton instance (useful for testing).
3388
- * Stops auto-refresh if running.
3389
- */
3390
- static resetInstance() {
3391
- if (_TokenRegistry.instance) {
3392
- _TokenRegistry.instance.stopAutoRefresh();
3393
- }
3394
- _TokenRegistry.instance = null;
3395
- }
3396
- /**
3397
- * Destroy the singleton: stop auto-refresh and reset.
3398
- */
3399
- static destroy() {
3400
- _TokenRegistry.resetInstance();
3401
- }
3402
- /**
3403
- * Wait for the initial data load (cache or remote) to complete.
3404
- * Returns true if data was loaded, false if not (timeout or no data source).
3405
- *
3406
- * @param timeoutMs - Maximum wait time in ms (default: 10s). Set to 0 for no timeout.
3407
- */
3408
- static async waitForReady(timeoutMs = 1e4) {
3409
- const instance = _TokenRegistry.getInstance();
3410
- if (!instance.initialLoadPromise) {
3411
- return instance.definitionsById.size > 0;
3412
- }
3413
- if (timeoutMs <= 0) {
3414
- return instance.initialLoadPromise;
3415
- }
3416
- return Promise.race([
3417
- instance.initialLoadPromise,
3418
- new Promise((resolve) => setTimeout(() => resolve(false), timeoutMs))
3419
- ]);
3420
- }
3421
- // ===========================================================================
3422
- // Initial Load
3423
- // ===========================================================================
3424
- /**
3425
- * Perform initial data load: try cache first, fall back to remote fetch.
3426
- * After initial data is available, start periodic auto-refresh if configured.
3427
- */
3428
- async performInitialLoad(autoRefresh) {
3429
- let loaded = false;
3430
- if (this.storage) {
3431
- loaded = await this.loadFromCache();
3432
- }
3433
- if (loaded) {
3434
- if (autoRefresh && this.remoteUrl) {
3435
- this.startAutoRefresh();
3436
- }
3437
- return true;
3438
- }
3439
- if (autoRefresh && this.remoteUrl) {
3440
- loaded = await this.refreshFromRemote();
3441
- this.stopAutoRefresh();
3442
- this.refreshTimer = setInterval(() => {
3443
- this.refreshFromRemote();
3444
- }, this.refreshIntervalMs);
3445
- return loaded;
3446
- }
3447
- return false;
3448
- }
3449
- // ===========================================================================
3450
- // Cache (StorageProvider)
3451
- // ===========================================================================
3452
- /**
3453
- * Per-network cache keys. The registry cache MUST be namespaced by remoteUrl:
3454
- * testnet and testnet2 ship DIFFERENT coinIds for the same symbol, so a single
3455
- * global key let a stale testnet snapshot be served for a testnet2 request —
3456
- * wrong/missing icons AND self-mint resolving the wrong-network coinId. Keying
3457
- * by remoteUrl isolates the snapshots so a network can never read another's.
3458
- */
3459
- cacheKeys() {
3460
- const base = STORAGE_KEYS_GLOBAL.TOKEN_REGISTRY_CACHE;
3461
- const baseTs = STORAGE_KEYS_GLOBAL.TOKEN_REGISTRY_CACHE_TS;
3462
- if (!this.remoteUrl) return { data: base, ts: baseTs };
3463
- return { data: `${base}:${this.remoteUrl}`, ts: `${baseTs}:${this.remoteUrl}` };
3464
- }
3465
- /**
3466
- * Load definitions from StorageProvider cache.
3467
- * Only applies if cache exists and is fresh (within refreshIntervalMs).
3468
- */
3469
- async loadFromCache() {
3470
- if (!this.storage) return false;
3471
- try {
3472
- const { data: dataKey, ts: tsKey } = this.cacheKeys();
3473
- const [cached, cachedTs] = await Promise.all([
3474
- this.storage.get(dataKey),
3475
- this.storage.get(tsKey)
3476
- ]);
3477
- if (!cached || !cachedTs) return false;
3478
- const ts = parseInt(cachedTs, 10);
3479
- if (isNaN(ts)) return false;
3480
- const age = Date.now() - ts;
3481
- if (age > this.refreshIntervalMs) return false;
3482
- if (this.lastRefreshAt > ts) return false;
3483
- const data = JSON.parse(cached);
3484
- if (!this.isValidDefinitionsArray(data)) return false;
3485
- this.applyDefinitions(data);
3486
- this.lastRefreshAt = ts;
3487
- return true;
3488
- } catch {
3489
- return false;
3490
- }
3491
- }
3492
- /**
3493
- * Save definitions to StorageProvider cache.
3494
- */
3495
- async saveToCache(definitions) {
3496
- if (!this.storage) return;
3497
- try {
3498
- const { data: dataKey, ts: tsKey } = this.cacheKeys();
3499
- await Promise.all([
3500
- this.storage.set(dataKey, JSON.stringify(definitions)),
3501
- this.storage.set(tsKey, String(Date.now()))
3502
- ]);
3503
- } catch {
3504
- }
3505
- }
3506
- // ===========================================================================
3507
- // Remote Refresh
3508
- // ===========================================================================
3509
- /**
3510
- * Apply an array of token definitions to the internal maps.
3511
- * Clears existing data before applying.
3512
- */
3513
- applyDefinitions(definitions) {
3514
- this.definitionsById.clear();
3515
- this.definitionsBySymbol.clear();
3516
- this.definitionsByName.clear();
3517
- for (const def of definitions) {
3518
- const idLower = def.id.toLowerCase();
3519
- this.definitionsById.set(idLower, def);
3520
- if (def.symbol) {
3521
- this.definitionsBySymbol.set(def.symbol.toUpperCase(), def);
3522
- }
3523
- this.definitionsByName.set(def.name.toLowerCase(), def);
3524
- }
3525
- }
3526
- /**
3527
- * Validate that data is an array of objects with 'id' field
3528
- */
3529
- isValidDefinitionsArray(data) {
3530
- return Array.isArray(data) && data.every((item) => item && typeof item === "object" && "id" in item);
3531
- }
3532
- /**
3533
- * Fetch token definitions from the remote URL and update the registry.
3534
- * On success, also persists to StorageProvider cache.
3535
- * Returns true on success, false on failure. On failure, existing data is preserved.
3536
- * Concurrent calls are deduplicated — only one fetch runs at a time.
3537
- */
3538
- async refreshFromRemote() {
3539
- if (!this.remoteUrl) {
3540
- return false;
3541
- }
3542
- if (this.refreshPromise) {
3543
- return this.refreshPromise;
3544
- }
3545
- this.refreshPromise = this.doRefresh();
3546
- try {
3547
- return await this.refreshPromise;
3548
- } finally {
3549
- this.refreshPromise = null;
3550
- }
3551
- }
3552
- async doRefresh() {
3553
- try {
3554
- const controller = new AbortController();
3555
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
3556
- let response;
3557
- try {
3558
- response = await fetch(this.remoteUrl, {
3559
- headers: { Accept: "application/json" },
3560
- signal: controller.signal
3561
- });
3562
- } finally {
3563
- clearTimeout(timer);
3564
- }
3565
- if (!response.ok) {
3566
- logger.warn("TokenRegistry", `Remote fetch failed: HTTP ${response.status} ${response.statusText}`);
3567
- return false;
3568
- }
3569
- const data = await response.json();
3570
- if (!this.isValidDefinitionsArray(data)) {
3571
- logger.warn("TokenRegistry", "Remote data is not a valid token definitions array");
3572
- return false;
3573
- }
3574
- const definitions = data;
3575
- this.applyDefinitions(definitions);
3576
- this.lastRefreshAt = Date.now();
3577
- this.saveToCache(definitions);
3578
- return true;
3579
- } catch (error) {
3580
- const message = error instanceof Error ? error.message : String(error);
3581
- logger.warn("TokenRegistry", `Remote refresh failed: ${message}`);
3582
- return false;
3583
- }
3584
- }
3585
- /**
3586
- * Start periodic auto-refresh from the remote URL.
3587
- * Does an immediate fetch, then repeats at the configured interval.
3588
- */
3589
- startAutoRefresh(intervalMs) {
3590
- this.stopAutoRefresh();
3591
- if (intervalMs !== void 0) {
3592
- this.refreshIntervalMs = intervalMs;
3593
- }
3594
- this.refreshFromRemote();
3595
- this.refreshTimer = setInterval(() => {
3596
- this.refreshFromRemote();
3597
- }, this.refreshIntervalMs);
3598
- }
3599
- /**
3600
- * Stop periodic auto-refresh
3601
- */
3602
- stopAutoRefresh() {
3603
- if (this.refreshTimer !== null) {
3604
- clearInterval(this.refreshTimer);
3605
- this.refreshTimer = null;
3606
- }
3607
- }
3608
- /**
3609
- * Timestamp of the last successful remote refresh (0 if never refreshed)
3610
- */
3611
- getLastRefreshAt() {
3612
- return this.lastRefreshAt;
3613
- }
3614
- // ===========================================================================
3615
- // Lookup Methods
3616
- // ===========================================================================
3617
- /**
3618
- * Get token definition by hex coin ID
3619
- * @param coinId - 64-character hex string
3620
- * @returns Token definition or undefined if not found
3621
- */
3622
- getDefinition(coinId) {
3623
- if (!coinId) return void 0;
3624
- return this.definitionsById.get(coinId.toLowerCase());
3625
- }
3626
- /**
3627
- * Get token definition by symbol (e.g., "UCT", "BTC")
3628
- * @param symbol - Token symbol (case-insensitive)
3629
- * @returns Token definition or undefined if not found
3630
- */
3631
- getDefinitionBySymbol(symbol) {
3632
- if (!symbol) return void 0;
3633
- return this.definitionsBySymbol.get(symbol.toUpperCase());
3634
- }
3635
- /**
3636
- * Get token definition by name (e.g., "bitcoin", "ethereum")
3637
- * @param name - Token name (case-insensitive)
3638
- * @returns Token definition or undefined if not found
3639
- */
3640
- getDefinitionByName(name) {
3641
- if (!name) return void 0;
3642
- return this.definitionsByName.get(name.toLowerCase());
3643
- }
3644
- /**
3645
- * Get token symbol for a coin ID
3646
- * @param coinId - 64-character hex string
3647
- * @returns Symbol (e.g., "UCT") or truncated ID if not found
3648
- */
3649
- getSymbol(coinId) {
3650
- const def = this.getDefinition(coinId);
3651
- if (def?.symbol) {
3652
- return def.symbol;
3653
- }
3654
- return coinId.slice(0, 6).toUpperCase();
3655
- }
3656
- /**
3657
- * Get token name for a coin ID
3658
- * @param coinId - 64-character hex string
3659
- * @returns Name (e.g., "Bitcoin") or coin ID if not found
3660
- */
3661
- getName(coinId) {
3662
- const def = this.getDefinition(coinId);
3663
- if (def?.name) {
3664
- return def.name.charAt(0).toUpperCase() + def.name.slice(1);
3665
- }
3666
- return coinId;
3667
- }
3668
- /**
3669
- * Get decimal places for a coin ID
3670
- * @param coinId - 64-character hex string
3671
- * @returns Decimals or 0 if not found
3672
- */
3673
- getDecimals(coinId) {
3674
- const def = this.getDefinition(coinId);
3675
- return def?.decimals ?? 0;
3676
- }
3677
- /**
3678
- * Get icon URL for a coin ID
3679
- * @param coinId - 64-character hex string
3680
- * @param preferPng - Prefer PNG format over SVG
3681
- * @returns Icon URL or null if not found
3682
- */
3683
- getIconUrl(coinId, preferPng = true) {
3684
- const def = this.getDefinition(coinId);
3685
- if (!def?.icons || def.icons.length === 0) {
3686
- return null;
3687
- }
3688
- if (preferPng) {
3689
- const pngIcon = def.icons.find((i) => i.url.toLowerCase().includes(".png"));
3690
- if (pngIcon) return pngIcon.url;
3691
- }
3692
- return def.icons[0].url;
3693
- }
3694
- /**
3695
- * Check if a coin ID is known in the registry
3696
- * @param coinId - 64-character hex string
3697
- * @returns true if the coin is in the registry
3698
- */
3699
- isKnown(coinId) {
3700
- return this.definitionsById.has(coinId.toLowerCase());
3701
- }
3702
- /**
3703
- * Get all token definitions
3704
- * @returns Array of all token definitions
3705
- */
3706
- getAllDefinitions() {
3707
- return Array.from(this.definitionsById.values());
3708
- }
3709
- /**
3710
- * Get all fungible token definitions
3711
- * @returns Array of fungible token definitions
3712
- */
3713
- getFungibleTokens() {
3714
- return this.getAllDefinitions().filter((def) => def.assetKind === "fungible");
3715
- }
3716
- /**
3717
- * Get all non-fungible token definitions
3718
- * @returns Array of non-fungible token definitions
3719
- */
3720
- getNonFungibleTokens() {
3721
- return this.getAllDefinitions().filter((def) => def.assetKind === "non-fungible");
3722
- }
3723
- /**
3724
- * Get coin ID by symbol
3725
- * @param symbol - Token symbol (e.g., "UCT")
3726
- * @returns Coin ID hex string or undefined if not found
3727
- */
3728
- getCoinIdBySymbol(symbol) {
3729
- const def = this.getDefinitionBySymbol(symbol);
3730
- return def?.id;
3731
- }
3732
- /**
3733
- * Get coin ID by name
3734
- * @param name - Token name (e.g., "bitcoin")
3735
- * @returns Coin ID hex string or undefined if not found
3736
- */
3737
- getCoinIdByName(name) {
3738
- const def = this.getDefinitionByName(name);
3739
- return def?.id;
3740
- }
3741
- };
3742
-
3743
3572
  // impl/shared/resolvers.ts
3744
3573
  function getNetworkConfig(network = "mainnet") {
3745
3574
  return NETWORKS[network];
@@ -3833,8 +3662,6 @@ function createBrowserProviders(config) {
3833
3662
  const priceConfig = resolvePriceConfig(config?.price, storage);
3834
3663
  const groupChat = resolveGroupChatConfig(network, config?.groupChat);
3835
3664
  const market = resolveMarketConfig(config?.market);
3836
- const networkConfig = getNetworkConfig(network);
3837
- TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
3838
3665
  return {
3839
3666
  storage,
3840
3667
  groupChat,