@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
@@ -597,6 +597,52 @@ function createView(arr) {
597
597
  function rotr(word, shift) {
598
598
  return word << 32 - shift | word >>> shift;
599
599
  }
600
+ var hasHexBuiltin = /* @__PURE__ */ (() => (
601
+ // @ts-ignore
602
+ typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
603
+ ))();
604
+ var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
605
+ function bytesToHex(bytes) {
606
+ abytes(bytes);
607
+ if (hasHexBuiltin)
608
+ return bytes.toHex();
609
+ let hex = "";
610
+ for (let i = 0; i < bytes.length; i++) {
611
+ hex += hexes[bytes[i]];
612
+ }
613
+ return hex;
614
+ }
615
+ var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
616
+ function asciiToBase16(ch) {
617
+ if (ch >= asciis._0 && ch <= asciis._9)
618
+ return ch - asciis._0;
619
+ if (ch >= asciis.A && ch <= asciis.F)
620
+ return ch - (asciis.A - 10);
621
+ if (ch >= asciis.a && ch <= asciis.f)
622
+ return ch - (asciis.a - 10);
623
+ return;
624
+ }
625
+ function hexToBytes(hex) {
626
+ if (typeof hex !== "string")
627
+ throw new Error("hex string expected, got " + typeof hex);
628
+ if (hasHexBuiltin)
629
+ return Uint8Array.fromHex(hex);
630
+ const hl = hex.length;
631
+ const al = hl / 2;
632
+ if (hl % 2)
633
+ throw new Error("hex string expected, got unpadded hex of length " + hl);
634
+ const array = new Uint8Array(al);
635
+ for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
636
+ const n1 = asciiToBase16(hex.charCodeAt(hi));
637
+ const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
638
+ if (n1 === void 0 || n2 === void 0) {
639
+ const char = hex[hi] + hex[hi + 1];
640
+ throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
641
+ }
642
+ array[ai] = n1 * 16 + n2;
643
+ }
644
+ return array;
645
+ }
600
646
  function createHasher(hashCons, info = {}) {
601
647
  const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
602
648
  const tmp = hashCons(void 0);
@@ -1092,7 +1138,7 @@ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
1092
1138
  const programBytes = hash160ToBytes(pubKeyHash);
1093
1139
  return encodeBech32(prefix, witnessVersion, programBytes);
1094
1140
  }
1095
- function hexToBytes(hex) {
1141
+ function hexToBytes2(hex) {
1096
1142
  const matches = hex.match(/../g);
1097
1143
  if (!matches) {
1098
1144
  return new Uint8Array(0);
@@ -1119,6 +1165,8 @@ function defaultUUIDGenerator() {
1119
1165
  }
1120
1166
 
1121
1167
  // transport/NostrTransportProvider.ts
1168
+ var COMPOSING_INDICATOR_KIND = 25050;
1169
+ var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
1122
1170
  var EVENT_KINDS = NOSTR_EVENT_KINDS;
1123
1171
  function hashAddressForTag(address) {
1124
1172
  const bytes = new TextEncoder().encode("unicity:address:" + address);
@@ -1199,6 +1247,10 @@ var NostrTransportProvider = class {
1199
1247
  transferHandlers = /* @__PURE__ */ new Set();
1200
1248
  paymentRequestHandlers = /* @__PURE__ */ new Set();
1201
1249
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1250
+ readReceiptHandlers = /* @__PURE__ */ new Set();
1251
+ typingIndicatorHandlers = /* @__PURE__ */ new Set();
1252
+ composingHandlers = /* @__PURE__ */ new Set();
1253
+ pendingMessages = [];
1202
1254
  broadcastHandlers = /* @__PURE__ */ new Map();
1203
1255
  eventCallbacks = /* @__PURE__ */ new Set();
1204
1256
  constructor(config) {
@@ -1450,6 +1502,18 @@ var NostrTransportProvider = class {
1450
1502
  const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1451
1503
  const giftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1452
1504
  await this.publishEvent(giftWrap);
1505
+ const selfWrapContent = JSON.stringify({
1506
+ selfWrap: true,
1507
+ originalId: giftWrap.id,
1508
+ recipientPubkey,
1509
+ senderNametag,
1510
+ text: content
1511
+ });
1512
+ const selfPubkey = this.keyManager.getPublicKeyHex();
1513
+ const selfGiftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
1514
+ this.publishEvent(selfGiftWrap).catch((err) => {
1515
+ this.log("Self-wrap publish failed:", err);
1516
+ });
1453
1517
  this.emitEvent({
1454
1518
  type: "message:sent",
1455
1519
  timestamp: Date.now(),
@@ -1459,6 +1523,18 @@ var NostrTransportProvider = class {
1459
1523
  }
1460
1524
  onMessage(handler) {
1461
1525
  this.messageHandlers.add(handler);
1526
+ if (this.pendingMessages.length > 0) {
1527
+ const pending = this.pendingMessages;
1528
+ this.pendingMessages = [];
1529
+ this.log("Flushing", pending.length, "buffered messages to new handler");
1530
+ for (const message of pending) {
1531
+ try {
1532
+ handler(message);
1533
+ } catch (error) {
1534
+ this.log("Message handler error (buffered):", error);
1535
+ }
1536
+ }
1537
+ }
1462
1538
  return () => this.messageHandlers.delete(handler);
1463
1539
  }
1464
1540
  async sendTokenTransfer(recipientPubkey, payload) {
@@ -1548,6 +1624,50 @@ var NostrTransportProvider = class {
1548
1624
  this.paymentRequestResponseHandlers.add(handler);
1549
1625
  return () => this.paymentRequestResponseHandlers.delete(handler);
1550
1626
  }
1627
+ // ===========================================================================
1628
+ // Read Receipts
1629
+ // ===========================================================================
1630
+ async sendReadReceipt(recipientTransportPubkey, messageEventId) {
1631
+ if (!this.keyManager) throw new Error("Not initialized");
1632
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1633
+ const event = import_nostr_js_sdk.NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
1634
+ await this.publishEvent(event);
1635
+ this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
1636
+ }
1637
+ onReadReceipt(handler) {
1638
+ this.readReceiptHandlers.add(handler);
1639
+ return () => this.readReceiptHandlers.delete(handler);
1640
+ }
1641
+ // ===========================================================================
1642
+ // Typing Indicators
1643
+ // ===========================================================================
1644
+ async sendTypingIndicator(recipientTransportPubkey) {
1645
+ if (!this.keyManager) throw new Error("Not initialized");
1646
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1647
+ const content = JSON.stringify({
1648
+ type: "typing",
1649
+ senderNametag: this.identity?.nametag
1650
+ });
1651
+ const event = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
1652
+ await this.publishEvent(event);
1653
+ }
1654
+ onTypingIndicator(handler) {
1655
+ this.typingIndicatorHandlers.add(handler);
1656
+ return () => this.typingIndicatorHandlers.delete(handler);
1657
+ }
1658
+ // ===========================================================================
1659
+ // Composing Indicators (NIP-59 kind 25050)
1660
+ // ===========================================================================
1661
+ onComposing(handler) {
1662
+ this.composingHandlers.add(handler);
1663
+ return () => this.composingHandlers.delete(handler);
1664
+ }
1665
+ async sendComposingIndicator(recipientPubkey, content) {
1666
+ this.ensureReady();
1667
+ const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
1668
+ const giftWrap = this.createCustomKindGiftWrap(nostrRecipient, content, COMPOSING_INDICATOR_KIND);
1669
+ await this.publishEvent(giftWrap);
1670
+ }
1551
1671
  /**
1552
1672
  * Resolve any identifier to full peer information.
1553
1673
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -2001,11 +2121,98 @@ var NostrTransportProvider = class {
2001
2121
  const pm = import_nostr_js_sdk.NIP17.unwrap(event, this.keyManager);
2002
2122
  this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
2003
2123
  if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
2004
- this.log("Skipping own message");
2124
+ try {
2125
+ const parsed = JSON.parse(pm.content);
2126
+ if (parsed?.selfWrap && parsed.recipientPubkey) {
2127
+ this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
2128
+ const message2 = {
2129
+ id: parsed.originalId || pm.eventId,
2130
+ senderTransportPubkey: pm.senderPubkey,
2131
+ senderNametag: parsed.senderNametag,
2132
+ recipientTransportPubkey: parsed.recipientPubkey,
2133
+ content: parsed.text ?? "",
2134
+ timestamp: pm.timestamp * 1e3,
2135
+ encrypted: true,
2136
+ isSelfWrap: true
2137
+ };
2138
+ for (const handler of this.messageHandlers) {
2139
+ try {
2140
+ handler(message2);
2141
+ } catch (e) {
2142
+ this.log("Self-wrap handler error:", e);
2143
+ }
2144
+ }
2145
+ return;
2146
+ }
2147
+ } catch {
2148
+ }
2149
+ this.log("Skipping own non-self-wrap message");
2150
+ return;
2151
+ }
2152
+ if ((0, import_nostr_js_sdk.isReadReceipt)(pm)) {
2153
+ this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
2154
+ if (pm.replyToEventId) {
2155
+ const receipt = {
2156
+ senderTransportPubkey: pm.senderPubkey,
2157
+ messageEventId: pm.replyToEventId,
2158
+ timestamp: pm.timestamp * 1e3
2159
+ };
2160
+ for (const handler of this.readReceiptHandlers) {
2161
+ try {
2162
+ handler(receipt);
2163
+ } catch (e) {
2164
+ this.log("Read receipt handler error:", e);
2165
+ }
2166
+ }
2167
+ }
2168
+ return;
2169
+ }
2170
+ if (pm.kind === COMPOSING_INDICATOR_KIND) {
2171
+ let senderNametag2;
2172
+ let expiresIn = 3e4;
2173
+ try {
2174
+ const parsed = JSON.parse(pm.content);
2175
+ senderNametag2 = parsed.senderNametag || void 0;
2176
+ expiresIn = parsed.expiresIn ?? 3e4;
2177
+ } catch {
2178
+ }
2179
+ const indicator = {
2180
+ senderPubkey: pm.senderPubkey,
2181
+ senderNametag: senderNametag2,
2182
+ expiresIn
2183
+ };
2184
+ this.log("Composing indicator from:", indicator.senderNametag || pm.senderPubkey?.slice(0, 16));
2185
+ for (const handler of this.composingHandlers) {
2186
+ try {
2187
+ handler(indicator);
2188
+ } catch (e) {
2189
+ this.log("Composing handler error:", e);
2190
+ }
2191
+ }
2005
2192
  return;
2006
2193
  }
2007
- if (pm.kind !== import_nostr_js_sdk.EventKinds.CHAT_MESSAGE) {
2008
- this.log("Skipping non-chat message, kind:", pm.kind);
2194
+ try {
2195
+ const parsed = JSON.parse(pm.content);
2196
+ if (parsed?.type === "typing") {
2197
+ this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
2198
+ const indicator = {
2199
+ senderTransportPubkey: pm.senderPubkey,
2200
+ senderNametag: parsed.senderNametag,
2201
+ timestamp: pm.timestamp * 1e3
2202
+ };
2203
+ for (const handler of this.typingIndicatorHandlers) {
2204
+ try {
2205
+ handler(indicator);
2206
+ } catch (e) {
2207
+ this.log("Typing handler error:", e);
2208
+ }
2209
+ }
2210
+ return;
2211
+ }
2212
+ } catch {
2213
+ }
2214
+ if (!(0, import_nostr_js_sdk.isChatMessage)(pm)) {
2215
+ this.log("Skipping unknown message kind:", pm.kind);
2009
2216
  return;
2010
2217
  }
2011
2218
  let content = pm.content;
@@ -2020,7 +2227,9 @@ var NostrTransportProvider = class {
2020
2227
  }
2021
2228
  this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
2022
2229
  const message = {
2023
- id: pm.eventId,
2230
+ // Use outer gift wrap event.id so it matches the sender's stored giftWrap.id.
2231
+ // This ensures read receipts reference an ID the sender recognizes.
2232
+ id: event.id,
2024
2233
  senderTransportPubkey: pm.senderPubkey,
2025
2234
  senderNametag,
2026
2235
  content,
@@ -2028,12 +2237,17 @@ var NostrTransportProvider = class {
2028
2237
  encrypted: true
2029
2238
  };
2030
2239
  this.emitEvent({ type: "message:received", timestamp: Date.now() });
2031
- this.log("Dispatching to", this.messageHandlers.size, "handlers");
2032
- for (const handler of this.messageHandlers) {
2033
- try {
2034
- handler(message);
2035
- } catch (error) {
2036
- this.log("Message handler error:", error);
2240
+ if (this.messageHandlers.size === 0) {
2241
+ this.log("No message handlers registered, buffering message for later delivery");
2242
+ this.pendingMessages.push(message);
2243
+ } else {
2244
+ this.log("Dispatching to", this.messageHandlers.size, "handlers");
2245
+ for (const handler of this.messageHandlers) {
2246
+ try {
2247
+ handler(message);
2248
+ } catch (error) {
2249
+ this.log("Message handler error:", error);
2250
+ }
2037
2251
  }
2038
2252
  }
2039
2253
  } catch (err) {
@@ -2437,6 +2651,39 @@ var NostrTransportProvider = class {
2437
2651
  }
2438
2652
  }
2439
2653
  }
2654
+ /**
2655
+ * Create a NIP-17 gift wrap with a custom inner rumor kind.
2656
+ * Replicates the three-layer NIP-59 envelope (rumor → seal → gift wrap)
2657
+ * because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
2658
+ */
2659
+ createCustomKindGiftWrap(recipientPubkeyHex, content, rumorKind) {
2660
+ const senderPubkey = this.keyManager.getPublicKeyHex();
2661
+ const now = Math.floor(Date.now() / 1e3);
2662
+ const rumorTags = [["p", recipientPubkeyHex]];
2663
+ const rumorSerialized = JSON.stringify([0, senderPubkey, now, rumorKind, rumorTags, content]);
2664
+ const rumorId = bytesToHex(sha256(new TextEncoder().encode(rumorSerialized)));
2665
+ const rumor = { id: rumorId, pubkey: senderPubkey, created_at: now, kind: rumorKind, tags: rumorTags, content };
2666
+ const recipientPubkeyBytes = hexToBytes(recipientPubkeyHex);
2667
+ const encryptedRumor = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(rumor), this.keyManager.getPrivateKey(), recipientPubkeyBytes);
2668
+ const sealTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
2669
+ const seal = import_nostr_js_sdk.Event.create(this.keyManager, {
2670
+ kind: import_nostr_js_sdk.EventKinds.SEAL,
2671
+ tags: [],
2672
+ content: encryptedRumor,
2673
+ created_at: sealTimestamp
2674
+ });
2675
+ const ephemeralKeys = import_nostr_js_sdk.NostrKeyManager.generate();
2676
+ const encryptedSeal = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(seal.toJSON()), ephemeralKeys.getPrivateKey(), recipientPubkeyBytes);
2677
+ const wrapTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
2678
+ const giftWrap = import_nostr_js_sdk.Event.create(ephemeralKeys, {
2679
+ kind: import_nostr_js_sdk.EventKinds.GIFT_WRAP,
2680
+ tags: [["p", recipientPubkeyHex]],
2681
+ content: encryptedSeal,
2682
+ created_at: wrapTimestamp
2683
+ });
2684
+ ephemeralKeys.clear();
2685
+ return giftWrap;
2686
+ }
2440
2687
  log(...args) {
2441
2688
  if (this.config.debug) {
2442
2689
  console.log("[NostrTransportProvider]", ...args);
@@ -3013,7 +3260,7 @@ async function loadLibp2pModules() {
3013
3260
  };
3014
3261
  }
3015
3262
  function deriveEd25519KeyMaterial(privateKeyHex, info = IPNS_HKDF_INFO) {
3016
- const walletSecret = hexToBytes(privateKeyHex);
3263
+ const walletSecret = hexToBytes2(privateKeyHex);
3017
3264
  const infoBytes = new TextEncoder().encode(info);
3018
3265
  return hkdf(sha256, walletSecret, void 0, infoBytes, 32);
3019
3266
  }
@@ -5498,6 +5745,11 @@ function resolveGroupChatConfig(network, config) {
5498
5745
  relays: config.relays ?? [...netConfig.groupRelays]
5499
5746
  };
5500
5747
  }
5748
+ function resolveMarketConfig(config) {
5749
+ if (!config) return void 0;
5750
+ if (config === true) return {};
5751
+ return { apiUrl: config.apiUrl, timeout: config.timeout };
5752
+ }
5501
5753
 
5502
5754
  // impl/nodejs/index.ts
5503
5755
  function createNodeProviders(config) {
@@ -5513,11 +5765,13 @@ function createNodeProviders(config) {
5513
5765
  const ipfsSync = config?.tokenSync?.ipfs;
5514
5766
  const ipfsTokenStorage = ipfsSync?.enabled ? createNodeIpfsStorageProvider(ipfsSync.config, storage) : void 0;
5515
5767
  const groupChat = resolveGroupChatConfig(network, config?.groupChat);
5768
+ const market = resolveMarketConfig(config?.market);
5516
5769
  const networkConfig = getNetworkConfig(network);
5517
5770
  TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
5518
5771
  return {
5519
5772
  storage,
5520
5773
  groupChat,
5774
+ market,
5521
5775
  tokenStorage: createFileTokenStorageProvider({
5522
5776
  tokensDir: config?.tokensDir ?? "./sphere-tokens"
5523
5777
  }),