@unicitylabs/sphere-sdk 0.6.1 → 0.6.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.
package/dist/index.cjs CHANGED
@@ -845,8 +845,8 @@ __export(index_exports, {
845
845
  NETWORKS: () => NETWORKS,
846
846
  NIP29_KINDS: () => NIP29_KINDS,
847
847
  NOSTR_EVENT_KINDS: () => NOSTR_EVENT_KINDS,
848
- NostrClient: () => import_nostr_js_sdk5.NostrClient,
849
- NostrKeyManager: () => import_nostr_js_sdk5.NostrKeyManager,
848
+ NostrClient: () => import_nostr_js_sdk6.NostrClient,
849
+ NostrKeyManager: () => import_nostr_js_sdk6.NostrKeyManager,
850
850
  PaymentsModule: () => PaymentsModule,
851
851
  SIGN_MESSAGE_PREFIX: () => SIGN_MESSAGE_PREFIX,
852
852
  STORAGE_KEYS: () => STORAGE_KEYS,
@@ -862,7 +862,7 @@ __export(index_exports, {
862
862
  TokenRegistry: () => TokenRegistry,
863
863
  TokenValidator: () => TokenValidator,
864
864
  archivedKeyFromTokenId: () => archivedKeyFromTokenId,
865
- areSameNametag: () => import_nostr_js_sdk4.areSameNametag,
865
+ areSameNametag: () => import_nostr_js_sdk5.areSameNametag,
866
866
  base58Decode: () => base58Decode,
867
867
  base58Encode: () => base58Encode2,
868
868
  buildTxfStorageData: () => buildTxfStorageData,
@@ -884,7 +884,7 @@ __export(index_exports, {
884
884
  createTokenValidator: () => createTokenValidator,
885
885
  decodeBech32: () => decodeBech32,
886
886
  decryptCMasterKey: () => decryptCMasterKey,
887
- decryptNametag: () => import_nostr_js_sdk4.decryptNametag,
887
+ decryptNametag: () => import_nostr_js_sdk5.decryptNametag,
888
888
  decryptPrivateKey: () => decryptPrivateKey,
889
889
  decryptTextFormatKey: () => decryptTextFormatKey,
890
890
  deriveAddressInfo: () => deriveAddressInfo,
@@ -892,7 +892,7 @@ __export(index_exports, {
892
892
  deriveKeyAtPath: () => deriveKeyAtPath,
893
893
  doubleSha256: () => doubleSha256,
894
894
  encodeBech32: () => encodeBech32,
895
- encryptNametag: () => import_nostr_js_sdk4.encryptNametag,
895
+ encryptNametag: () => import_nostr_js_sdk5.encryptNametag,
896
896
  extractFromText: () => extractFromText,
897
897
  findPattern: () => findPattern,
898
898
  forkedKeyFromTokenIdAndState: () => forkedKeyFromTokenIdAndState,
@@ -917,8 +917,8 @@ __export(index_exports, {
917
917
  hasUncommittedTransactions: () => hasUncommittedTransactions,
918
918
  hasValidTxfData: () => hasValidTxfData,
919
919
  hash160: () => hash160,
920
- hashAddressForTag: () => import_nostr_js_sdk4.hashAddressForTag,
921
- hashNametag: () => import_nostr_js_sdk4.hashNametag,
920
+ hashAddressForTag: () => import_nostr_js_sdk5.hashAddressForTag,
921
+ hashNametag: () => import_nostr_js_sdk5.hashNametag,
922
922
  hashSignMessage: () => hashSignMessage,
923
923
  hexToBytes: () => hexToBytes,
924
924
  identityFromMnemonicSync: () => identityFromMnemonicSync,
@@ -932,7 +932,7 @@ __export(index_exports, {
932
932
  isKnownToken: () => isKnownToken,
933
933
  isPaymentSessionTerminal: () => isPaymentSessionTerminal,
934
934
  isPaymentSessionTimedOut: () => isPaymentSessionTimedOut,
935
- isPhoneNumber: () => import_nostr_js_sdk4.isPhoneNumber,
935
+ isPhoneNumber: () => import_nostr_js_sdk5.isPhoneNumber,
936
936
  isSQLiteDatabase: () => isSQLiteDatabase,
937
937
  isSphereError: () => isSphereError,
938
938
  isTextWalletEncrypted: () => isTextWalletEncrypted,
@@ -947,7 +947,7 @@ __export(index_exports, {
947
947
  loadSphere: () => loadSphere,
948
948
  logger: () => logger,
949
949
  mnemonicToSeedSync: () => mnemonicToSeedSync2,
950
- normalizeNametag: () => import_nostr_js_sdk4.normalizeNametag,
950
+ normalizeNametag: () => import_nostr_js_sdk5.normalizeNametag,
951
951
  normalizeSdkTokenToStorage: () => normalizeSdkTokenToStorage,
952
952
  objectToTxf: () => objectToTxf,
953
953
  parseAndDecryptWalletDat: () => parseAndDecryptWalletDat,
@@ -979,6 +979,1102 @@ module.exports = __toCommonJS(index_exports);
979
979
  init_logger();
980
980
  init_errors();
981
981
 
982
+ // transport/MultiAddressTransportMux.ts
983
+ var import_buffer = require("buffer");
984
+ var import_nostr_js_sdk = require("@unicitylabs/nostr-js-sdk");
985
+ init_logger();
986
+ init_errors();
987
+
988
+ // transport/websocket.ts
989
+ function defaultUUIDGenerator() {
990
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
991
+ return crypto.randomUUID();
992
+ }
993
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
994
+ const r = Math.random() * 16 | 0;
995
+ const v = c === "x" ? r : r & 3 | 8;
996
+ return v.toString(16);
997
+ });
998
+ }
999
+
1000
+ // transport/MultiAddressTransportMux.ts
1001
+ init_constants();
1002
+ var EVENT_KINDS = NOSTR_EVENT_KINDS;
1003
+ var COMPOSING_INDICATOR_KIND = 25050;
1004
+ var MultiAddressTransportMux = class {
1005
+ config;
1006
+ storage = null;
1007
+ // Single NostrClient — one WebSocket connection for all addresses
1008
+ nostrClient = null;
1009
+ // KeyManager used for NostrClient creation (uses first address or temp key)
1010
+ primaryKeyManager = null;
1011
+ status = "disconnected";
1012
+ // Per-address entries
1013
+ addresses = /* @__PURE__ */ new Map();
1014
+ // pubkey → address index (for fast routing)
1015
+ pubkeyToIndex = /* @__PURE__ */ new Map();
1016
+ // Subscription IDs
1017
+ walletSubscriptionId = null;
1018
+ chatSubscriptionId = null;
1019
+ chatEoseFired = false;
1020
+ chatEoseHandlers = [];
1021
+ // Dedup
1022
+ processedEventIds = /* @__PURE__ */ new Set();
1023
+ // Event callbacks (mux-level, forwarded to all adapters)
1024
+ eventCallbacks = /* @__PURE__ */ new Set();
1025
+ constructor(config) {
1026
+ this.config = {
1027
+ relays: config.relays ?? [...DEFAULT_NOSTR_RELAYS],
1028
+ timeout: config.timeout ?? TIMEOUTS.WEBSOCKET_CONNECT,
1029
+ autoReconnect: config.autoReconnect ?? true,
1030
+ reconnectDelay: config.reconnectDelay ?? TIMEOUTS.NOSTR_RECONNECT_DELAY,
1031
+ maxReconnectAttempts: config.maxReconnectAttempts ?? TIMEOUTS.MAX_RECONNECT_ATTEMPTS,
1032
+ createWebSocket: config.createWebSocket,
1033
+ generateUUID: config.generateUUID ?? defaultUUIDGenerator
1034
+ };
1035
+ this.storage = config.storage ?? null;
1036
+ }
1037
+ // ===========================================================================
1038
+ // Address Management
1039
+ // ===========================================================================
1040
+ /**
1041
+ * Add an address to the multiplexer.
1042
+ * Creates an AddressTransportAdapter for this address.
1043
+ * If already connected, updates subscriptions to include the new pubkey.
1044
+ */
1045
+ async addAddress(index, identity, resolveDelegate) {
1046
+ const existing = this.addresses.get(index);
1047
+ if (existing) {
1048
+ existing.identity = identity;
1049
+ existing.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(import_buffer.Buffer.from(identity.privateKey, "hex"));
1050
+ existing.nostrPubkey = existing.keyManager.getPublicKeyHex();
1051
+ for (const [pk, idx] of this.pubkeyToIndex) {
1052
+ if (idx === index) this.pubkeyToIndex.delete(pk);
1053
+ }
1054
+ this.pubkeyToIndex.set(existing.nostrPubkey, index);
1055
+ logger.debug("Mux", `Updated address ${index}, pubkey: ${existing.nostrPubkey.slice(0, 16)}...`);
1056
+ await this.updateSubscriptions();
1057
+ return existing.adapter;
1058
+ }
1059
+ const keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(import_buffer.Buffer.from(identity.privateKey, "hex"));
1060
+ const nostrPubkey = keyManager.getPublicKeyHex();
1061
+ const adapter = new AddressTransportAdapter(this, index, identity, resolveDelegate);
1062
+ const entry = {
1063
+ index,
1064
+ identity,
1065
+ keyManager,
1066
+ nostrPubkey,
1067
+ adapter,
1068
+ lastEventTs: 0,
1069
+ fallbackSince: null
1070
+ };
1071
+ this.addresses.set(index, entry);
1072
+ this.pubkeyToIndex.set(nostrPubkey, index);
1073
+ logger.debug("Mux", `Added address ${index}, pubkey: ${nostrPubkey.slice(0, 16)}..., total: ${this.addresses.size}`);
1074
+ if (this.addresses.size === 1) {
1075
+ this.primaryKeyManager = keyManager;
1076
+ }
1077
+ if (this.isConnected()) {
1078
+ await this.updateSubscriptions();
1079
+ }
1080
+ return adapter;
1081
+ }
1082
+ /**
1083
+ * Remove an address from the multiplexer.
1084
+ * Stops routing events to this address.
1085
+ */
1086
+ async removeAddress(index) {
1087
+ const entry = this.addresses.get(index);
1088
+ if (!entry) return;
1089
+ this.pubkeyToIndex.delete(entry.nostrPubkey);
1090
+ this.addresses.delete(index);
1091
+ logger.debug("Mux", `Removed address ${index}, remaining: ${this.addresses.size}`);
1092
+ if (this.isConnected() && this.addresses.size > 0) {
1093
+ await this.updateSubscriptions();
1094
+ }
1095
+ }
1096
+ /**
1097
+ * Get adapter for a specific address index.
1098
+ */
1099
+ getAdapter(index) {
1100
+ return this.addresses.get(index)?.adapter;
1101
+ }
1102
+ /**
1103
+ * Set fallback 'since' for an address (consumed once on next subscription setup).
1104
+ */
1105
+ setFallbackSince(index, sinceSeconds) {
1106
+ const entry = this.addresses.get(index);
1107
+ if (entry) {
1108
+ entry.fallbackSince = sinceSeconds;
1109
+ }
1110
+ }
1111
+ // ===========================================================================
1112
+ // Connection Management (delegated from adapters)
1113
+ // ===========================================================================
1114
+ async connect() {
1115
+ if (this.status === "connected") return;
1116
+ this.status = "connecting";
1117
+ try {
1118
+ if (!this.primaryKeyManager) {
1119
+ const tempKey = import_buffer.Buffer.alloc(32);
1120
+ crypto.getRandomValues(tempKey);
1121
+ this.primaryKeyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(tempKey);
1122
+ }
1123
+ this.nostrClient = new import_nostr_js_sdk.NostrClient(this.primaryKeyManager, {
1124
+ autoReconnect: this.config.autoReconnect,
1125
+ reconnectIntervalMs: this.config.reconnectDelay,
1126
+ maxReconnectIntervalMs: this.config.reconnectDelay * 16,
1127
+ pingIntervalMs: 15e3
1128
+ });
1129
+ this.nostrClient.addConnectionListener({
1130
+ onConnect: (url) => {
1131
+ logger.debug("Mux", "Connected to relay:", url);
1132
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
1133
+ },
1134
+ onDisconnect: (url, reason) => {
1135
+ logger.debug("Mux", "Disconnected from relay:", url, "reason:", reason);
1136
+ },
1137
+ onReconnecting: (url, attempt) => {
1138
+ logger.debug("Mux", "Reconnecting to relay:", url, "attempt:", attempt);
1139
+ this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
1140
+ },
1141
+ onReconnected: (url) => {
1142
+ logger.debug("Mux", "Reconnected to relay:", url);
1143
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
1144
+ }
1145
+ });
1146
+ await Promise.race([
1147
+ this.nostrClient.connect(...this.config.relays),
1148
+ new Promise(
1149
+ (_, reject) => setTimeout(() => reject(new Error(
1150
+ `Transport connection timed out after ${this.config.timeout}ms`
1151
+ )), this.config.timeout)
1152
+ )
1153
+ ]);
1154
+ if (!this.nostrClient.isConnected()) {
1155
+ throw new SphereError("Failed to connect to any relay", "TRANSPORT_ERROR");
1156
+ }
1157
+ this.status = "connected";
1158
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
1159
+ if (this.addresses.size > 0) {
1160
+ await this.updateSubscriptions();
1161
+ }
1162
+ } catch (error) {
1163
+ this.status = "error";
1164
+ throw error;
1165
+ }
1166
+ }
1167
+ async disconnect() {
1168
+ if (this.nostrClient) {
1169
+ this.nostrClient.disconnect();
1170
+ this.nostrClient = null;
1171
+ }
1172
+ this.walletSubscriptionId = null;
1173
+ this.chatSubscriptionId = null;
1174
+ this.chatEoseFired = false;
1175
+ this.status = "disconnected";
1176
+ this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
1177
+ }
1178
+ isConnected() {
1179
+ return this.status === "connected" && this.nostrClient?.isConnected() === true;
1180
+ }
1181
+ getStatus() {
1182
+ return this.status;
1183
+ }
1184
+ // ===========================================================================
1185
+ // Relay Management
1186
+ // ===========================================================================
1187
+ getRelays() {
1188
+ return [...this.config.relays];
1189
+ }
1190
+ getConnectedRelays() {
1191
+ if (!this.nostrClient) return [];
1192
+ return Array.from(this.nostrClient.getConnectedRelays());
1193
+ }
1194
+ async addRelay(relayUrl) {
1195
+ if (this.config.relays.includes(relayUrl)) return false;
1196
+ this.config.relays.push(relayUrl);
1197
+ if (this.status === "connected" && this.nostrClient) {
1198
+ try {
1199
+ await this.nostrClient.connect(relayUrl);
1200
+ this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: true } });
1201
+ return true;
1202
+ } catch (error) {
1203
+ this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: false, error: String(error) } });
1204
+ return false;
1205
+ }
1206
+ }
1207
+ return true;
1208
+ }
1209
+ async removeRelay(relayUrl) {
1210
+ const idx = this.config.relays.indexOf(relayUrl);
1211
+ if (idx === -1) return false;
1212
+ this.config.relays.splice(idx, 1);
1213
+ this.emitEvent({ type: "transport:relay_removed", timestamp: Date.now(), data: { relay: relayUrl } });
1214
+ return true;
1215
+ }
1216
+ hasRelay(relayUrl) {
1217
+ return this.config.relays.includes(relayUrl);
1218
+ }
1219
+ isRelayConnected(relayUrl) {
1220
+ if (!this.nostrClient) return false;
1221
+ return this.nostrClient.getConnectedRelays().has(relayUrl);
1222
+ }
1223
+ // ===========================================================================
1224
+ // Subscription Management
1225
+ // ===========================================================================
1226
+ /**
1227
+ * Update Nostr subscriptions to listen for events on ALL registered address pubkeys.
1228
+ * Called whenever addresses are added/removed.
1229
+ */
1230
+ async updateSubscriptions() {
1231
+ if (!this.nostrClient || this.addresses.size === 0) return;
1232
+ if (this.walletSubscriptionId) {
1233
+ this.nostrClient.unsubscribe(this.walletSubscriptionId);
1234
+ this.walletSubscriptionId = null;
1235
+ }
1236
+ if (this.chatSubscriptionId) {
1237
+ this.nostrClient.unsubscribe(this.chatSubscriptionId);
1238
+ this.chatSubscriptionId = null;
1239
+ }
1240
+ const allPubkeys = [];
1241
+ for (const entry of this.addresses.values()) {
1242
+ allPubkeys.push(entry.nostrPubkey);
1243
+ }
1244
+ logger.debug("Mux", `Subscribing for ${allPubkeys.length} address(es):`, allPubkeys.map((p) => p.slice(0, 12)).join(", "));
1245
+ let globalSince = Math.floor(Date.now() / 1e3);
1246
+ for (const entry of this.addresses.values()) {
1247
+ const since = await this.getAddressSince(entry);
1248
+ if (since < globalSince) {
1249
+ globalSince = since;
1250
+ }
1251
+ }
1252
+ const walletFilter = new import_nostr_js_sdk.Filter();
1253
+ walletFilter.kinds = [
1254
+ EVENT_KINDS.DIRECT_MESSAGE,
1255
+ EVENT_KINDS.TOKEN_TRANSFER,
1256
+ EVENT_KINDS.PAYMENT_REQUEST,
1257
+ EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
1258
+ ];
1259
+ walletFilter["#p"] = allPubkeys;
1260
+ walletFilter.since = globalSince;
1261
+ this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
1262
+ onEvent: (event) => {
1263
+ this.handleEvent({
1264
+ id: event.id,
1265
+ kind: event.kind,
1266
+ content: event.content,
1267
+ tags: event.tags,
1268
+ pubkey: event.pubkey,
1269
+ created_at: event.created_at,
1270
+ sig: event.sig
1271
+ });
1272
+ },
1273
+ onEndOfStoredEvents: () => {
1274
+ logger.debug("Mux", "Wallet subscription EOSE");
1275
+ },
1276
+ onError: (_subId, error) => {
1277
+ logger.debug("Mux", "Wallet subscription error:", error);
1278
+ }
1279
+ });
1280
+ const chatFilter = new import_nostr_js_sdk.Filter();
1281
+ chatFilter.kinds = [import_nostr_js_sdk.EventKinds.GIFT_WRAP];
1282
+ chatFilter["#p"] = allPubkeys;
1283
+ this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
1284
+ onEvent: (event) => {
1285
+ this.handleEvent({
1286
+ id: event.id,
1287
+ kind: event.kind,
1288
+ content: event.content,
1289
+ tags: event.tags,
1290
+ pubkey: event.pubkey,
1291
+ created_at: event.created_at,
1292
+ sig: event.sig
1293
+ });
1294
+ },
1295
+ onEndOfStoredEvents: () => {
1296
+ logger.debug("Mux", "Chat subscription EOSE");
1297
+ if (!this.chatEoseFired) {
1298
+ this.chatEoseFired = true;
1299
+ for (const handler of this.chatEoseHandlers) {
1300
+ try {
1301
+ handler();
1302
+ } catch {
1303
+ }
1304
+ }
1305
+ }
1306
+ },
1307
+ onError: (_subId, error) => {
1308
+ logger.debug("Mux", "Chat subscription error:", error);
1309
+ }
1310
+ });
1311
+ }
1312
+ /**
1313
+ * Determine 'since' timestamp for an address entry.
1314
+ */
1315
+ async getAddressSince(entry) {
1316
+ if (this.storage) {
1317
+ const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
1318
+ try {
1319
+ const stored = await this.storage.get(storageKey);
1320
+ if (stored) {
1321
+ const ts = parseInt(stored, 10);
1322
+ entry.lastEventTs = ts;
1323
+ entry.fallbackSince = null;
1324
+ return ts;
1325
+ } else if (entry.fallbackSince !== null) {
1326
+ const ts = entry.fallbackSince;
1327
+ entry.lastEventTs = ts;
1328
+ entry.fallbackSince = null;
1329
+ return ts;
1330
+ }
1331
+ } catch {
1332
+ }
1333
+ }
1334
+ return Math.floor(Date.now() / 1e3);
1335
+ }
1336
+ // ===========================================================================
1337
+ // Event Routing
1338
+ // ===========================================================================
1339
+ /**
1340
+ * Route an incoming Nostr event to the correct address adapter.
1341
+ */
1342
+ async handleEvent(event) {
1343
+ if (event.id && this.processedEventIds.has(event.id)) return;
1344
+ if (event.id) this.processedEventIds.add(event.id);
1345
+ try {
1346
+ if (event.kind === import_nostr_js_sdk.EventKinds.GIFT_WRAP) {
1347
+ await this.routeGiftWrap(event);
1348
+ } else {
1349
+ const recipientPubkey = this.extractRecipientPubkey(event);
1350
+ if (!recipientPubkey) {
1351
+ logger.debug("Mux", "Event has no #p tag, dropping:", event.id?.slice(0, 12));
1352
+ return;
1353
+ }
1354
+ const addressIndex = this.pubkeyToIndex.get(recipientPubkey);
1355
+ if (addressIndex === void 0) {
1356
+ logger.debug("Mux", "Event for unknown pubkey:", recipientPubkey.slice(0, 16), "dropping");
1357
+ return;
1358
+ }
1359
+ const entry = this.addresses.get(addressIndex);
1360
+ if (!entry) return;
1361
+ await this.dispatchWalletEvent(entry, event);
1362
+ }
1363
+ } catch (error) {
1364
+ logger.debug("Mux", "Failed to handle event:", event.id?.slice(0, 12), error);
1365
+ }
1366
+ }
1367
+ /**
1368
+ * Extract recipient pubkey from event's #p tag.
1369
+ * Returns the first #p value that matches a known address pubkey,
1370
+ * or the first #p value if none match.
1371
+ */
1372
+ extractRecipientPubkey(event) {
1373
+ const pTags = event.tags?.filter((t) => t[0] === "p");
1374
+ if (!pTags || pTags.length === 0) return null;
1375
+ for (const tag of pTags) {
1376
+ if (tag[1] && this.pubkeyToIndex.has(tag[1])) {
1377
+ return tag[1];
1378
+ }
1379
+ }
1380
+ return pTags[0]?.[1] ?? null;
1381
+ }
1382
+ /**
1383
+ * Route a gift wrap event by trying decryption with each address keyManager.
1384
+ */
1385
+ async routeGiftWrap(event) {
1386
+ for (const entry of this.addresses.values()) {
1387
+ try {
1388
+ const pm = import_nostr_js_sdk.NIP17.unwrap(event, entry.keyManager);
1389
+ logger.debug("Mux", `Gift wrap decrypted by address ${entry.index}, sender: ${pm.senderPubkey?.slice(0, 16)}`);
1390
+ if (pm.senderPubkey === entry.nostrPubkey) {
1391
+ try {
1392
+ const parsed = JSON.parse(pm.content);
1393
+ if (parsed?.selfWrap && parsed.recipientPubkey) {
1394
+ const message2 = {
1395
+ id: parsed.originalId || pm.eventId,
1396
+ senderTransportPubkey: pm.senderPubkey,
1397
+ senderNametag: parsed.senderNametag,
1398
+ recipientTransportPubkey: parsed.recipientPubkey,
1399
+ content: parsed.text ?? "",
1400
+ timestamp: pm.timestamp * 1e3,
1401
+ encrypted: true,
1402
+ isSelfWrap: true
1403
+ };
1404
+ entry.adapter.dispatchMessage(message2);
1405
+ return;
1406
+ }
1407
+ } catch {
1408
+ }
1409
+ return;
1410
+ }
1411
+ if ((0, import_nostr_js_sdk.isReadReceipt)(pm)) {
1412
+ if (pm.replyToEventId) {
1413
+ const receipt = {
1414
+ senderTransportPubkey: pm.senderPubkey,
1415
+ messageEventId: pm.replyToEventId,
1416
+ timestamp: pm.timestamp * 1e3
1417
+ };
1418
+ entry.adapter.dispatchReadReceipt(receipt);
1419
+ }
1420
+ return;
1421
+ }
1422
+ if (pm.kind === COMPOSING_INDICATOR_KIND) {
1423
+ let senderNametag2;
1424
+ let expiresIn = 3e4;
1425
+ try {
1426
+ const parsed = JSON.parse(pm.content);
1427
+ senderNametag2 = parsed.senderNametag || void 0;
1428
+ expiresIn = parsed.expiresIn ?? 3e4;
1429
+ } catch {
1430
+ }
1431
+ entry.adapter.dispatchComposingIndicator({
1432
+ senderPubkey: pm.senderPubkey,
1433
+ senderNametag: senderNametag2,
1434
+ expiresIn
1435
+ });
1436
+ return;
1437
+ }
1438
+ try {
1439
+ const parsed = JSON.parse(pm.content);
1440
+ if (parsed?.type === "typing") {
1441
+ const indicator = {
1442
+ senderTransportPubkey: pm.senderPubkey,
1443
+ senderNametag: parsed.senderNametag,
1444
+ timestamp: pm.timestamp * 1e3
1445
+ };
1446
+ entry.adapter.dispatchTypingIndicator(indicator);
1447
+ return;
1448
+ }
1449
+ } catch {
1450
+ }
1451
+ if (!(0, import_nostr_js_sdk.isChatMessage)(pm)) return;
1452
+ let content = pm.content;
1453
+ let senderNametag;
1454
+ try {
1455
+ const parsed = JSON.parse(content);
1456
+ if (typeof parsed === "object" && parsed.text !== void 0) {
1457
+ content = parsed.text;
1458
+ senderNametag = parsed.senderNametag || void 0;
1459
+ }
1460
+ } catch {
1461
+ }
1462
+ const message = {
1463
+ id: event.id,
1464
+ senderTransportPubkey: pm.senderPubkey,
1465
+ senderNametag,
1466
+ content,
1467
+ timestamp: pm.timestamp * 1e3,
1468
+ encrypted: true
1469
+ };
1470
+ entry.adapter.dispatchMessage(message);
1471
+ return;
1472
+ } catch {
1473
+ continue;
1474
+ }
1475
+ }
1476
+ logger.debug("Mux", "Gift wrap could not be decrypted by any address");
1477
+ }
1478
+ /**
1479
+ * Dispatch a wallet event (non-gift-wrap) to the correct address adapter.
1480
+ */
1481
+ async dispatchWalletEvent(entry, event) {
1482
+ switch (event.kind) {
1483
+ case EVENT_KINDS.DIRECT_MESSAGE:
1484
+ break;
1485
+ case EVENT_KINDS.TOKEN_TRANSFER:
1486
+ await this.handleTokenTransfer(entry, event);
1487
+ break;
1488
+ case EVENT_KINDS.PAYMENT_REQUEST:
1489
+ await this.handlePaymentRequest(entry, event);
1490
+ break;
1491
+ case EVENT_KINDS.PAYMENT_REQUEST_RESPONSE:
1492
+ await this.handlePaymentRequestResponse(entry, event);
1493
+ break;
1494
+ }
1495
+ if (event.created_at) {
1496
+ this.updateLastEventTimestamp(entry, event.created_at);
1497
+ }
1498
+ }
1499
+ async handleTokenTransfer(entry, event) {
1500
+ try {
1501
+ const content = await this.decryptContent(entry, event.content, event.pubkey);
1502
+ const payload = JSON.parse(content);
1503
+ const transfer = {
1504
+ id: event.id,
1505
+ senderTransportPubkey: event.pubkey,
1506
+ payload,
1507
+ timestamp: event.created_at * 1e3
1508
+ };
1509
+ entry.adapter.dispatchTokenTransfer(transfer);
1510
+ } catch (err) {
1511
+ logger.debug("Mux", `Token transfer decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
1512
+ }
1513
+ }
1514
+ async handlePaymentRequest(entry, event) {
1515
+ try {
1516
+ const content = await this.decryptContent(entry, event.content, event.pubkey);
1517
+ const requestData = JSON.parse(content);
1518
+ const request = {
1519
+ id: event.id,
1520
+ senderTransportPubkey: event.pubkey,
1521
+ request: {
1522
+ requestId: requestData.requestId,
1523
+ amount: requestData.amount,
1524
+ coinId: requestData.coinId,
1525
+ message: requestData.message,
1526
+ recipientNametag: requestData.recipientNametag,
1527
+ metadata: requestData.metadata
1528
+ },
1529
+ timestamp: event.created_at * 1e3
1530
+ };
1531
+ entry.adapter.dispatchPaymentRequest(request);
1532
+ } catch (err) {
1533
+ logger.debug("Mux", `Payment request decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
1534
+ }
1535
+ }
1536
+ async handlePaymentRequestResponse(entry, event) {
1537
+ try {
1538
+ const content = await this.decryptContent(entry, event.content, event.pubkey);
1539
+ const responseData = JSON.parse(content);
1540
+ const response = {
1541
+ id: event.id,
1542
+ responderTransportPubkey: event.pubkey,
1543
+ response: {
1544
+ requestId: responseData.requestId,
1545
+ responseType: responseData.responseType,
1546
+ message: responseData.message,
1547
+ transferId: responseData.transferId
1548
+ },
1549
+ timestamp: event.created_at * 1e3
1550
+ };
1551
+ entry.adapter.dispatchPaymentRequestResponse(response);
1552
+ } catch (err) {
1553
+ logger.debug("Mux", `Payment response decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
1554
+ }
1555
+ }
1556
+ // ===========================================================================
1557
+ // Crypto Helpers
1558
+ // ===========================================================================
1559
+ async decryptContent(entry, content, senderPubkey) {
1560
+ const decrypted = await import_nostr_js_sdk.NIP04.decryptHex(
1561
+ content,
1562
+ entry.keyManager.getPrivateKeyHex(),
1563
+ senderPubkey
1564
+ );
1565
+ return this.stripContentPrefix(decrypted);
1566
+ }
1567
+ stripContentPrefix(content) {
1568
+ const prefixes = ["payment_request:", "token_transfer:", "payment_response:"];
1569
+ for (const prefix of prefixes) {
1570
+ if (content.startsWith(prefix)) return content.slice(prefix.length);
1571
+ }
1572
+ return content;
1573
+ }
1574
+ // ===========================================================================
1575
+ // Sending (called by adapters)
1576
+ // ===========================================================================
1577
+ /**
1578
+ * Create an encrypted event using a specific address's keyManager.
1579
+ * Used by AddressTransportAdapter for sending.
1580
+ */
1581
+ async createAndPublishEncryptedEvent(addressIndex, kind, content, tags) {
1582
+ const entry = this.addresses.get(addressIndex);
1583
+ if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
1584
+ if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
1585
+ const recipientTag = tags.find((t) => t[0] === "p");
1586
+ if (!recipientTag?.[1]) throw new SphereError("No recipient pubkey in tags", "VALIDATION_ERROR");
1587
+ const encrypted = await import_nostr_js_sdk.NIP04.encryptHex(
1588
+ content,
1589
+ entry.keyManager.getPrivateKeyHex(),
1590
+ recipientTag[1]
1591
+ );
1592
+ const signedEvent = import_nostr_js_sdk.Event.create(entry.keyManager, { kind, content: encrypted, tags });
1593
+ const nostrEvent = import_nostr_js_sdk.Event.fromJSON({
1594
+ id: signedEvent.id,
1595
+ kind: signedEvent.kind,
1596
+ content: signedEvent.content,
1597
+ tags: signedEvent.tags,
1598
+ pubkey: signedEvent.pubkey,
1599
+ created_at: signedEvent.created_at,
1600
+ sig: signedEvent.sig
1601
+ });
1602
+ await this.nostrClient.publishEvent(nostrEvent);
1603
+ return signedEvent.id;
1604
+ }
1605
+ /**
1606
+ * Create and publish a NIP-17 gift wrap message for a specific address.
1607
+ */
1608
+ async sendGiftWrap(addressIndex, recipientPubkey, content) {
1609
+ const entry = this.addresses.get(addressIndex);
1610
+ if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
1611
+ if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
1612
+ const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
1613
+ const giftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(entry.keyManager, nostrRecipient, content);
1614
+ const giftWrapEvent = import_nostr_js_sdk.Event.fromJSON(giftWrap);
1615
+ await this.nostrClient.publishEvent(giftWrapEvent);
1616
+ const selfPubkey = entry.keyManager.getPublicKeyHex();
1617
+ const senderNametag = entry.identity.nametag;
1618
+ const selfWrapContent = JSON.stringify({
1619
+ selfWrap: true,
1620
+ originalId: giftWrap.id,
1621
+ recipientPubkey,
1622
+ senderNametag,
1623
+ text: content
1624
+ });
1625
+ const selfGiftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(entry.keyManager, selfPubkey, selfWrapContent);
1626
+ const selfGiftWrapEvent = import_nostr_js_sdk.Event.fromJSON(selfGiftWrap);
1627
+ this.nostrClient.publishEvent(selfGiftWrapEvent).catch((err) => {
1628
+ logger.debug("Mux", "Self-wrap publish failed:", err);
1629
+ });
1630
+ return giftWrap.id;
1631
+ }
1632
+ /**
1633
+ * Publish a raw event (e.g., identity binding, broadcast).
1634
+ */
1635
+ async publishRawEvent(addressIndex, kind, content, tags) {
1636
+ const entry = this.addresses.get(addressIndex);
1637
+ if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
1638
+ if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
1639
+ const signedEvent = import_nostr_js_sdk.Event.create(entry.keyManager, { kind, content, tags });
1640
+ const nostrEvent = import_nostr_js_sdk.Event.fromJSON({
1641
+ id: signedEvent.id,
1642
+ kind: signedEvent.kind,
1643
+ content: signedEvent.content,
1644
+ tags: signedEvent.tags,
1645
+ pubkey: signedEvent.pubkey,
1646
+ created_at: signedEvent.created_at,
1647
+ sig: signedEvent.sig
1648
+ });
1649
+ await this.nostrClient.publishEvent(nostrEvent);
1650
+ return signedEvent.id;
1651
+ }
1652
+ // ===========================================================================
1653
+ // Resolve Methods (delegates to inner — these are stateless relay queries)
1654
+ // ===========================================================================
1655
+ /**
1656
+ * Get the NostrClient for resolve operations.
1657
+ * Adapters use this for resolve*, publishIdentityBinding, etc.
1658
+ */
1659
+ getNostrClient() {
1660
+ return this.nostrClient;
1661
+ }
1662
+ /**
1663
+ * Get keyManager for a specific address (used by adapters for resolve/binding).
1664
+ */
1665
+ getKeyManager(addressIndex) {
1666
+ return this.addresses.get(addressIndex)?.keyManager ?? null;
1667
+ }
1668
+ /**
1669
+ * Get identity for a specific address.
1670
+ */
1671
+ getIdentity(addressIndex) {
1672
+ return this.addresses.get(addressIndex)?.identity ?? null;
1673
+ }
1674
+ // ===========================================================================
1675
+ // Event timestamp persistence
1676
+ // ===========================================================================
1677
+ updateLastEventTimestamp(entry, createdAt) {
1678
+ if (!this.storage) return;
1679
+ if (createdAt <= entry.lastEventTs) return;
1680
+ entry.lastEventTs = createdAt;
1681
+ const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
1682
+ this.storage.set(storageKey, createdAt.toString()).catch((err) => {
1683
+ logger.debug("Mux", "Failed to save last event timestamp:", err);
1684
+ });
1685
+ }
1686
+ // ===========================================================================
1687
+ // Mux-level event system
1688
+ // ===========================================================================
1689
+ onTransportEvent(callback) {
1690
+ this.eventCallbacks.add(callback);
1691
+ return () => this.eventCallbacks.delete(callback);
1692
+ }
1693
+ onChatReady(handler) {
1694
+ if (this.chatEoseFired) {
1695
+ try {
1696
+ handler();
1697
+ } catch {
1698
+ }
1699
+ return () => {
1700
+ };
1701
+ }
1702
+ this.chatEoseHandlers.push(handler);
1703
+ return () => {
1704
+ const idx = this.chatEoseHandlers.indexOf(handler);
1705
+ if (idx >= 0) this.chatEoseHandlers.splice(idx, 1);
1706
+ };
1707
+ }
1708
+ emitEvent(event) {
1709
+ for (const cb of this.eventCallbacks) {
1710
+ try {
1711
+ cb(event);
1712
+ } catch {
1713
+ }
1714
+ }
1715
+ for (const entry of this.addresses.values()) {
1716
+ entry.adapter.emitTransportEvent(event);
1717
+ }
1718
+ }
1719
+ // ===========================================================================
1720
+ // Dedup Management
1721
+ // ===========================================================================
1722
+ /**
1723
+ * Clear processed event IDs (e.g., on address change or periodic cleanup).
1724
+ */
1725
+ clearProcessedEvents() {
1726
+ this.processedEventIds.clear();
1727
+ }
1728
+ /**
1729
+ * Get the storage adapter (for adapters that need it).
1730
+ */
1731
+ getStorage() {
1732
+ return this.storage;
1733
+ }
1734
+ /**
1735
+ * Get the UUID generator.
1736
+ */
1737
+ getUUIDGenerator() {
1738
+ return this.config.generateUUID;
1739
+ }
1740
+ };
1741
+ var AddressTransportAdapter = class {
1742
+ id;
1743
+ name;
1744
+ type = "p2p";
1745
+ description;
1746
+ mux;
1747
+ addressIndex;
1748
+ identity;
1749
+ resolveDelegate;
1750
+ // Per-address handler sets
1751
+ messageHandlers = /* @__PURE__ */ new Set();
1752
+ transferHandlers = /* @__PURE__ */ new Set();
1753
+ paymentRequestHandlers = /* @__PURE__ */ new Set();
1754
+ paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1755
+ readReceiptHandlers = /* @__PURE__ */ new Set();
1756
+ typingIndicatorHandlers = /* @__PURE__ */ new Set();
1757
+ composingHandlers = /* @__PURE__ */ new Set();
1758
+ instantSplitBundleHandlers = /* @__PURE__ */ new Set();
1759
+ broadcastHandlers = /* @__PURE__ */ new Map();
1760
+ eventCallbacks = /* @__PURE__ */ new Set();
1761
+ pendingMessages = [];
1762
+ chatEoseHandlers = [];
1763
+ constructor(mux, addressIndex, identity, resolveDelegate) {
1764
+ this.mux = mux;
1765
+ this.addressIndex = addressIndex;
1766
+ this.identity = identity;
1767
+ this.resolveDelegate = resolveDelegate ?? null;
1768
+ this.id = `nostr-addr-${addressIndex}`;
1769
+ this.name = `Nostr Transport (address ${addressIndex})`;
1770
+ this.description = `P2P messaging for address index ${addressIndex}`;
1771
+ }
1772
+ // ===========================================================================
1773
+ // BaseProvider — delegates to mux
1774
+ // ===========================================================================
1775
+ async connect() {
1776
+ await this.mux.connect();
1777
+ }
1778
+ async disconnect() {
1779
+ }
1780
+ isConnected() {
1781
+ return this.mux.isConnected();
1782
+ }
1783
+ getStatus() {
1784
+ return this.mux.getStatus();
1785
+ }
1786
+ // ===========================================================================
1787
+ // Identity (no-op — mux manages identity via addAddress)
1788
+ // ===========================================================================
1789
+ async setIdentity(identity) {
1790
+ this.identity = identity;
1791
+ await this.mux.addAddress(this.addressIndex, identity);
1792
+ }
1793
+ // ===========================================================================
1794
+ // Sending — delegates to mux with this address's keyManager
1795
+ // ===========================================================================
1796
+ async sendMessage(recipientPubkey, content) {
1797
+ const senderNametag = this.identity.nametag;
1798
+ const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1799
+ return this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, wrappedContent);
1800
+ }
1801
+ async sendTokenTransfer(recipientPubkey, payload) {
1802
+ const content = "token_transfer:" + JSON.stringify(payload);
1803
+ const uniqueD = `token-transfer-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1804
+ return this.mux.createAndPublishEncryptedEvent(
1805
+ this.addressIndex,
1806
+ EVENT_KINDS.TOKEN_TRANSFER,
1807
+ content,
1808
+ [["p", recipientPubkey], ["d", uniqueD], ["type", "token_transfer"]]
1809
+ );
1810
+ }
1811
+ async sendPaymentRequest(recipientPubkey, payload) {
1812
+ const requestId2 = this.mux.getUUIDGenerator()();
1813
+ const amount = typeof payload.amount === "bigint" ? payload.amount.toString() : payload.amount;
1814
+ const requestContent = {
1815
+ requestId: requestId2,
1816
+ amount,
1817
+ coinId: payload.coinId,
1818
+ message: payload.message,
1819
+ recipientNametag: payload.recipientNametag,
1820
+ deadline: Date.now() + 5 * 60 * 1e3
1821
+ };
1822
+ const content = "payment_request:" + JSON.stringify(requestContent);
1823
+ const tags = [
1824
+ ["p", recipientPubkey],
1825
+ ["type", "payment_request"],
1826
+ ["amount", amount]
1827
+ ];
1828
+ if (payload.recipientNametag) {
1829
+ tags.push(["recipient", payload.recipientNametag]);
1830
+ }
1831
+ return this.mux.createAndPublishEncryptedEvent(
1832
+ this.addressIndex,
1833
+ EVENT_KINDS.PAYMENT_REQUEST,
1834
+ content,
1835
+ tags
1836
+ );
1837
+ }
1838
+ async sendPaymentRequestResponse(recipientPubkey, response) {
1839
+ const content = "payment_response:" + JSON.stringify(response);
1840
+ return this.mux.createAndPublishEncryptedEvent(
1841
+ this.addressIndex,
1842
+ EVENT_KINDS.PAYMENT_REQUEST_RESPONSE,
1843
+ content,
1844
+ [["p", recipientPubkey], ["type", "payment_response"]]
1845
+ );
1846
+ }
1847
+ async sendReadReceipt(recipientPubkey, messageEventId) {
1848
+ const content = JSON.stringify({ type: "read_receipt", messageEventId });
1849
+ await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
1850
+ }
1851
+ async sendTypingIndicator(recipientPubkey) {
1852
+ const content = JSON.stringify({
1853
+ type: "typing",
1854
+ senderNametag: this.identity.nametag
1855
+ });
1856
+ await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
1857
+ }
1858
+ async sendComposingIndicator(recipientPubkey, content) {
1859
+ await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
1860
+ }
1861
+ async sendInstantSplitBundle(recipientPubkey, bundle) {
1862
+ const content = "token_transfer:" + JSON.stringify({
1863
+ type: "instant_split",
1864
+ ...bundle
1865
+ });
1866
+ const uniqueD = `instant-split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1867
+ return this.mux.createAndPublishEncryptedEvent(
1868
+ this.addressIndex,
1869
+ EVENT_KINDS.TOKEN_TRANSFER,
1870
+ content,
1871
+ [["p", recipientPubkey], ["d", uniqueD], ["type", "instant_split"]]
1872
+ );
1873
+ }
1874
+ // ===========================================================================
1875
+ // Subscription handlers — per-address
1876
+ // ===========================================================================
1877
+ onMessage(handler) {
1878
+ this.messageHandlers.add(handler);
1879
+ if (this.pendingMessages.length > 0) {
1880
+ const pending2 = this.pendingMessages;
1881
+ this.pendingMessages = [];
1882
+ for (const msg of pending2) {
1883
+ try {
1884
+ handler(msg);
1885
+ } catch {
1886
+ }
1887
+ }
1888
+ }
1889
+ return () => this.messageHandlers.delete(handler);
1890
+ }
1891
+ onTokenTransfer(handler) {
1892
+ this.transferHandlers.add(handler);
1893
+ return () => this.transferHandlers.delete(handler);
1894
+ }
1895
+ onPaymentRequest(handler) {
1896
+ this.paymentRequestHandlers.add(handler);
1897
+ return () => this.paymentRequestHandlers.delete(handler);
1898
+ }
1899
+ onPaymentRequestResponse(handler) {
1900
+ this.paymentRequestResponseHandlers.add(handler);
1901
+ return () => this.paymentRequestResponseHandlers.delete(handler);
1902
+ }
1903
+ onReadReceipt(handler) {
1904
+ this.readReceiptHandlers.add(handler);
1905
+ return () => this.readReceiptHandlers.delete(handler);
1906
+ }
1907
+ onTypingIndicator(handler) {
1908
+ this.typingIndicatorHandlers.add(handler);
1909
+ return () => this.typingIndicatorHandlers.delete(handler);
1910
+ }
1911
+ onComposing(handler) {
1912
+ this.composingHandlers.add(handler);
1913
+ return () => this.composingHandlers.delete(handler);
1914
+ }
1915
+ onInstantSplitReceived(handler) {
1916
+ this.instantSplitBundleHandlers.add(handler);
1917
+ return () => this.instantSplitBundleHandlers.delete(handler);
1918
+ }
1919
+ subscribeToBroadcast(tags, handler) {
1920
+ const key = tags.sort().join(":");
1921
+ if (!this.broadcastHandlers.has(key)) {
1922
+ this.broadcastHandlers.set(key, /* @__PURE__ */ new Set());
1923
+ }
1924
+ this.broadcastHandlers.get(key).add(handler);
1925
+ return () => this.broadcastHandlers.get(key)?.delete(handler);
1926
+ }
1927
+ async publishBroadcast(content, tags) {
1928
+ const eventTags = tags ? tags.map((t) => ["t", t]) : [];
1929
+ return this.mux.publishRawEvent(this.addressIndex, 30023, content, eventTags);
1930
+ }
1931
+ // ===========================================================================
1932
+ // Resolve methods — delegate to original NostrTransportProvider
1933
+ // These are stateless relay queries, shared across all addresses
1934
+ // ===========================================================================
1935
+ async resolve(identifier) {
1936
+ return this.resolveDelegate?.resolve?.(identifier) ?? null;
1937
+ }
1938
+ async resolveNametag(nametag) {
1939
+ return this.resolveDelegate?.resolveNametag?.(nametag) ?? null;
1940
+ }
1941
+ async resolveNametagInfo(nametag) {
1942
+ return this.resolveDelegate?.resolveNametagInfo?.(nametag) ?? null;
1943
+ }
1944
+ async resolveAddressInfo(address) {
1945
+ return this.resolveDelegate?.resolveAddressInfo?.(address) ?? null;
1946
+ }
1947
+ async resolveTransportPubkeyInfo(transportPubkey) {
1948
+ return this.resolveDelegate?.resolveTransportPubkeyInfo?.(transportPubkey) ?? null;
1949
+ }
1950
+ async discoverAddresses(transportPubkeys) {
1951
+ return this.resolveDelegate?.discoverAddresses?.(transportPubkeys) ?? [];
1952
+ }
1953
+ async recoverNametag() {
1954
+ return this.resolveDelegate?.recoverNametag?.() ?? null;
1955
+ }
1956
+ async publishIdentityBinding(chainPubkey, l1Address, directAddress, nametag) {
1957
+ return this.resolveDelegate?.publishIdentityBinding?.(chainPubkey, l1Address, directAddress, nametag) ?? false;
1958
+ }
1959
+ // ===========================================================================
1960
+ // Relay Management — delegates to mux
1961
+ // ===========================================================================
1962
+ getRelays() {
1963
+ return this.mux.getRelays();
1964
+ }
1965
+ getConnectedRelays() {
1966
+ return this.mux.getConnectedRelays();
1967
+ }
1968
+ async addRelay(relayUrl) {
1969
+ return this.mux.addRelay(relayUrl);
1970
+ }
1971
+ async removeRelay(relayUrl) {
1972
+ return this.mux.removeRelay(relayUrl);
1973
+ }
1974
+ hasRelay(relayUrl) {
1975
+ return this.mux.hasRelay(relayUrl);
1976
+ }
1977
+ isRelayConnected(relayUrl) {
1978
+ return this.mux.isRelayConnected(relayUrl);
1979
+ }
1980
+ setFallbackSince(sinceSeconds) {
1981
+ this.mux.setFallbackSince(this.addressIndex, sinceSeconds);
1982
+ }
1983
+ async fetchPendingEvents() {
1984
+ }
1985
+ onChatReady(handler) {
1986
+ return this.mux.onChatReady(handler);
1987
+ }
1988
+ // ===========================================================================
1989
+ // Dispatch methods — called by MultiAddressTransportMux to route events
1990
+ // ===========================================================================
1991
+ dispatchMessage(message) {
1992
+ if (this.messageHandlers.size === 0) {
1993
+ this.pendingMessages.push(message);
1994
+ return;
1995
+ }
1996
+ for (const handler of this.messageHandlers) {
1997
+ try {
1998
+ handler(message);
1999
+ } catch (e) {
2000
+ logger.debug("MuxAdapter", "Message handler error:", e);
2001
+ }
2002
+ }
2003
+ }
2004
+ dispatchTokenTransfer(transfer) {
2005
+ for (const handler of this.transferHandlers) {
2006
+ try {
2007
+ handler(transfer);
2008
+ } catch (e) {
2009
+ logger.debug("MuxAdapter", "Transfer handler error:", e);
2010
+ }
2011
+ }
2012
+ }
2013
+ dispatchPaymentRequest(request) {
2014
+ for (const handler of this.paymentRequestHandlers) {
2015
+ try {
2016
+ handler(request);
2017
+ } catch (e) {
2018
+ logger.debug("MuxAdapter", "Payment request handler error:", e);
2019
+ }
2020
+ }
2021
+ }
2022
+ dispatchPaymentRequestResponse(response) {
2023
+ for (const handler of this.paymentRequestResponseHandlers) {
2024
+ try {
2025
+ handler(response);
2026
+ } catch (e) {
2027
+ logger.debug("MuxAdapter", "Payment response handler error:", e);
2028
+ }
2029
+ }
2030
+ }
2031
+ dispatchReadReceipt(receipt) {
2032
+ for (const handler of this.readReceiptHandlers) {
2033
+ try {
2034
+ handler(receipt);
2035
+ } catch (e) {
2036
+ logger.debug("MuxAdapter", "Read receipt handler error:", e);
2037
+ }
2038
+ }
2039
+ }
2040
+ dispatchTypingIndicator(indicator) {
2041
+ for (const handler of this.typingIndicatorHandlers) {
2042
+ try {
2043
+ handler(indicator);
2044
+ } catch (e) {
2045
+ logger.debug("MuxAdapter", "Typing handler error:", e);
2046
+ }
2047
+ }
2048
+ }
2049
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2050
+ dispatchComposingIndicator(indicator) {
2051
+ for (const handler of this.composingHandlers) {
2052
+ try {
2053
+ handler(indicator);
2054
+ } catch (e) {
2055
+ logger.debug("MuxAdapter", "Composing handler error:", e);
2056
+ }
2057
+ }
2058
+ }
2059
+ dispatchInstantSplitBundle(bundle) {
2060
+ for (const handler of this.instantSplitBundleHandlers) {
2061
+ try {
2062
+ handler(bundle);
2063
+ } catch (e) {
2064
+ logger.debug("MuxAdapter", "Instant split handler error:", e);
2065
+ }
2066
+ }
2067
+ }
2068
+ emitTransportEvent(event) {
2069
+ for (const cb of this.eventCallbacks) {
2070
+ try {
2071
+ cb(event);
2072
+ } catch {
2073
+ }
2074
+ }
2075
+ }
2076
+ };
2077
+
982
2078
  // modules/payments/L1PaymentsModule.ts
983
2079
  init_errors();
984
2080
  init_constants();
@@ -2934,7 +4030,7 @@ var import_MintCommitment = require("@unicitylabs/state-transition-sdk/lib/trans
2934
4030
  var import_HashAlgorithm2 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
2935
4031
  var import_UnmaskedPredicate2 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
2936
4032
  var import_InclusionProofUtils2 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
2937
- var import_nostr_js_sdk = require("@unicitylabs/nostr-js-sdk");
4033
+ var import_nostr_js_sdk2 = require("@unicitylabs/nostr-js-sdk");
2938
4034
  var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
2939
4035
  var NametagMinter = class {
2940
4036
  client;
@@ -2958,7 +4054,7 @@ var NametagMinter = class {
2958
4054
  async isNametagAvailable(nametag) {
2959
4055
  try {
2960
4056
  const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
2961
- const cleanNametag = (0, import_nostr_js_sdk.normalizeNametag)(stripped);
4057
+ const cleanNametag = (0, import_nostr_js_sdk2.normalizeNametag)(stripped);
2962
4058
  const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
2963
4059
  const isMinted = await this.client.isMinted(this.trustBase, nametagTokenId);
2964
4060
  return !isMinted;
@@ -2976,7 +4072,7 @@ var NametagMinter = class {
2976
4072
  */
2977
4073
  async mintNametag(nametag, ownerAddress) {
2978
4074
  const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
2979
- const cleanNametag = (0, import_nostr_js_sdk.normalizeNametag)(stripped);
4075
+ const cleanNametag = (0, import_nostr_js_sdk2.normalizeNametag)(stripped);
2980
4076
  this.log(`Starting mint for nametag: ${cleanNametag}`);
2981
4077
  try {
2982
4078
  const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
@@ -5084,6 +6180,15 @@ var PaymentsModule = class _PaymentsModule {
5084
6180
  this.unsubscribePaymentRequests = null;
5085
6181
  this.unsubscribePaymentRequestResponses?.();
5086
6182
  this.unsubscribePaymentRequestResponses = null;
6183
+ this.stopProofPolling();
6184
+ this.proofPollingJobs.clear();
6185
+ this.stopResolveUnconfirmedPolling();
6186
+ this.unsubscribeStorageEvents();
6187
+ for (const [, resolver] of this.pendingResponseResolvers) {
6188
+ clearTimeout(resolver.timeout);
6189
+ resolver.reject(new Error("Address switched"));
6190
+ }
6191
+ this.pendingResponseResolvers.clear();
5087
6192
  this.tokens.clear();
5088
6193
  this.pendingTransfers.clear();
5089
6194
  this.tombstones = [];
@@ -5132,6 +6237,13 @@ var PaymentsModule = class _PaymentsModule {
5132
6237
  try {
5133
6238
  const result = await provider.load();
5134
6239
  if (result.success && result.data) {
6240
+ const loadedMeta = result.data?._meta;
6241
+ const currentL1 = this.deps.identity.l1Address;
6242
+ const currentChain = this.deps.identity.chainPubkey;
6243
+ if (loadedMeta?.address && currentL1 && loadedMeta.address !== currentL1 && loadedMeta.address !== currentChain) {
6244
+ logger.warn("Payments", `Load: rejecting data from provider ${id} \u2014 address mismatch (got=${loadedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
6245
+ continue;
6246
+ }
5135
6247
  this.loadFromStorageData(result.data);
5136
6248
  const txfData = result.data;
5137
6249
  if (txfData._history && txfData._history.length > 0) {
@@ -5213,6 +6325,11 @@ var PaymentsModule = class _PaymentsModule {
5213
6325
  */
5214
6326
  async send(request) {
5215
6327
  this.ensureInitialized();
6328
+ let resolveSendTracker;
6329
+ const sendTracker = new Promise((r) => {
6330
+ resolveSendTracker = r;
6331
+ });
6332
+ this.pendingBackgroundTasks.push(sendTracker);
5216
6333
  const result = {
5217
6334
  id: crypto.randomUUID(),
5218
6335
  status: "pending",
@@ -5500,6 +6617,8 @@ var PaymentsModule = class _PaymentsModule {
5500
6617
  }
5501
6618
  this.deps.emitEvent("transfer:failed", result);
5502
6619
  throw error;
6620
+ } finally {
6621
+ resolveSendTracker();
5503
6622
  }
5504
6623
  }
5505
6624
  /**
@@ -6512,9 +7631,12 @@ var PaymentsModule = class _PaymentsModule {
6512
7631
  * Call this before process exit to ensure all tokens are saved.
6513
7632
  */
6514
7633
  async waitForPendingOperations() {
7634
+ logger.debug("Payments", `waitForPendingOperations: ${this.pendingBackgroundTasks.length} pending tasks`);
6515
7635
  if (this.pendingBackgroundTasks.length > 0) {
7636
+ logger.debug("Payments", "waitForPendingOperations: waiting...");
6516
7637
  await Promise.allSettled(this.pendingBackgroundTasks);
6517
7638
  this.pendingBackgroundTasks = [];
7639
+ logger.debug("Payments", "waitForPendingOperations: all tasks completed");
6518
7640
  }
6519
7641
  }
6520
7642
  /**
@@ -7756,6 +8878,13 @@ var PaymentsModule = class _PaymentsModule {
7756
8878
  try {
7757
8879
  const result = await provider.sync(localData);
7758
8880
  if (result.success && result.merged) {
8881
+ const mergedMeta = result.merged?._meta;
8882
+ const currentL1 = this.deps.identity.l1Address;
8883
+ const currentChain = this.deps.identity.chainPubkey;
8884
+ if (mergedMeta?.address && currentL1 && mergedMeta.address !== currentL1 && mergedMeta.address !== currentChain) {
8885
+ logger.warn("Payments", `Sync: rejecting data from provider ${providerId} \u2014 address mismatch (got=${mergedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
8886
+ continue;
8887
+ }
7759
8888
  const savedTokens = new Map(this.tokens);
7760
8889
  this.loadFromStorageData(result.merged);
7761
8890
  let restoredCount = 0;
@@ -8767,6 +9896,12 @@ var CommunicationsModule = class {
8767
9896
  this.unsubscribeComposing = deps.transport.onComposing?.((indicator) => {
8768
9897
  this.handleComposingIndicator(indicator);
8769
9898
  }) ?? null;
9899
+ if (deps.transport.onChatReady) {
9900
+ deps.transport.onChatReady(() => {
9901
+ const conversations = this.getConversations();
9902
+ deps.emitEvent("communications:ready", { conversationCount: conversations.size });
9903
+ });
9904
+ }
8770
9905
  }
8771
9906
  /**
8772
9907
  * Load messages from storage.
@@ -9178,7 +10313,7 @@ function createCommunicationsModule(config) {
9178
10313
  }
9179
10314
 
9180
10315
  // modules/groupchat/GroupChatModule.ts
9181
- var import_nostr_js_sdk2 = require("@unicitylabs/nostr-js-sdk");
10316
+ var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
9182
10317
  init_logger();
9183
10318
  init_errors();
9184
10319
  init_constants();
@@ -9196,7 +10331,7 @@ var GroupVisibility = {
9196
10331
 
9197
10332
  // modules/groupchat/GroupChatModule.ts
9198
10333
  function createNip29Filter(data) {
9199
- return new import_nostr_js_sdk2.Filter(data);
10334
+ return new import_nostr_js_sdk3.Filter(data);
9200
10335
  }
9201
10336
  var GroupChatModule = class {
9202
10337
  config;
@@ -9245,7 +10380,7 @@ var GroupChatModule = class {
9245
10380
  }
9246
10381
  this.deps = deps;
9247
10382
  const secretKey = Buffer.from(deps.identity.privateKey, "hex");
9248
- this.keyManager = import_nostr_js_sdk2.NostrKeyManager.fromPrivateKey(secretKey);
10383
+ this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
9249
10384
  }
9250
10385
  async load() {
9251
10386
  this.ensureInitialized();
@@ -9380,7 +10515,7 @@ var GroupChatModule = class {
9380
10515
  }
9381
10516
  this.subscriptionIds = [];
9382
10517
  const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
9383
- this.keyManager = import_nostr_js_sdk2.NostrKeyManager.fromPrivateKey(secretKey);
10518
+ this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
9384
10519
  if (this.groups.size === 0) {
9385
10520
  await this.restoreJoinedGroups();
9386
10521
  } else {
@@ -9392,13 +10527,13 @@ var GroupChatModule = class {
9392
10527
  this.ensureInitialized();
9393
10528
  if (!this.keyManager) {
9394
10529
  const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
9395
- this.keyManager = import_nostr_js_sdk2.NostrKeyManager.fromPrivateKey(secretKey);
10530
+ this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
9396
10531
  }
9397
10532
  const primaryRelay = this.config.relays[0];
9398
10533
  if (primaryRelay) {
9399
10534
  await this.checkAndClearOnRelayChange(primaryRelay);
9400
10535
  }
9401
- this.client = new import_nostr_js_sdk2.NostrClient(this.keyManager);
10536
+ this.client = new import_nostr_js_sdk3.NostrClient(this.keyManager);
9402
10537
  try {
9403
10538
  await this.client.connect(...this.config.relays);
9404
10539
  this.connected = true;
@@ -9409,6 +10544,7 @@ var GroupChatModule = class {
9409
10544
  await this.subscribeToJoinedGroups();
9410
10545
  }
9411
10546
  this.deps.emitEvent("groupchat:connection", { connected: true });
10547
+ this.deps.emitEvent("groupchat:ready", { groupCount: this.groups.size });
9412
10548
  } catch (error) {
9413
10549
  logger.error("GroupChat", "Failed to connect to relays", error);
9414
10550
  this.deps.emitEvent("groupchat:connection", { connected: false });
@@ -9656,7 +10792,7 @@ var GroupChatModule = class {
9656
10792
  if (!myPubkey) return [];
9657
10793
  const groupIdsWithMembership = /* @__PURE__ */ new Set();
9658
10794
  await this.oneshotSubscription(
9659
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
10795
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
9660
10796
  {
9661
10797
  onEvent: (event) => {
9662
10798
  const groupId = this.getGroupIdFromMetadataEvent(event);
@@ -9707,7 +10843,7 @@ var GroupChatModule = class {
9707
10843
  const memberCountsMap = /* @__PURE__ */ new Map();
9708
10844
  await Promise.all([
9709
10845
  this.oneshotSubscription(
9710
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
10846
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
9711
10847
  {
9712
10848
  onEvent: (event) => {
9713
10849
  const group = this.parseGroupMetadata(event);
@@ -9725,7 +10861,7 @@ var GroupChatModule = class {
9725
10861
  }
9726
10862
  ),
9727
10863
  this.oneshotSubscription(
9728
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
10864
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
9729
10865
  {
9730
10866
  onEvent: (event) => {
9731
10867
  const groupId = this.getGroupIdFromMetadataEvent(event);
@@ -10043,6 +11179,19 @@ var GroupChatModule = class {
10043
11179
  getMessages(groupId) {
10044
11180
  return (this.messages.get(groupId) || []).sort((a, b) => a.timestamp - b.timestamp);
10045
11181
  }
11182
+ getMessagesPage(groupId, options) {
11183
+ const limit = options?.limit ?? 20;
11184
+ const before = options?.before ?? Infinity;
11185
+ const groupMessages = this.messages.get(groupId) ?? [];
11186
+ const filtered = groupMessages.filter((m) => m.timestamp < before).sort((a, b) => b.timestamp - a.timestamp);
11187
+ const page = filtered.slice(0, limit);
11188
+ return {
11189
+ messages: page.reverse(),
11190
+ // chronological order
11191
+ hasMore: filtered.length > limit,
11192
+ oldestTimestamp: page.length > 0 ? page[0].timestamp : null
11193
+ };
11194
+ }
10046
11195
  getMembers(groupId) {
10047
11196
  return (this.members.get(groupId) || []).sort((a, b) => a.joinedAt - b.joinedAt);
10048
11197
  }
@@ -10215,7 +11364,7 @@ var GroupChatModule = class {
10215
11364
  if (!this.client) return /* @__PURE__ */ new Set();
10216
11365
  const adminPubkeys = /* @__PURE__ */ new Set();
10217
11366
  return this.oneshotSubscription(
10218
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
11367
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
10219
11368
  {
10220
11369
  onEvent: (event) => {
10221
11370
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -10237,7 +11386,7 @@ var GroupChatModule = class {
10237
11386
  if (!this.client) return null;
10238
11387
  let result = null;
10239
11388
  return this.oneshotSubscription(
10240
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
11389
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
10241
11390
  {
10242
11391
  onEvent: (event) => {
10243
11392
  if (!result) result = this.parseGroupMetadata(event);
@@ -10274,7 +11423,7 @@ var GroupChatModule = class {
10274
11423
  if (!this.client) return [];
10275
11424
  const members = [];
10276
11425
  return this.oneshotSubscription(
10277
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
11426
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
10278
11427
  {
10279
11428
  onEvent: (event) => {
10280
11429
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -10295,7 +11444,7 @@ var GroupChatModule = class {
10295
11444
  if (!this.client) return [];
10296
11445
  const adminPubkeys = [];
10297
11446
  return this.oneshotSubscription(
10298
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
11447
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
10299
11448
  {
10300
11449
  onEvent: (event) => {
10301
11450
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -13964,9 +15113,9 @@ var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign
13964
15113
  var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
13965
15114
  var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
13966
15115
  var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
13967
- var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
15116
+ var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
13968
15117
  function isValidNametag(nametag) {
13969
- if ((0, import_nostr_js_sdk3.isPhoneNumber)(nametag)) return true;
15118
+ if ((0, import_nostr_js_sdk4.isPhoneNumber)(nametag)) return true;
13970
15119
  return /^[a-z0-9_-]{3,20}$/.test(nametag);
13971
15120
  }
13972
15121
  var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
@@ -14010,11 +15159,18 @@ var Sphere = class _Sphere {
14010
15159
  _transport;
14011
15160
  _oracle;
14012
15161
  _priceProvider;
14013
- // Modules
15162
+ // Modules (single-instance — backward compat, delegates to active address)
14014
15163
  _payments;
14015
15164
  _communications;
14016
15165
  _groupChat = null;
14017
15166
  _market = null;
15167
+ // Per-address module instances (Phase 2: independent parallel operation)
15168
+ _addressModules = /* @__PURE__ */ new Map();
15169
+ _transportMux = null;
15170
+ // Stored configs for creating per-address modules
15171
+ _l1Config;
15172
+ _groupChatConfig;
15173
+ _marketConfig;
14018
15174
  // Events
14019
15175
  eventHandlers = /* @__PURE__ */ new Map();
14020
15176
  // Provider management
@@ -14032,6 +15188,9 @@ var Sphere = class _Sphere {
14032
15188
  if (tokenStorage) {
14033
15189
  this._tokenStorageProviders.set(tokenStorage.id, tokenStorage);
14034
15190
  }
15191
+ this._l1Config = l1Config;
15192
+ this._groupChatConfig = groupChatConfig;
15193
+ this._marketConfig = marketConfig;
14035
15194
  this._payments = createPaymentsModule({ l1: l1Config });
14036
15195
  this._communications = createCommunicationsModule();
14037
15196
  this._groupChat = groupChatConfig ? createGroupChatModule(groupChatConfig) : null;
@@ -15290,7 +16449,7 @@ var Sphere = class _Sphere {
15290
16449
  nametags.set(0, newNametag);
15291
16450
  }
15292
16451
  const nametag = this._addressNametags.get(addressId)?.get(0);
15293
- this._identity = {
16452
+ const newIdentity = {
15294
16453
  privateKey: addressInfo.privateKey,
15295
16454
  chainPubkey: addressInfo.publicKey,
15296
16455
  l1Address: addressInfo.address,
@@ -15298,20 +16457,53 @@ var Sphere = class _Sphere {
15298
16457
  ipnsName: "12D3KooW" + ipnsHash,
15299
16458
  nametag
15300
16459
  };
16460
+ if (!this._addressModules.has(index)) {
16461
+ logger.debug("Sphere", `switchToAddress(${index}): creating per-address modules (lazy init)`);
16462
+ const addressTokenProviders = /* @__PURE__ */ new Map();
16463
+ for (const [providerId, provider] of this._tokenStorageProviders.entries()) {
16464
+ if (provider.createForAddress) {
16465
+ const newProvider = provider.createForAddress();
16466
+ newProvider.setIdentity(newIdentity);
16467
+ await newProvider.initialize();
16468
+ addressTokenProviders.set(providerId, newProvider);
16469
+ } else {
16470
+ logger.warn("Sphere", `Token storage provider ${providerId} does not support createForAddress, reusing shared instance`);
16471
+ addressTokenProviders.set(providerId, provider);
16472
+ }
16473
+ }
16474
+ await this.initializeAddressModules(index, newIdentity, addressTokenProviders);
16475
+ } else {
16476
+ const moduleSet = this._addressModules.get(index);
16477
+ if (nametag !== moduleSet.identity.nametag) {
16478
+ moduleSet.identity = newIdentity;
16479
+ const addressTransport = moduleSet.transportAdapter ?? this._transport;
16480
+ moduleSet.payments.initialize({
16481
+ identity: newIdentity,
16482
+ storage: this._storage,
16483
+ tokenStorageProviders: moduleSet.tokenStorageProviders,
16484
+ transport: addressTransport,
16485
+ oracle: this._oracle,
16486
+ emitEvent: this.emitEvent.bind(this),
16487
+ chainCode: this._masterKey?.chainCode || void 0,
16488
+ price: this._priceProvider ?? void 0
16489
+ });
16490
+ }
16491
+ }
16492
+ this._identity = newIdentity;
15301
16493
  this._currentAddressIndex = index;
15302
16494
  await this._updateCachedProxyAddress();
16495
+ const activeModules = this._addressModules.get(index);
16496
+ this._payments = activeModules.payments;
16497
+ this._communications = activeModules.communications;
16498
+ this._groupChat = activeModules.groupChat;
16499
+ this._market = activeModules.market;
15303
16500
  await this._storage.set(STORAGE_KEYS_GLOBAL.CURRENT_ADDRESS_INDEX, index.toString());
15304
16501
  this._storage.setIdentity(this._identity);
15305
- await this._transport.setIdentity(this._identity);
15306
- logger.debug("Sphere", `switchToAddress(${index}): re-initializing ${this._tokenStorageProviders.size} token storage provider(s)`);
15307
- for (const [providerId, provider] of this._tokenStorageProviders.entries()) {
15308
- logger.debug("Sphere", `switchToAddress(${index}): shutdown provider=${providerId}`);
15309
- await provider.shutdown();
15310
- provider.setIdentity(this._identity);
15311
- logger.debug("Sphere", `switchToAddress(${index}): initialize provider=${providerId}`);
15312
- await provider.initialize();
16502
+ if (this._transport.setFallbackSince) {
16503
+ const fallbackTs = Math.floor(Date.now() / 1e3) - 86400;
16504
+ this._transport.setFallbackSince(fallbackTs);
15313
16505
  }
15314
- await this.reinitializeModulesForNewAddress();
16506
+ await this._transport.setIdentity(this._identity);
15315
16507
  this.emitEvent("identity:changed", {
15316
16508
  l1Address: this._identity.l1Address,
15317
16509
  directAddress: this._identity.directAddress,
@@ -15366,42 +16558,104 @@ var Sphere = class _Sphere {
15366
16558
  }
15367
16559
  }
15368
16560
  /**
15369
- * Re-initialize modules after address switch
16561
+ * Create a new set of per-address modules for the given index.
16562
+ * Each address gets its own PaymentsModule, CommunicationsModule, etc.
16563
+ * Modules are fully independent — they have their own token storage,
16564
+ * and can sync/finalize/split in background regardless of active address.
16565
+ *
16566
+ * @param index - HD address index
16567
+ * @param identity - Full identity for this address
16568
+ * @param tokenStorageProviders - Token storage providers for this address
15370
16569
  */
15371
- async reinitializeModulesForNewAddress() {
16570
+ async initializeAddressModules(index, identity, tokenStorageProviders) {
15372
16571
  const emitEvent = this.emitEvent.bind(this);
15373
- this._payments.initialize({
15374
- identity: this._identity,
16572
+ const adapter = await this.ensureTransportMux(index, identity);
16573
+ const addressTransport = adapter ?? this._transport;
16574
+ const payments = createPaymentsModule({ l1: this._l1Config });
16575
+ const communications = createCommunicationsModule();
16576
+ const groupChat = this._groupChatConfig ? createGroupChatModule(this._groupChatConfig) : null;
16577
+ const market = this._marketConfig ? createMarketModule(this._marketConfig) : null;
16578
+ payments.initialize({
16579
+ identity,
15375
16580
  storage: this._storage,
15376
- tokenStorageProviders: this._tokenStorageProviders,
15377
- transport: this._transport,
16581
+ tokenStorageProviders,
16582
+ transport: addressTransport,
15378
16583
  oracle: this._oracle,
15379
16584
  emitEvent,
15380
16585
  chainCode: this._masterKey?.chainCode || void 0,
15381
16586
  price: this._priceProvider ?? void 0
15382
16587
  });
15383
- this._communications.initialize({
15384
- identity: this._identity,
16588
+ communications.initialize({
16589
+ identity,
15385
16590
  storage: this._storage,
15386
- transport: this._transport,
16591
+ transport: addressTransport,
15387
16592
  emitEvent
15388
16593
  });
15389
- this._groupChat?.initialize({
15390
- identity: this._identity,
16594
+ groupChat?.initialize({
16595
+ identity,
15391
16596
  storage: this._storage,
15392
16597
  emitEvent
15393
16598
  });
15394
- this._market?.initialize({
15395
- identity: this._identity,
16599
+ market?.initialize({
16600
+ identity,
15396
16601
  emitEvent
15397
16602
  });
15398
- await this._payments.load();
15399
- await this._communications.load();
15400
- await this._groupChat?.load();
15401
- await this._market?.load();
15402
- this._payments.sync().catch((err) => {
15403
- logger.warn("Sphere", "Post-switch sync failed:", err);
16603
+ await payments.load();
16604
+ await communications.load();
16605
+ await groupChat?.load();
16606
+ await market?.load();
16607
+ const moduleSet = {
16608
+ index,
16609
+ identity,
16610
+ payments,
16611
+ communications,
16612
+ groupChat,
16613
+ market,
16614
+ transportAdapter: adapter,
16615
+ tokenStorageProviders: new Map(tokenStorageProviders),
16616
+ initialized: true
16617
+ };
16618
+ this._addressModules.set(index, moduleSet);
16619
+ logger.debug("Sphere", `Initialized per-address modules for address ${index} (transport: ${adapter ? "mux adapter" : "primary"})`);
16620
+ payments.sync().catch((err) => {
16621
+ logger.warn("Sphere", `Post-init sync failed for address ${index}:`, err);
15404
16622
  });
16623
+ return moduleSet;
16624
+ }
16625
+ /**
16626
+ * Ensure the transport multiplexer exists and register an address.
16627
+ * Creates the mux on first call. Returns an AddressTransportAdapter
16628
+ * that routes events for this address independently.
16629
+ * @returns AddressTransportAdapter or null if transport is not Nostr-based
16630
+ */
16631
+ async ensureTransportMux(index, identity) {
16632
+ const transport = this._transport;
16633
+ if (typeof transport.getWebSocketFactory !== "function" || typeof transport.getConfiguredRelays !== "function") {
16634
+ logger.debug("Sphere", "Transport does not support mux interface, skipping");
16635
+ return null;
16636
+ }
16637
+ const nostrTransport = transport;
16638
+ if (!this._transportMux) {
16639
+ this._transportMux = new MultiAddressTransportMux({
16640
+ relays: nostrTransport.getConfiguredRelays(),
16641
+ createWebSocket: nostrTransport.getWebSocketFactory(),
16642
+ storage: nostrTransport.getStorageAdapter() ?? void 0
16643
+ });
16644
+ await this._transportMux.connect();
16645
+ if (typeof nostrTransport.suppressSubscriptions === "function") {
16646
+ nostrTransport.suppressSubscriptions();
16647
+ }
16648
+ logger.debug("Sphere", "Transport mux created and connected");
16649
+ }
16650
+ const adapter = await this._transportMux.addAddress(index, identity, this._transport);
16651
+ return adapter;
16652
+ }
16653
+ /**
16654
+ * Get per-address modules for any address index (creates lazily if needed).
16655
+ * This allows accessing any address's modules without switching.
16656
+ */
16657
+ getAddressPayments(index) {
16658
+ return this._addressModules.get(index)?.payments;
15405
16659
  }
15406
16660
  /**
15407
16661
  * Derive address at a specific index
@@ -16322,17 +17576,40 @@ var Sphere = class _Sphere {
16322
17576
  */
16323
17577
  cleanNametag(raw) {
16324
17578
  const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
16325
- return (0, import_nostr_js_sdk3.normalizeNametag)(stripped);
17579
+ return (0, import_nostr_js_sdk4.normalizeNametag)(stripped);
16326
17580
  }
16327
17581
  // ===========================================================================
16328
17582
  // Public Methods - Lifecycle
16329
17583
  // ===========================================================================
16330
17584
  async destroy() {
16331
17585
  this.cleanupProviderEventSubscriptions();
17586
+ for (const [idx, moduleSet] of this._addressModules.entries()) {
17587
+ try {
17588
+ moduleSet.payments.destroy();
17589
+ moduleSet.communications.destroy();
17590
+ moduleSet.groupChat?.destroy();
17591
+ moduleSet.market?.destroy();
17592
+ for (const provider of moduleSet.tokenStorageProviders.values()) {
17593
+ try {
17594
+ await provider.shutdown();
17595
+ } catch {
17596
+ }
17597
+ }
17598
+ moduleSet.tokenStorageProviders.clear();
17599
+ logger.debug("Sphere", `Destroyed modules for address ${idx}`);
17600
+ } catch (err) {
17601
+ logger.warn("Sphere", `Error destroying modules for address ${idx}:`, err);
17602
+ }
17603
+ }
17604
+ this._addressModules.clear();
16332
17605
  this._payments.destroy();
16333
17606
  this._communications.destroy();
16334
17607
  this._groupChat?.destroy();
16335
17608
  this._market?.destroy();
17609
+ if (this._transportMux) {
17610
+ await this._transportMux.disconnect();
17611
+ this._transportMux = null;
17612
+ }
16336
17613
  await this._transport.disconnect();
16337
17614
  await this._storage.disconnect();
16338
17615
  await this._oracle.disconnect();
@@ -16527,6 +17804,9 @@ var Sphere = class _Sphere {
16527
17804
  // ===========================================================================
16528
17805
  async initializeProviders() {
16529
17806
  this._storage.setIdentity(this._identity);
17807
+ if (this._transport.setFallbackSince) {
17808
+ this._transport.setFallbackSince(Math.floor(Date.now() / 1e3) - 86400);
17809
+ }
16530
17810
  await this._transport.setIdentity(this._identity);
16531
17811
  for (const provider of this._tokenStorageProviders.values()) {
16532
17812
  provider.setIdentity(this._identity);
@@ -16617,11 +17897,13 @@ var Sphere = class _Sphere {
16617
17897
  }
16618
17898
  async initializeModules() {
16619
17899
  const emitEvent = this.emitEvent.bind(this);
17900
+ const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
17901
+ const moduleTransport = adapter ?? this._transport;
16620
17902
  this._payments.initialize({
16621
17903
  identity: this._identity,
16622
17904
  storage: this._storage,
16623
17905
  tokenStorageProviders: this._tokenStorageProviders,
16624
- transport: this._transport,
17906
+ transport: moduleTransport,
16625
17907
  oracle: this._oracle,
16626
17908
  emitEvent,
16627
17909
  // Pass chain code for L1 HD derivation
@@ -16632,7 +17914,7 @@ var Sphere = class _Sphere {
16632
17914
  this._communications.initialize({
16633
17915
  identity: this._identity,
16634
17916
  storage: this._storage,
16635
- transport: this._transport,
17917
+ transport: moduleTransport,
16636
17918
  emitEvent
16637
17919
  });
16638
17920
  this._groupChat?.initialize({
@@ -16648,6 +17930,17 @@ var Sphere = class _Sphere {
16648
17930
  await this._communications.load();
16649
17931
  await this._groupChat?.load();
16650
17932
  await this._market?.load();
17933
+ this._addressModules.set(this._currentAddressIndex, {
17934
+ index: this._currentAddressIndex,
17935
+ identity: this._identity,
17936
+ payments: this._payments,
17937
+ communications: this._communications,
17938
+ groupChat: this._groupChat,
17939
+ market: this._market,
17940
+ transportAdapter: adapter,
17941
+ tokenStorageProviders: new Map(this._tokenStorageProviders),
17942
+ initialized: true
17943
+ });
16651
17944
  }
16652
17945
  // ===========================================================================
16653
17946
  // Private: Helpers
@@ -17319,8 +18612,8 @@ function createTokenValidator(options) {
17319
18612
  }
17320
18613
 
17321
18614
  // index.ts
17322
- var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
17323
18615
  var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
18616
+ var import_nostr_js_sdk6 = require("@unicitylabs/nostr-js-sdk");
17324
18617
 
17325
18618
  // price/CoinGeckoPriceProvider.ts
17326
18619
  init_logger();