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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +15 -5
  2. package/dist/connect/index.cjs +26 -25
  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 +26 -25
  7. package/dist/connect/index.js.map +1 -1
  8. package/dist/core/index.cjs +728 -227
  9. package/dist/core/index.cjs.map +1 -1
  10. package/dist/core/index.d.cts +174 -23
  11. package/dist/core/index.d.ts +174 -23
  12. package/dist/core/index.js +728 -226
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/impl/browser/connect/index.cjs +16 -25
  15. package/dist/impl/browser/connect/index.cjs.map +1 -1
  16. package/dist/impl/browser/connect/index.js +16 -25
  17. package/dist/impl/browser/connect/index.js.map +1 -1
  18. package/dist/impl/browser/index.cjs +333 -506
  19. package/dist/impl/browser/index.cjs.map +1 -1
  20. package/dist/impl/browser/index.js +333 -506
  21. package/dist/impl/browser/index.js.map +1 -1
  22. package/dist/impl/nodejs/connect/index.cjs +15 -24
  23. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  24. package/dist/impl/nodejs/connect/index.js +15 -24
  25. package/dist/impl/nodejs/connect/index.js.map +1 -1
  26. package/dist/impl/nodejs/index.cjs +300 -571
  27. package/dist/impl/nodejs/index.cjs.map +1 -1
  28. package/dist/impl/nodejs/index.d.cts +92 -15
  29. package/dist/impl/nodejs/index.d.ts +92 -15
  30. package/dist/impl/nodejs/index.js +300 -571
  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 +730 -227
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +229 -110
  37. package/dist/index.d.ts +229 -110
  38. package/dist/index.js +730 -226
  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
@@ -143,6 +143,58 @@ var logger = {
143
143
  }
144
144
  };
145
145
 
146
+ // core/global-cell.ts
147
+ var CELLS_KEY = "__sphere_sdk_cells_v1__";
148
+ var bundleLocalBag = null;
149
+ function isObject(value) {
150
+ return typeof value === "object" && value !== null;
151
+ }
152
+ function installBag(host, fresh) {
153
+ try {
154
+ Object.defineProperty(host, CELLS_KEY, {
155
+ value: fresh,
156
+ writable: true,
157
+ configurable: true,
158
+ enumerable: false
159
+ });
160
+ return host[CELLS_KEY] === fresh;
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+ function cellBag() {
166
+ if (bundleLocalBag) return bundleLocalBag;
167
+ const host = globalThis;
168
+ const found = host[CELLS_KEY];
169
+ if (isObject(found)) return found;
170
+ const fresh = {};
171
+ if (!installBag(host, fresh)) bundleLocalBag = fresh;
172
+ return fresh;
173
+ }
174
+ function accepted(intact, candidate) {
175
+ try {
176
+ return intact(candidate);
177
+ } catch {
178
+ return false;
179
+ }
180
+ }
181
+ function put(bag, name, cell) {
182
+ try {
183
+ bag[name] = cell;
184
+ if (bag[name] === cell) return;
185
+ } catch {
186
+ }
187
+ if (bag !== bundleLocalBag) bundleLocalBag = { [name]: cell };
188
+ }
189
+ function sharedCell(name, create, intact) {
190
+ const bag = cellBag();
191
+ const found = bag[name];
192
+ if (isObject(found) && accepted(intact, found)) return found;
193
+ const fresh = create();
194
+ put(bag, name, fresh);
195
+ return fresh;
196
+ }
197
+
146
198
  // core/errors.ts
147
199
  var SphereError = class extends Error {
148
200
  code;
@@ -245,7 +297,6 @@ function isNetworkScopedAddressKey(key) {
245
297
  return false;
246
298
  }
247
299
  var DEFAULT_NOSTR_RELAYS = [
248
- "wss://relay.unicity.network",
249
300
  "wss://relay.damus.io",
250
301
  "wss://nos.lol",
251
302
  "wss://relay.nostr.band"
@@ -258,13 +309,9 @@ var NOSTR_EVENT_KINDS = {
258
309
  /** Public broadcast */
259
310
  BROADCAST: 1
260
311
  };
261
- var DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
262
- var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
263
312
  var DEFAULT_AGGREGATOR_TIMEOUT = 3e4;
264
313
  var DEFAULT_BASE_PATH = "m/44'/0'/0'";
265
314
  var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
266
- var TOKEN_REGISTRY_URL = "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
267
- var TOKEN_REGISTRY_REFRESH_INTERVAL = 36e5;
268
315
  var TEST_NOSTR_RELAYS = [
269
316
  "wss://nostr-relay.testnet.unicity.network"
270
317
  ];
@@ -274,16 +321,25 @@ var DEFAULT_GROUP_RELAYS = [
274
321
  var NETWORKS = {
275
322
  mainnet: {
276
323
  name: "Mainnet",
277
- aggregatorUrl: DEFAULT_AGGREGATOR_URL,
278
- nostrRelays: DEFAULT_NOSTR_RELAYS,
324
+ networkId: 1,
325
+ // v3 state-transition gateway (networkId 1 comes from the trust base). apiKey is env-injected;
326
+ // unlike testnet2's, a mainnet gateway key is a SECRET — never commit one.
327
+ aggregatorUrl: "https://gateway.mainnet.unicity.network",
328
+ // Mainnet has no relay of its own yet — it shares the testnet relay until one is stood up.
329
+ // Consequence while shared: nametag bindings for both networks live in ONE namespace, and
330
+ // since bindings carry no network the cross-network recipient guard can only signal (#734).
331
+ nostrRelays: TEST_NOSTR_RELAYS,
279
332
  groupRelays: DEFAULT_GROUP_RELAYS,
280
- tokenRegistryUrl: TOKEN_REGISTRY_URL
333
+ // Published, but currently only the non-fungible base token type — no fungible
334
+ // coins yet, so those still miss the registry (decimals 0, symbol falls back to
335
+ // six hex chars). Presentation only: the money path treats coinId as opaque bytes.
336
+ tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.mainnet.json"
281
337
  },
282
338
  // v1 cutover: 'testnet' now POINTS AT TESTNET2 (the v2 gateway network). The
283
339
  // old goggregator testnet spoke the removed v1 protocol — a v2 engine cannot
284
340
  // run against it. 'testnet2' stays as an alias of the same configuration.
285
341
  testnet: {
286
- name: "Testnet2",
342
+ name: "Testnet",
287
343
  networkId: 4,
288
344
  // v2 state-transition gateway (networkId 4 comes from the trust base). apiKey is env-injected.
289
345
  aggregatorUrl: "https://gateway.testnet2.unicity.network",
@@ -293,7 +349,7 @@ var NETWORKS = {
293
349
  tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet2.json"
294
350
  },
295
351
  testnet2: {
296
- name: "Testnet2",
352
+ name: "Testnet",
297
353
  networkId: 4,
298
354
  // v2 state-transition gateway (networkId 4 comes from the trust base). apiKey is env-injected.
299
355
  aggregatorUrl: "https://gateway.testnet2.unicity.network",
@@ -301,19 +357,10 @@ var NETWORKS = {
301
357
  // reuse testnet infra (shared relays/ipfs)
302
358
  groupRelays: DEFAULT_GROUP_RELAYS,
303
359
  tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet2.json"
304
- },
305
- // NOTE: mainnet/dev still point at v1-era aggregators. The v2 engine cannot
306
- // operate against them until their gateways are cut over to the v2 protocol —
307
- // wallet operations on these networks fail loudly (AGGREGATOR_ERROR) until then.
308
- dev: {
309
- name: "Development",
310
- aggregatorUrl: DEV_AGGREGATOR_URL,
311
- nostrRelays: TEST_NOSTR_RELAYS,
312
- groupRelays: DEFAULT_GROUP_RELAYS,
313
- tokenRegistryUrl: TOKEN_REGISTRY_URL
314
360
  }
315
361
  };
316
362
  var SPHERE_NETWORKS = {
363
+ mainnet: { id: NETWORKS.mainnet.networkId, name: "mainnet" },
317
364
  testnet2: { id: NETWORKS.testnet2.networkId, name: "testnet2" }
318
365
  };
319
366
  var TIMEOUTS = {
@@ -329,12 +376,106 @@ var TIMEOUTS = {
329
376
  SYNC_INTERVAL: 6e4
330
377
  };
331
378
 
379
+ // storage/tracked-addresses.ts
380
+ function isDerivableIndex(value) {
381
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 4294967295;
382
+ }
383
+ function num(value, fallback) {
384
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
385
+ }
386
+ function toEntry(value) {
387
+ if (typeof value !== "object" || value === null) return null;
388
+ const e = value;
389
+ if (!isDerivableIndex(e.index)) return null;
390
+ return {
391
+ ...e,
392
+ index: e.index,
393
+ hidden: e.hidden === true,
394
+ createdAt: num(e.createdAt, 0),
395
+ updatedAt: num(e.updatedAt, 0)
396
+ };
397
+ }
398
+ function parseTrackedAddresses(raw) {
399
+ if (!raw) return [];
400
+ let parsed;
401
+ try {
402
+ parsed = JSON.parse(raw);
403
+ } catch {
404
+ return [];
405
+ }
406
+ if (typeof parsed !== "object" || parsed === null) return [];
407
+ const addresses = parsed.addresses;
408
+ if (!Array.isArray(addresses)) return [];
409
+ const entries = [];
410
+ for (const row of addresses) {
411
+ const entry = toEntry(row);
412
+ if (entry) entries.push(entry);
413
+ }
414
+ return entries;
415
+ }
416
+ function mergeTrackedAddresses(onDisk, incoming) {
417
+ const merged = /* @__PURE__ */ new Map();
418
+ for (const entry of onDisk) {
419
+ if (isDerivableIndex(entry.index)) merged.set(entry.index, entry);
420
+ }
421
+ for (const entry of incoming) {
422
+ if (!isDerivableIndex(entry.index)) {
423
+ throw new SphereError(
424
+ `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.`,
425
+ "VALIDATION_ERROR"
426
+ );
427
+ }
428
+ const existing = merged.get(entry.index);
429
+ if (!existing) {
430
+ merged.set(entry.index, entry);
431
+ continue;
432
+ }
433
+ const winner = entry.updatedAt >= existing.updatedAt ? entry : existing;
434
+ merged.set(entry.index, {
435
+ ...existing,
436
+ ...winner,
437
+ index: entry.index,
438
+ createdAt: Math.min(existing.createdAt, entry.createdAt)
439
+ });
440
+ }
441
+ return Array.from(merged.values()).sort((a, b) => a.index - b.index);
442
+ }
443
+
332
444
  // impl/browser/storage/LocalStorageProvider.ts
445
+ var storeIdentity = sharedCell(
446
+ "impl.browser.localStorage.identity@1",
447
+ () => ({ tags: /* @__PURE__ */ new WeakMap(), writeChains: /* @__PURE__ */ new Map(), seq: 0 }),
448
+ (cell) => {
449
+ const c = cell;
450
+ return c.tags instanceof WeakMap && c.writeChains instanceof Map && typeof c.seq === "number";
451
+ }
452
+ );
453
+ function storageObjectTag(storage) {
454
+ let tag = storeIdentity.tags.get(storage);
455
+ if (!tag) {
456
+ tag = String(++storeIdentity.seq);
457
+ storeIdentity.tags.set(storage, tag);
458
+ }
459
+ return tag;
460
+ }
461
+ var trackedWriteChains = storeIdentity.writeChains;
462
+ function serializeTrackedWrite(storeId, task) {
463
+ const previous = trackedWriteChains.get(storeId) ?? Promise.resolve();
464
+ const run = previous.then(task);
465
+ const tail = run.then(() => void 0, () => void 0);
466
+ trackedWriteChains.set(storeId, tail);
467
+ void tail.then(() => {
468
+ if (trackedWriteChains.get(storeId) === tail) trackedWriteChains.delete(storeId);
469
+ });
470
+ return run;
471
+ }
333
472
  var LocalStorageProvider = class {
334
473
  id = "localStorage";
335
474
  name = "Local Storage";
336
475
  type = "local";
337
476
  description = "Browser localStorage for single-device persistence";
477
+ /** The `Storage` object + prefix — two providers over one pair share erasure (#766). */
478
+ backingStoreId;
338
479
  config;
339
480
  network;
340
481
  identity = null;
@@ -347,6 +488,7 @@ var LocalStorageProvider = class {
347
488
  debug: config?.debug ?? false
348
489
  };
349
490
  this.network = config?.network;
491
+ this.backingStoreId = `localstorage:${storageObjectTag(storage)}:${encodeURIComponent(this.config.prefix)}`;
350
492
  }
351
493
  // ===========================================================================
352
494
  // BaseProvider Implementation
@@ -422,18 +564,27 @@ var LocalStorageProvider = class {
422
564
  await this.remove(key);
423
565
  }
424
566
  }
567
+ /**
568
+ * Persist the tracked-address registry by MERGING, never replacing.
569
+ *
570
+ * Every Sphere over this storage holds its own snapshot and writes it in
571
+ * full, so a wholesale write drops the addresses this writer never saw
572
+ * (#766 item 5 — a lost update, reproducible on one network). localStorage
573
+ * offers no transaction, so the read-merge-write is serialized by BACKING
574
+ * STORE — see `serializeTrackedWrite`, and the realm limit stated there.
575
+ */
425
576
  async saveTrackedAddresses(entries) {
426
- await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
577
+ await serializeTrackedWrite(this.backingStoreId, async () => {
578
+ const onDisk = parseTrackedAddresses(await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES));
579
+ const file = {
580
+ version: 1,
581
+ addresses: mergeTrackedAddresses(onDisk, entries)
582
+ };
583
+ await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify(file));
584
+ });
427
585
  }
428
586
  async loadTrackedAddresses() {
429
- const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
430
- if (!data) return [];
431
- try {
432
- const parsed = JSON.parse(data);
433
- return parsed.addresses ?? [];
434
- } catch {
435
- return [];
436
- }
587
+ return parseTrackedAddresses(await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES));
437
588
  }
438
589
  // ===========================================================================
439
590
  // Helpers
@@ -520,6 +671,8 @@ var IndexedDBStorageProvider = class {
520
671
  name = "IndexedDB Storage";
521
672
  type = "local";
522
673
  description = "Browser IndexedDB for large-capacity persistence";
674
+ /** The database + prefix pair — two providers over one pair share erasure (#766). */
675
+ backingStoreId;
523
676
  prefix;
524
677
  dbName;
525
678
  network;
@@ -534,6 +687,7 @@ var IndexedDBStorageProvider = class {
534
687
  this.dbName = config?.dbName ?? DB_NAME;
535
688
  this.network = config?.network;
536
689
  this.debug = config?.debug ?? false;
690
+ this.backingStoreId = `indexeddb:${encodeURIComponent(this.dbName)}:${encodeURIComponent(this.prefix)}`;
537
691
  }
538
692
  // ===========================================================================
539
693
  // BaseProvider Implementation
@@ -663,18 +817,27 @@ var IndexedDBStorageProvider = class {
663
817
  await this.remove(key);
664
818
  }
665
819
  }
820
+ /**
821
+ * Persist the tracked-address registry by MERGING, never replacing.
822
+ *
823
+ * Every Sphere over this storage holds its own snapshot and writes it in
824
+ * full, so a wholesale write drops the addresses this writer never saw
825
+ * (#766 item 5 — a lost update, reproducible on one network). The read, the
826
+ * merge and the write share ONE transaction: a lock on this object would
827
+ * order only this object's calls, and `backingStoreId` exists precisely
828
+ * because two provider objects — or two TABS — can address one database.
829
+ * IndexedDB serializes overlapping readwrite transactions across every
830
+ * connection, so both are covered.
831
+ */
666
832
  async saveTrackedAddresses(entries) {
667
- await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
833
+ this.ensureConnected();
834
+ await this.idbMergeTrackedAddresses(
835
+ this.getFullKey(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES),
836
+ entries
837
+ );
668
838
  }
669
839
  async loadTrackedAddresses() {
670
- const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
671
- if (!data) return [];
672
- try {
673
- const parsed = JSON.parse(data);
674
- return parsed.addresses ?? [];
675
- } catch {
676
- return [];
677
- }
840
+ return parseTrackedAddresses(await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES));
678
841
  }
679
842
  // ===========================================================================
680
843
  // Helpers
@@ -791,6 +954,25 @@ var IndexedDBStorageProvider = class {
791
954
  request.onsuccess = () => resolve(request.result ?? []);
792
955
  });
793
956
  }
957
+ idbMergeTrackedAddresses(key, entries) {
958
+ return new Promise((resolve, reject) => {
959
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
960
+ const store = tx.objectStore(STORE_NAME);
961
+ const read = store.get(key);
962
+ read.onerror = () => reject(read.error);
963
+ read.onsuccess = () => {
964
+ const onDisk = parseTrackedAddresses(read.result?.v ?? null);
965
+ const file = {
966
+ version: 1,
967
+ addresses: mergeTrackedAddresses(onDisk, entries)
968
+ };
969
+ store.put({ k: key, v: JSON.stringify(file) });
970
+ };
971
+ tx.oncomplete = () => resolve();
972
+ tx.onerror = () => reject(tx.error);
973
+ tx.onabort = () => reject(tx.error ?? new Error("tracked-address write aborted"));
974
+ });
975
+ }
794
976
  idbClear() {
795
977
  return new Promise((resolve, reject) => {
796
978
  const tx = this.db.transaction(STORE_NAME, "readwrite");
@@ -1369,14 +1551,10 @@ var NostrTransportProvider = class _NostrTransportProvider {
1369
1551
  });
1370
1552
  }
1371
1553
  });
1372
- await Promise.race([
1373
- this.nostrClient.connect(...this.config.relays),
1374
- new Promise(
1375
- (_, reject) => setTimeout(() => reject(new Error(
1376
- `Transport connection timed out after ${this.config.timeout}ms`
1377
- )), this.config.timeout)
1378
- )
1379
- ]);
1554
+ await this.connectWithDeadline(
1555
+ this.nostrClient,
1556
+ `Transport connection timed out after ${this.config.timeout}ms`
1557
+ );
1380
1558
  if (!this.nostrClient.isConnected()) {
1381
1559
  throw new SphereError("Failed to connect to any relay", "TRANSPORT_ERROR");
1382
1560
  }
@@ -1391,6 +1569,27 @@ var NostrTransportProvider = class _NostrTransportProvider {
1391
1569
  throw error;
1392
1570
  }
1393
1571
  }
1572
+ /**
1573
+ * Race one client's relay connect against the configured timeout.
1574
+ *
1575
+ * The timer is cleared in a `finally` on EVERY path. `Promise.race` does not
1576
+ * cancel the loser: an un-cleared `setTimeout` keeps Node's event loop pinned
1577
+ * for the whole `config.timeout` after the call has already returned (and
1578
+ * then rejects a promise nobody is listening to any more).
1579
+ */
1580
+ async connectWithDeadline(client, timeoutMessage) {
1581
+ let timer;
1582
+ try {
1583
+ await Promise.race([
1584
+ client.connect(...this.config.relays),
1585
+ new Promise((_, reject) => {
1586
+ timer = setTimeout(() => reject(new Error(timeoutMessage)), this.config.timeout);
1587
+ })
1588
+ ]);
1589
+ } finally {
1590
+ if (timer !== void 0) clearTimeout(timer);
1591
+ }
1592
+ }
1394
1593
  async disconnect() {
1395
1594
  if (this.nostrClient) {
1396
1595
  this.nostrClient.disconnect();
@@ -1509,19 +1708,22 @@ var NostrTransportProvider = class _NostrTransportProvider {
1509
1708
  // TransportProvider Implementation
1510
1709
  // ===========================================================================
1511
1710
  async setIdentity(identity) {
1512
- this.identity = identity;
1513
- this.processedEventIds.clear();
1514
- this.lastEventTs = 0;
1515
- this.lastDmEventTs = 0;
1516
- this.fallbackDmSince = null;
1517
1711
  const secretKey = import_buffer.Buffer.from(identity.privateKey, "hex");
1518
- this.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(secretKey);
1519
- const nostrPubkey = this.keyManager.getPublicKeyHex();
1520
- logger.debug("Nostr", "Identity set, Nostr pubkey:", nostrPubkey.slice(0, 16) + "...");
1712
+ const nextKeyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(secretKey);
1713
+ const nostrPubkey = nextKeyManager.getPublicKeyHex();
1714
+ logger.debug("Nostr", "Identity staged, Nostr pubkey:", nostrPubkey.slice(0, 16) + "...");
1715
+ const applyStagedIdentity = () => {
1716
+ this.identity = identity;
1717
+ this.keyManager = nextKeyManager;
1718
+ this.processedEventIds.clear();
1719
+ this.lastEventTs = 0;
1720
+ this.lastDmEventTs = 0;
1721
+ this.fallbackDmSince = null;
1722
+ };
1521
1723
  if (this.nostrClient && this.status === "connected") {
1522
1724
  logger.debug("Nostr", "Identity changed while connected - recreating NostrClient");
1523
1725
  const oldClient = this.nostrClient;
1524
- this.nostrClient = new import_nostr_js_sdk.NostrClient(this.keyManager, {
1726
+ const nextClient = new import_nostr_js_sdk.NostrClient(nextKeyManager, {
1525
1727
  autoReconnect: this.config.autoReconnect,
1526
1728
  reconnectIntervalMs: this.config.reconnectDelay,
1527
1729
  maxReconnectIntervalMs: this.config.reconnectDelay * 16,
@@ -1530,7 +1732,7 @@ var NostrTransportProvider = class _NostrTransportProvider {
1530
1732
  queryTimeoutMs: 2e4
1531
1733
  // see same option above for rationale
1532
1734
  });
1533
- this.nostrClient.addConnectionListener({
1735
+ nextClient.addConnectionListener({
1534
1736
  onConnect: (url) => {
1535
1737
  logger.debug("Nostr", "NostrClient connected to relay:", url);
1536
1738
  },
@@ -1544,18 +1746,43 @@ var NostrTransportProvider = class _NostrTransportProvider {
1544
1746
  logger.debug("Nostr", "NostrClient reconnected to relay:", url);
1545
1747
  }
1546
1748
  });
1547
- await Promise.race([
1548
- this.nostrClient.connect(...this.config.relays),
1549
- new Promise(
1550
- (_, reject) => setTimeout(() => reject(new Error(
1551
- `Transport reconnection timed out after ${this.config.timeout}ms`
1552
- )), this.config.timeout)
1553
- )
1554
- ]);
1555
- await this.subscribeToEvents();
1556
- oldClient.disconnect();
1749
+ try {
1750
+ await this.connectWithDeadline(
1751
+ nextClient,
1752
+ `Transport reconnection timed out after ${this.config.timeout}ms`
1753
+ );
1754
+ } catch (error) {
1755
+ try {
1756
+ nextClient.disconnect();
1757
+ } catch {
1758
+ }
1759
+ throw error;
1760
+ }
1761
+ if (this.nostrClient !== oldClient || this.status !== "connected") {
1762
+ try {
1763
+ nextClient.disconnect();
1764
+ } catch {
1765
+ }
1766
+ throw new SphereError(
1767
+ "Transport client changed or left the connected state during setIdentity - identity not applied",
1768
+ "TRANSPORT_ERROR"
1769
+ );
1770
+ }
1771
+ this.nostrClient = nextClient;
1772
+ applyStagedIdentity();
1773
+ try {
1774
+ await this.subscribeToEvents();
1775
+ } finally {
1776
+ try {
1777
+ oldClient.disconnect();
1778
+ } catch {
1779
+ }
1780
+ }
1557
1781
  } else if (this.isConnected()) {
1782
+ applyStagedIdentity();
1558
1783
  await this.subscribeToEvents();
1784
+ } else {
1785
+ applyStagedIdentity();
1559
1786
  }
1560
1787
  }
1561
1788
  setFallbackSince(sinceSeconds) {
@@ -2850,26 +3077,6 @@ var UnicityAggregatorProvider = class {
2850
3077
  var UnicityOracleProvider = UnicityAggregatorProvider;
2851
3078
 
2852
3079
  // assets/trustbase.ts
2853
- var TRUSTBASE_TESTNET = {
2854
- version: 1,
2855
- networkId: 3,
2856
- epoch: 1,
2857
- epochStartRound: 1,
2858
- rootNodes: [
2859
- {
2860
- nodeId: "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU",
2861
- sigKey: "0x039afb2acb65f5fbc272d8907f763d0a5d189aadc9b97afdcc5897ea4dd112e68b",
2862
- stake: 1
2863
- }
2864
- ],
2865
- quorumThreshold: 1,
2866
- stateHash: "",
2867
- changeRecordHash: "",
2868
- previousEntryHash: "",
2869
- signatures: {
2870
- "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU": "0xf157c9fdd8a378e3ca70d354ccc4475ab2cd8de360127bc46b0aeab4b453a80f07fd9136a5843b60a8babaff23e20acc8879861f7651440a5e2829f7541b31f100"
2871
- }
2872
- };
2873
3080
  var TRUSTBASE_TESTNET2 = {
2874
3081
  version: 1,
2875
3082
  networkId: 4,
@@ -2908,8 +3115,44 @@ var TRUSTBASE_TESTNET2 = {
2908
3115
  "16Uiu2HAmU5T1TqKyKkUB3sitWb431hVFfp9nqvaLgVagQF6ywLFF": "0x9f28f3e3c50d4bfdaf54d7290fbbe311fffb3be2281a1d211be56818fa43d06a2e9a81925e9d365e552c8b772c99285a686de1d9fb581920be7524c7a5d467a301"
2909
3116
  }
2910
3117
  };
2911
- var TRUSTBASE_MAINNET = null;
2912
- var TRUSTBASE_DEV = TRUSTBASE_TESTNET;
3118
+ var TRUSTBASE_MAINNET = {
3119
+ version: 1,
3120
+ networkId: 1,
3121
+ epoch: 1,
3122
+ epochStartRound: 1,
3123
+ rootNodes: [
3124
+ {
3125
+ nodeId: "16Uiu2HAkvk4EpXoyh7SVLTX3TcsZkjbni5jY1sBgJ7qCEWfx1Gma",
3126
+ sigKey: "0x03059e30b5c980ce92e2c6d4e14775838be91a6f0e993f3a4899ab459c4b5225f2",
3127
+ stake: 1
3128
+ },
3129
+ {
3130
+ nodeId: "16Uiu2HAmF9Mq9pH9yUTdao3TpJeFZY9oX9gyMqfxggVA4fNmzmXr",
3131
+ sigKey: "0x03a514db8b17b1c79b4a6affeb5d88a129c4d5ff0f92c8f86c311971b0beb94335",
3132
+ stake: 1
3133
+ },
3134
+ {
3135
+ nodeId: "16Uiu2HAmLAHTSMvZ2apRRtBN9piYVhaK2i7sTMaQeSWSxsLh75TG",
3136
+ sigKey: "0x02e5669b2daa5d1afccfe66603f7d3fe171ecbd7ed891487ab3683cdebc3c3cd8b",
3137
+ stake: 1
3138
+ },
3139
+ {
3140
+ nodeId: "16Uiu2HAmNNaWfK4RTTAq7Ap8w2yZUryrEejqmmGf44ogMaYEnkJ4",
3141
+ sigKey: "0x03ffee964da8ca276d4fc1cf391929e6759db106ef1d0aea998154b51e5a7fb1b7",
3142
+ stake: 1
3143
+ }
3144
+ ],
3145
+ quorumThreshold: 3,
3146
+ stateHash: "",
3147
+ changeRecordHash: "",
3148
+ previousEntryHash: "",
3149
+ signatures: {
3150
+ "16Uiu2HAkvk4EpXoyh7SVLTX3TcsZkjbni5jY1sBgJ7qCEWfx1Gma": "0xc2f1d67dbb47fe4dcf502301d8cd5021b8903c0d3385781653d09c703d566a3a3a4e59c75792b36f929e32564ddc14c40ef728c0bde5859774d08d802de2318c00",
3151
+ "16Uiu2HAmF9Mq9pH9yUTdao3TpJeFZY9oX9gyMqfxggVA4fNmzmXr": "0x3e3ad3a7540daa0eded457e31d2ebcacfb354036798f49f63433d26c44857e76157e4abd36cbb1d39de8cdf6e1d33f88b250077d4347c3e5bf0f35a64f2d815300",
3152
+ "16Uiu2HAmLAHTSMvZ2apRRtBN9piYVhaK2i7sTMaQeSWSxsLh75TG": "0x6101bd9df8ab87439d90b52cd74727bc8bf31dd74173c8f731ec3ea501d3d86556ba7c0e84643337959f3be0e448637c301e8e0b28196e780d4f2ff930d9062100",
3153
+ "16Uiu2HAmNNaWfK4RTTAq7Ap8w2yZUryrEejqmmGf44ogMaYEnkJ4": "0xda13c777b832425e5cbd8d6e49eb08ddbde35c1e4a9bee8bd495c3883f039e434b64560865c3bc52b68d65780932b1f2bf8971f245d2244face822af5984945c00"
3154
+ }
3155
+ };
2913
3156
 
2914
3157
  // impl/shared/trustbase-loader.ts
2915
3158
  function getEmbeddedTrustBase(network) {
@@ -2922,8 +3165,6 @@ function getEmbeddedTrustBase(network) {
2922
3165
  case "testnet":
2923
3166
  case "testnet2":
2924
3167
  return TRUSTBASE_TESTNET2;
2925
- case "dev":
2926
- return TRUSTBASE_DEV;
2927
3168
  default:
2928
3169
  throw new SphereError(`getEmbeddedTrustBase: unknown network "${network}"`, "INVALID_CONFIG");
2929
3170
  }
@@ -3080,6 +3321,7 @@ async function readFileAsUint8Array(file) {
3080
3321
 
3081
3322
  // impl/shared/network.ts
3082
3323
  var EXPECTED_NETWORK_ID = {
3324
+ mainnet: 1,
3083
3325
  // v1 cutover: 'testnet' is an alias of testnet2 (the v2 gateway network),
3084
3326
  // so both expect the testnet2 trust base (networkId 4).
3085
3327
  testnet: 4,
@@ -3100,7 +3342,7 @@ function assertNetworkConsistency(network) {
3100
3342
  );
3101
3343
  }
3102
3344
  const expected = EXPECTED_NETWORK_ID[network];
3103
- if (expected !== void 0 && networkId !== expected) {
3345
+ if (networkId !== expected) {
3104
3346
  throw new SphereError(
3105
3347
  `Network "${network}" trust base networkId ${networkId} does not match expected ${expected} (wrong trust base wired to this network).`,
3106
3348
  "INVALID_CONFIG"
@@ -3365,419 +3607,6 @@ function createPriceProvider(config) {
3365
3607
  }
3366
3608
  }
3367
3609
 
3368
- // registry/TokenRegistry.ts
3369
- var FETCH_TIMEOUT_MS = 1e4;
3370
- var TokenRegistry = class _TokenRegistry {
3371
- static instance = null;
3372
- definitionsById;
3373
- definitionsBySymbol;
3374
- definitionsByName;
3375
- // Remote refresh state
3376
- remoteUrl = null;
3377
- storage = null;
3378
- refreshIntervalMs = TOKEN_REGISTRY_REFRESH_INTERVAL;
3379
- refreshTimer = null;
3380
- lastRefreshAt = 0;
3381
- refreshPromise = null;
3382
- initialLoadPromise = null;
3383
- constructor() {
3384
- this.definitionsById = /* @__PURE__ */ new Map();
3385
- this.definitionsBySymbol = /* @__PURE__ */ new Map();
3386
- this.definitionsByName = /* @__PURE__ */ new Map();
3387
- }
3388
- /**
3389
- * Get singleton instance of TokenRegistry
3390
- */
3391
- static getInstance() {
3392
- if (!_TokenRegistry.instance) {
3393
- _TokenRegistry.instance = new _TokenRegistry();
3394
- }
3395
- return _TokenRegistry.instance;
3396
- }
3397
- /**
3398
- * Configure remote registry refresh with persistent caching.
3399
- *
3400
- * On first call:
3401
- * 1. Loads cached data from StorageProvider (if available and fresh)
3402
- * 2. Starts periodic remote fetch (if autoRefresh is true, which is default)
3403
- *
3404
- * @param options - Configuration options
3405
- * @param options.remoteUrl - Remote URL to fetch definitions from
3406
- * @param options.storage - StorageProvider for persistent caching
3407
- * @param options.refreshIntervalMs - Refresh interval in ms (default: 1 hour)
3408
- * @param options.autoRefresh - Start auto-refresh immediately (default: true)
3409
- */
3410
- static configure(options) {
3411
- const instance = _TokenRegistry.getInstance();
3412
- if (options.remoteUrl !== void 0) {
3413
- instance.remoteUrl = options.remoteUrl;
3414
- }
3415
- if (options.storage !== void 0) {
3416
- instance.storage = options.storage;
3417
- }
3418
- if (options.refreshIntervalMs !== void 0) {
3419
- instance.refreshIntervalMs = options.refreshIntervalMs;
3420
- }
3421
- const autoRefresh = options.autoRefresh ?? true;
3422
- instance.initialLoadPromise = instance.performInitialLoad(autoRefresh);
3423
- }
3424
- /**
3425
- * Reset the singleton instance (useful for testing).
3426
- * Stops auto-refresh if running.
3427
- */
3428
- static resetInstance() {
3429
- if (_TokenRegistry.instance) {
3430
- _TokenRegistry.instance.stopAutoRefresh();
3431
- }
3432
- _TokenRegistry.instance = null;
3433
- }
3434
- /**
3435
- * Destroy the singleton: stop auto-refresh and reset.
3436
- */
3437
- static destroy() {
3438
- _TokenRegistry.resetInstance();
3439
- }
3440
- /**
3441
- * Wait for the initial data load (cache or remote) to complete.
3442
- * Returns true if data was loaded, false if not (timeout or no data source).
3443
- *
3444
- * @param timeoutMs - Maximum wait time in ms (default: 10s). Set to 0 for no timeout.
3445
- */
3446
- static async waitForReady(timeoutMs = 1e4) {
3447
- const instance = _TokenRegistry.getInstance();
3448
- if (!instance.initialLoadPromise) {
3449
- return instance.definitionsById.size > 0;
3450
- }
3451
- if (timeoutMs <= 0) {
3452
- return instance.initialLoadPromise;
3453
- }
3454
- return Promise.race([
3455
- instance.initialLoadPromise,
3456
- new Promise((resolve) => setTimeout(() => resolve(false), timeoutMs))
3457
- ]);
3458
- }
3459
- // ===========================================================================
3460
- // Initial Load
3461
- // ===========================================================================
3462
- /**
3463
- * Perform initial data load: try cache first, fall back to remote fetch.
3464
- * After initial data is available, start periodic auto-refresh if configured.
3465
- */
3466
- async performInitialLoad(autoRefresh) {
3467
- let loaded = false;
3468
- if (this.storage) {
3469
- loaded = await this.loadFromCache();
3470
- }
3471
- if (loaded) {
3472
- if (autoRefresh && this.remoteUrl) {
3473
- this.startAutoRefresh();
3474
- }
3475
- return true;
3476
- }
3477
- if (autoRefresh && this.remoteUrl) {
3478
- loaded = await this.refreshFromRemote();
3479
- this.stopAutoRefresh();
3480
- this.refreshTimer = setInterval(() => {
3481
- this.refreshFromRemote();
3482
- }, this.refreshIntervalMs);
3483
- return loaded;
3484
- }
3485
- return false;
3486
- }
3487
- // ===========================================================================
3488
- // Cache (StorageProvider)
3489
- // ===========================================================================
3490
- /**
3491
- * Per-network cache keys. The registry cache MUST be namespaced by remoteUrl:
3492
- * testnet and testnet2 ship DIFFERENT coinIds for the same symbol, so a single
3493
- * global key let a stale testnet snapshot be served for a testnet2 request —
3494
- * wrong/missing icons AND self-mint resolving the wrong-network coinId. Keying
3495
- * by remoteUrl isolates the snapshots so a network can never read another's.
3496
- */
3497
- cacheKeys() {
3498
- const base = STORAGE_KEYS_GLOBAL.TOKEN_REGISTRY_CACHE;
3499
- const baseTs = STORAGE_KEYS_GLOBAL.TOKEN_REGISTRY_CACHE_TS;
3500
- if (!this.remoteUrl) return { data: base, ts: baseTs };
3501
- return { data: `${base}:${this.remoteUrl}`, ts: `${baseTs}:${this.remoteUrl}` };
3502
- }
3503
- /**
3504
- * Load definitions from StorageProvider cache.
3505
- * Only applies if cache exists and is fresh (within refreshIntervalMs).
3506
- */
3507
- async loadFromCache() {
3508
- if (!this.storage) return false;
3509
- try {
3510
- const { data: dataKey, ts: tsKey } = this.cacheKeys();
3511
- const [cached, cachedTs] = await Promise.all([
3512
- this.storage.get(dataKey),
3513
- this.storage.get(tsKey)
3514
- ]);
3515
- if (!cached || !cachedTs) return false;
3516
- const ts = parseInt(cachedTs, 10);
3517
- if (isNaN(ts)) return false;
3518
- const age = Date.now() - ts;
3519
- if (age > this.refreshIntervalMs) return false;
3520
- if (this.lastRefreshAt > ts) return false;
3521
- const data = JSON.parse(cached);
3522
- if (!this.isValidDefinitionsArray(data)) return false;
3523
- this.applyDefinitions(data);
3524
- this.lastRefreshAt = ts;
3525
- return true;
3526
- } catch {
3527
- return false;
3528
- }
3529
- }
3530
- /**
3531
- * Save definitions to StorageProvider cache.
3532
- */
3533
- async saveToCache(definitions) {
3534
- if (!this.storage) return;
3535
- try {
3536
- const { data: dataKey, ts: tsKey } = this.cacheKeys();
3537
- await Promise.all([
3538
- this.storage.set(dataKey, JSON.stringify(definitions)),
3539
- this.storage.set(tsKey, String(Date.now()))
3540
- ]);
3541
- } catch {
3542
- }
3543
- }
3544
- // ===========================================================================
3545
- // Remote Refresh
3546
- // ===========================================================================
3547
- /**
3548
- * Apply an array of token definitions to the internal maps.
3549
- * Clears existing data before applying.
3550
- */
3551
- applyDefinitions(definitions) {
3552
- this.definitionsById.clear();
3553
- this.definitionsBySymbol.clear();
3554
- this.definitionsByName.clear();
3555
- for (const def of definitions) {
3556
- const idLower = def.id.toLowerCase();
3557
- this.definitionsById.set(idLower, def);
3558
- if (def.symbol) {
3559
- this.definitionsBySymbol.set(def.symbol.toUpperCase(), def);
3560
- }
3561
- this.definitionsByName.set(def.name.toLowerCase(), def);
3562
- }
3563
- }
3564
- /**
3565
- * Validate that data is an array of objects with 'id' field
3566
- */
3567
- isValidDefinitionsArray(data) {
3568
- return Array.isArray(data) && data.every((item) => item && typeof item === "object" && "id" in item);
3569
- }
3570
- /**
3571
- * Fetch token definitions from the remote URL and update the registry.
3572
- * On success, also persists to StorageProvider cache.
3573
- * Returns true on success, false on failure. On failure, existing data is preserved.
3574
- * Concurrent calls are deduplicated — only one fetch runs at a time.
3575
- */
3576
- async refreshFromRemote() {
3577
- if (!this.remoteUrl) {
3578
- return false;
3579
- }
3580
- if (this.refreshPromise) {
3581
- return this.refreshPromise;
3582
- }
3583
- this.refreshPromise = this.doRefresh();
3584
- try {
3585
- return await this.refreshPromise;
3586
- } finally {
3587
- this.refreshPromise = null;
3588
- }
3589
- }
3590
- async doRefresh() {
3591
- try {
3592
- const controller = new AbortController();
3593
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
3594
- let response;
3595
- try {
3596
- response = await fetch(this.remoteUrl, {
3597
- headers: { Accept: "application/json" },
3598
- signal: controller.signal
3599
- });
3600
- } finally {
3601
- clearTimeout(timer);
3602
- }
3603
- if (!response.ok) {
3604
- logger.warn("TokenRegistry", `Remote fetch failed: HTTP ${response.status} ${response.statusText}`);
3605
- return false;
3606
- }
3607
- const data = await response.json();
3608
- if (!this.isValidDefinitionsArray(data)) {
3609
- logger.warn("TokenRegistry", "Remote data is not a valid token definitions array");
3610
- return false;
3611
- }
3612
- const definitions = data;
3613
- this.applyDefinitions(definitions);
3614
- this.lastRefreshAt = Date.now();
3615
- this.saveToCache(definitions);
3616
- return true;
3617
- } catch (error) {
3618
- const message = error instanceof Error ? error.message : String(error);
3619
- logger.warn("TokenRegistry", `Remote refresh failed: ${message}`);
3620
- return false;
3621
- }
3622
- }
3623
- /**
3624
- * Start periodic auto-refresh from the remote URL.
3625
- * Does an immediate fetch, then repeats at the configured interval.
3626
- */
3627
- startAutoRefresh(intervalMs) {
3628
- this.stopAutoRefresh();
3629
- if (intervalMs !== void 0) {
3630
- this.refreshIntervalMs = intervalMs;
3631
- }
3632
- this.refreshFromRemote();
3633
- this.refreshTimer = setInterval(() => {
3634
- this.refreshFromRemote();
3635
- }, this.refreshIntervalMs);
3636
- }
3637
- /**
3638
- * Stop periodic auto-refresh
3639
- */
3640
- stopAutoRefresh() {
3641
- if (this.refreshTimer !== null) {
3642
- clearInterval(this.refreshTimer);
3643
- this.refreshTimer = null;
3644
- }
3645
- }
3646
- /**
3647
- * Timestamp of the last successful remote refresh (0 if never refreshed)
3648
- */
3649
- getLastRefreshAt() {
3650
- return this.lastRefreshAt;
3651
- }
3652
- // ===========================================================================
3653
- // Lookup Methods
3654
- // ===========================================================================
3655
- /**
3656
- * Get token definition by hex coin ID
3657
- * @param coinId - 64-character hex string
3658
- * @returns Token definition or undefined if not found
3659
- */
3660
- getDefinition(coinId) {
3661
- if (!coinId) return void 0;
3662
- return this.definitionsById.get(coinId.toLowerCase());
3663
- }
3664
- /**
3665
- * Get token definition by symbol (e.g., "UCT", "BTC")
3666
- * @param symbol - Token symbol (case-insensitive)
3667
- * @returns Token definition or undefined if not found
3668
- */
3669
- getDefinitionBySymbol(symbol) {
3670
- if (!symbol) return void 0;
3671
- return this.definitionsBySymbol.get(symbol.toUpperCase());
3672
- }
3673
- /**
3674
- * Get token definition by name (e.g., "bitcoin", "ethereum")
3675
- * @param name - Token name (case-insensitive)
3676
- * @returns Token definition or undefined if not found
3677
- */
3678
- getDefinitionByName(name) {
3679
- if (!name) return void 0;
3680
- return this.definitionsByName.get(name.toLowerCase());
3681
- }
3682
- /**
3683
- * Get token symbol for a coin ID
3684
- * @param coinId - 64-character hex string
3685
- * @returns Symbol (e.g., "UCT") or truncated ID if not found
3686
- */
3687
- getSymbol(coinId) {
3688
- const def = this.getDefinition(coinId);
3689
- if (def?.symbol) {
3690
- return def.symbol;
3691
- }
3692
- return coinId.slice(0, 6).toUpperCase();
3693
- }
3694
- /**
3695
- * Get token name for a coin ID
3696
- * @param coinId - 64-character hex string
3697
- * @returns Name (e.g., "Bitcoin") or coin ID if not found
3698
- */
3699
- getName(coinId) {
3700
- const def = this.getDefinition(coinId);
3701
- if (def?.name) {
3702
- return def.name.charAt(0).toUpperCase() + def.name.slice(1);
3703
- }
3704
- return coinId;
3705
- }
3706
- /**
3707
- * Get decimal places for a coin ID
3708
- * @param coinId - 64-character hex string
3709
- * @returns Decimals or 0 if not found
3710
- */
3711
- getDecimals(coinId) {
3712
- const def = this.getDefinition(coinId);
3713
- return def?.decimals ?? 0;
3714
- }
3715
- /**
3716
- * Get icon URL for a coin ID
3717
- * @param coinId - 64-character hex string
3718
- * @param preferPng - Prefer PNG format over SVG
3719
- * @returns Icon URL or null if not found
3720
- */
3721
- getIconUrl(coinId, preferPng = true) {
3722
- const def = this.getDefinition(coinId);
3723
- if (!def?.icons || def.icons.length === 0) {
3724
- return null;
3725
- }
3726
- if (preferPng) {
3727
- const pngIcon = def.icons.find((i) => i.url.toLowerCase().includes(".png"));
3728
- if (pngIcon) return pngIcon.url;
3729
- }
3730
- return def.icons[0].url;
3731
- }
3732
- /**
3733
- * Check if a coin ID is known in the registry
3734
- * @param coinId - 64-character hex string
3735
- * @returns true if the coin is in the registry
3736
- */
3737
- isKnown(coinId) {
3738
- return this.definitionsById.has(coinId.toLowerCase());
3739
- }
3740
- /**
3741
- * Get all token definitions
3742
- * @returns Array of all token definitions
3743
- */
3744
- getAllDefinitions() {
3745
- return Array.from(this.definitionsById.values());
3746
- }
3747
- /**
3748
- * Get all fungible token definitions
3749
- * @returns Array of fungible token definitions
3750
- */
3751
- getFungibleTokens() {
3752
- return this.getAllDefinitions().filter((def) => def.assetKind === "fungible");
3753
- }
3754
- /**
3755
- * Get all non-fungible token definitions
3756
- * @returns Array of non-fungible token definitions
3757
- */
3758
- getNonFungibleTokens() {
3759
- return this.getAllDefinitions().filter((def) => def.assetKind === "non-fungible");
3760
- }
3761
- /**
3762
- * Get coin ID by symbol
3763
- * @param symbol - Token symbol (e.g., "UCT")
3764
- * @returns Coin ID hex string or undefined if not found
3765
- */
3766
- getCoinIdBySymbol(symbol) {
3767
- const def = this.getDefinitionBySymbol(symbol);
3768
- return def?.id;
3769
- }
3770
- /**
3771
- * Get coin ID by name
3772
- * @param name - Token name (e.g., "bitcoin")
3773
- * @returns Coin ID hex string or undefined if not found
3774
- */
3775
- getCoinIdByName(name) {
3776
- const def = this.getDefinitionByName(name);
3777
- return def?.id;
3778
- }
3779
- };
3780
-
3781
3610
  // impl/shared/resolvers.ts
3782
3611
  function getNetworkConfig(network = "mainnet") {
3783
3612
  return NETWORKS[network];
@@ -3871,8 +3700,6 @@ function createBrowserProviders(config) {
3871
3700
  const priceConfig = resolvePriceConfig(config?.price, storage);
3872
3701
  const groupChat = resolveGroupChatConfig(network, config?.groupChat);
3873
3702
  const market = resolveMarketConfig(config?.market);
3874
- const networkConfig = getNetworkConfig(network);
3875
- TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
3876
3703
  return {
3877
3704
  storage,
3878
3705
  groupChat,