@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
@@ -548,6 +548,52 @@ function createView(arr) {
548
548
  function rotr(word, shift) {
549
549
  return word << 32 - shift | word >>> shift;
550
550
  }
551
+ var hasHexBuiltin = /* @__PURE__ */ (() => (
552
+ // @ts-ignore
553
+ typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
554
+ ))();
555
+ var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
556
+ function bytesToHex(bytes) {
557
+ abytes(bytes);
558
+ if (hasHexBuiltin)
559
+ return bytes.toHex();
560
+ let hex = "";
561
+ for (let i = 0; i < bytes.length; i++) {
562
+ hex += hexes[bytes[i]];
563
+ }
564
+ return hex;
565
+ }
566
+ var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
567
+ function asciiToBase16(ch) {
568
+ if (ch >= asciis._0 && ch <= asciis._9)
569
+ return ch - asciis._0;
570
+ if (ch >= asciis.A && ch <= asciis.F)
571
+ return ch - (asciis.A - 10);
572
+ if (ch >= asciis.a && ch <= asciis.f)
573
+ return ch - (asciis.a - 10);
574
+ return;
575
+ }
576
+ function hexToBytes(hex) {
577
+ if (typeof hex !== "string")
578
+ throw new Error("hex string expected, got " + typeof hex);
579
+ if (hasHexBuiltin)
580
+ return Uint8Array.fromHex(hex);
581
+ const hl = hex.length;
582
+ const al = hl / 2;
583
+ if (hl % 2)
584
+ throw new Error("hex string expected, got unpadded hex of length " + hl);
585
+ const array = new Uint8Array(al);
586
+ for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
587
+ const n1 = asciiToBase16(hex.charCodeAt(hi));
588
+ const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
589
+ if (n1 === void 0 || n2 === void 0) {
590
+ const char = hex[hi] + hex[hi + 1];
591
+ throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
592
+ }
593
+ array[ai] = n1 * 16 + n2;
594
+ }
595
+ return array;
596
+ }
551
597
  function createHasher(hashCons, info = {}) {
552
598
  const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
553
599
  const tmp = hashCons(void 0);
@@ -937,11 +983,14 @@ import {
937
983
  NostrKeyManager,
938
984
  NIP04,
939
985
  NIP17,
986
+ NIP44,
940
987
  Event as NostrEventClass,
941
988
  EventKinds,
942
989
  hashNametag,
943
990
  NostrClient,
944
- Filter
991
+ Filter,
992
+ isChatMessage,
993
+ isReadReceipt
945
994
  } from "@unicitylabs/nostr-js-sdk";
946
995
 
947
996
  // core/crypto.ts
@@ -1052,7 +1101,7 @@ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
1052
1101
  const programBytes = hash160ToBytes(pubKeyHash);
1053
1102
  return encodeBech32(prefix, witnessVersion, programBytes);
1054
1103
  }
1055
- function hexToBytes(hex) {
1104
+ function hexToBytes2(hex) {
1056
1105
  const matches = hex.match(/../g);
1057
1106
  if (!matches) {
1058
1107
  return new Uint8Array(0);
@@ -1079,6 +1128,8 @@ function defaultUUIDGenerator() {
1079
1128
  }
1080
1129
 
1081
1130
  // transport/NostrTransportProvider.ts
1131
+ var COMPOSING_INDICATOR_KIND = 25050;
1132
+ var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
1082
1133
  var EVENT_KINDS = NOSTR_EVENT_KINDS;
1083
1134
  function hashAddressForTag(address) {
1084
1135
  const bytes = new TextEncoder().encode("unicity:address:" + address);
@@ -1159,6 +1210,10 @@ var NostrTransportProvider = class {
1159
1210
  transferHandlers = /* @__PURE__ */ new Set();
1160
1211
  paymentRequestHandlers = /* @__PURE__ */ new Set();
1161
1212
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1213
+ readReceiptHandlers = /* @__PURE__ */ new Set();
1214
+ typingIndicatorHandlers = /* @__PURE__ */ new Set();
1215
+ composingHandlers = /* @__PURE__ */ new Set();
1216
+ pendingMessages = [];
1162
1217
  broadcastHandlers = /* @__PURE__ */ new Map();
1163
1218
  eventCallbacks = /* @__PURE__ */ new Set();
1164
1219
  constructor(config) {
@@ -1410,6 +1465,18 @@ var NostrTransportProvider = class {
1410
1465
  const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1411
1466
  const giftWrap = NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1412
1467
  await this.publishEvent(giftWrap);
1468
+ const selfWrapContent = JSON.stringify({
1469
+ selfWrap: true,
1470
+ originalId: giftWrap.id,
1471
+ recipientPubkey,
1472
+ senderNametag,
1473
+ text: content
1474
+ });
1475
+ const selfPubkey = this.keyManager.getPublicKeyHex();
1476
+ const selfGiftWrap = NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
1477
+ this.publishEvent(selfGiftWrap).catch((err) => {
1478
+ this.log("Self-wrap publish failed:", err);
1479
+ });
1413
1480
  this.emitEvent({
1414
1481
  type: "message:sent",
1415
1482
  timestamp: Date.now(),
@@ -1419,6 +1486,18 @@ var NostrTransportProvider = class {
1419
1486
  }
1420
1487
  onMessage(handler) {
1421
1488
  this.messageHandlers.add(handler);
1489
+ if (this.pendingMessages.length > 0) {
1490
+ const pending = this.pendingMessages;
1491
+ this.pendingMessages = [];
1492
+ this.log("Flushing", pending.length, "buffered messages to new handler");
1493
+ for (const message of pending) {
1494
+ try {
1495
+ handler(message);
1496
+ } catch (error) {
1497
+ this.log("Message handler error (buffered):", error);
1498
+ }
1499
+ }
1500
+ }
1422
1501
  return () => this.messageHandlers.delete(handler);
1423
1502
  }
1424
1503
  async sendTokenTransfer(recipientPubkey, payload) {
@@ -1508,6 +1587,50 @@ var NostrTransportProvider = class {
1508
1587
  this.paymentRequestResponseHandlers.add(handler);
1509
1588
  return () => this.paymentRequestResponseHandlers.delete(handler);
1510
1589
  }
1590
+ // ===========================================================================
1591
+ // Read Receipts
1592
+ // ===========================================================================
1593
+ async sendReadReceipt(recipientTransportPubkey, messageEventId) {
1594
+ if (!this.keyManager) throw new Error("Not initialized");
1595
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1596
+ const event = NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
1597
+ await this.publishEvent(event);
1598
+ this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
1599
+ }
1600
+ onReadReceipt(handler) {
1601
+ this.readReceiptHandlers.add(handler);
1602
+ return () => this.readReceiptHandlers.delete(handler);
1603
+ }
1604
+ // ===========================================================================
1605
+ // Typing Indicators
1606
+ // ===========================================================================
1607
+ async sendTypingIndicator(recipientTransportPubkey) {
1608
+ if (!this.keyManager) throw new Error("Not initialized");
1609
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1610
+ const content = JSON.stringify({
1611
+ type: "typing",
1612
+ senderNametag: this.identity?.nametag
1613
+ });
1614
+ const event = NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
1615
+ await this.publishEvent(event);
1616
+ }
1617
+ onTypingIndicator(handler) {
1618
+ this.typingIndicatorHandlers.add(handler);
1619
+ return () => this.typingIndicatorHandlers.delete(handler);
1620
+ }
1621
+ // ===========================================================================
1622
+ // Composing Indicators (NIP-59 kind 25050)
1623
+ // ===========================================================================
1624
+ onComposing(handler) {
1625
+ this.composingHandlers.add(handler);
1626
+ return () => this.composingHandlers.delete(handler);
1627
+ }
1628
+ async sendComposingIndicator(recipientPubkey, content) {
1629
+ this.ensureReady();
1630
+ const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
1631
+ const giftWrap = this.createCustomKindGiftWrap(nostrRecipient, content, COMPOSING_INDICATOR_KIND);
1632
+ await this.publishEvent(giftWrap);
1633
+ }
1511
1634
  /**
1512
1635
  * Resolve any identifier to full peer information.
1513
1636
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -1961,11 +2084,98 @@ var NostrTransportProvider = class {
1961
2084
  const pm = NIP17.unwrap(event, this.keyManager);
1962
2085
  this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
1963
2086
  if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
1964
- this.log("Skipping own message");
2087
+ try {
2088
+ const parsed = JSON.parse(pm.content);
2089
+ if (parsed?.selfWrap && parsed.recipientPubkey) {
2090
+ this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
2091
+ const message2 = {
2092
+ id: parsed.originalId || pm.eventId,
2093
+ senderTransportPubkey: pm.senderPubkey,
2094
+ senderNametag: parsed.senderNametag,
2095
+ recipientTransportPubkey: parsed.recipientPubkey,
2096
+ content: parsed.text ?? "",
2097
+ timestamp: pm.timestamp * 1e3,
2098
+ encrypted: true,
2099
+ isSelfWrap: true
2100
+ };
2101
+ for (const handler of this.messageHandlers) {
2102
+ try {
2103
+ handler(message2);
2104
+ } catch (e) {
2105
+ this.log("Self-wrap handler error:", e);
2106
+ }
2107
+ }
2108
+ return;
2109
+ }
2110
+ } catch {
2111
+ }
2112
+ this.log("Skipping own non-self-wrap message");
2113
+ return;
2114
+ }
2115
+ if (isReadReceipt(pm)) {
2116
+ this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
2117
+ if (pm.replyToEventId) {
2118
+ const receipt = {
2119
+ senderTransportPubkey: pm.senderPubkey,
2120
+ messageEventId: pm.replyToEventId,
2121
+ timestamp: pm.timestamp * 1e3
2122
+ };
2123
+ for (const handler of this.readReceiptHandlers) {
2124
+ try {
2125
+ handler(receipt);
2126
+ } catch (e) {
2127
+ this.log("Read receipt handler error:", e);
2128
+ }
2129
+ }
2130
+ }
2131
+ return;
2132
+ }
2133
+ if (pm.kind === COMPOSING_INDICATOR_KIND) {
2134
+ let senderNametag2;
2135
+ let expiresIn = 3e4;
2136
+ try {
2137
+ const parsed = JSON.parse(pm.content);
2138
+ senderNametag2 = parsed.senderNametag || void 0;
2139
+ expiresIn = parsed.expiresIn ?? 3e4;
2140
+ } catch {
2141
+ }
2142
+ const indicator = {
2143
+ senderPubkey: pm.senderPubkey,
2144
+ senderNametag: senderNametag2,
2145
+ expiresIn
2146
+ };
2147
+ this.log("Composing indicator from:", indicator.senderNametag || pm.senderPubkey?.slice(0, 16));
2148
+ for (const handler of this.composingHandlers) {
2149
+ try {
2150
+ handler(indicator);
2151
+ } catch (e) {
2152
+ this.log("Composing handler error:", e);
2153
+ }
2154
+ }
1965
2155
  return;
1966
2156
  }
1967
- if (pm.kind !== EventKinds.CHAT_MESSAGE) {
1968
- this.log("Skipping non-chat message, kind:", pm.kind);
2157
+ try {
2158
+ const parsed = JSON.parse(pm.content);
2159
+ if (parsed?.type === "typing") {
2160
+ this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
2161
+ const indicator = {
2162
+ senderTransportPubkey: pm.senderPubkey,
2163
+ senderNametag: parsed.senderNametag,
2164
+ timestamp: pm.timestamp * 1e3
2165
+ };
2166
+ for (const handler of this.typingIndicatorHandlers) {
2167
+ try {
2168
+ handler(indicator);
2169
+ } catch (e) {
2170
+ this.log("Typing handler error:", e);
2171
+ }
2172
+ }
2173
+ return;
2174
+ }
2175
+ } catch {
2176
+ }
2177
+ if (!isChatMessage(pm)) {
2178
+ this.log("Skipping unknown message kind:", pm.kind);
1969
2179
  return;
1970
2180
  }
1971
2181
  let content = pm.content;
@@ -1980,7 +2190,9 @@ var NostrTransportProvider = class {
1980
2190
  }
1981
2191
  this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
1982
2192
  const message = {
1983
- id: pm.eventId,
2193
+ // Use outer gift wrap event.id so it matches the sender's stored giftWrap.id.
2194
+ // This ensures read receipts reference an ID the sender recognizes.
2195
+ id: event.id,
1984
2196
  senderTransportPubkey: pm.senderPubkey,
1985
2197
  senderNametag,
1986
2198
  content,
@@ -1988,12 +2200,17 @@ var NostrTransportProvider = class {
1988
2200
  encrypted: true
1989
2201
  };
1990
2202
  this.emitEvent({ type: "message:received", timestamp: Date.now() });
1991
- this.log("Dispatching to", this.messageHandlers.size, "handlers");
1992
- for (const handler of this.messageHandlers) {
1993
- try {
1994
- handler(message);
1995
- } catch (error) {
1996
- this.log("Message handler error:", error);
2203
+ if (this.messageHandlers.size === 0) {
2204
+ this.log("No message handlers registered, buffering message for later delivery");
2205
+ this.pendingMessages.push(message);
2206
+ } else {
2207
+ this.log("Dispatching to", this.messageHandlers.size, "handlers");
2208
+ for (const handler of this.messageHandlers) {
2209
+ try {
2210
+ handler(message);
2211
+ } catch (error) {
2212
+ this.log("Message handler error:", error);
2213
+ }
1997
2214
  }
1998
2215
  }
1999
2216
  } catch (err) {
@@ -2397,6 +2614,39 @@ var NostrTransportProvider = class {
2397
2614
  }
2398
2615
  }
2399
2616
  }
2617
+ /**
2618
+ * Create a NIP-17 gift wrap with a custom inner rumor kind.
2619
+ * Replicates the three-layer NIP-59 envelope (rumor → seal → gift wrap)
2620
+ * because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
2621
+ */
2622
+ createCustomKindGiftWrap(recipientPubkeyHex, content, rumorKind) {
2623
+ const senderPubkey = this.keyManager.getPublicKeyHex();
2624
+ const now = Math.floor(Date.now() / 1e3);
2625
+ const rumorTags = [["p", recipientPubkeyHex]];
2626
+ const rumorSerialized = JSON.stringify([0, senderPubkey, now, rumorKind, rumorTags, content]);
2627
+ const rumorId = bytesToHex(sha256(new TextEncoder().encode(rumorSerialized)));
2628
+ const rumor = { id: rumorId, pubkey: senderPubkey, created_at: now, kind: rumorKind, tags: rumorTags, content };
2629
+ const recipientPubkeyBytes = hexToBytes(recipientPubkeyHex);
2630
+ const encryptedRumor = NIP44.encrypt(JSON.stringify(rumor), this.keyManager.getPrivateKey(), recipientPubkeyBytes);
2631
+ const sealTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
2632
+ const seal = NostrEventClass.create(this.keyManager, {
2633
+ kind: EventKinds.SEAL,
2634
+ tags: [],
2635
+ content: encryptedRumor,
2636
+ created_at: sealTimestamp
2637
+ });
2638
+ const ephemeralKeys = NostrKeyManager.generate();
2639
+ const encryptedSeal = NIP44.encrypt(JSON.stringify(seal.toJSON()), ephemeralKeys.getPrivateKey(), recipientPubkeyBytes);
2640
+ const wrapTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
2641
+ const giftWrap = NostrEventClass.create(ephemeralKeys, {
2642
+ kind: EventKinds.GIFT_WRAP,
2643
+ tags: [["p", recipientPubkeyHex]],
2644
+ content: encryptedSeal,
2645
+ created_at: wrapTimestamp
2646
+ });
2647
+ ephemeralKeys.clear();
2648
+ return giftWrap;
2649
+ }
2400
2650
  log(...args) {
2401
2651
  if (this.config.debug) {
2402
2652
  console.log("[NostrTransportProvider]", ...args);
@@ -2973,7 +3223,7 @@ async function loadLibp2pModules() {
2973
3223
  };
2974
3224
  }
2975
3225
  function deriveEd25519KeyMaterial(privateKeyHex, info = IPNS_HKDF_INFO) {
2976
- const walletSecret = hexToBytes(privateKeyHex);
3226
+ const walletSecret = hexToBytes2(privateKeyHex);
2977
3227
  const infoBytes = new TextEncoder().encode(info);
2978
3228
  return hkdf(sha256, walletSecret, void 0, infoBytes, 32);
2979
3229
  }
@@ -5458,6 +5708,11 @@ function resolveGroupChatConfig(network, config) {
5458
5708
  relays: config.relays ?? [...netConfig.groupRelays]
5459
5709
  };
5460
5710
  }
5711
+ function resolveMarketConfig(config) {
5712
+ if (!config) return void 0;
5713
+ if (config === true) return {};
5714
+ return { apiUrl: config.apiUrl, timeout: config.timeout };
5715
+ }
5461
5716
 
5462
5717
  // impl/nodejs/index.ts
5463
5718
  function createNodeProviders(config) {
@@ -5473,11 +5728,13 @@ function createNodeProviders(config) {
5473
5728
  const ipfsSync = config?.tokenSync?.ipfs;
5474
5729
  const ipfsTokenStorage = ipfsSync?.enabled ? createNodeIpfsStorageProvider(ipfsSync.config, storage) : void 0;
5475
5730
  const groupChat = resolveGroupChatConfig(network, config?.groupChat);
5731
+ const market = resolveMarketConfig(config?.market);
5476
5732
  const networkConfig = getNetworkConfig(network);
5477
5733
  TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
5478
5734
  return {
5479
5735
  storage,
5480
5736
  groupChat,
5737
+ market,
5481
5738
  tokenStorage: createFileTokenStorageProvider({
5482
5739
  tokensDir: config?.tokensDir ?? "./sphere-tokens"
5483
5740
  }),