@unicitylabs/sphere-sdk 0.6.8-dev.1 → 0.6.8-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +30 -0
  2. package/dist/connect/index.cjs +2 -0
  3. package/dist/connect/index.cjs.map +1 -1
  4. package/dist/connect/index.js +2 -0
  5. package/dist/connect/index.js.map +1 -1
  6. package/dist/core/index.cjs +140 -6
  7. package/dist/core/index.cjs.map +1 -1
  8. package/dist/core/index.d.cts +18 -0
  9. package/dist/core/index.d.ts +18 -0
  10. package/dist/core/index.js +140 -6
  11. package/dist/core/index.js.map +1 -1
  12. package/dist/impl/browser/connect/index.cjs +2 -0
  13. package/dist/impl/browser/connect/index.cjs.map +1 -1
  14. package/dist/impl/browser/connect/index.js +2 -0
  15. package/dist/impl/browser/connect/index.js.map +1 -1
  16. package/dist/impl/browser/index.cjs +66 -0
  17. package/dist/impl/browser/index.cjs.map +1 -1
  18. package/dist/impl/browser/index.js +66 -0
  19. package/dist/impl/browser/index.js.map +1 -1
  20. package/dist/impl/browser/ipfs.cjs +2 -0
  21. package/dist/impl/browser/ipfs.cjs.map +1 -1
  22. package/dist/impl/browser/ipfs.js +2 -0
  23. package/dist/impl/browser/ipfs.js.map +1 -1
  24. package/dist/impl/nodejs/connect/index.cjs +2 -0
  25. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  26. package/dist/impl/nodejs/connect/index.js +2 -0
  27. package/dist/impl/nodejs/connect/index.js.map +1 -1
  28. package/dist/impl/nodejs/index.cjs +66 -0
  29. package/dist/impl/nodejs/index.cjs.map +1 -1
  30. package/dist/impl/nodejs/index.d.cts +15 -0
  31. package/dist/impl/nodejs/index.d.ts +15 -0
  32. package/dist/impl/nodejs/index.js +66 -0
  33. package/dist/impl/nodejs/index.js.map +1 -1
  34. package/dist/index.cjs +140 -6
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +18 -0
  37. package/dist/index.d.ts +18 -0
  38. package/dist/index.js +140 -6
  39. package/dist/index.js.map +1 -1
  40. package/dist/l1/index.cjs +2 -0
  41. package/dist/l1/index.cjs.map +1 -1
  42. package/dist/l1/index.js +2 -0
  43. package/dist/l1/index.js.map +1 -1
  44. package/package.json +1 -1
@@ -1418,6 +1418,14 @@ interface TransportProvider extends BaseProvider {
1418
1418
  * @param sinceSeconds - Unix timestamp in seconds
1419
1419
  */
1420
1420
  setFallbackSince?(sinceSeconds: number): void;
1421
+ /**
1422
+ * Set fallback 'since' timestamp for DM (gift-wrap) subscriptions.
1423
+ * Used when no persisted DM timestamp exists in storage (e.g. first connect).
1424
+ * Consumed once by the next subscription setup, then cleared.
1425
+ *
1426
+ * @param sinceSeconds - Unix timestamp in seconds
1427
+ */
1428
+ setFallbackDmSince?(sinceSeconds: number): void;
1421
1429
  /**
1422
1430
  * Fetch pending events from transport (one-shot query).
1423
1431
  * Creates a temporary subscription, processes events through normal handlers,
@@ -1729,6 +1737,7 @@ declare class MultiAddressTransportMux {
1729
1737
  * Set fallback 'since' for an address (consumed once on next subscription setup).
1730
1738
  */
1731
1739
  setFallbackSince(index: number, sinceSeconds: number): void;
1740
+ setFallbackDmSince(index: number, sinceSeconds: number): void;
1732
1741
  connect(): Promise<void>;
1733
1742
  disconnect(): Promise<void>;
1734
1743
  isConnected(): boolean;
@@ -1806,6 +1815,8 @@ declare class MultiAddressTransportMux {
1806
1815
  */
1807
1816
  getIdentity(addressIndex: number): FullIdentity | null;
1808
1817
  private updateLastEventTimestamp;
1818
+ private updateLastDmEventTimestamp;
1819
+ private getAddressDmSince;
1809
1820
  onTransportEvent(callback: TransportEventCallback): () => void;
1810
1821
  onChatReady(handler: () => void): () => void;
1811
1822
  private emitEvent;
@@ -1887,6 +1898,7 @@ declare class AddressTransportAdapter implements TransportProvider {
1887
1898
  hasRelay(relayUrl: string): boolean;
1888
1899
  isRelayConnected(relayUrl: string): boolean;
1889
1900
  setFallbackSince(sinceSeconds: number): void;
1901
+ setFallbackDmSince(sinceSeconds: number): void;
1890
1902
  fetchPendingEvents(): Promise<void>;
1891
1903
  onChatReady(handler: () => void): () => void;
1892
1904
  dispatchMessage(message: IncomingMessage): void;
@@ -3891,6 +3903,12 @@ interface SphereInitOptions {
3891
3903
  * - false/undefined: no auto-discovery (default)
3892
3904
  */
3893
3905
  discoverAddresses?: boolean | DiscoverAddressesOptions;
3906
+ /**
3907
+ * Fallback 'since' timestamp (unix seconds) for the DM (gift-wrap) subscription.
3908
+ * Used when no persisted DM timestamp exists in storage (e.g. first connect).
3909
+ * Without this, a fresh wallet starts from "now" and misses older DMs.
3910
+ */
3911
+ dmSince?: number;
3894
3912
  /** Enable debug logging (default: false) */
3895
3913
  debug?: boolean;
3896
3914
  /** Optional callback to report initialization progress steps */
@@ -1418,6 +1418,14 @@ interface TransportProvider extends BaseProvider {
1418
1418
  * @param sinceSeconds - Unix timestamp in seconds
1419
1419
  */
1420
1420
  setFallbackSince?(sinceSeconds: number): void;
1421
+ /**
1422
+ * Set fallback 'since' timestamp for DM (gift-wrap) subscriptions.
1423
+ * Used when no persisted DM timestamp exists in storage (e.g. first connect).
1424
+ * Consumed once by the next subscription setup, then cleared.
1425
+ *
1426
+ * @param sinceSeconds - Unix timestamp in seconds
1427
+ */
1428
+ setFallbackDmSince?(sinceSeconds: number): void;
1421
1429
  /**
1422
1430
  * Fetch pending events from transport (one-shot query).
1423
1431
  * Creates a temporary subscription, processes events through normal handlers,
@@ -1729,6 +1737,7 @@ declare class MultiAddressTransportMux {
1729
1737
  * Set fallback 'since' for an address (consumed once on next subscription setup).
1730
1738
  */
1731
1739
  setFallbackSince(index: number, sinceSeconds: number): void;
1740
+ setFallbackDmSince(index: number, sinceSeconds: number): void;
1732
1741
  connect(): Promise<void>;
1733
1742
  disconnect(): Promise<void>;
1734
1743
  isConnected(): boolean;
@@ -1806,6 +1815,8 @@ declare class MultiAddressTransportMux {
1806
1815
  */
1807
1816
  getIdentity(addressIndex: number): FullIdentity | null;
1808
1817
  private updateLastEventTimestamp;
1818
+ private updateLastDmEventTimestamp;
1819
+ private getAddressDmSince;
1809
1820
  onTransportEvent(callback: TransportEventCallback): () => void;
1810
1821
  onChatReady(handler: () => void): () => void;
1811
1822
  private emitEvent;
@@ -1887,6 +1898,7 @@ declare class AddressTransportAdapter implements TransportProvider {
1887
1898
  hasRelay(relayUrl: string): boolean;
1888
1899
  isRelayConnected(relayUrl: string): boolean;
1889
1900
  setFallbackSince(sinceSeconds: number): void;
1901
+ setFallbackDmSince(sinceSeconds: number): void;
1890
1902
  fetchPendingEvents(): Promise<void>;
1891
1903
  onChatReady(handler: () => void): () => void;
1892
1904
  dispatchMessage(message: IncomingMessage): void;
@@ -3891,6 +3903,12 @@ interface SphereInitOptions {
3891
3903
  * - false/undefined: no auto-discovery (default)
3892
3904
  */
3893
3905
  discoverAddresses?: boolean | DiscoverAddressesOptions;
3906
+ /**
3907
+ * Fallback 'since' timestamp (unix seconds) for the DM (gift-wrap) subscription.
3908
+ * Used when no persisted DM timestamp exists in storage (e.g. first connect).
3909
+ * Without this, a fresh wallet starts from "now" and misses older DMs.
3910
+ */
3911
+ dmSince?: number;
3894
3912
  /** Enable debug logging (default: false) */
3895
3913
  debug?: boolean;
3896
3914
  /** Optional callback to report initialization progress steps */
@@ -175,6 +175,8 @@ var init_constants = __esm({
175
175
  TRACKED_ADDRESSES: "tracked_addresses",
176
176
  /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
177
177
  LAST_WALLET_EVENT_TS: "last_wallet_event_ts",
178
+ /** Last processed Nostr DM (gift-wrap) event timestamp (unix seconds), keyed per pubkey */
179
+ LAST_DM_EVENT_TS: "last_dm_event_ts",
178
180
  /** Group chat: last used relay URL (stale data detection) — global, same relay for all addresses */
179
181
  GROUP_CHAT_RELAY_URL: "group_chat_relay_url",
180
182
  /** Cached token registry JSON (fetched from remote) */
@@ -1234,8 +1236,12 @@ var NostrTransportProvider = class _NostrTransportProvider {
1234
1236
  storage = null;
1235
1237
  /** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
1236
1238
  lastEventTs = 0;
1239
+ /** In-memory max DM (gift-wrap) event timestamp. */
1240
+ lastDmEventTs = 0;
1237
1241
  /** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
1238
1242
  fallbackSince = null;
1243
+ /** Fallback 'since' timestamp for DM (gift-wrap) subscriptions (consumed once). */
1244
+ fallbackDmSince = null;
1239
1245
  identity = null;
1240
1246
  keyManager = null;
1241
1247
  status = "disconnected";
@@ -1490,6 +1496,8 @@ var NostrTransportProvider = class _NostrTransportProvider {
1490
1496
  this.identity = identity;
1491
1497
  this.processedEventIds.clear();
1492
1498
  this.lastEventTs = 0;
1499
+ this.lastDmEventTs = 0;
1500
+ this.fallbackDmSince = null;
1493
1501
  const secretKey = Buffer2.from(identity.privateKey, "hex");
1494
1502
  this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
1495
1503
  const nostrPubkey = this.keyManager.getPublicKeyHex();
@@ -1535,6 +1543,9 @@ var NostrTransportProvider = class _NostrTransportProvider {
1535
1543
  setFallbackSince(sinceSeconds) {
1536
1544
  this.fallbackSince = sinceSeconds;
1537
1545
  }
1546
+ setFallbackDmSince(sinceSeconds) {
1547
+ this.fallbackDmSince = sinceSeconds;
1548
+ }
1538
1549
  /**
1539
1550
  * Get the Nostr-format public key (32 bytes / 64 hex chars)
1540
1551
  * This is the x-coordinate only, without the 02/03 prefix.
@@ -2069,6 +2080,17 @@ var NostrTransportProvider = class _NostrTransportProvider {
2069
2080
  logger.debug("Nostr", "Failed to save last event timestamp:", err);
2070
2081
  });
2071
2082
  }
2083
+ /** Persist the max DM (gift-wrap) event timestamp for the since filter on next connect. */
2084
+ updateLastDmEventTimestamp(createdAt) {
2085
+ if (!this.storage || !this.keyManager) return;
2086
+ if (createdAt <= this.lastDmEventTs) return;
2087
+ this.lastDmEventTs = createdAt;
2088
+ const pubkey = this.keyManager.getPublicKeyHex();
2089
+ const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_DM_EVENT_TS}_${pubkey.slice(0, 16)}`;
2090
+ this.storage.set(storageKey, createdAt.toString()).catch((err) => {
2091
+ logger.debug("Nostr", "Failed to save last DM event timestamp:", err);
2092
+ });
2093
+ }
2072
2094
  async handleDirectMessage(event) {
2073
2095
  logger.debug("Nostr", "Ignoring NIP-04 kind 4 event (DMs use NIP-17):", event.id?.slice(0, 12));
2074
2096
  }
@@ -2079,6 +2101,9 @@ var NostrTransportProvider = class _NostrTransportProvider {
2079
2101
  }
2080
2102
  try {
2081
2103
  const pm = NIP17.unwrap(event, this.keyManager);
2104
+ if (event.created_at) {
2105
+ this.updateLastDmEventTimestamp(event.created_at);
2106
+ }
2082
2107
  logger.debug("Nostr", "Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
2083
2108
  if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
2084
2109
  try {
@@ -2525,9 +2550,50 @@ var NostrTransportProvider = class _NostrTransportProvider {
2525
2550
  }
2526
2551
  });
2527
2552
  logger.debug("Nostr", "Wallet subscription created, subId:", this.walletSubscriptionId);
2553
+ let dmSince;
2554
+ if (this.storage) {
2555
+ const dmStorageKey = `${STORAGE_KEYS_GLOBAL.LAST_DM_EVENT_TS}_${nostrPubkey.slice(0, 16)}`;
2556
+ try {
2557
+ const stored = await this.storage.get(dmStorageKey);
2558
+ const parsed = stored ? parseInt(stored, 10) : NaN;
2559
+ if (Number.isFinite(parsed)) {
2560
+ dmSince = parsed;
2561
+ this.lastDmEventTs = dmSince;
2562
+ this.fallbackDmSince = null;
2563
+ logger.debug("Nostr", "DM resuming from stored timestamp:", dmSince);
2564
+ } else if (this.fallbackDmSince !== null) {
2565
+ dmSince = this.fallbackDmSince;
2566
+ this.lastDmEventTs = dmSince;
2567
+ this.fallbackDmSince = null;
2568
+ logger.debug("Nostr", "DM using fallback since timestamp:", dmSince);
2569
+ } else {
2570
+ dmSince = Math.floor(Date.now() / 1e3);
2571
+ logger.debug("Nostr", "No stored DM timestamp, starting from now:", dmSince);
2572
+ }
2573
+ } catch (err) {
2574
+ if (this.fallbackDmSince !== null) {
2575
+ dmSince = this.fallbackDmSince;
2576
+ this.lastDmEventTs = dmSince;
2577
+ this.fallbackDmSince = null;
2578
+ logger.debug("Nostr", "Storage read failed, using DM fallback since:", dmSince, err);
2579
+ } else {
2580
+ dmSince = Math.floor(Date.now() / 1e3);
2581
+ logger.debug("Nostr", "Failed to read last DM event timestamp, falling back to now:", err);
2582
+ }
2583
+ }
2584
+ } else if (this.fallbackDmSince !== null) {
2585
+ dmSince = this.fallbackDmSince;
2586
+ this.lastDmEventTs = dmSince;
2587
+ this.fallbackDmSince = null;
2588
+ logger.debug("Nostr", "No storage adapter for DM, using fallback since:", dmSince);
2589
+ } else {
2590
+ dmSince = Math.floor(Date.now() / 1e3);
2591
+ logger.debug("Nostr", "No storage adapter for DM, starting from now:", dmSince);
2592
+ }
2528
2593
  const chatFilter = new Filter();
2529
2594
  chatFilter.kinds = [EventKinds.GIFT_WRAP];
2530
2595
  chatFilter["#p"] = [nostrPubkey];
2596
+ chatFilter.since = dmSince;
2531
2597
  this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
2532
2598
  onEvent: (event) => {
2533
2599
  logger.debug("Nostr", "Received chat event kind:", event.kind, "id:", event.id?.slice(0, 12));
@@ -2772,7 +2838,9 @@ var MultiAddressTransportMux = class {
2772
2838
  nostrPubkey,
2773
2839
  adapter,
2774
2840
  lastEventTs: 0,
2775
- fallbackSince: null
2841
+ lastDmEventTs: 0,
2842
+ fallbackSince: null,
2843
+ fallbackDmSince: null
2776
2844
  };
2777
2845
  this.addresses.set(index, entry);
2778
2846
  this.pubkeyToIndex.set(nostrPubkey, index);
@@ -2814,6 +2882,12 @@ var MultiAddressTransportMux = class {
2814
2882
  entry.fallbackSince = sinceSeconds;
2815
2883
  }
2816
2884
  }
2885
+ setFallbackDmSince(index, sinceSeconds) {
2886
+ const entry = this.addresses.get(index);
2887
+ if (entry) {
2888
+ entry.fallbackDmSince = sinceSeconds;
2889
+ }
2890
+ }
2817
2891
  // ===========================================================================
2818
2892
  // Connection Management (delegated from adapters)
2819
2893
  // ===========================================================================
@@ -2949,11 +3023,20 @@ var MultiAddressTransportMux = class {
2949
3023
  }
2950
3024
  logger.debug("Mux", `Subscribing for ${allPubkeys.length} address(es):`, allPubkeys.map((p) => p.slice(0, 12)).join(", "));
2951
3025
  let globalSince = Math.floor(Date.now() / 1e3);
2952
- for (const entry of this.addresses.values()) {
2953
- const since = await this.getAddressSince(entry);
2954
- if (since < globalSince) {
2955
- globalSince = since;
2956
- }
3026
+ let globalDmSince = Math.floor(Date.now() / 1e3);
3027
+ const entries = [...this.addresses.values()];
3028
+ const sinceResults = await Promise.all(
3029
+ entries.map(async (entry) => {
3030
+ const [walletSince, dmSince] = await Promise.all([
3031
+ this.getAddressSince(entry),
3032
+ this.getAddressDmSince(entry)
3033
+ ]);
3034
+ return { walletSince, dmSince };
3035
+ })
3036
+ );
3037
+ for (const { walletSince, dmSince } of sinceResults) {
3038
+ if (walletSince < globalSince) globalSince = walletSince;
3039
+ if (dmSince < globalDmSince) globalDmSince = dmSince;
2957
3040
  }
2958
3041
  const walletFilter = new Filter2();
2959
3042
  walletFilter.kinds = [
@@ -2986,6 +3069,7 @@ var MultiAddressTransportMux = class {
2986
3069
  const chatFilter = new Filter2();
2987
3070
  chatFilter.kinds = [EventKinds2.GIFT_WRAP];
2988
3071
  chatFilter["#p"] = allPubkeys;
3072
+ chatFilter.since = globalDmSince;
2989
3073
  this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
2990
3074
  onEvent: (event) => {
2991
3075
  this.handleEvent({
@@ -3092,6 +3176,9 @@ var MultiAddressTransportMux = class {
3092
3176
  for (const entry of this.addresses.values()) {
3093
3177
  try {
3094
3178
  const pm = NIP172.unwrap(event, entry.keyManager);
3179
+ if (event.created_at) {
3180
+ this.updateLastDmEventTimestamp(entry, event.created_at);
3181
+ }
3095
3182
  logger.debug("Mux", `Gift wrap decrypted by address ${entry.index}, sender: ${pm.senderPubkey?.slice(0, 16)}`);
3096
3183
  if (pm.senderPubkey === entry.nostrPubkey) {
3097
3184
  try {
@@ -3448,6 +3535,47 @@ var MultiAddressTransportMux = class {
3448
3535
  logger.debug("Mux", "Failed to save last event timestamp:", err);
3449
3536
  });
3450
3537
  }
3538
+ updateLastDmEventTimestamp(entry, createdAt) {
3539
+ if (!this.storage) return;
3540
+ if (createdAt <= entry.lastDmEventTs) return;
3541
+ entry.lastDmEventTs = createdAt;
3542
+ const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_DM_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
3543
+ this.storage.set(storageKey, createdAt.toString()).catch((err) => {
3544
+ logger.debug("Mux", "Failed to save last DM event timestamp:", err);
3545
+ });
3546
+ }
3547
+ async getAddressDmSince(entry) {
3548
+ if (this.storage) {
3549
+ const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_DM_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
3550
+ try {
3551
+ const stored = await this.storage.get(storageKey);
3552
+ const parsed = stored ? parseInt(stored, 10) : NaN;
3553
+ if (Number.isFinite(parsed)) {
3554
+ entry.lastDmEventTs = parsed;
3555
+ entry.fallbackDmSince = null;
3556
+ return parsed;
3557
+ } else if (entry.fallbackDmSince !== null) {
3558
+ const ts = entry.fallbackDmSince;
3559
+ entry.lastDmEventTs = ts;
3560
+ entry.fallbackDmSince = null;
3561
+ return ts;
3562
+ }
3563
+ } catch {
3564
+ if (entry.fallbackDmSince !== null) {
3565
+ const ts = entry.fallbackDmSince;
3566
+ entry.lastDmEventTs = ts;
3567
+ entry.fallbackDmSince = null;
3568
+ return ts;
3569
+ }
3570
+ }
3571
+ } else if (entry.fallbackDmSince !== null) {
3572
+ const ts = entry.fallbackDmSince;
3573
+ entry.lastDmEventTs = ts;
3574
+ entry.fallbackDmSince = null;
3575
+ return ts;
3576
+ }
3577
+ return Math.floor(Date.now() / 1e3);
3578
+ }
3451
3579
  // ===========================================================================
3452
3580
  // Mux-level event system
3453
3581
  // ===========================================================================
@@ -3744,6 +3872,9 @@ var AddressTransportAdapter = class {
3744
3872
  setFallbackSince(sinceSeconds) {
3745
3873
  this.mux.setFallbackSince(this.addressIndex, sinceSeconds);
3746
3874
  }
3875
+ setFallbackDmSince(sinceSeconds) {
3876
+ this.mux.setFallbackDmSince(this.addressIndex, sinceSeconds);
3877
+ }
3747
3878
  async fetchPendingEvents() {
3748
3879
  }
3749
3880
  onChatReady(handler) {
@@ -16493,6 +16624,9 @@ var Sphere = class _Sphere {
16493
16624
  const groupChat = _Sphere.resolveGroupChatConfig(options.groupChat, options.network);
16494
16625
  const market = _Sphere.resolveMarketConfig(options.market);
16495
16626
  const walletExists = await _Sphere.exists(options.storage);
16627
+ if (options.dmSince != null && options.transport.setFallbackDmSince) {
16628
+ options.transport.setFallbackDmSince(options.dmSince);
16629
+ }
16496
16630
  if (walletExists) {
16497
16631
  const sphere2 = await _Sphere.load({
16498
16632
  storage: options.storage,