@unicitylabs/sphere-sdk 0.3.8 → 0.4.0

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 (49) hide show
  1. package/dist/connect/index.cjs +770 -0
  2. package/dist/connect/index.cjs.map +1 -0
  3. package/dist/connect/index.d.cts +312 -0
  4. package/dist/connect/index.d.ts +312 -0
  5. package/dist/connect/index.js +747 -0
  6. package/dist/connect/index.js.map +1 -0
  7. package/dist/core/index.cjs +2744 -56
  8. package/dist/core/index.cjs.map +1 -1
  9. package/dist/core/index.d.cts +277 -3
  10. package/dist/core/index.d.ts +277 -3
  11. package/dist/core/index.js +2740 -52
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/impl/browser/connect/index.cjs +271 -0
  14. package/dist/impl/browser/connect/index.cjs.map +1 -0
  15. package/dist/impl/browser/connect/index.d.cts +137 -0
  16. package/dist/impl/browser/connect/index.d.ts +137 -0
  17. package/dist/impl/browser/connect/index.js +248 -0
  18. package/dist/impl/browser/connect/index.js.map +1 -0
  19. package/dist/impl/browser/index.cjs +583 -45
  20. package/dist/impl/browser/index.cjs.map +1 -1
  21. package/dist/impl/browser/index.js +587 -46
  22. package/dist/impl/browser/index.js.map +1 -1
  23. package/dist/impl/browser/ipfs.cjs.map +1 -1
  24. package/dist/impl/browser/ipfs.js.map +1 -1
  25. package/dist/impl/nodejs/connect/index.cjs +372 -0
  26. package/dist/impl/nodejs/connect/index.cjs.map +1 -0
  27. package/dist/impl/nodejs/connect/index.d.cts +178 -0
  28. package/dist/impl/nodejs/connect/index.d.ts +178 -0
  29. package/dist/impl/nodejs/connect/index.js +333 -0
  30. package/dist/impl/nodejs/connect/index.js.map +1 -0
  31. package/dist/impl/nodejs/index.cjs +266 -12
  32. package/dist/impl/nodejs/index.cjs.map +1 -1
  33. package/dist/impl/nodejs/index.d.cts +96 -0
  34. package/dist/impl/nodejs/index.d.ts +96 -0
  35. package/dist/impl/nodejs/index.js +270 -13
  36. package/dist/impl/nodejs/index.js.map +1 -1
  37. package/dist/index.cjs +2761 -56
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +375 -5
  40. package/dist/index.d.ts +375 -5
  41. package/dist/index.js +2750 -52
  42. package/dist/index.js.map +1 -1
  43. package/dist/l1/index.cjs +5 -1
  44. package/dist/l1/index.cjs.map +1 -1
  45. package/dist/l1/index.d.cts +2 -1
  46. package/dist/l1/index.d.ts +2 -1
  47. package/dist/l1/index.js +5 -1
  48. package/dist/l1/index.js.map +1 -1
  49. package/package.json +31 -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,264 @@ 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
+ try {
440
+ this.db = await Promise.race([
441
+ this.openDatabase(),
442
+ new Promise(
443
+ (_, reject) => setTimeout(() => reject(new Error("IndexedDB open timed out after 5s")), 5e3)
444
+ )
445
+ ]);
446
+ this.status = "connected";
447
+ this.log("Connected to IndexedDB");
448
+ } catch (error) {
449
+ this.status = "error";
450
+ throw new Error(`IndexedDB not available: ${error}`);
451
+ }
452
+ }
453
+ async disconnect() {
454
+ if (this.db) {
455
+ this.db.close();
456
+ this.db = null;
457
+ }
458
+ this.status = "disconnected";
459
+ this.log("Disconnected from IndexedDB");
460
+ }
461
+ isConnected() {
462
+ return this.status === "connected" && this.db !== null;
463
+ }
464
+ getStatus() {
465
+ return this.status;
466
+ }
467
+ // ===========================================================================
468
+ // StorageProvider Implementation
469
+ // ===========================================================================
470
+ setIdentity(identity) {
471
+ this.identity = identity;
472
+ this.log("Identity set:", identity.l1Address);
473
+ }
474
+ async get(key) {
475
+ this.ensureConnected();
476
+ const fullKey = this.getFullKey(key);
477
+ const result = await this.idbGet(fullKey);
478
+ return result?.v ?? null;
479
+ }
480
+ async set(key, value) {
481
+ this.ensureConnected();
482
+ const fullKey = this.getFullKey(key);
483
+ await this.idbPut({ k: fullKey, v: value });
484
+ }
485
+ async remove(key) {
486
+ this.ensureConnected();
487
+ const fullKey = this.getFullKey(key);
488
+ await this.idbDelete(fullKey);
489
+ }
490
+ async has(key) {
491
+ this.ensureConnected();
492
+ const fullKey = this.getFullKey(key);
493
+ const count = await this.idbCount(fullKey);
494
+ return count > 0;
495
+ }
496
+ async keys(prefix) {
497
+ this.ensureConnected();
498
+ const basePrefix = this.getFullKey("");
499
+ const searchPrefix = prefix ? this.getFullKey(prefix) : basePrefix;
500
+ const allEntries = await this.idbGetAll();
501
+ const result = [];
502
+ for (const entry of allEntries) {
503
+ if (entry.k.startsWith(searchPrefix)) {
504
+ result.push(entry.k.slice(basePrefix.length));
505
+ }
506
+ }
507
+ return result;
508
+ }
509
+ async clear(prefix) {
510
+ if (!prefix) {
511
+ if (this.db) {
512
+ this.db.close();
513
+ this.db = null;
514
+ }
515
+ this.status = "disconnected";
516
+ await new Promise((resolve) => {
517
+ try {
518
+ const req = indexedDB.deleteDatabase(this.dbName);
519
+ req.onsuccess = () => resolve();
520
+ req.onerror = () => resolve();
521
+ req.onblocked = () => resolve();
522
+ } catch {
523
+ resolve();
524
+ }
525
+ });
526
+ this.log("Database deleted:", this.dbName);
527
+ return;
528
+ }
529
+ this.ensureConnected();
530
+ const keysToRemove = await this.keys(prefix);
531
+ for (const key of keysToRemove) {
532
+ await this.remove(key);
533
+ }
534
+ }
535
+ async saveTrackedAddresses(entries) {
536
+ await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
537
+ }
538
+ async loadTrackedAddresses() {
539
+ const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
540
+ if (!data) return [];
541
+ try {
542
+ const parsed = JSON.parse(data);
543
+ return parsed.addresses ?? [];
544
+ } catch {
545
+ return [];
546
+ }
547
+ }
548
+ // ===========================================================================
549
+ // Helpers
550
+ // ===========================================================================
551
+ /**
552
+ * Get JSON data
553
+ */
554
+ async getJSON(key) {
555
+ const value = await this.get(key);
556
+ if (!value) return null;
557
+ try {
558
+ return JSON.parse(value);
559
+ } catch {
560
+ return null;
561
+ }
562
+ }
563
+ /**
564
+ * Set JSON data
565
+ */
566
+ async setJSON(key, value) {
567
+ await this.set(key, JSON.stringify(value));
568
+ }
569
+ // ===========================================================================
570
+ // Private: Key Scoping
571
+ // ===========================================================================
572
+ getFullKey(key) {
573
+ const isPerAddressKey = Object.values(STORAGE_KEYS_ADDRESS).includes(key);
574
+ if (isPerAddressKey && this.identity?.directAddress) {
575
+ const addressId = getAddressId(this.identity.directAddress);
576
+ return `${this.prefix}${addressId}_${key}`;
577
+ }
578
+ return `${this.prefix}${key}`;
579
+ }
580
+ ensureConnected() {
581
+ if (this.status !== "connected" || !this.db) {
582
+ throw new Error("IndexedDBStorageProvider not connected");
583
+ }
584
+ }
585
+ // ===========================================================================
586
+ // Private: IndexedDB Operations
587
+ // ===========================================================================
588
+ openDatabase() {
589
+ return new Promise((resolve, reject) => {
590
+ const request = indexedDB.open(this.dbName, DB_VERSION);
591
+ request.onerror = () => reject(request.error);
592
+ request.onsuccess = () => resolve(request.result);
593
+ request.onblocked = () => {
594
+ console.warn("[IndexedDBStorageProvider] open blocked by another connection");
595
+ };
596
+ request.onupgradeneeded = (event) => {
597
+ const db = event.target.result;
598
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
599
+ db.createObjectStore(STORE_NAME, { keyPath: "k" });
600
+ }
601
+ };
602
+ });
603
+ }
604
+ idbGet(key) {
605
+ return new Promise((resolve, reject) => {
606
+ const tx = this.db.transaction(STORE_NAME, "readonly");
607
+ const store = tx.objectStore(STORE_NAME);
608
+ const request = store.get(key);
609
+ request.onerror = () => reject(request.error);
610
+ request.onsuccess = () => resolve(request.result ?? void 0);
611
+ });
612
+ }
613
+ idbPut(entry) {
614
+ return new Promise((resolve, reject) => {
615
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
616
+ const store = tx.objectStore(STORE_NAME);
617
+ const request = store.put(entry);
618
+ request.onerror = () => reject(request.error);
619
+ request.onsuccess = () => resolve();
620
+ });
621
+ }
622
+ idbDelete(key) {
623
+ return new Promise((resolve, reject) => {
624
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
625
+ const store = tx.objectStore(STORE_NAME);
626
+ const request = store.delete(key);
627
+ request.onerror = () => reject(request.error);
628
+ request.onsuccess = () => resolve();
629
+ });
630
+ }
631
+ idbCount(key) {
632
+ return new Promise((resolve, reject) => {
633
+ const tx = this.db.transaction(STORE_NAME, "readonly");
634
+ const store = tx.objectStore(STORE_NAME);
635
+ const request = store.count(key);
636
+ request.onerror = () => reject(request.error);
637
+ request.onsuccess = () => resolve(request.result);
638
+ });
639
+ }
640
+ idbGetAll() {
641
+ return new Promise((resolve, reject) => {
642
+ const tx = this.db.transaction(STORE_NAME, "readonly");
643
+ const store = tx.objectStore(STORE_NAME);
644
+ const request = store.getAll();
645
+ request.onerror = () => reject(request.error);
646
+ request.onsuccess = () => resolve(request.result ?? []);
647
+ });
648
+ }
649
+ idbClear() {
650
+ return new Promise((resolve, reject) => {
651
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
652
+ const store = tx.objectStore(STORE_NAME);
653
+ const request = store.clear();
654
+ request.onerror = () => reject(request.error);
655
+ request.onsuccess = () => resolve();
656
+ });
657
+ }
658
+ log(...args) {
659
+ if (this.debug) {
660
+ console.log("[IndexedDBStorageProvider]", ...args);
661
+ }
662
+ }
663
+ };
664
+ function createIndexedDBStorageProvider(config) {
665
+ return new IndexedDBStorageProvider(config);
666
+ }
667
+
668
+ // impl/browser/storage/IndexedDBTokenStorageProvider.ts
669
+ var DB_NAME2 = "sphere-token-storage";
670
+ var DB_VERSION2 = 1;
414
671
  var STORE_TOKENS = "tokens";
415
672
  var STORE_META = "meta";
416
673
  var IndexedDBTokenStorageProvider = class {
@@ -423,7 +680,7 @@ var IndexedDBTokenStorageProvider = class {
423
680
  status = "disconnected";
424
681
  identity = null;
425
682
  constructor(config) {
426
- this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME;
683
+ this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME2;
427
684
  this.dbName = this.dbNamePrefix;
428
685
  }
429
686
  setIdentity(identity) {
@@ -437,6 +694,7 @@ var IndexedDBTokenStorageProvider = class {
437
694
  try {
438
695
  this.db = await this.openDatabase();
439
696
  this.status = "connected";
697
+ this.cleanupStaleDatabases();
440
698
  return true;
441
699
  } catch (error) {
442
700
  console.error("[IndexedDBTokenStorage] Failed to initialize:", error);
@@ -594,37 +852,43 @@ var IndexedDBTokenStorageProvider = class {
594
852
  return meta !== null;
595
853
  }
596
854
  async clear() {
597
- if (this.db) {
598
- this.db.close();
599
- this.db = null;
600
- }
601
- this.status = "disconnected";
602
- const CLEAR_TIMEOUT = 1500;
603
- const withTimeout = (promise, ms, label) => Promise.race([
604
- promise,
605
- new Promise(
606
- (_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
607
- )
608
- ]);
609
- const deleteDb = (name) => new Promise((resolve) => {
610
- const req = indexedDB.deleteDatabase(name);
611
- req.onsuccess = () => resolve();
612
- req.onerror = () => resolve();
613
- req.onblocked = () => resolve();
614
- });
615
855
  try {
856
+ if (this.db) {
857
+ this.db.close();
858
+ this.db = null;
859
+ }
860
+ this.status = "disconnected";
861
+ const dbNames = [this.dbName];
616
862
  if (typeof indexedDB.databases === "function") {
617
- const dbs = await withTimeout(
618
- indexedDB.databases(),
619
- CLEAR_TIMEOUT,
620
- "indexedDB.databases()"
621
- );
622
- await Promise.all(
623
- dbs.filter((db) => db.name?.startsWith(this.dbNamePrefix)).map((db) => deleteDb(db.name))
624
- );
625
- } else {
626
- await deleteDb(this.dbName);
863
+ try {
864
+ const dbs = await Promise.race([
865
+ indexedDB.databases(),
866
+ new Promise(
867
+ (_, reject) => setTimeout(() => reject(new Error("timeout")), 1500)
868
+ )
869
+ ]);
870
+ for (const dbInfo of dbs) {
871
+ if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
872
+ dbNames.push(dbInfo.name);
873
+ }
874
+ }
875
+ } catch {
876
+ }
627
877
  }
878
+ await Promise.all(dbNames.map(
879
+ (name) => new Promise((resolve) => {
880
+ try {
881
+ const req = indexedDB.deleteDatabase(name);
882
+ req.onsuccess = () => resolve();
883
+ req.onerror = () => resolve();
884
+ req.onblocked = () => {
885
+ resolve();
886
+ };
887
+ } catch {
888
+ resolve();
889
+ }
890
+ })
891
+ ));
628
892
  return true;
629
893
  } catch (err) {
630
894
  console.warn("[IndexedDBTokenStorage] clear() failed:", err);
@@ -634,9 +898,29 @@ var IndexedDBTokenStorageProvider = class {
634
898
  // =========================================================================
635
899
  // Private IndexedDB helpers
636
900
  // =========================================================================
901
+ /**
902
+ * Delete stale databases from other addresses (fire-and-forget, background).
903
+ * Called after the current database is already open, so deleteDatabase
904
+ * on other databases won't block anything.
905
+ */
906
+ cleanupStaleDatabases() {
907
+ if (typeof indexedDB.databases !== "function") return;
908
+ indexedDB.databases().then((dbs) => {
909
+ for (const dbInfo of dbs) {
910
+ if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
911
+ const req = indexedDB.deleteDatabase(dbInfo.name);
912
+ req.onerror = () => {
913
+ };
914
+ req.onblocked = () => {
915
+ };
916
+ }
917
+ }
918
+ }).catch(() => {
919
+ });
920
+ }
637
921
  openDatabase() {
638
922
  return new Promise((resolve, reject) => {
639
- const request = indexedDB.open(this.dbName, DB_VERSION);
923
+ const request = indexedDB.open(this.dbName, DB_VERSION2);
640
924
  request.onerror = () => {
641
925
  reject(request.error);
642
926
  };
@@ -779,6 +1063,52 @@ function createView(arr) {
779
1063
  function rotr(word, shift) {
780
1064
  return word << 32 - shift | word >>> shift;
781
1065
  }
1066
+ var hasHexBuiltin = /* @__PURE__ */ (() => (
1067
+ // @ts-ignore
1068
+ typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
1069
+ ))();
1070
+ var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
1071
+ function bytesToHex(bytes) {
1072
+ abytes(bytes);
1073
+ if (hasHexBuiltin)
1074
+ return bytes.toHex();
1075
+ let hex = "";
1076
+ for (let i = 0; i < bytes.length; i++) {
1077
+ hex += hexes[bytes[i]];
1078
+ }
1079
+ return hex;
1080
+ }
1081
+ var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
1082
+ function asciiToBase16(ch) {
1083
+ if (ch >= asciis._0 && ch <= asciis._9)
1084
+ return ch - asciis._0;
1085
+ if (ch >= asciis.A && ch <= asciis.F)
1086
+ return ch - (asciis.A - 10);
1087
+ if (ch >= asciis.a && ch <= asciis.f)
1088
+ return ch - (asciis.a - 10);
1089
+ return;
1090
+ }
1091
+ function hexToBytes(hex) {
1092
+ if (typeof hex !== "string")
1093
+ throw new Error("hex string expected, got " + typeof hex);
1094
+ if (hasHexBuiltin)
1095
+ return Uint8Array.fromHex(hex);
1096
+ const hl = hex.length;
1097
+ const al = hl / 2;
1098
+ if (hl % 2)
1099
+ throw new Error("hex string expected, got unpadded hex of length " + hl);
1100
+ const array = new Uint8Array(al);
1101
+ for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
1102
+ const n1 = asciiToBase16(hex.charCodeAt(hi));
1103
+ const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
1104
+ if (n1 === void 0 || n2 === void 0) {
1105
+ const char = hex[hi] + hex[hi + 1];
1106
+ throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
1107
+ }
1108
+ array[ai] = n1 * 16 + n2;
1109
+ }
1110
+ return array;
1111
+ }
782
1112
  function createHasher(hashCons, info = {}) {
783
1113
  const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
784
1114
  const tmp = hashCons(void 0);
@@ -1274,7 +1604,7 @@ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
1274
1604
  const programBytes = hash160ToBytes(pubKeyHash);
1275
1605
  return encodeBech32(prefix, witnessVersion, programBytes);
1276
1606
  }
1277
- function hexToBytes(hex) {
1607
+ function hexToBytes2(hex) {
1278
1608
  const matches = hex.match(/../g);
1279
1609
  if (!matches) {
1280
1610
  return new Uint8Array(0);
@@ -1301,6 +1631,8 @@ function defaultUUIDGenerator() {
1301
1631
  }
1302
1632
 
1303
1633
  // transport/NostrTransportProvider.ts
1634
+ var COMPOSING_INDICATOR_KIND = 25050;
1635
+ var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
1304
1636
  var EVENT_KINDS = NOSTR_EVENT_KINDS;
1305
1637
  function hashAddressForTag(address) {
1306
1638
  const bytes = new TextEncoder().encode("unicity:address:" + address);
@@ -1381,6 +1713,10 @@ var NostrTransportProvider = class {
1381
1713
  transferHandlers = /* @__PURE__ */ new Set();
1382
1714
  paymentRequestHandlers = /* @__PURE__ */ new Set();
1383
1715
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1716
+ readReceiptHandlers = /* @__PURE__ */ new Set();
1717
+ typingIndicatorHandlers = /* @__PURE__ */ new Set();
1718
+ composingHandlers = /* @__PURE__ */ new Set();
1719
+ pendingMessages = [];
1384
1720
  broadcastHandlers = /* @__PURE__ */ new Map();
1385
1721
  eventCallbacks = /* @__PURE__ */ new Set();
1386
1722
  constructor(config) {
@@ -1632,6 +1968,18 @@ var NostrTransportProvider = class {
1632
1968
  const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1633
1969
  const giftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1634
1970
  await this.publishEvent(giftWrap);
1971
+ const selfWrapContent = JSON.stringify({
1972
+ selfWrap: true,
1973
+ originalId: giftWrap.id,
1974
+ recipientPubkey,
1975
+ senderNametag,
1976
+ text: content
1977
+ });
1978
+ const selfPubkey = this.keyManager.getPublicKeyHex();
1979
+ const selfGiftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
1980
+ this.publishEvent(selfGiftWrap).catch((err) => {
1981
+ this.log("Self-wrap publish failed:", err);
1982
+ });
1635
1983
  this.emitEvent({
1636
1984
  type: "message:sent",
1637
1985
  timestamp: Date.now(),
@@ -1641,6 +1989,18 @@ var NostrTransportProvider = class {
1641
1989
  }
1642
1990
  onMessage(handler) {
1643
1991
  this.messageHandlers.add(handler);
1992
+ if (this.pendingMessages.length > 0) {
1993
+ const pending = this.pendingMessages;
1994
+ this.pendingMessages = [];
1995
+ this.log("Flushing", pending.length, "buffered messages to new handler");
1996
+ for (const message of pending) {
1997
+ try {
1998
+ handler(message);
1999
+ } catch (error) {
2000
+ this.log("Message handler error (buffered):", error);
2001
+ }
2002
+ }
2003
+ }
1644
2004
  return () => this.messageHandlers.delete(handler);
1645
2005
  }
1646
2006
  async sendTokenTransfer(recipientPubkey, payload) {
@@ -1730,6 +2090,50 @@ var NostrTransportProvider = class {
1730
2090
  this.paymentRequestResponseHandlers.add(handler);
1731
2091
  return () => this.paymentRequestResponseHandlers.delete(handler);
1732
2092
  }
2093
+ // ===========================================================================
2094
+ // Read Receipts
2095
+ // ===========================================================================
2096
+ async sendReadReceipt(recipientTransportPubkey, messageEventId) {
2097
+ if (!this.keyManager) throw new Error("Not initialized");
2098
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
2099
+ const event = import_nostr_js_sdk.NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
2100
+ await this.publishEvent(event);
2101
+ this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
2102
+ }
2103
+ onReadReceipt(handler) {
2104
+ this.readReceiptHandlers.add(handler);
2105
+ return () => this.readReceiptHandlers.delete(handler);
2106
+ }
2107
+ // ===========================================================================
2108
+ // Typing Indicators
2109
+ // ===========================================================================
2110
+ async sendTypingIndicator(recipientTransportPubkey) {
2111
+ if (!this.keyManager) throw new Error("Not initialized");
2112
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
2113
+ const content = JSON.stringify({
2114
+ type: "typing",
2115
+ senderNametag: this.identity?.nametag
2116
+ });
2117
+ const event = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
2118
+ await this.publishEvent(event);
2119
+ }
2120
+ onTypingIndicator(handler) {
2121
+ this.typingIndicatorHandlers.add(handler);
2122
+ return () => this.typingIndicatorHandlers.delete(handler);
2123
+ }
2124
+ // ===========================================================================
2125
+ // Composing Indicators (NIP-59 kind 25050)
2126
+ // ===========================================================================
2127
+ onComposing(handler) {
2128
+ this.composingHandlers.add(handler);
2129
+ return () => this.composingHandlers.delete(handler);
2130
+ }
2131
+ async sendComposingIndicator(recipientPubkey, content) {
2132
+ this.ensureReady();
2133
+ const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
2134
+ const giftWrap = this.createCustomKindGiftWrap(nostrRecipient, content, COMPOSING_INDICATOR_KIND);
2135
+ await this.publishEvent(giftWrap);
2136
+ }
1733
2137
  /**
1734
2138
  * Resolve any identifier to full peer information.
1735
2139
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -2183,11 +2587,98 @@ var NostrTransportProvider = class {
2183
2587
  const pm = import_nostr_js_sdk.NIP17.unwrap(event, this.keyManager);
2184
2588
  this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
2185
2589
  if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
2186
- this.log("Skipping own message");
2590
+ try {
2591
+ const parsed = JSON.parse(pm.content);
2592
+ if (parsed?.selfWrap && parsed.recipientPubkey) {
2593
+ this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
2594
+ const message2 = {
2595
+ id: parsed.originalId || pm.eventId,
2596
+ senderTransportPubkey: pm.senderPubkey,
2597
+ senderNametag: parsed.senderNametag,
2598
+ recipientTransportPubkey: parsed.recipientPubkey,
2599
+ content: parsed.text ?? "",
2600
+ timestamp: pm.timestamp * 1e3,
2601
+ encrypted: true,
2602
+ isSelfWrap: true
2603
+ };
2604
+ for (const handler of this.messageHandlers) {
2605
+ try {
2606
+ handler(message2);
2607
+ } catch (e) {
2608
+ this.log("Self-wrap handler error:", e);
2609
+ }
2610
+ }
2611
+ return;
2612
+ }
2613
+ } catch {
2614
+ }
2615
+ this.log("Skipping own non-self-wrap message");
2187
2616
  return;
2188
2617
  }
2189
- if (pm.kind !== import_nostr_js_sdk.EventKinds.CHAT_MESSAGE) {
2190
- this.log("Skipping non-chat message, kind:", pm.kind);
2618
+ if ((0, import_nostr_js_sdk.isReadReceipt)(pm)) {
2619
+ this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
2620
+ if (pm.replyToEventId) {
2621
+ const receipt = {
2622
+ senderTransportPubkey: pm.senderPubkey,
2623
+ messageEventId: pm.replyToEventId,
2624
+ timestamp: pm.timestamp * 1e3
2625
+ };
2626
+ for (const handler of this.readReceiptHandlers) {
2627
+ try {
2628
+ handler(receipt);
2629
+ } catch (e) {
2630
+ this.log("Read receipt handler error:", e);
2631
+ }
2632
+ }
2633
+ }
2634
+ return;
2635
+ }
2636
+ if (pm.kind === COMPOSING_INDICATOR_KIND) {
2637
+ let senderNametag2;
2638
+ let expiresIn = 3e4;
2639
+ try {
2640
+ const parsed = JSON.parse(pm.content);
2641
+ senderNametag2 = parsed.senderNametag || void 0;
2642
+ expiresIn = parsed.expiresIn ?? 3e4;
2643
+ } catch {
2644
+ }
2645
+ const indicator = {
2646
+ senderPubkey: pm.senderPubkey,
2647
+ senderNametag: senderNametag2,
2648
+ expiresIn
2649
+ };
2650
+ this.log("Composing indicator from:", indicator.senderNametag || pm.senderPubkey?.slice(0, 16));
2651
+ for (const handler of this.composingHandlers) {
2652
+ try {
2653
+ handler(indicator);
2654
+ } catch (e) {
2655
+ this.log("Composing handler error:", e);
2656
+ }
2657
+ }
2658
+ return;
2659
+ }
2660
+ try {
2661
+ const parsed = JSON.parse(pm.content);
2662
+ if (parsed?.type === "typing") {
2663
+ this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
2664
+ const indicator = {
2665
+ senderTransportPubkey: pm.senderPubkey,
2666
+ senderNametag: parsed.senderNametag,
2667
+ timestamp: pm.timestamp * 1e3
2668
+ };
2669
+ for (const handler of this.typingIndicatorHandlers) {
2670
+ try {
2671
+ handler(indicator);
2672
+ } catch (e) {
2673
+ this.log("Typing handler error:", e);
2674
+ }
2675
+ }
2676
+ return;
2677
+ }
2678
+ } catch {
2679
+ }
2680
+ if (!(0, import_nostr_js_sdk.isChatMessage)(pm)) {
2681
+ this.log("Skipping unknown message kind:", pm.kind);
2191
2682
  return;
2192
2683
  }
2193
2684
  let content = pm.content;
@@ -2202,7 +2693,9 @@ var NostrTransportProvider = class {
2202
2693
  }
2203
2694
  this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
2204
2695
  const message = {
2205
- id: pm.eventId,
2696
+ // Use outer gift wrap event.id so it matches the sender's stored giftWrap.id.
2697
+ // This ensures read receipts reference an ID the sender recognizes.
2698
+ id: event.id,
2206
2699
  senderTransportPubkey: pm.senderPubkey,
2207
2700
  senderNametag,
2208
2701
  content,
@@ -2210,12 +2703,17 @@ var NostrTransportProvider = class {
2210
2703
  encrypted: true
2211
2704
  };
2212
2705
  this.emitEvent({ type: "message:received", timestamp: Date.now() });
2213
- this.log("Dispatching to", this.messageHandlers.size, "handlers");
2214
- for (const handler of this.messageHandlers) {
2215
- try {
2216
- handler(message);
2217
- } catch (error) {
2218
- this.log("Message handler error:", error);
2706
+ if (this.messageHandlers.size === 0) {
2707
+ this.log("No message handlers registered, buffering message for later delivery");
2708
+ this.pendingMessages.push(message);
2709
+ } else {
2710
+ this.log("Dispatching to", this.messageHandlers.size, "handlers");
2711
+ for (const handler of this.messageHandlers) {
2712
+ try {
2713
+ handler(message);
2714
+ } catch (error) {
2715
+ this.log("Message handler error:", error);
2716
+ }
2219
2717
  }
2220
2718
  }
2221
2719
  } catch (err) {
@@ -2619,6 +3117,39 @@ var NostrTransportProvider = class {
2619
3117
  }
2620
3118
  }
2621
3119
  }
3120
+ /**
3121
+ * Create a NIP-17 gift wrap with a custom inner rumor kind.
3122
+ * Replicates the three-layer NIP-59 envelope (rumor → seal → gift wrap)
3123
+ * because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
3124
+ */
3125
+ createCustomKindGiftWrap(recipientPubkeyHex, content, rumorKind) {
3126
+ const senderPubkey = this.keyManager.getPublicKeyHex();
3127
+ const now = Math.floor(Date.now() / 1e3);
3128
+ const rumorTags = [["p", recipientPubkeyHex]];
3129
+ const rumorSerialized = JSON.stringify([0, senderPubkey, now, rumorKind, rumorTags, content]);
3130
+ const rumorId = bytesToHex(sha256(new TextEncoder().encode(rumorSerialized)));
3131
+ const rumor = { id: rumorId, pubkey: senderPubkey, created_at: now, kind: rumorKind, tags: rumorTags, content };
3132
+ const recipientPubkeyBytes = hexToBytes(recipientPubkeyHex);
3133
+ const encryptedRumor = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(rumor), this.keyManager.getPrivateKey(), recipientPubkeyBytes);
3134
+ const sealTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
3135
+ const seal = import_nostr_js_sdk.Event.create(this.keyManager, {
3136
+ kind: import_nostr_js_sdk.EventKinds.SEAL,
3137
+ tags: [],
3138
+ content: encryptedRumor,
3139
+ created_at: sealTimestamp
3140
+ });
3141
+ const ephemeralKeys = import_nostr_js_sdk.NostrKeyManager.generate();
3142
+ const encryptedSeal = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(seal.toJSON()), ephemeralKeys.getPrivateKey(), recipientPubkeyBytes);
3143
+ const wrapTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
3144
+ const giftWrap = import_nostr_js_sdk.Event.create(ephemeralKeys, {
3145
+ kind: import_nostr_js_sdk.EventKinds.GIFT_WRAP,
3146
+ tags: [["p", recipientPubkeyHex]],
3147
+ content: encryptedSeal,
3148
+ created_at: wrapTimestamp
3149
+ });
3150
+ ephemeralKeys.clear();
3151
+ return giftWrap;
3152
+ }
2622
3153
  log(...args) {
2623
3154
  if (this.config.debug) {
2624
3155
  console.log("[NostrTransportProvider]", ...args);
@@ -3248,7 +3779,7 @@ async function loadLibp2pModules() {
3248
3779
  };
3249
3780
  }
3250
3781
  function deriveEd25519KeyMaterial(privateKeyHex, info = IPNS_HKDF_INFO) {
3251
- const walletSecret = hexToBytes(privateKeyHex);
3782
+ const walletSecret = hexToBytes2(privateKeyHex);
3252
3783
  const infoBytes = new TextEncoder().encode(info);
3253
3784
  return hkdf(sha256, walletSecret, void 0, infoBytes, 32);
3254
3785
  }
@@ -5746,6 +6277,11 @@ function resolveGroupChatConfig(network, config) {
5746
6277
  relays: config.relays ?? [...netConfig.groupRelays]
5747
6278
  };
5748
6279
  }
6280
+ function resolveMarketConfig(config) {
6281
+ if (!config) return void 0;
6282
+ if (config === true) return {};
6283
+ return { apiUrl: config.apiUrl, timeout: config.timeout };
6284
+ }
5749
6285
 
5750
6286
  // impl/browser/index.ts
5751
6287
  if (typeof globalThis.Buffer === "undefined") {
@@ -5803,7 +6339,7 @@ function createBrowserProviders(config) {
5803
6339
  const oracleConfig = resolveOracleConfig(network, config?.oracle);
5804
6340
  const l1Config = resolveL1Config(network, config?.l1);
5805
6341
  const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
5806
- const storage = createLocalStorageProvider(config?.storage);
6342
+ const storage = createIndexedDBStorageProvider(config?.storage);
5807
6343
  const priceConfig = resolvePriceConfig(config?.price, storage);
5808
6344
  const ipfsConfig = tokenSyncConfig?.ipfs;
5809
6345
  const ipfsTokenStorage = ipfsConfig?.enabled ? createBrowserIpfsStorageProvider({
@@ -5812,11 +6348,13 @@ function createBrowserProviders(config) {
5812
6348
  // reuse debug-like flag
5813
6349
  }) : void 0;
5814
6350
  const groupChat = resolveGroupChatConfig(network, config?.groupChat);
6351
+ const market = resolveMarketConfig(config?.market);
5815
6352
  const networkConfig = getNetworkConfig(network);
5816
6353
  TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
5817
6354
  return {
5818
6355
  storage,
5819
6356
  groupChat,
6357
+ market,
5820
6358
  transport: createNostrTransportProvider({
5821
6359
  relays: transportConfig.relays,
5822
6360
  timeout: transportConfig.timeout,