@unicitylabs/sphere-sdk 0.6.2 → 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;
@@ -9184,7 +10313,7 @@ function createCommunicationsModule(config) {
9184
10313
  }
9185
10314
 
9186
10315
  // modules/groupchat/GroupChatModule.ts
9187
- var import_nostr_js_sdk2 = require("@unicitylabs/nostr-js-sdk");
10316
+ var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
9188
10317
  init_logger();
9189
10318
  init_errors();
9190
10319
  init_constants();
@@ -9202,7 +10331,7 @@ var GroupVisibility = {
9202
10331
 
9203
10332
  // modules/groupchat/GroupChatModule.ts
9204
10333
  function createNip29Filter(data) {
9205
- return new import_nostr_js_sdk2.Filter(data);
10334
+ return new import_nostr_js_sdk3.Filter(data);
9206
10335
  }
9207
10336
  var GroupChatModule = class {
9208
10337
  config;
@@ -9251,7 +10380,7 @@ var GroupChatModule = class {
9251
10380
  }
9252
10381
  this.deps = deps;
9253
10382
  const secretKey = Buffer.from(deps.identity.privateKey, "hex");
9254
- this.keyManager = import_nostr_js_sdk2.NostrKeyManager.fromPrivateKey(secretKey);
10383
+ this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
9255
10384
  }
9256
10385
  async load() {
9257
10386
  this.ensureInitialized();
@@ -9386,7 +10515,7 @@ var GroupChatModule = class {
9386
10515
  }
9387
10516
  this.subscriptionIds = [];
9388
10517
  const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
9389
- this.keyManager = import_nostr_js_sdk2.NostrKeyManager.fromPrivateKey(secretKey);
10518
+ this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
9390
10519
  if (this.groups.size === 0) {
9391
10520
  await this.restoreJoinedGroups();
9392
10521
  } else {
@@ -9398,13 +10527,13 @@ var GroupChatModule = class {
9398
10527
  this.ensureInitialized();
9399
10528
  if (!this.keyManager) {
9400
10529
  const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
9401
- this.keyManager = import_nostr_js_sdk2.NostrKeyManager.fromPrivateKey(secretKey);
10530
+ this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
9402
10531
  }
9403
10532
  const primaryRelay = this.config.relays[0];
9404
10533
  if (primaryRelay) {
9405
10534
  await this.checkAndClearOnRelayChange(primaryRelay);
9406
10535
  }
9407
- this.client = new import_nostr_js_sdk2.NostrClient(this.keyManager);
10536
+ this.client = new import_nostr_js_sdk3.NostrClient(this.keyManager);
9408
10537
  try {
9409
10538
  await this.client.connect(...this.config.relays);
9410
10539
  this.connected = true;
@@ -9663,7 +10792,7 @@ var GroupChatModule = class {
9663
10792
  if (!myPubkey) return [];
9664
10793
  const groupIdsWithMembership = /* @__PURE__ */ new Set();
9665
10794
  await this.oneshotSubscription(
9666
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
10795
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
9667
10796
  {
9668
10797
  onEvent: (event) => {
9669
10798
  const groupId = this.getGroupIdFromMetadataEvent(event);
@@ -9714,7 +10843,7 @@ var GroupChatModule = class {
9714
10843
  const memberCountsMap = /* @__PURE__ */ new Map();
9715
10844
  await Promise.all([
9716
10845
  this.oneshotSubscription(
9717
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
10846
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
9718
10847
  {
9719
10848
  onEvent: (event) => {
9720
10849
  const group = this.parseGroupMetadata(event);
@@ -9732,7 +10861,7 @@ var GroupChatModule = class {
9732
10861
  }
9733
10862
  ),
9734
10863
  this.oneshotSubscription(
9735
- new import_nostr_js_sdk2.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
10864
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
9736
10865
  {
9737
10866
  onEvent: (event) => {
9738
10867
  const groupId = this.getGroupIdFromMetadataEvent(event);
@@ -10235,7 +11364,7 @@ var GroupChatModule = class {
10235
11364
  if (!this.client) return /* @__PURE__ */ new Set();
10236
11365
  const adminPubkeys = /* @__PURE__ */ new Set();
10237
11366
  return this.oneshotSubscription(
10238
- 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": ["", "_"] }),
10239
11368
  {
10240
11369
  onEvent: (event) => {
10241
11370
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -10257,7 +11386,7 @@ var GroupChatModule = class {
10257
11386
  if (!this.client) return null;
10258
11387
  let result = null;
10259
11388
  return this.oneshotSubscription(
10260
- 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] }),
10261
11390
  {
10262
11391
  onEvent: (event) => {
10263
11392
  if (!result) result = this.parseGroupMetadata(event);
@@ -10294,7 +11423,7 @@ var GroupChatModule = class {
10294
11423
  if (!this.client) return [];
10295
11424
  const members = [];
10296
11425
  return this.oneshotSubscription(
10297
- 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] }),
10298
11427
  {
10299
11428
  onEvent: (event) => {
10300
11429
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -10315,7 +11444,7 @@ var GroupChatModule = class {
10315
11444
  if (!this.client) return [];
10316
11445
  const adminPubkeys = [];
10317
11446
  return this.oneshotSubscription(
10318
- 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] }),
10319
11448
  {
10320
11449
  onEvent: (event) => {
10321
11450
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -13984,9 +15113,9 @@ var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign
13984
15113
  var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
13985
15114
  var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
13986
15115
  var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
13987
- var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
15116
+ var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
13988
15117
  function isValidNametag(nametag) {
13989
- if ((0, import_nostr_js_sdk3.isPhoneNumber)(nametag)) return true;
15118
+ if ((0, import_nostr_js_sdk4.isPhoneNumber)(nametag)) return true;
13990
15119
  return /^[a-z0-9_-]{3,20}$/.test(nametag);
13991
15120
  }
13992
15121
  var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
@@ -14030,11 +15159,18 @@ var Sphere = class _Sphere {
14030
15159
  _transport;
14031
15160
  _oracle;
14032
15161
  _priceProvider;
14033
- // Modules
15162
+ // Modules (single-instance — backward compat, delegates to active address)
14034
15163
  _payments;
14035
15164
  _communications;
14036
15165
  _groupChat = null;
14037
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;
14038
15174
  // Events
14039
15175
  eventHandlers = /* @__PURE__ */ new Map();
14040
15176
  // Provider management
@@ -14052,6 +15188,9 @@ var Sphere = class _Sphere {
14052
15188
  if (tokenStorage) {
14053
15189
  this._tokenStorageProviders.set(tokenStorage.id, tokenStorage);
14054
15190
  }
15191
+ this._l1Config = l1Config;
15192
+ this._groupChatConfig = groupChatConfig;
15193
+ this._marketConfig = marketConfig;
14055
15194
  this._payments = createPaymentsModule({ l1: l1Config });
14056
15195
  this._communications = createCommunicationsModule();
14057
15196
  this._groupChat = groupChatConfig ? createGroupChatModule(groupChatConfig) : null;
@@ -15310,7 +16449,7 @@ var Sphere = class _Sphere {
15310
16449
  nametags.set(0, newNametag);
15311
16450
  }
15312
16451
  const nametag = this._addressNametags.get(addressId)?.get(0);
15313
- this._identity = {
16452
+ const newIdentity = {
15314
16453
  privateKey: addressInfo.privateKey,
15315
16454
  chainPubkey: addressInfo.publicKey,
15316
16455
  l1Address: addressInfo.address,
@@ -15318,20 +16457,53 @@ var Sphere = class _Sphere {
15318
16457
  ipnsName: "12D3KooW" + ipnsHash,
15319
16458
  nametag
15320
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;
15321
16493
  this._currentAddressIndex = index;
15322
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;
15323
16500
  await this._storage.set(STORAGE_KEYS_GLOBAL.CURRENT_ADDRESS_INDEX, index.toString());
15324
16501
  this._storage.setIdentity(this._identity);
15325
- await this._transport.setIdentity(this._identity);
15326
- logger.debug("Sphere", `switchToAddress(${index}): re-initializing ${this._tokenStorageProviders.size} token storage provider(s)`);
15327
- for (const [providerId, provider] of this._tokenStorageProviders.entries()) {
15328
- logger.debug("Sphere", `switchToAddress(${index}): shutdown provider=${providerId}`);
15329
- await provider.shutdown();
15330
- provider.setIdentity(this._identity);
15331
- logger.debug("Sphere", `switchToAddress(${index}): initialize provider=${providerId}`);
15332
- await provider.initialize();
16502
+ if (this._transport.setFallbackSince) {
16503
+ const fallbackTs = Math.floor(Date.now() / 1e3) - 86400;
16504
+ this._transport.setFallbackSince(fallbackTs);
15333
16505
  }
15334
- await this.reinitializeModulesForNewAddress();
16506
+ await this._transport.setIdentity(this._identity);
15335
16507
  this.emitEvent("identity:changed", {
15336
16508
  l1Address: this._identity.l1Address,
15337
16509
  directAddress: this._identity.directAddress,
@@ -15386,42 +16558,104 @@ var Sphere = class _Sphere {
15386
16558
  }
15387
16559
  }
15388
16560
  /**
15389
- * 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
15390
16569
  */
15391
- async reinitializeModulesForNewAddress() {
16570
+ async initializeAddressModules(index, identity, tokenStorageProviders) {
15392
16571
  const emitEvent = this.emitEvent.bind(this);
15393
- this._payments.initialize({
15394
- 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,
15395
16580
  storage: this._storage,
15396
- tokenStorageProviders: this._tokenStorageProviders,
15397
- transport: this._transport,
16581
+ tokenStorageProviders,
16582
+ transport: addressTransport,
15398
16583
  oracle: this._oracle,
15399
16584
  emitEvent,
15400
16585
  chainCode: this._masterKey?.chainCode || void 0,
15401
16586
  price: this._priceProvider ?? void 0
15402
16587
  });
15403
- this._communications.initialize({
15404
- identity: this._identity,
16588
+ communications.initialize({
16589
+ identity,
15405
16590
  storage: this._storage,
15406
- transport: this._transport,
16591
+ transport: addressTransport,
15407
16592
  emitEvent
15408
16593
  });
15409
- this._groupChat?.initialize({
15410
- identity: this._identity,
16594
+ groupChat?.initialize({
16595
+ identity,
15411
16596
  storage: this._storage,
15412
16597
  emitEvent
15413
16598
  });
15414
- this._market?.initialize({
15415
- identity: this._identity,
16599
+ market?.initialize({
16600
+ identity,
15416
16601
  emitEvent
15417
16602
  });
15418
- await this._payments.load();
15419
- await this._communications.load();
15420
- await this._groupChat?.load();
15421
- await this._market?.load();
15422
- this._payments.sync().catch((err) => {
15423
- 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);
15424
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;
15425
16659
  }
15426
16660
  /**
15427
16661
  * Derive address at a specific index
@@ -16342,17 +17576,40 @@ var Sphere = class _Sphere {
16342
17576
  */
16343
17577
  cleanNametag(raw) {
16344
17578
  const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
16345
- return (0, import_nostr_js_sdk3.normalizeNametag)(stripped);
17579
+ return (0, import_nostr_js_sdk4.normalizeNametag)(stripped);
16346
17580
  }
16347
17581
  // ===========================================================================
16348
17582
  // Public Methods - Lifecycle
16349
17583
  // ===========================================================================
16350
17584
  async destroy() {
16351
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();
16352
17605
  this._payments.destroy();
16353
17606
  this._communications.destroy();
16354
17607
  this._groupChat?.destroy();
16355
17608
  this._market?.destroy();
17609
+ if (this._transportMux) {
17610
+ await this._transportMux.disconnect();
17611
+ this._transportMux = null;
17612
+ }
16356
17613
  await this._transport.disconnect();
16357
17614
  await this._storage.disconnect();
16358
17615
  await this._oracle.disconnect();
@@ -16547,6 +17804,9 @@ var Sphere = class _Sphere {
16547
17804
  // ===========================================================================
16548
17805
  async initializeProviders() {
16549
17806
  this._storage.setIdentity(this._identity);
17807
+ if (this._transport.setFallbackSince) {
17808
+ this._transport.setFallbackSince(Math.floor(Date.now() / 1e3) - 86400);
17809
+ }
16550
17810
  await this._transport.setIdentity(this._identity);
16551
17811
  for (const provider of this._tokenStorageProviders.values()) {
16552
17812
  provider.setIdentity(this._identity);
@@ -16637,11 +17897,13 @@ var Sphere = class _Sphere {
16637
17897
  }
16638
17898
  async initializeModules() {
16639
17899
  const emitEvent = this.emitEvent.bind(this);
17900
+ const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
17901
+ const moduleTransport = adapter ?? this._transport;
16640
17902
  this._payments.initialize({
16641
17903
  identity: this._identity,
16642
17904
  storage: this._storage,
16643
17905
  tokenStorageProviders: this._tokenStorageProviders,
16644
- transport: this._transport,
17906
+ transport: moduleTransport,
16645
17907
  oracle: this._oracle,
16646
17908
  emitEvent,
16647
17909
  // Pass chain code for L1 HD derivation
@@ -16652,7 +17914,7 @@ var Sphere = class _Sphere {
16652
17914
  this._communications.initialize({
16653
17915
  identity: this._identity,
16654
17916
  storage: this._storage,
16655
- transport: this._transport,
17917
+ transport: moduleTransport,
16656
17918
  emitEvent
16657
17919
  });
16658
17920
  this._groupChat?.initialize({
@@ -16668,6 +17930,17 @@ var Sphere = class _Sphere {
16668
17930
  await this._communications.load();
16669
17931
  await this._groupChat?.load();
16670
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
+ });
16671
17944
  }
16672
17945
  // ===========================================================================
16673
17946
  // Private: Helpers
@@ -17339,8 +18612,8 @@ function createTokenValidator(options) {
17339
18612
  }
17340
18613
 
17341
18614
  // index.ts
17342
- var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
17343
18615
  var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
18616
+ var import_nostr_js_sdk6 = require("@unicitylabs/nostr-js-sdk");
17344
18617
 
17345
18618
  // price/CoinGeckoPriceProvider.ts
17346
18619
  init_logger();