@unicitylabs/sphere-sdk 0.2.2 → 0.2.3

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.
@@ -74,7 +74,9 @@ var STORAGE_KEYS_GLOBAL = {
74
74
  /** Nametag cache per address (separate from tracked addresses registry) */
75
75
  ADDRESS_NAMETAGS: "address_nametags",
76
76
  /** Active addresses registry (JSON: TrackedAddressesStorage) */
77
- TRACKED_ADDRESSES: "tracked_addresses"
77
+ TRACKED_ADDRESSES: "tracked_addresses",
78
+ /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
79
+ LAST_WALLET_EVENT_TS: "last_wallet_event_ts"
78
80
  };
79
81
  var STORAGE_KEYS_ADDRESS = {
80
82
  /** Pending transfers for this address */
@@ -1141,6 +1143,9 @@ var NostrTransportProvider = class {
1141
1143
  type = "p2p";
1142
1144
  description = "P2P messaging via Nostr protocol";
1143
1145
  config;
1146
+ storage = null;
1147
+ /** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
1148
+ lastEventTs = 0;
1144
1149
  identity = null;
1145
1150
  keyManager = null;
1146
1151
  status = "disconnected";
@@ -1166,6 +1171,7 @@ var NostrTransportProvider = class {
1166
1171
  createWebSocket: config.createWebSocket,
1167
1172
  generateUUID: config.generateUUID ?? defaultUUIDGenerator
1168
1173
  };
1174
+ this.storage = config.storage ?? null;
1169
1175
  }
1170
1176
  // ===========================================================================
1171
1177
  // BaseProvider Implementation
@@ -1204,7 +1210,14 @@ var NostrTransportProvider = class {
1204
1210
  this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
1205
1211
  }
1206
1212
  });
1207
- await this.nostrClient.connect(...this.config.relays);
1213
+ await Promise.race([
1214
+ this.nostrClient.connect(...this.config.relays),
1215
+ new Promise(
1216
+ (_, reject) => setTimeout(() => reject(new Error(
1217
+ `Transport connection timed out after ${this.config.timeout}ms`
1218
+ )), this.config.timeout)
1219
+ )
1220
+ ]);
1208
1221
  if (!this.nostrClient.isConnected()) {
1209
1222
  throw new Error("Failed to connect to any relay");
1210
1223
  }
@@ -1212,7 +1225,7 @@ var NostrTransportProvider = class {
1212
1225
  this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
1213
1226
  this.log("Connected to", this.nostrClient.getConnectedRelays().size, "relays");
1214
1227
  if (this.identity) {
1215
- this.subscribeToEvents();
1228
+ await this.subscribeToEvents();
1216
1229
  }
1217
1230
  } catch (error) {
1218
1231
  this.status = "error";
@@ -1365,11 +1378,18 @@ var NostrTransportProvider = class {
1365
1378
  this.log("NostrClient reconnected to relay:", url);
1366
1379
  }
1367
1380
  });
1368
- await this.nostrClient.connect(...this.config.relays);
1369
- this.subscribeToEvents();
1381
+ await Promise.race([
1382
+ this.nostrClient.connect(...this.config.relays),
1383
+ new Promise(
1384
+ (_, reject) => setTimeout(() => reject(new Error(
1385
+ `Transport reconnection timed out after ${this.config.timeout}ms`
1386
+ )), this.config.timeout)
1387
+ )
1388
+ ]);
1389
+ await this.subscribeToEvents();
1370
1390
  oldClient.disconnect();
1371
1391
  } else if (this.isConnected()) {
1372
- this.subscribeToEvents();
1392
+ await this.subscribeToEvents();
1373
1393
  }
1374
1394
  }
1375
1395
  /**
@@ -1897,10 +1917,31 @@ var NostrTransportProvider = class {
1897
1917
  this.handleBroadcast(event);
1898
1918
  break;
1899
1919
  }
1920
+ if (event.created_at && this.storage && this.keyManager) {
1921
+ const kind = event.kind;
1922
+ if (kind === EVENT_KINDS.DIRECT_MESSAGE || kind === EVENT_KINDS.TOKEN_TRANSFER || kind === EVENT_KINDS.PAYMENT_REQUEST || kind === EVENT_KINDS.PAYMENT_REQUEST_RESPONSE) {
1923
+ this.updateLastEventTimestamp(event.created_at);
1924
+ }
1925
+ }
1900
1926
  } catch (error) {
1901
1927
  this.log("Failed to handle event:", error);
1902
1928
  }
1903
1929
  }
1930
+ /**
1931
+ * Save the max event timestamp to storage (fire-and-forget, no await needed by caller).
1932
+ * Uses in-memory `lastEventTs` to avoid read-before-write race conditions
1933
+ * when multiple events arrive in quick succession.
1934
+ */
1935
+ updateLastEventTimestamp(createdAt) {
1936
+ if (!this.storage || !this.keyManager) return;
1937
+ if (createdAt <= this.lastEventTs) return;
1938
+ this.lastEventTs = createdAt;
1939
+ const pubkey = this.keyManager.getPublicKeyHex();
1940
+ const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${pubkey.slice(0, 16)}`;
1941
+ this.storage.set(storageKey, createdAt.toString()).catch((err) => {
1942
+ this.log("Failed to save last event timestamp:", err);
1943
+ });
1944
+ }
1904
1945
  async handleDirectMessage(event) {
1905
1946
  this.log("Ignoring NIP-04 kind 4 event (DMs use NIP-17):", event.id?.slice(0, 12));
1906
1947
  }
@@ -1979,6 +2020,7 @@ var NostrTransportProvider = class {
1979
2020
  const request = {
1980
2021
  id: event.id,
1981
2022
  senderTransportPubkey: event.pubkey,
2023
+ senderNametag: requestData.recipientNametag,
1982
2024
  request: {
1983
2025
  requestId: requestData.requestId,
1984
2026
  amount: requestData.amount,
@@ -2133,7 +2175,7 @@ var NostrTransportProvider = class {
2133
2175
  // Track subscription IDs for cleanup
2134
2176
  walletSubscriptionId = null;
2135
2177
  chatSubscriptionId = null;
2136
- subscribeToEvents() {
2178
+ async subscribeToEvents() {
2137
2179
  this.log("subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
2138
2180
  if (!this.identity || !this.keyManager || !this.nostrClient) {
2139
2181
  this.log("subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
@@ -2153,6 +2195,27 @@ var NostrTransportProvider = class {
2153
2195
  }
2154
2196
  const nostrPubkey = this.keyManager.getPublicKeyHex();
2155
2197
  this.log("Subscribing with Nostr pubkey:", nostrPubkey);
2198
+ let since;
2199
+ if (this.storage) {
2200
+ const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${nostrPubkey.slice(0, 16)}`;
2201
+ try {
2202
+ const stored = await this.storage.get(storageKey);
2203
+ if (stored) {
2204
+ since = parseInt(stored, 10);
2205
+ this.lastEventTs = since;
2206
+ this.log("Resuming from stored event timestamp:", since);
2207
+ } else {
2208
+ since = Math.floor(Date.now() / 1e3);
2209
+ this.log("No stored timestamp, starting from now:", since);
2210
+ }
2211
+ } catch (err) {
2212
+ this.log("Failed to read last event timestamp, falling back to now:", err);
2213
+ since = Math.floor(Date.now() / 1e3);
2214
+ }
2215
+ } else {
2216
+ since = Math.floor(Date.now() / 1e3) - 86400;
2217
+ this.log("No storage adapter, using 24h fallback");
2218
+ }
2156
2219
  const walletFilter = new import_nostr_js_sdk.Filter();
2157
2220
  walletFilter.kinds = [
2158
2221
  EVENT_KINDS.DIRECT_MESSAGE,
@@ -2161,7 +2224,7 @@ var NostrTransportProvider = class {
2161
2224
  EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
2162
2225
  ];
2163
2226
  walletFilter["#p"] = [nostrPubkey];
2164
- walletFilter.since = Math.floor(Date.now() / 1e3) - 86400;
2227
+ walletFilter.since = since;
2165
2228
  this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
2166
2229
  onEvent: (event) => {
2167
2230
  this.log("Received wallet event kind:", event.kind, "id:", event.id?.slice(0, 12));
@@ -2958,10 +3021,11 @@ function createNodeProviders(config) {
2958
3021
  const oracleConfig = resolveOracleConfig(network, config?.oracle);
2959
3022
  const l1Config = resolveL1Config(network, config?.l1);
2960
3023
  const priceConfig = resolvePriceConfig(config?.price);
3024
+ const storage = createFileStorageProvider({
3025
+ dataDir: config?.dataDir ?? "./sphere-data"
3026
+ });
2961
3027
  return {
2962
- storage: createFileStorageProvider({
2963
- dataDir: config?.dataDir ?? "./sphere-data"
2964
- }),
3028
+ storage,
2965
3029
  tokenStorage: createFileTokenStorageProvider({
2966
3030
  tokensDir: config?.tokensDir ?? "./sphere-tokens"
2967
3031
  }),
@@ -2969,7 +3033,8 @@ function createNodeProviders(config) {
2969
3033
  relays: transportConfig.relays,
2970
3034
  timeout: transportConfig.timeout,
2971
3035
  autoReconnect: transportConfig.autoReconnect,
2972
- debug: transportConfig.debug
3036
+ debug: transportConfig.debug,
3037
+ storage
2973
3038
  }),
2974
3039
  oracle: createUnicityAggregatorProvider({
2975
3040
  url: oracleConfig.url,