@unicitylabs/sphere-sdk 0.3.9 → 0.4.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 (43) hide show
  1. package/dist/connect/index.cjs +79 -3
  2. package/dist/connect/index.cjs.map +1 -1
  3. package/dist/connect/index.d.cts +16 -0
  4. package/dist/connect/index.d.ts +16 -0
  5. package/dist/connect/index.js +79 -3
  6. package/dist/connect/index.js.map +1 -1
  7. package/dist/core/index.cjs +2686 -56
  8. package/dist/core/index.cjs.map +1 -1
  9. package/dist/core/index.d.cts +228 -3
  10. package/dist/core/index.d.ts +228 -3
  11. package/dist/core/index.js +2682 -52
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/impl/browser/connect/index.cjs +11 -2
  14. package/dist/impl/browser/connect/index.cjs.map +1 -1
  15. package/dist/impl/browser/connect/index.js +11 -2
  16. package/dist/impl/browser/connect/index.js.map +1 -1
  17. package/dist/impl/browser/index.cjs +467 -47
  18. package/dist/impl/browser/index.cjs.map +1 -1
  19. package/dist/impl/browser/index.js +468 -47
  20. package/dist/impl/browser/index.js.map +1 -1
  21. package/dist/impl/nodejs/connect/index.cjs +11 -2
  22. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  23. package/dist/impl/nodejs/connect/index.js +11 -2
  24. package/dist/impl/nodejs/connect/index.js.map +1 -1
  25. package/dist/impl/nodejs/index.cjs +152 -8
  26. package/dist/impl/nodejs/index.cjs.map +1 -1
  27. package/dist/impl/nodejs/index.d.cts +47 -0
  28. package/dist/impl/nodejs/index.d.ts +47 -0
  29. package/dist/impl/nodejs/index.js +153 -8
  30. package/dist/impl/nodejs/index.js.map +1 -1
  31. package/dist/index.cjs +2703 -56
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +326 -5
  34. package/dist/index.d.ts +326 -5
  35. package/dist/index.js +2692 -52
  36. package/dist/index.js.map +1 -1
  37. package/dist/l1/index.cjs +5 -1
  38. package/dist/l1/index.cjs.map +1 -1
  39. package/dist/l1/index.d.cts +2 -1
  40. package/dist/l1/index.d.ts +2 -1
  41. package/dist/l1/index.js +5 -1
  42. package/dist/l1/index.js.map +1 -1
  43. package/package.json +1 -1
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var browser_exports = {};
32
32
  __export(browser_exports, {
33
33
  BrowserTrustBaseLoader: () => BrowserTrustBaseLoader,
34
+ IndexedDBStorageProvider: () => IndexedDBStorageProvider,
34
35
  IndexedDBTokenStorageProvider: () => IndexedDBTokenStorageProvider,
35
36
  LocalStorageProvider: () => LocalStorageProvider,
36
37
  NostrTransportProvider: () => NostrTransportProvider,
@@ -40,6 +41,7 @@ __export(browser_exports, {
40
41
  createBrowserProviders: () => createBrowserProviders,
41
42
  createBrowserTrustBaseLoader: () => createBrowserTrustBaseLoader,
42
43
  createBrowserWebSocket: () => createBrowserWebSocket,
44
+ createIndexedDBStorageProvider: () => createIndexedDBStorageProvider,
43
45
  createIndexedDBTokenStorageProvider: () => createIndexedDBTokenStorageProvider,
44
46
  createLocalStorageProvider: () => createLocalStorageProvider,
45
47
  createNostrTransportProvider: () => createNostrTransportProvider,
@@ -408,9 +410,275 @@ function createLocalStorageProvider(config) {
408
410
  return new LocalStorageProvider(config);
409
411
  }
410
412
 
411
- // impl/browser/storage/IndexedDBTokenStorageProvider.ts
412
- var DB_NAME = "sphere-token-storage";
413
+ // impl/browser/storage/IndexedDBStorageProvider.ts
414
+ var DB_NAME = "sphere-storage";
413
415
  var DB_VERSION = 1;
416
+ var STORE_NAME = "kv";
417
+ var IndexedDBStorageProvider = class {
418
+ id = "indexeddb-storage";
419
+ name = "IndexedDB Storage";
420
+ type = "local";
421
+ description = "Browser IndexedDB for large-capacity persistence";
422
+ prefix;
423
+ dbName;
424
+ debug;
425
+ identity = null;
426
+ status = "disconnected";
427
+ db = null;
428
+ constructor(config) {
429
+ this.prefix = config?.prefix ?? "sphere_";
430
+ this.dbName = config?.dbName ?? DB_NAME;
431
+ this.debug = config?.debug ?? false;
432
+ }
433
+ // ===========================================================================
434
+ // BaseProvider Implementation
435
+ // ===========================================================================
436
+ async connect() {
437
+ if (this.status === "connected" && this.db) return;
438
+ this.status = "connecting";
439
+ console.log(`[IndexedDBStorage] connect: opening db=${this.dbName}`);
440
+ try {
441
+ this.db = await Promise.race([
442
+ this.openDatabase(),
443
+ new Promise(
444
+ (_, reject) => setTimeout(() => reject(new Error("IndexedDB open timed out after 5s")), 5e3)
445
+ )
446
+ ]);
447
+ this.status = "connected";
448
+ console.log(`[IndexedDBStorage] connect: connected to db=${this.dbName}`);
449
+ } catch (error) {
450
+ this.status = "error";
451
+ throw new Error(`IndexedDB not available: ${error}`);
452
+ }
453
+ }
454
+ async disconnect() {
455
+ console.log(`[IndexedDBStorage] disconnect: closing db=${this.dbName}, wasConnected=${!!this.db}`);
456
+ if (this.db) {
457
+ this.db.close();
458
+ this.db = null;
459
+ }
460
+ this.status = "disconnected";
461
+ }
462
+ isConnected() {
463
+ return this.status === "connected" && this.db !== null;
464
+ }
465
+ getStatus() {
466
+ return this.status;
467
+ }
468
+ // ===========================================================================
469
+ // StorageProvider Implementation
470
+ // ===========================================================================
471
+ setIdentity(identity) {
472
+ this.identity = identity;
473
+ this.log("Identity set:", identity.l1Address);
474
+ }
475
+ async get(key) {
476
+ this.ensureConnected();
477
+ const fullKey = this.getFullKey(key);
478
+ const result = await this.idbGet(fullKey);
479
+ return result?.v ?? null;
480
+ }
481
+ async set(key, value) {
482
+ this.ensureConnected();
483
+ const fullKey = this.getFullKey(key);
484
+ await this.idbPut({ k: fullKey, v: value });
485
+ }
486
+ async remove(key) {
487
+ this.ensureConnected();
488
+ const fullKey = this.getFullKey(key);
489
+ await this.idbDelete(fullKey);
490
+ }
491
+ async has(key) {
492
+ this.ensureConnected();
493
+ const fullKey = this.getFullKey(key);
494
+ const count = await this.idbCount(fullKey);
495
+ return count > 0;
496
+ }
497
+ async keys(prefix) {
498
+ this.ensureConnected();
499
+ const basePrefix = this.getFullKey("");
500
+ const searchPrefix = prefix ? this.getFullKey(prefix) : basePrefix;
501
+ const allEntries = await this.idbGetAll();
502
+ const result = [];
503
+ for (const entry of allEntries) {
504
+ if (entry.k.startsWith(searchPrefix)) {
505
+ result.push(entry.k.slice(basePrefix.length));
506
+ }
507
+ }
508
+ return result;
509
+ }
510
+ async clear(prefix) {
511
+ if (!prefix) {
512
+ console.log(`[IndexedDBStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
513
+ if (this.db) {
514
+ this.db.close();
515
+ this.db = null;
516
+ }
517
+ this.status = "disconnected";
518
+ await new Promise((resolve) => {
519
+ try {
520
+ const req = indexedDB.deleteDatabase(this.dbName);
521
+ req.onsuccess = () => {
522
+ console.log(`[IndexedDBStorage] clear: deleted db=${this.dbName}`);
523
+ resolve();
524
+ };
525
+ req.onerror = () => {
526
+ console.warn(`[IndexedDBStorage] clear: error deleting db=${this.dbName}`, req.error);
527
+ resolve();
528
+ };
529
+ req.onblocked = () => {
530
+ console.warn(`[IndexedDBStorage] clear: deleteDatabase blocked for db=${this.dbName}`);
531
+ resolve();
532
+ };
533
+ } catch {
534
+ resolve();
535
+ }
536
+ });
537
+ this.log("Database deleted:", this.dbName);
538
+ return;
539
+ }
540
+ this.ensureConnected();
541
+ const keysToRemove = await this.keys(prefix);
542
+ for (const key of keysToRemove) {
543
+ await this.remove(key);
544
+ }
545
+ }
546
+ async saveTrackedAddresses(entries) {
547
+ await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
548
+ }
549
+ async loadTrackedAddresses() {
550
+ const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
551
+ if (!data) return [];
552
+ try {
553
+ const parsed = JSON.parse(data);
554
+ return parsed.addresses ?? [];
555
+ } catch {
556
+ return [];
557
+ }
558
+ }
559
+ // ===========================================================================
560
+ // Helpers
561
+ // ===========================================================================
562
+ /**
563
+ * Get JSON data
564
+ */
565
+ async getJSON(key) {
566
+ const value = await this.get(key);
567
+ if (!value) return null;
568
+ try {
569
+ return JSON.parse(value);
570
+ } catch {
571
+ return null;
572
+ }
573
+ }
574
+ /**
575
+ * Set JSON data
576
+ */
577
+ async setJSON(key, value) {
578
+ await this.set(key, JSON.stringify(value));
579
+ }
580
+ // ===========================================================================
581
+ // Private: Key Scoping
582
+ // ===========================================================================
583
+ getFullKey(key) {
584
+ const isPerAddressKey = Object.values(STORAGE_KEYS_ADDRESS).includes(key);
585
+ if (isPerAddressKey && this.identity?.directAddress) {
586
+ const addressId = getAddressId(this.identity.directAddress);
587
+ return `${this.prefix}${addressId}_${key}`;
588
+ }
589
+ return `${this.prefix}${key}`;
590
+ }
591
+ ensureConnected() {
592
+ if (this.status !== "connected" || !this.db) {
593
+ throw new Error("IndexedDBStorageProvider not connected");
594
+ }
595
+ }
596
+ // ===========================================================================
597
+ // Private: IndexedDB Operations
598
+ // ===========================================================================
599
+ openDatabase() {
600
+ return new Promise((resolve, reject) => {
601
+ const request = indexedDB.open(this.dbName, DB_VERSION);
602
+ request.onerror = () => reject(request.error);
603
+ request.onsuccess = () => resolve(request.result);
604
+ request.onblocked = () => {
605
+ console.warn("[IndexedDBStorageProvider] open blocked by another connection");
606
+ };
607
+ request.onupgradeneeded = (event) => {
608
+ const db = event.target.result;
609
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
610
+ db.createObjectStore(STORE_NAME, { keyPath: "k" });
611
+ }
612
+ };
613
+ });
614
+ }
615
+ idbGet(key) {
616
+ return new Promise((resolve, reject) => {
617
+ const tx = this.db.transaction(STORE_NAME, "readonly");
618
+ const store = tx.objectStore(STORE_NAME);
619
+ const request = store.get(key);
620
+ request.onerror = () => reject(request.error);
621
+ request.onsuccess = () => resolve(request.result ?? void 0);
622
+ });
623
+ }
624
+ idbPut(entry) {
625
+ return new Promise((resolve, reject) => {
626
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
627
+ const store = tx.objectStore(STORE_NAME);
628
+ const request = store.put(entry);
629
+ request.onerror = () => reject(request.error);
630
+ request.onsuccess = () => resolve();
631
+ });
632
+ }
633
+ idbDelete(key) {
634
+ return new Promise((resolve, reject) => {
635
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
636
+ const store = tx.objectStore(STORE_NAME);
637
+ const request = store.delete(key);
638
+ request.onerror = () => reject(request.error);
639
+ request.onsuccess = () => resolve();
640
+ });
641
+ }
642
+ idbCount(key) {
643
+ return new Promise((resolve, reject) => {
644
+ const tx = this.db.transaction(STORE_NAME, "readonly");
645
+ const store = tx.objectStore(STORE_NAME);
646
+ const request = store.count(key);
647
+ request.onerror = () => reject(request.error);
648
+ request.onsuccess = () => resolve(request.result);
649
+ });
650
+ }
651
+ idbGetAll() {
652
+ return new Promise((resolve, reject) => {
653
+ const tx = this.db.transaction(STORE_NAME, "readonly");
654
+ const store = tx.objectStore(STORE_NAME);
655
+ const request = store.getAll();
656
+ request.onerror = () => reject(request.error);
657
+ request.onsuccess = () => resolve(request.result ?? []);
658
+ });
659
+ }
660
+ idbClear() {
661
+ return new Promise((resolve, reject) => {
662
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
663
+ const store = tx.objectStore(STORE_NAME);
664
+ const request = store.clear();
665
+ request.onerror = () => reject(request.error);
666
+ request.onsuccess = () => resolve();
667
+ });
668
+ }
669
+ log(...args) {
670
+ if (this.debug) {
671
+ console.log("[IndexedDBStorageProvider]", ...args);
672
+ }
673
+ }
674
+ };
675
+ function createIndexedDBStorageProvider(config) {
676
+ return new IndexedDBStorageProvider(config);
677
+ }
678
+
679
+ // impl/browser/storage/IndexedDBTokenStorageProvider.ts
680
+ var DB_NAME2 = "sphere-token-storage";
681
+ var DB_VERSION2 = 1;
414
682
  var STORE_TOKENS = "tokens";
415
683
  var STORE_META = "meta";
416
684
  var IndexedDBTokenStorageProvider = class {
@@ -423,7 +691,7 @@ var IndexedDBTokenStorageProvider = class {
423
691
  status = "disconnected";
424
692
  identity = null;
425
693
  constructor(config) {
426
- this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME;
694
+ this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME2;
427
695
  this.dbName = this.dbNamePrefix;
428
696
  }
429
697
  setIdentity(identity) {
@@ -432,11 +700,19 @@ var IndexedDBTokenStorageProvider = class {
432
700
  const addressId = getAddressId(identity.directAddress);
433
701
  this.dbName = `${this.dbNamePrefix}-${addressId}`;
434
702
  }
703
+ console.log(`[IndexedDBTokenStorage] setIdentity \u2192 db=${this.dbName}`);
435
704
  }
436
705
  async initialize() {
437
706
  try {
707
+ if (this.db) {
708
+ console.log(`[IndexedDBTokenStorage] initialize: closing existing connection before re-open (db=${this.dbName})`);
709
+ this.db.close();
710
+ this.db = null;
711
+ }
712
+ console.log(`[IndexedDBTokenStorage] initialize: opening db=${this.dbName}`);
438
713
  this.db = await this.openDatabase();
439
714
  this.status = "connected";
715
+ console.log(`[IndexedDBTokenStorage] initialize: connected to db=${this.dbName}`);
440
716
  return true;
441
717
  } catch (error) {
442
718
  console.error("[IndexedDBTokenStorage] Failed to initialize:", error);
@@ -445,6 +721,7 @@ var IndexedDBTokenStorageProvider = class {
445
721
  }
446
722
  }
447
723
  async shutdown() {
724
+ console.log(`[IndexedDBTokenStorage] shutdown: closing db=${this.dbName}, wasConnected=${!!this.db}`);
448
725
  if (this.db) {
449
726
  this.db.close();
450
727
  this.db = null;
@@ -465,6 +742,7 @@ var IndexedDBTokenStorageProvider = class {
465
742
  }
466
743
  async load() {
467
744
  if (!this.db) {
745
+ console.warn(`[IndexedDBTokenStorage] load: db not initialized (db=${this.dbName})`);
468
746
  return {
469
747
  success: false,
470
748
  error: "Database not initialized",
@@ -513,6 +791,8 @@ var IndexedDBTokenStorageProvider = class {
513
791
  if (invalid) {
514
792
  data._invalid = invalid;
515
793
  }
794
+ const tokenKeys = Object.keys(data).filter((k) => k.startsWith("_") && !["_meta", "_tombstones", "_outbox", "_sent", "_invalid"].includes(k));
795
+ console.log(`[IndexedDBTokenStorage] load: db=${this.dbName}, tokens=${tokenKeys.length}`);
516
796
  return {
517
797
  success: true,
518
798
  data,
@@ -520,6 +800,7 @@ var IndexedDBTokenStorageProvider = class {
520
800
  timestamp: Date.now()
521
801
  };
522
802
  } catch (error) {
803
+ console.error(`[IndexedDBTokenStorage] load failed: db=${this.dbName}`, error);
523
804
  return {
524
805
  success: false,
525
806
  error: error instanceof Error ? error.message : "Unknown error",
@@ -530,6 +811,7 @@ var IndexedDBTokenStorageProvider = class {
530
811
  }
531
812
  async save(data) {
532
813
  if (!this.db) {
814
+ console.warn(`[IndexedDBTokenStorage] save: db not initialized (db=${this.dbName})`);
533
815
  return {
534
816
  success: false,
535
817
  error: "Database not initialized",
@@ -537,6 +819,9 @@ var IndexedDBTokenStorageProvider = class {
537
819
  };
538
820
  }
539
821
  try {
822
+ const tokenKeys = Object.keys(data).filter((k) => k.startsWith("_") && !["_meta", "_tombstones", "_outbox", "_sent", "_invalid"].includes(k));
823
+ const archivedKeys = Object.keys(data).filter((k) => k.startsWith("archived-"));
824
+ console.log(`[IndexedDBTokenStorage] save: db=${this.dbName}, tokens=${tokenKeys.length}, archived=${archivedKeys.length}, tombstones=${data._tombstones?.length ?? 0}`);
540
825
  await this.putToStore(STORE_META, "meta", data._meta);
541
826
  if (data._tombstones) {
542
827
  await this.putToStore(STORE_META, "tombstones", data._tombstones);
@@ -594,15 +879,14 @@ var IndexedDBTokenStorageProvider = class {
594
879
  return meta !== null;
595
880
  }
596
881
  async clear() {
597
- const dbNames = [this.dbName];
598
882
  try {
883
+ console.log(`[IndexedDBTokenStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
599
884
  if (this.db) {
600
- await this.clearStore(STORE_TOKENS);
601
- await this.clearStore(STORE_META);
602
885
  this.db.close();
603
886
  this.db = null;
604
887
  }
605
888
  this.status = "disconnected";
889
+ const dbNames = [this.dbName];
606
890
  if (typeof indexedDB.databases === "function") {
607
891
  try {
608
892
  const dbs = await Promise.race([
@@ -614,42 +898,34 @@ var IndexedDBTokenStorageProvider = class {
614
898
  for (const dbInfo of dbs) {
615
899
  if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
616
900
  dbNames.push(dbInfo.name);
617
- try {
618
- const db = await new Promise((resolve, reject) => {
619
- const req = indexedDB.open(dbInfo.name, DB_VERSION);
620
- req.onsuccess = () => resolve(req.result);
621
- req.onerror = () => reject(req.error);
622
- req.onupgradeneeded = (e) => {
623
- const d = e.target.result;
624
- if (!d.objectStoreNames.contains(STORE_TOKENS)) d.createObjectStore(STORE_TOKENS, { keyPath: "id" });
625
- if (!d.objectStoreNames.contains(STORE_META)) d.createObjectStore(STORE_META);
626
- };
627
- });
628
- const clearTx = db.transaction([STORE_TOKENS, STORE_META], "readwrite");
629
- clearTx.objectStore(STORE_TOKENS).clear();
630
- clearTx.objectStore(STORE_META).clear();
631
- await new Promise((resolve) => {
632
- clearTx.oncomplete = () => resolve();
633
- clearTx.onerror = () => resolve();
634
- });
635
- db.close();
636
- } catch {
637
- }
638
901
  }
639
902
  }
640
903
  } catch {
641
904
  }
642
905
  }
643
- for (const name of dbNames) {
644
- try {
645
- const req = indexedDB.deleteDatabase(name);
646
- req.onerror = () => {
647
- };
648
- req.onblocked = () => {
649
- };
650
- } catch {
651
- }
652
- }
906
+ console.log(`[IndexedDBTokenStorage] clear: deleting ${dbNames.length} database(s):`, dbNames);
907
+ await Promise.all(dbNames.map(
908
+ (name) => new Promise((resolve) => {
909
+ try {
910
+ const req = indexedDB.deleteDatabase(name);
911
+ req.onsuccess = () => {
912
+ console.log(`[IndexedDBTokenStorage] clear: deleted db=${name}`);
913
+ resolve();
914
+ };
915
+ req.onerror = () => {
916
+ console.warn(`[IndexedDBTokenStorage] clear: error deleting db=${name}`, req.error);
917
+ resolve();
918
+ };
919
+ req.onblocked = () => {
920
+ console.warn(`[IndexedDBTokenStorage] clear: deleteDatabase blocked for db=${name}`);
921
+ resolve();
922
+ };
923
+ } catch {
924
+ resolve();
925
+ }
926
+ })
927
+ ));
928
+ console.log(`[IndexedDBTokenStorage] clear: done`);
653
929
  return true;
654
930
  } catch (err) {
655
931
  console.warn("[IndexedDBTokenStorage] clear() failed:", err);
@@ -661,7 +937,7 @@ var IndexedDBTokenStorageProvider = class {
661
937
  // =========================================================================
662
938
  openDatabase() {
663
939
  return new Promise((resolve, reject) => {
664
- const request = indexedDB.open(this.dbName, DB_VERSION);
940
+ const request = indexedDB.open(this.dbName, DB_VERSION2);
665
941
  request.onerror = () => {
666
942
  reject(request.error);
667
943
  };
@@ -804,6 +1080,52 @@ function createView(arr) {
804
1080
  function rotr(word, shift) {
805
1081
  return word << 32 - shift | word >>> shift;
806
1082
  }
1083
+ var hasHexBuiltin = /* @__PURE__ */ (() => (
1084
+ // @ts-ignore
1085
+ typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
1086
+ ))();
1087
+ var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
1088
+ function bytesToHex(bytes) {
1089
+ abytes(bytes);
1090
+ if (hasHexBuiltin)
1091
+ return bytes.toHex();
1092
+ let hex = "";
1093
+ for (let i = 0; i < bytes.length; i++) {
1094
+ hex += hexes[bytes[i]];
1095
+ }
1096
+ return hex;
1097
+ }
1098
+ var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
1099
+ function asciiToBase16(ch) {
1100
+ if (ch >= asciis._0 && ch <= asciis._9)
1101
+ return ch - asciis._0;
1102
+ if (ch >= asciis.A && ch <= asciis.F)
1103
+ return ch - (asciis.A - 10);
1104
+ if (ch >= asciis.a && ch <= asciis.f)
1105
+ return ch - (asciis.a - 10);
1106
+ return;
1107
+ }
1108
+ function hexToBytes(hex) {
1109
+ if (typeof hex !== "string")
1110
+ throw new Error("hex string expected, got " + typeof hex);
1111
+ if (hasHexBuiltin)
1112
+ return Uint8Array.fromHex(hex);
1113
+ const hl = hex.length;
1114
+ const al = hl / 2;
1115
+ if (hl % 2)
1116
+ throw new Error("hex string expected, got unpadded hex of length " + hl);
1117
+ const array = new Uint8Array(al);
1118
+ for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
1119
+ const n1 = asciiToBase16(hex.charCodeAt(hi));
1120
+ const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
1121
+ if (n1 === void 0 || n2 === void 0) {
1122
+ const char = hex[hi] + hex[hi + 1];
1123
+ throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
1124
+ }
1125
+ array[ai] = n1 * 16 + n2;
1126
+ }
1127
+ return array;
1128
+ }
807
1129
  function createHasher(hashCons, info = {}) {
808
1130
  const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
809
1131
  const tmp = hashCons(void 0);
@@ -1299,7 +1621,7 @@ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
1299
1621
  const programBytes = hash160ToBytes(pubKeyHash);
1300
1622
  return encodeBech32(prefix, witnessVersion, programBytes);
1301
1623
  }
1302
- function hexToBytes(hex) {
1624
+ function hexToBytes2(hex) {
1303
1625
  const matches = hex.match(/../g);
1304
1626
  if (!matches) {
1305
1627
  return new Uint8Array(0);
@@ -1326,6 +1648,8 @@ function defaultUUIDGenerator() {
1326
1648
  }
1327
1649
 
1328
1650
  // transport/NostrTransportProvider.ts
1651
+ var COMPOSING_INDICATOR_KIND = 25050;
1652
+ var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
1329
1653
  var EVENT_KINDS = NOSTR_EVENT_KINDS;
1330
1654
  function hashAddressForTag(address) {
1331
1655
  const bytes = new TextEncoder().encode("unicity:address:" + address);
@@ -1408,6 +1732,8 @@ var NostrTransportProvider = class {
1408
1732
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1409
1733
  readReceiptHandlers = /* @__PURE__ */ new Set();
1410
1734
  typingIndicatorHandlers = /* @__PURE__ */ new Set();
1735
+ composingHandlers = /* @__PURE__ */ new Set();
1736
+ pendingMessages = [];
1411
1737
  broadcastHandlers = /* @__PURE__ */ new Map();
1412
1738
  eventCallbacks = /* @__PURE__ */ new Set();
1413
1739
  constructor(config) {
@@ -1680,6 +2006,18 @@ var NostrTransportProvider = class {
1680
2006
  }
1681
2007
  onMessage(handler) {
1682
2008
  this.messageHandlers.add(handler);
2009
+ if (this.pendingMessages.length > 0) {
2010
+ const pending = this.pendingMessages;
2011
+ this.pendingMessages = [];
2012
+ this.log("Flushing", pending.length, "buffered messages to new handler");
2013
+ for (const message of pending) {
2014
+ try {
2015
+ handler(message);
2016
+ } catch (error) {
2017
+ this.log("Message handler error (buffered):", error);
2018
+ }
2019
+ }
2020
+ }
1683
2021
  return () => this.messageHandlers.delete(handler);
1684
2022
  }
1685
2023
  async sendTokenTransfer(recipientPubkey, payload) {
@@ -1800,6 +2138,19 @@ var NostrTransportProvider = class {
1800
2138
  this.typingIndicatorHandlers.add(handler);
1801
2139
  return () => this.typingIndicatorHandlers.delete(handler);
1802
2140
  }
2141
+ // ===========================================================================
2142
+ // Composing Indicators (NIP-59 kind 25050)
2143
+ // ===========================================================================
2144
+ onComposing(handler) {
2145
+ this.composingHandlers.add(handler);
2146
+ return () => this.composingHandlers.delete(handler);
2147
+ }
2148
+ async sendComposingIndicator(recipientPubkey, content) {
2149
+ this.ensureReady();
2150
+ const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
2151
+ const giftWrap = this.createCustomKindGiftWrap(nostrRecipient, content, COMPOSING_INDICATOR_KIND);
2152
+ await this.publishEvent(giftWrap);
2153
+ }
1803
2154
  /**
1804
2155
  * Resolve any identifier to full peer information.
1805
2156
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -2299,6 +2650,30 @@ var NostrTransportProvider = class {
2299
2650
  }
2300
2651
  return;
2301
2652
  }
2653
+ if (pm.kind === COMPOSING_INDICATOR_KIND) {
2654
+ let senderNametag2;
2655
+ let expiresIn = 3e4;
2656
+ try {
2657
+ const parsed = JSON.parse(pm.content);
2658
+ senderNametag2 = parsed.senderNametag || void 0;
2659
+ expiresIn = parsed.expiresIn ?? 3e4;
2660
+ } catch {
2661
+ }
2662
+ const indicator = {
2663
+ senderPubkey: pm.senderPubkey,
2664
+ senderNametag: senderNametag2,
2665
+ expiresIn
2666
+ };
2667
+ this.log("Composing indicator from:", indicator.senderNametag || pm.senderPubkey?.slice(0, 16));
2668
+ for (const handler of this.composingHandlers) {
2669
+ try {
2670
+ handler(indicator);
2671
+ } catch (e) {
2672
+ this.log("Composing handler error:", e);
2673
+ }
2674
+ }
2675
+ return;
2676
+ }
2302
2677
  try {
2303
2678
  const parsed = JSON.parse(pm.content);
2304
2679
  if (parsed?.type === "typing") {
@@ -2345,12 +2720,17 @@ var NostrTransportProvider = class {
2345
2720
  encrypted: true
2346
2721
  };
2347
2722
  this.emitEvent({ type: "message:received", timestamp: Date.now() });
2348
- this.log("Dispatching to", this.messageHandlers.size, "handlers");
2349
- for (const handler of this.messageHandlers) {
2350
- try {
2351
- handler(message);
2352
- } catch (error) {
2353
- this.log("Message handler error:", error);
2723
+ if (this.messageHandlers.size === 0) {
2724
+ this.log("No message handlers registered, buffering message for later delivery");
2725
+ this.pendingMessages.push(message);
2726
+ } else {
2727
+ this.log("Dispatching to", this.messageHandlers.size, "handlers");
2728
+ for (const handler of this.messageHandlers) {
2729
+ try {
2730
+ handler(message);
2731
+ } catch (error) {
2732
+ this.log("Message handler error:", error);
2733
+ }
2354
2734
  }
2355
2735
  }
2356
2736
  } catch (err) {
@@ -2754,6 +3134,39 @@ var NostrTransportProvider = class {
2754
3134
  }
2755
3135
  }
2756
3136
  }
3137
+ /**
3138
+ * Create a NIP-17 gift wrap with a custom inner rumor kind.
3139
+ * Replicates the three-layer NIP-59 envelope (rumor → seal → gift wrap)
3140
+ * because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
3141
+ */
3142
+ createCustomKindGiftWrap(recipientPubkeyHex, content, rumorKind) {
3143
+ const senderPubkey = this.keyManager.getPublicKeyHex();
3144
+ const now = Math.floor(Date.now() / 1e3);
3145
+ const rumorTags = [["p", recipientPubkeyHex]];
3146
+ const rumorSerialized = JSON.stringify([0, senderPubkey, now, rumorKind, rumorTags, content]);
3147
+ const rumorId = bytesToHex(sha256(new TextEncoder().encode(rumorSerialized)));
3148
+ const rumor = { id: rumorId, pubkey: senderPubkey, created_at: now, kind: rumorKind, tags: rumorTags, content };
3149
+ const recipientPubkeyBytes = hexToBytes(recipientPubkeyHex);
3150
+ const encryptedRumor = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(rumor), this.keyManager.getPrivateKey(), recipientPubkeyBytes);
3151
+ const sealTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
3152
+ const seal = import_nostr_js_sdk.Event.create(this.keyManager, {
3153
+ kind: import_nostr_js_sdk.EventKinds.SEAL,
3154
+ tags: [],
3155
+ content: encryptedRumor,
3156
+ created_at: sealTimestamp
3157
+ });
3158
+ const ephemeralKeys = import_nostr_js_sdk.NostrKeyManager.generate();
3159
+ const encryptedSeal = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(seal.toJSON()), ephemeralKeys.getPrivateKey(), recipientPubkeyBytes);
3160
+ const wrapTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
3161
+ const giftWrap = import_nostr_js_sdk.Event.create(ephemeralKeys, {
3162
+ kind: import_nostr_js_sdk.EventKinds.GIFT_WRAP,
3163
+ tags: [["p", recipientPubkeyHex]],
3164
+ content: encryptedSeal,
3165
+ created_at: wrapTimestamp
3166
+ });
3167
+ ephemeralKeys.clear();
3168
+ return giftWrap;
3169
+ }
2757
3170
  log(...args) {
2758
3171
  if (this.config.debug) {
2759
3172
  console.log("[NostrTransportProvider]", ...args);
@@ -3383,7 +3796,7 @@ async function loadLibp2pModules() {
3383
3796
  };
3384
3797
  }
3385
3798
  function deriveEd25519KeyMaterial(privateKeyHex, info = IPNS_HKDF_INFO) {
3386
- const walletSecret = hexToBytes(privateKeyHex);
3799
+ const walletSecret = hexToBytes2(privateKeyHex);
3387
3800
  const infoBytes = new TextEncoder().encode(info);
3388
3801
  return hkdf(sha256, walletSecret, void 0, infoBytes, 32);
3389
3802
  }
@@ -5881,6 +6294,11 @@ function resolveGroupChatConfig(network, config) {
5881
6294
  relays: config.relays ?? [...netConfig.groupRelays]
5882
6295
  };
5883
6296
  }
6297
+ function resolveMarketConfig(config) {
6298
+ if (!config) return void 0;
6299
+ if (config === true) return {};
6300
+ return { apiUrl: config.apiUrl, timeout: config.timeout };
6301
+ }
5884
6302
 
5885
6303
  // impl/browser/index.ts
5886
6304
  if (typeof globalThis.Buffer === "undefined") {
@@ -5938,7 +6356,7 @@ function createBrowserProviders(config) {
5938
6356
  const oracleConfig = resolveOracleConfig(network, config?.oracle);
5939
6357
  const l1Config = resolveL1Config(network, config?.l1);
5940
6358
  const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
5941
- const storage = createLocalStorageProvider(config?.storage);
6359
+ const storage = createIndexedDBStorageProvider(config?.storage);
5942
6360
  const priceConfig = resolvePriceConfig(config?.price, storage);
5943
6361
  const ipfsConfig = tokenSyncConfig?.ipfs;
5944
6362
  const ipfsTokenStorage = ipfsConfig?.enabled ? createBrowserIpfsStorageProvider({
@@ -5947,11 +6365,13 @@ function createBrowserProviders(config) {
5947
6365
  // reuse debug-like flag
5948
6366
  }) : void 0;
5949
6367
  const groupChat = resolveGroupChatConfig(network, config?.groupChat);
6368
+ const market = resolveMarketConfig(config?.market);
5950
6369
  const networkConfig = getNetworkConfig(network);
5951
6370
  TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
5952
6371
  return {
5953
6372
  storage,
5954
6373
  groupChat,
6374
+ market,
5955
6375
  transport: createNostrTransportProvider({
5956
6376
  relays: transportConfig.relays,
5957
6377
  timeout: transportConfig.timeout,