@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.js CHANGED
@@ -806,6 +806,1112 @@ var init_network = __esm({
806
806
  init_logger();
807
807
  init_errors();
808
808
 
809
+ // transport/MultiAddressTransportMux.ts
810
+ init_logger();
811
+ init_errors();
812
+ import { Buffer as Buffer2 } from "buffer";
813
+ import {
814
+ NostrKeyManager,
815
+ NIP04,
816
+ NIP17,
817
+ Event as NostrEventClass,
818
+ EventKinds,
819
+ NostrClient,
820
+ Filter,
821
+ isChatMessage,
822
+ isReadReceipt
823
+ } from "@unicitylabs/nostr-js-sdk";
824
+
825
+ // transport/websocket.ts
826
+ function defaultUUIDGenerator() {
827
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
828
+ return crypto.randomUUID();
829
+ }
830
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
831
+ const r = Math.random() * 16 | 0;
832
+ const v = c === "x" ? r : r & 3 | 8;
833
+ return v.toString(16);
834
+ });
835
+ }
836
+
837
+ // transport/MultiAddressTransportMux.ts
838
+ init_constants();
839
+ var EVENT_KINDS = NOSTR_EVENT_KINDS;
840
+ var COMPOSING_INDICATOR_KIND = 25050;
841
+ var MultiAddressTransportMux = class {
842
+ config;
843
+ storage = null;
844
+ // Single NostrClient — one WebSocket connection for all addresses
845
+ nostrClient = null;
846
+ // KeyManager used for NostrClient creation (uses first address or temp key)
847
+ primaryKeyManager = null;
848
+ status = "disconnected";
849
+ // Per-address entries
850
+ addresses = /* @__PURE__ */ new Map();
851
+ // pubkey → address index (for fast routing)
852
+ pubkeyToIndex = /* @__PURE__ */ new Map();
853
+ // Subscription IDs
854
+ walletSubscriptionId = null;
855
+ chatSubscriptionId = null;
856
+ chatEoseFired = false;
857
+ chatEoseHandlers = [];
858
+ // Dedup
859
+ processedEventIds = /* @__PURE__ */ new Set();
860
+ // Event callbacks (mux-level, forwarded to all adapters)
861
+ eventCallbacks = /* @__PURE__ */ new Set();
862
+ constructor(config) {
863
+ this.config = {
864
+ relays: config.relays ?? [...DEFAULT_NOSTR_RELAYS],
865
+ timeout: config.timeout ?? TIMEOUTS.WEBSOCKET_CONNECT,
866
+ autoReconnect: config.autoReconnect ?? true,
867
+ reconnectDelay: config.reconnectDelay ?? TIMEOUTS.NOSTR_RECONNECT_DELAY,
868
+ maxReconnectAttempts: config.maxReconnectAttempts ?? TIMEOUTS.MAX_RECONNECT_ATTEMPTS,
869
+ createWebSocket: config.createWebSocket,
870
+ generateUUID: config.generateUUID ?? defaultUUIDGenerator
871
+ };
872
+ this.storage = config.storage ?? null;
873
+ }
874
+ // ===========================================================================
875
+ // Address Management
876
+ // ===========================================================================
877
+ /**
878
+ * Add an address to the multiplexer.
879
+ * Creates an AddressTransportAdapter for this address.
880
+ * If already connected, updates subscriptions to include the new pubkey.
881
+ */
882
+ async addAddress(index, identity, resolveDelegate) {
883
+ const existing = this.addresses.get(index);
884
+ if (existing) {
885
+ existing.identity = identity;
886
+ existing.keyManager = NostrKeyManager.fromPrivateKey(Buffer2.from(identity.privateKey, "hex"));
887
+ existing.nostrPubkey = existing.keyManager.getPublicKeyHex();
888
+ for (const [pk, idx] of this.pubkeyToIndex) {
889
+ if (idx === index) this.pubkeyToIndex.delete(pk);
890
+ }
891
+ this.pubkeyToIndex.set(existing.nostrPubkey, index);
892
+ logger.debug("Mux", `Updated address ${index}, pubkey: ${existing.nostrPubkey.slice(0, 16)}...`);
893
+ await this.updateSubscriptions();
894
+ return existing.adapter;
895
+ }
896
+ const keyManager = NostrKeyManager.fromPrivateKey(Buffer2.from(identity.privateKey, "hex"));
897
+ const nostrPubkey = keyManager.getPublicKeyHex();
898
+ const adapter = new AddressTransportAdapter(this, index, identity, resolveDelegate);
899
+ const entry = {
900
+ index,
901
+ identity,
902
+ keyManager,
903
+ nostrPubkey,
904
+ adapter,
905
+ lastEventTs: 0,
906
+ fallbackSince: null
907
+ };
908
+ this.addresses.set(index, entry);
909
+ this.pubkeyToIndex.set(nostrPubkey, index);
910
+ logger.debug("Mux", `Added address ${index}, pubkey: ${nostrPubkey.slice(0, 16)}..., total: ${this.addresses.size}`);
911
+ if (this.addresses.size === 1) {
912
+ this.primaryKeyManager = keyManager;
913
+ }
914
+ if (this.isConnected()) {
915
+ await this.updateSubscriptions();
916
+ }
917
+ return adapter;
918
+ }
919
+ /**
920
+ * Remove an address from the multiplexer.
921
+ * Stops routing events to this address.
922
+ */
923
+ async removeAddress(index) {
924
+ const entry = this.addresses.get(index);
925
+ if (!entry) return;
926
+ this.pubkeyToIndex.delete(entry.nostrPubkey);
927
+ this.addresses.delete(index);
928
+ logger.debug("Mux", `Removed address ${index}, remaining: ${this.addresses.size}`);
929
+ if (this.isConnected() && this.addresses.size > 0) {
930
+ await this.updateSubscriptions();
931
+ }
932
+ }
933
+ /**
934
+ * Get adapter for a specific address index.
935
+ */
936
+ getAdapter(index) {
937
+ return this.addresses.get(index)?.adapter;
938
+ }
939
+ /**
940
+ * Set fallback 'since' for an address (consumed once on next subscription setup).
941
+ */
942
+ setFallbackSince(index, sinceSeconds) {
943
+ const entry = this.addresses.get(index);
944
+ if (entry) {
945
+ entry.fallbackSince = sinceSeconds;
946
+ }
947
+ }
948
+ // ===========================================================================
949
+ // Connection Management (delegated from adapters)
950
+ // ===========================================================================
951
+ async connect() {
952
+ if (this.status === "connected") return;
953
+ this.status = "connecting";
954
+ try {
955
+ if (!this.primaryKeyManager) {
956
+ const tempKey = Buffer2.alloc(32);
957
+ crypto.getRandomValues(tempKey);
958
+ this.primaryKeyManager = NostrKeyManager.fromPrivateKey(tempKey);
959
+ }
960
+ this.nostrClient = new NostrClient(this.primaryKeyManager, {
961
+ autoReconnect: this.config.autoReconnect,
962
+ reconnectIntervalMs: this.config.reconnectDelay,
963
+ maxReconnectIntervalMs: this.config.reconnectDelay * 16,
964
+ pingIntervalMs: 15e3
965
+ });
966
+ this.nostrClient.addConnectionListener({
967
+ onConnect: (url) => {
968
+ logger.debug("Mux", "Connected to relay:", url);
969
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
970
+ },
971
+ onDisconnect: (url, reason) => {
972
+ logger.debug("Mux", "Disconnected from relay:", url, "reason:", reason);
973
+ },
974
+ onReconnecting: (url, attempt) => {
975
+ logger.debug("Mux", "Reconnecting to relay:", url, "attempt:", attempt);
976
+ this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
977
+ },
978
+ onReconnected: (url) => {
979
+ logger.debug("Mux", "Reconnected to relay:", url);
980
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
981
+ }
982
+ });
983
+ await Promise.race([
984
+ this.nostrClient.connect(...this.config.relays),
985
+ new Promise(
986
+ (_, reject) => setTimeout(() => reject(new Error(
987
+ `Transport connection timed out after ${this.config.timeout}ms`
988
+ )), this.config.timeout)
989
+ )
990
+ ]);
991
+ if (!this.nostrClient.isConnected()) {
992
+ throw new SphereError("Failed to connect to any relay", "TRANSPORT_ERROR");
993
+ }
994
+ this.status = "connected";
995
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
996
+ if (this.addresses.size > 0) {
997
+ await this.updateSubscriptions();
998
+ }
999
+ } catch (error) {
1000
+ this.status = "error";
1001
+ throw error;
1002
+ }
1003
+ }
1004
+ async disconnect() {
1005
+ if (this.nostrClient) {
1006
+ this.nostrClient.disconnect();
1007
+ this.nostrClient = null;
1008
+ }
1009
+ this.walletSubscriptionId = null;
1010
+ this.chatSubscriptionId = null;
1011
+ this.chatEoseFired = false;
1012
+ this.status = "disconnected";
1013
+ this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
1014
+ }
1015
+ isConnected() {
1016
+ return this.status === "connected" && this.nostrClient?.isConnected() === true;
1017
+ }
1018
+ getStatus() {
1019
+ return this.status;
1020
+ }
1021
+ // ===========================================================================
1022
+ // Relay Management
1023
+ // ===========================================================================
1024
+ getRelays() {
1025
+ return [...this.config.relays];
1026
+ }
1027
+ getConnectedRelays() {
1028
+ if (!this.nostrClient) return [];
1029
+ return Array.from(this.nostrClient.getConnectedRelays());
1030
+ }
1031
+ async addRelay(relayUrl) {
1032
+ if (this.config.relays.includes(relayUrl)) return false;
1033
+ this.config.relays.push(relayUrl);
1034
+ if (this.status === "connected" && this.nostrClient) {
1035
+ try {
1036
+ await this.nostrClient.connect(relayUrl);
1037
+ this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: true } });
1038
+ return true;
1039
+ } catch (error) {
1040
+ this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: false, error: String(error) } });
1041
+ return false;
1042
+ }
1043
+ }
1044
+ return true;
1045
+ }
1046
+ async removeRelay(relayUrl) {
1047
+ const idx = this.config.relays.indexOf(relayUrl);
1048
+ if (idx === -1) return false;
1049
+ this.config.relays.splice(idx, 1);
1050
+ this.emitEvent({ type: "transport:relay_removed", timestamp: Date.now(), data: { relay: relayUrl } });
1051
+ return true;
1052
+ }
1053
+ hasRelay(relayUrl) {
1054
+ return this.config.relays.includes(relayUrl);
1055
+ }
1056
+ isRelayConnected(relayUrl) {
1057
+ if (!this.nostrClient) return false;
1058
+ return this.nostrClient.getConnectedRelays().has(relayUrl);
1059
+ }
1060
+ // ===========================================================================
1061
+ // Subscription Management
1062
+ // ===========================================================================
1063
+ /**
1064
+ * Update Nostr subscriptions to listen for events on ALL registered address pubkeys.
1065
+ * Called whenever addresses are added/removed.
1066
+ */
1067
+ async updateSubscriptions() {
1068
+ if (!this.nostrClient || this.addresses.size === 0) return;
1069
+ if (this.walletSubscriptionId) {
1070
+ this.nostrClient.unsubscribe(this.walletSubscriptionId);
1071
+ this.walletSubscriptionId = null;
1072
+ }
1073
+ if (this.chatSubscriptionId) {
1074
+ this.nostrClient.unsubscribe(this.chatSubscriptionId);
1075
+ this.chatSubscriptionId = null;
1076
+ }
1077
+ const allPubkeys = [];
1078
+ for (const entry of this.addresses.values()) {
1079
+ allPubkeys.push(entry.nostrPubkey);
1080
+ }
1081
+ logger.debug("Mux", `Subscribing for ${allPubkeys.length} address(es):`, allPubkeys.map((p) => p.slice(0, 12)).join(", "));
1082
+ let globalSince = Math.floor(Date.now() / 1e3);
1083
+ for (const entry of this.addresses.values()) {
1084
+ const since = await this.getAddressSince(entry);
1085
+ if (since < globalSince) {
1086
+ globalSince = since;
1087
+ }
1088
+ }
1089
+ const walletFilter = new Filter();
1090
+ walletFilter.kinds = [
1091
+ EVENT_KINDS.DIRECT_MESSAGE,
1092
+ EVENT_KINDS.TOKEN_TRANSFER,
1093
+ EVENT_KINDS.PAYMENT_REQUEST,
1094
+ EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
1095
+ ];
1096
+ walletFilter["#p"] = allPubkeys;
1097
+ walletFilter.since = globalSince;
1098
+ this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
1099
+ onEvent: (event) => {
1100
+ this.handleEvent({
1101
+ id: event.id,
1102
+ kind: event.kind,
1103
+ content: event.content,
1104
+ tags: event.tags,
1105
+ pubkey: event.pubkey,
1106
+ created_at: event.created_at,
1107
+ sig: event.sig
1108
+ });
1109
+ },
1110
+ onEndOfStoredEvents: () => {
1111
+ logger.debug("Mux", "Wallet subscription EOSE");
1112
+ },
1113
+ onError: (_subId, error) => {
1114
+ logger.debug("Mux", "Wallet subscription error:", error);
1115
+ }
1116
+ });
1117
+ const chatFilter = new Filter();
1118
+ chatFilter.kinds = [EventKinds.GIFT_WRAP];
1119
+ chatFilter["#p"] = allPubkeys;
1120
+ this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
1121
+ onEvent: (event) => {
1122
+ this.handleEvent({
1123
+ id: event.id,
1124
+ kind: event.kind,
1125
+ content: event.content,
1126
+ tags: event.tags,
1127
+ pubkey: event.pubkey,
1128
+ created_at: event.created_at,
1129
+ sig: event.sig
1130
+ });
1131
+ },
1132
+ onEndOfStoredEvents: () => {
1133
+ logger.debug("Mux", "Chat subscription EOSE");
1134
+ if (!this.chatEoseFired) {
1135
+ this.chatEoseFired = true;
1136
+ for (const handler of this.chatEoseHandlers) {
1137
+ try {
1138
+ handler();
1139
+ } catch {
1140
+ }
1141
+ }
1142
+ }
1143
+ },
1144
+ onError: (_subId, error) => {
1145
+ logger.debug("Mux", "Chat subscription error:", error);
1146
+ }
1147
+ });
1148
+ }
1149
+ /**
1150
+ * Determine 'since' timestamp for an address entry.
1151
+ */
1152
+ async getAddressSince(entry) {
1153
+ if (this.storage) {
1154
+ const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
1155
+ try {
1156
+ const stored = await this.storage.get(storageKey);
1157
+ if (stored) {
1158
+ const ts = parseInt(stored, 10);
1159
+ entry.lastEventTs = ts;
1160
+ entry.fallbackSince = null;
1161
+ return ts;
1162
+ } else if (entry.fallbackSince !== null) {
1163
+ const ts = entry.fallbackSince;
1164
+ entry.lastEventTs = ts;
1165
+ entry.fallbackSince = null;
1166
+ return ts;
1167
+ }
1168
+ } catch {
1169
+ }
1170
+ }
1171
+ return Math.floor(Date.now() / 1e3);
1172
+ }
1173
+ // ===========================================================================
1174
+ // Event Routing
1175
+ // ===========================================================================
1176
+ /**
1177
+ * Route an incoming Nostr event to the correct address adapter.
1178
+ */
1179
+ async handleEvent(event) {
1180
+ if (event.id && this.processedEventIds.has(event.id)) return;
1181
+ if (event.id) this.processedEventIds.add(event.id);
1182
+ try {
1183
+ if (event.kind === EventKinds.GIFT_WRAP) {
1184
+ await this.routeGiftWrap(event);
1185
+ } else {
1186
+ const recipientPubkey = this.extractRecipientPubkey(event);
1187
+ if (!recipientPubkey) {
1188
+ logger.debug("Mux", "Event has no #p tag, dropping:", event.id?.slice(0, 12));
1189
+ return;
1190
+ }
1191
+ const addressIndex = this.pubkeyToIndex.get(recipientPubkey);
1192
+ if (addressIndex === void 0) {
1193
+ logger.debug("Mux", "Event for unknown pubkey:", recipientPubkey.slice(0, 16), "dropping");
1194
+ return;
1195
+ }
1196
+ const entry = this.addresses.get(addressIndex);
1197
+ if (!entry) return;
1198
+ await this.dispatchWalletEvent(entry, event);
1199
+ }
1200
+ } catch (error) {
1201
+ logger.debug("Mux", "Failed to handle event:", event.id?.slice(0, 12), error);
1202
+ }
1203
+ }
1204
+ /**
1205
+ * Extract recipient pubkey from event's #p tag.
1206
+ * Returns the first #p value that matches a known address pubkey,
1207
+ * or the first #p value if none match.
1208
+ */
1209
+ extractRecipientPubkey(event) {
1210
+ const pTags = event.tags?.filter((t) => t[0] === "p");
1211
+ if (!pTags || pTags.length === 0) return null;
1212
+ for (const tag of pTags) {
1213
+ if (tag[1] && this.pubkeyToIndex.has(tag[1])) {
1214
+ return tag[1];
1215
+ }
1216
+ }
1217
+ return pTags[0]?.[1] ?? null;
1218
+ }
1219
+ /**
1220
+ * Route a gift wrap event by trying decryption with each address keyManager.
1221
+ */
1222
+ async routeGiftWrap(event) {
1223
+ for (const entry of this.addresses.values()) {
1224
+ try {
1225
+ const pm = NIP17.unwrap(event, entry.keyManager);
1226
+ logger.debug("Mux", `Gift wrap decrypted by address ${entry.index}, sender: ${pm.senderPubkey?.slice(0, 16)}`);
1227
+ if (pm.senderPubkey === entry.nostrPubkey) {
1228
+ try {
1229
+ const parsed = JSON.parse(pm.content);
1230
+ if (parsed?.selfWrap && parsed.recipientPubkey) {
1231
+ const message2 = {
1232
+ id: parsed.originalId || pm.eventId,
1233
+ senderTransportPubkey: pm.senderPubkey,
1234
+ senderNametag: parsed.senderNametag,
1235
+ recipientTransportPubkey: parsed.recipientPubkey,
1236
+ content: parsed.text ?? "",
1237
+ timestamp: pm.timestamp * 1e3,
1238
+ encrypted: true,
1239
+ isSelfWrap: true
1240
+ };
1241
+ entry.adapter.dispatchMessage(message2);
1242
+ return;
1243
+ }
1244
+ } catch {
1245
+ }
1246
+ return;
1247
+ }
1248
+ if (isReadReceipt(pm)) {
1249
+ if (pm.replyToEventId) {
1250
+ const receipt = {
1251
+ senderTransportPubkey: pm.senderPubkey,
1252
+ messageEventId: pm.replyToEventId,
1253
+ timestamp: pm.timestamp * 1e3
1254
+ };
1255
+ entry.adapter.dispatchReadReceipt(receipt);
1256
+ }
1257
+ return;
1258
+ }
1259
+ if (pm.kind === COMPOSING_INDICATOR_KIND) {
1260
+ let senderNametag2;
1261
+ let expiresIn = 3e4;
1262
+ try {
1263
+ const parsed = JSON.parse(pm.content);
1264
+ senderNametag2 = parsed.senderNametag || void 0;
1265
+ expiresIn = parsed.expiresIn ?? 3e4;
1266
+ } catch {
1267
+ }
1268
+ entry.adapter.dispatchComposingIndicator({
1269
+ senderPubkey: pm.senderPubkey,
1270
+ senderNametag: senderNametag2,
1271
+ expiresIn
1272
+ });
1273
+ return;
1274
+ }
1275
+ try {
1276
+ const parsed = JSON.parse(pm.content);
1277
+ if (parsed?.type === "typing") {
1278
+ const indicator = {
1279
+ senderTransportPubkey: pm.senderPubkey,
1280
+ senderNametag: parsed.senderNametag,
1281
+ timestamp: pm.timestamp * 1e3
1282
+ };
1283
+ entry.adapter.dispatchTypingIndicator(indicator);
1284
+ return;
1285
+ }
1286
+ } catch {
1287
+ }
1288
+ if (!isChatMessage(pm)) return;
1289
+ let content = pm.content;
1290
+ let senderNametag;
1291
+ try {
1292
+ const parsed = JSON.parse(content);
1293
+ if (typeof parsed === "object" && parsed.text !== void 0) {
1294
+ content = parsed.text;
1295
+ senderNametag = parsed.senderNametag || void 0;
1296
+ }
1297
+ } catch {
1298
+ }
1299
+ const message = {
1300
+ id: event.id,
1301
+ senderTransportPubkey: pm.senderPubkey,
1302
+ senderNametag,
1303
+ content,
1304
+ timestamp: pm.timestamp * 1e3,
1305
+ encrypted: true
1306
+ };
1307
+ entry.adapter.dispatchMessage(message);
1308
+ return;
1309
+ } catch {
1310
+ continue;
1311
+ }
1312
+ }
1313
+ logger.debug("Mux", "Gift wrap could not be decrypted by any address");
1314
+ }
1315
+ /**
1316
+ * Dispatch a wallet event (non-gift-wrap) to the correct address adapter.
1317
+ */
1318
+ async dispatchWalletEvent(entry, event) {
1319
+ switch (event.kind) {
1320
+ case EVENT_KINDS.DIRECT_MESSAGE:
1321
+ break;
1322
+ case EVENT_KINDS.TOKEN_TRANSFER:
1323
+ await this.handleTokenTransfer(entry, event);
1324
+ break;
1325
+ case EVENT_KINDS.PAYMENT_REQUEST:
1326
+ await this.handlePaymentRequest(entry, event);
1327
+ break;
1328
+ case EVENT_KINDS.PAYMENT_REQUEST_RESPONSE:
1329
+ await this.handlePaymentRequestResponse(entry, event);
1330
+ break;
1331
+ }
1332
+ if (event.created_at) {
1333
+ this.updateLastEventTimestamp(entry, event.created_at);
1334
+ }
1335
+ }
1336
+ async handleTokenTransfer(entry, event) {
1337
+ try {
1338
+ const content = await this.decryptContent(entry, event.content, event.pubkey);
1339
+ const payload = JSON.parse(content);
1340
+ const transfer = {
1341
+ id: event.id,
1342
+ senderTransportPubkey: event.pubkey,
1343
+ payload,
1344
+ timestamp: event.created_at * 1e3
1345
+ };
1346
+ entry.adapter.dispatchTokenTransfer(transfer);
1347
+ } catch (err) {
1348
+ logger.debug("Mux", `Token transfer decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
1349
+ }
1350
+ }
1351
+ async handlePaymentRequest(entry, event) {
1352
+ try {
1353
+ const content = await this.decryptContent(entry, event.content, event.pubkey);
1354
+ const requestData = JSON.parse(content);
1355
+ const request = {
1356
+ id: event.id,
1357
+ senderTransportPubkey: event.pubkey,
1358
+ request: {
1359
+ requestId: requestData.requestId,
1360
+ amount: requestData.amount,
1361
+ coinId: requestData.coinId,
1362
+ message: requestData.message,
1363
+ recipientNametag: requestData.recipientNametag,
1364
+ metadata: requestData.metadata
1365
+ },
1366
+ timestamp: event.created_at * 1e3
1367
+ };
1368
+ entry.adapter.dispatchPaymentRequest(request);
1369
+ } catch (err) {
1370
+ logger.debug("Mux", `Payment request decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
1371
+ }
1372
+ }
1373
+ async handlePaymentRequestResponse(entry, event) {
1374
+ try {
1375
+ const content = await this.decryptContent(entry, event.content, event.pubkey);
1376
+ const responseData = JSON.parse(content);
1377
+ const response = {
1378
+ id: event.id,
1379
+ responderTransportPubkey: event.pubkey,
1380
+ response: {
1381
+ requestId: responseData.requestId,
1382
+ responseType: responseData.responseType,
1383
+ message: responseData.message,
1384
+ transferId: responseData.transferId
1385
+ },
1386
+ timestamp: event.created_at * 1e3
1387
+ };
1388
+ entry.adapter.dispatchPaymentRequestResponse(response);
1389
+ } catch (err) {
1390
+ logger.debug("Mux", `Payment response decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
1391
+ }
1392
+ }
1393
+ // ===========================================================================
1394
+ // Crypto Helpers
1395
+ // ===========================================================================
1396
+ async decryptContent(entry, content, senderPubkey) {
1397
+ const decrypted = await NIP04.decryptHex(
1398
+ content,
1399
+ entry.keyManager.getPrivateKeyHex(),
1400
+ senderPubkey
1401
+ );
1402
+ return this.stripContentPrefix(decrypted);
1403
+ }
1404
+ stripContentPrefix(content) {
1405
+ const prefixes = ["payment_request:", "token_transfer:", "payment_response:"];
1406
+ for (const prefix of prefixes) {
1407
+ if (content.startsWith(prefix)) return content.slice(prefix.length);
1408
+ }
1409
+ return content;
1410
+ }
1411
+ // ===========================================================================
1412
+ // Sending (called by adapters)
1413
+ // ===========================================================================
1414
+ /**
1415
+ * Create an encrypted event using a specific address's keyManager.
1416
+ * Used by AddressTransportAdapter for sending.
1417
+ */
1418
+ async createAndPublishEncryptedEvent(addressIndex, kind, content, tags) {
1419
+ const entry = this.addresses.get(addressIndex);
1420
+ if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
1421
+ if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
1422
+ const recipientTag = tags.find((t) => t[0] === "p");
1423
+ if (!recipientTag?.[1]) throw new SphereError("No recipient pubkey in tags", "VALIDATION_ERROR");
1424
+ const encrypted = await NIP04.encryptHex(
1425
+ content,
1426
+ entry.keyManager.getPrivateKeyHex(),
1427
+ recipientTag[1]
1428
+ );
1429
+ const signedEvent = NostrEventClass.create(entry.keyManager, { kind, content: encrypted, tags });
1430
+ const nostrEvent = NostrEventClass.fromJSON({
1431
+ id: signedEvent.id,
1432
+ kind: signedEvent.kind,
1433
+ content: signedEvent.content,
1434
+ tags: signedEvent.tags,
1435
+ pubkey: signedEvent.pubkey,
1436
+ created_at: signedEvent.created_at,
1437
+ sig: signedEvent.sig
1438
+ });
1439
+ await this.nostrClient.publishEvent(nostrEvent);
1440
+ return signedEvent.id;
1441
+ }
1442
+ /**
1443
+ * Create and publish a NIP-17 gift wrap message for a specific address.
1444
+ */
1445
+ async sendGiftWrap(addressIndex, recipientPubkey, content) {
1446
+ const entry = this.addresses.get(addressIndex);
1447
+ if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
1448
+ if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
1449
+ const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
1450
+ const giftWrap = NIP17.createGiftWrap(entry.keyManager, nostrRecipient, content);
1451
+ const giftWrapEvent = NostrEventClass.fromJSON(giftWrap);
1452
+ await this.nostrClient.publishEvent(giftWrapEvent);
1453
+ const selfPubkey = entry.keyManager.getPublicKeyHex();
1454
+ const senderNametag = entry.identity.nametag;
1455
+ const selfWrapContent = JSON.stringify({
1456
+ selfWrap: true,
1457
+ originalId: giftWrap.id,
1458
+ recipientPubkey,
1459
+ senderNametag,
1460
+ text: content
1461
+ });
1462
+ const selfGiftWrap = NIP17.createGiftWrap(entry.keyManager, selfPubkey, selfWrapContent);
1463
+ const selfGiftWrapEvent = NostrEventClass.fromJSON(selfGiftWrap);
1464
+ this.nostrClient.publishEvent(selfGiftWrapEvent).catch((err) => {
1465
+ logger.debug("Mux", "Self-wrap publish failed:", err);
1466
+ });
1467
+ return giftWrap.id;
1468
+ }
1469
+ /**
1470
+ * Publish a raw event (e.g., identity binding, broadcast).
1471
+ */
1472
+ async publishRawEvent(addressIndex, kind, content, tags) {
1473
+ const entry = this.addresses.get(addressIndex);
1474
+ if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
1475
+ if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
1476
+ const signedEvent = NostrEventClass.create(entry.keyManager, { kind, content, tags });
1477
+ const nostrEvent = NostrEventClass.fromJSON({
1478
+ id: signedEvent.id,
1479
+ kind: signedEvent.kind,
1480
+ content: signedEvent.content,
1481
+ tags: signedEvent.tags,
1482
+ pubkey: signedEvent.pubkey,
1483
+ created_at: signedEvent.created_at,
1484
+ sig: signedEvent.sig
1485
+ });
1486
+ await this.nostrClient.publishEvent(nostrEvent);
1487
+ return signedEvent.id;
1488
+ }
1489
+ // ===========================================================================
1490
+ // Resolve Methods (delegates to inner — these are stateless relay queries)
1491
+ // ===========================================================================
1492
+ /**
1493
+ * Get the NostrClient for resolve operations.
1494
+ * Adapters use this for resolve*, publishIdentityBinding, etc.
1495
+ */
1496
+ getNostrClient() {
1497
+ return this.nostrClient;
1498
+ }
1499
+ /**
1500
+ * Get keyManager for a specific address (used by adapters for resolve/binding).
1501
+ */
1502
+ getKeyManager(addressIndex) {
1503
+ return this.addresses.get(addressIndex)?.keyManager ?? null;
1504
+ }
1505
+ /**
1506
+ * Get identity for a specific address.
1507
+ */
1508
+ getIdentity(addressIndex) {
1509
+ return this.addresses.get(addressIndex)?.identity ?? null;
1510
+ }
1511
+ // ===========================================================================
1512
+ // Event timestamp persistence
1513
+ // ===========================================================================
1514
+ updateLastEventTimestamp(entry, createdAt) {
1515
+ if (!this.storage) return;
1516
+ if (createdAt <= entry.lastEventTs) return;
1517
+ entry.lastEventTs = createdAt;
1518
+ const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
1519
+ this.storage.set(storageKey, createdAt.toString()).catch((err) => {
1520
+ logger.debug("Mux", "Failed to save last event timestamp:", err);
1521
+ });
1522
+ }
1523
+ // ===========================================================================
1524
+ // Mux-level event system
1525
+ // ===========================================================================
1526
+ onTransportEvent(callback) {
1527
+ this.eventCallbacks.add(callback);
1528
+ return () => this.eventCallbacks.delete(callback);
1529
+ }
1530
+ onChatReady(handler) {
1531
+ if (this.chatEoseFired) {
1532
+ try {
1533
+ handler();
1534
+ } catch {
1535
+ }
1536
+ return () => {
1537
+ };
1538
+ }
1539
+ this.chatEoseHandlers.push(handler);
1540
+ return () => {
1541
+ const idx = this.chatEoseHandlers.indexOf(handler);
1542
+ if (idx >= 0) this.chatEoseHandlers.splice(idx, 1);
1543
+ };
1544
+ }
1545
+ emitEvent(event) {
1546
+ for (const cb of this.eventCallbacks) {
1547
+ try {
1548
+ cb(event);
1549
+ } catch {
1550
+ }
1551
+ }
1552
+ for (const entry of this.addresses.values()) {
1553
+ entry.adapter.emitTransportEvent(event);
1554
+ }
1555
+ }
1556
+ // ===========================================================================
1557
+ // Dedup Management
1558
+ // ===========================================================================
1559
+ /**
1560
+ * Clear processed event IDs (e.g., on address change or periodic cleanup).
1561
+ */
1562
+ clearProcessedEvents() {
1563
+ this.processedEventIds.clear();
1564
+ }
1565
+ /**
1566
+ * Get the storage adapter (for adapters that need it).
1567
+ */
1568
+ getStorage() {
1569
+ return this.storage;
1570
+ }
1571
+ /**
1572
+ * Get the UUID generator.
1573
+ */
1574
+ getUUIDGenerator() {
1575
+ return this.config.generateUUID;
1576
+ }
1577
+ };
1578
+ var AddressTransportAdapter = class {
1579
+ id;
1580
+ name;
1581
+ type = "p2p";
1582
+ description;
1583
+ mux;
1584
+ addressIndex;
1585
+ identity;
1586
+ resolveDelegate;
1587
+ // Per-address handler sets
1588
+ messageHandlers = /* @__PURE__ */ new Set();
1589
+ transferHandlers = /* @__PURE__ */ new Set();
1590
+ paymentRequestHandlers = /* @__PURE__ */ new Set();
1591
+ paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1592
+ readReceiptHandlers = /* @__PURE__ */ new Set();
1593
+ typingIndicatorHandlers = /* @__PURE__ */ new Set();
1594
+ composingHandlers = /* @__PURE__ */ new Set();
1595
+ instantSplitBundleHandlers = /* @__PURE__ */ new Set();
1596
+ broadcastHandlers = /* @__PURE__ */ new Map();
1597
+ eventCallbacks = /* @__PURE__ */ new Set();
1598
+ pendingMessages = [];
1599
+ chatEoseHandlers = [];
1600
+ constructor(mux, addressIndex, identity, resolveDelegate) {
1601
+ this.mux = mux;
1602
+ this.addressIndex = addressIndex;
1603
+ this.identity = identity;
1604
+ this.resolveDelegate = resolveDelegate ?? null;
1605
+ this.id = `nostr-addr-${addressIndex}`;
1606
+ this.name = `Nostr Transport (address ${addressIndex})`;
1607
+ this.description = `P2P messaging for address index ${addressIndex}`;
1608
+ }
1609
+ // ===========================================================================
1610
+ // BaseProvider — delegates to mux
1611
+ // ===========================================================================
1612
+ async connect() {
1613
+ await this.mux.connect();
1614
+ }
1615
+ async disconnect() {
1616
+ }
1617
+ isConnected() {
1618
+ return this.mux.isConnected();
1619
+ }
1620
+ getStatus() {
1621
+ return this.mux.getStatus();
1622
+ }
1623
+ // ===========================================================================
1624
+ // Identity (no-op — mux manages identity via addAddress)
1625
+ // ===========================================================================
1626
+ async setIdentity(identity) {
1627
+ this.identity = identity;
1628
+ await this.mux.addAddress(this.addressIndex, identity);
1629
+ }
1630
+ // ===========================================================================
1631
+ // Sending — delegates to mux with this address's keyManager
1632
+ // ===========================================================================
1633
+ async sendMessage(recipientPubkey, content) {
1634
+ const senderNametag = this.identity.nametag;
1635
+ const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1636
+ return this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, wrappedContent);
1637
+ }
1638
+ async sendTokenTransfer(recipientPubkey, payload) {
1639
+ const content = "token_transfer:" + JSON.stringify(payload);
1640
+ const uniqueD = `token-transfer-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1641
+ return this.mux.createAndPublishEncryptedEvent(
1642
+ this.addressIndex,
1643
+ EVENT_KINDS.TOKEN_TRANSFER,
1644
+ content,
1645
+ [["p", recipientPubkey], ["d", uniqueD], ["type", "token_transfer"]]
1646
+ );
1647
+ }
1648
+ async sendPaymentRequest(recipientPubkey, payload) {
1649
+ const requestId2 = this.mux.getUUIDGenerator()();
1650
+ const amount = typeof payload.amount === "bigint" ? payload.amount.toString() : payload.amount;
1651
+ const requestContent = {
1652
+ requestId: requestId2,
1653
+ amount,
1654
+ coinId: payload.coinId,
1655
+ message: payload.message,
1656
+ recipientNametag: payload.recipientNametag,
1657
+ deadline: Date.now() + 5 * 60 * 1e3
1658
+ };
1659
+ const content = "payment_request:" + JSON.stringify(requestContent);
1660
+ const tags = [
1661
+ ["p", recipientPubkey],
1662
+ ["type", "payment_request"],
1663
+ ["amount", amount]
1664
+ ];
1665
+ if (payload.recipientNametag) {
1666
+ tags.push(["recipient", payload.recipientNametag]);
1667
+ }
1668
+ return this.mux.createAndPublishEncryptedEvent(
1669
+ this.addressIndex,
1670
+ EVENT_KINDS.PAYMENT_REQUEST,
1671
+ content,
1672
+ tags
1673
+ );
1674
+ }
1675
+ async sendPaymentRequestResponse(recipientPubkey, response) {
1676
+ const content = "payment_response:" + JSON.stringify(response);
1677
+ return this.mux.createAndPublishEncryptedEvent(
1678
+ this.addressIndex,
1679
+ EVENT_KINDS.PAYMENT_REQUEST_RESPONSE,
1680
+ content,
1681
+ [["p", recipientPubkey], ["type", "payment_response"]]
1682
+ );
1683
+ }
1684
+ async sendReadReceipt(recipientPubkey, messageEventId) {
1685
+ const content = JSON.stringify({ type: "read_receipt", messageEventId });
1686
+ await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
1687
+ }
1688
+ async sendTypingIndicator(recipientPubkey) {
1689
+ const content = JSON.stringify({
1690
+ type: "typing",
1691
+ senderNametag: this.identity.nametag
1692
+ });
1693
+ await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
1694
+ }
1695
+ async sendComposingIndicator(recipientPubkey, content) {
1696
+ await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
1697
+ }
1698
+ async sendInstantSplitBundle(recipientPubkey, bundle) {
1699
+ const content = "token_transfer:" + JSON.stringify({
1700
+ type: "instant_split",
1701
+ ...bundle
1702
+ });
1703
+ const uniqueD = `instant-split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1704
+ return this.mux.createAndPublishEncryptedEvent(
1705
+ this.addressIndex,
1706
+ EVENT_KINDS.TOKEN_TRANSFER,
1707
+ content,
1708
+ [["p", recipientPubkey], ["d", uniqueD], ["type", "instant_split"]]
1709
+ );
1710
+ }
1711
+ // ===========================================================================
1712
+ // Subscription handlers — per-address
1713
+ // ===========================================================================
1714
+ onMessage(handler) {
1715
+ this.messageHandlers.add(handler);
1716
+ if (this.pendingMessages.length > 0) {
1717
+ const pending2 = this.pendingMessages;
1718
+ this.pendingMessages = [];
1719
+ for (const msg of pending2) {
1720
+ try {
1721
+ handler(msg);
1722
+ } catch {
1723
+ }
1724
+ }
1725
+ }
1726
+ return () => this.messageHandlers.delete(handler);
1727
+ }
1728
+ onTokenTransfer(handler) {
1729
+ this.transferHandlers.add(handler);
1730
+ return () => this.transferHandlers.delete(handler);
1731
+ }
1732
+ onPaymentRequest(handler) {
1733
+ this.paymentRequestHandlers.add(handler);
1734
+ return () => this.paymentRequestHandlers.delete(handler);
1735
+ }
1736
+ onPaymentRequestResponse(handler) {
1737
+ this.paymentRequestResponseHandlers.add(handler);
1738
+ return () => this.paymentRequestResponseHandlers.delete(handler);
1739
+ }
1740
+ onReadReceipt(handler) {
1741
+ this.readReceiptHandlers.add(handler);
1742
+ return () => this.readReceiptHandlers.delete(handler);
1743
+ }
1744
+ onTypingIndicator(handler) {
1745
+ this.typingIndicatorHandlers.add(handler);
1746
+ return () => this.typingIndicatorHandlers.delete(handler);
1747
+ }
1748
+ onComposing(handler) {
1749
+ this.composingHandlers.add(handler);
1750
+ return () => this.composingHandlers.delete(handler);
1751
+ }
1752
+ onInstantSplitReceived(handler) {
1753
+ this.instantSplitBundleHandlers.add(handler);
1754
+ return () => this.instantSplitBundleHandlers.delete(handler);
1755
+ }
1756
+ subscribeToBroadcast(tags, handler) {
1757
+ const key = tags.sort().join(":");
1758
+ if (!this.broadcastHandlers.has(key)) {
1759
+ this.broadcastHandlers.set(key, /* @__PURE__ */ new Set());
1760
+ }
1761
+ this.broadcastHandlers.get(key).add(handler);
1762
+ return () => this.broadcastHandlers.get(key)?.delete(handler);
1763
+ }
1764
+ async publishBroadcast(content, tags) {
1765
+ const eventTags = tags ? tags.map((t) => ["t", t]) : [];
1766
+ return this.mux.publishRawEvent(this.addressIndex, 30023, content, eventTags);
1767
+ }
1768
+ // ===========================================================================
1769
+ // Resolve methods — delegate to original NostrTransportProvider
1770
+ // These are stateless relay queries, shared across all addresses
1771
+ // ===========================================================================
1772
+ async resolve(identifier) {
1773
+ return this.resolveDelegate?.resolve?.(identifier) ?? null;
1774
+ }
1775
+ async resolveNametag(nametag) {
1776
+ return this.resolveDelegate?.resolveNametag?.(nametag) ?? null;
1777
+ }
1778
+ async resolveNametagInfo(nametag) {
1779
+ return this.resolveDelegate?.resolveNametagInfo?.(nametag) ?? null;
1780
+ }
1781
+ async resolveAddressInfo(address) {
1782
+ return this.resolveDelegate?.resolveAddressInfo?.(address) ?? null;
1783
+ }
1784
+ async resolveTransportPubkeyInfo(transportPubkey) {
1785
+ return this.resolveDelegate?.resolveTransportPubkeyInfo?.(transportPubkey) ?? null;
1786
+ }
1787
+ async discoverAddresses(transportPubkeys) {
1788
+ return this.resolveDelegate?.discoverAddresses?.(transportPubkeys) ?? [];
1789
+ }
1790
+ async recoverNametag() {
1791
+ return this.resolveDelegate?.recoverNametag?.() ?? null;
1792
+ }
1793
+ async publishIdentityBinding(chainPubkey, l1Address, directAddress, nametag) {
1794
+ return this.resolveDelegate?.publishIdentityBinding?.(chainPubkey, l1Address, directAddress, nametag) ?? false;
1795
+ }
1796
+ // ===========================================================================
1797
+ // Relay Management — delegates to mux
1798
+ // ===========================================================================
1799
+ getRelays() {
1800
+ return this.mux.getRelays();
1801
+ }
1802
+ getConnectedRelays() {
1803
+ return this.mux.getConnectedRelays();
1804
+ }
1805
+ async addRelay(relayUrl) {
1806
+ return this.mux.addRelay(relayUrl);
1807
+ }
1808
+ async removeRelay(relayUrl) {
1809
+ return this.mux.removeRelay(relayUrl);
1810
+ }
1811
+ hasRelay(relayUrl) {
1812
+ return this.mux.hasRelay(relayUrl);
1813
+ }
1814
+ isRelayConnected(relayUrl) {
1815
+ return this.mux.isRelayConnected(relayUrl);
1816
+ }
1817
+ setFallbackSince(sinceSeconds) {
1818
+ this.mux.setFallbackSince(this.addressIndex, sinceSeconds);
1819
+ }
1820
+ async fetchPendingEvents() {
1821
+ }
1822
+ onChatReady(handler) {
1823
+ return this.mux.onChatReady(handler);
1824
+ }
1825
+ // ===========================================================================
1826
+ // Dispatch methods — called by MultiAddressTransportMux to route events
1827
+ // ===========================================================================
1828
+ dispatchMessage(message) {
1829
+ if (this.messageHandlers.size === 0) {
1830
+ this.pendingMessages.push(message);
1831
+ return;
1832
+ }
1833
+ for (const handler of this.messageHandlers) {
1834
+ try {
1835
+ handler(message);
1836
+ } catch (e) {
1837
+ logger.debug("MuxAdapter", "Message handler error:", e);
1838
+ }
1839
+ }
1840
+ }
1841
+ dispatchTokenTransfer(transfer) {
1842
+ for (const handler of this.transferHandlers) {
1843
+ try {
1844
+ handler(transfer);
1845
+ } catch (e) {
1846
+ logger.debug("MuxAdapter", "Transfer handler error:", e);
1847
+ }
1848
+ }
1849
+ }
1850
+ dispatchPaymentRequest(request) {
1851
+ for (const handler of this.paymentRequestHandlers) {
1852
+ try {
1853
+ handler(request);
1854
+ } catch (e) {
1855
+ logger.debug("MuxAdapter", "Payment request handler error:", e);
1856
+ }
1857
+ }
1858
+ }
1859
+ dispatchPaymentRequestResponse(response) {
1860
+ for (const handler of this.paymentRequestResponseHandlers) {
1861
+ try {
1862
+ handler(response);
1863
+ } catch (e) {
1864
+ logger.debug("MuxAdapter", "Payment response handler error:", e);
1865
+ }
1866
+ }
1867
+ }
1868
+ dispatchReadReceipt(receipt) {
1869
+ for (const handler of this.readReceiptHandlers) {
1870
+ try {
1871
+ handler(receipt);
1872
+ } catch (e) {
1873
+ logger.debug("MuxAdapter", "Read receipt handler error:", e);
1874
+ }
1875
+ }
1876
+ }
1877
+ dispatchTypingIndicator(indicator) {
1878
+ for (const handler of this.typingIndicatorHandlers) {
1879
+ try {
1880
+ handler(indicator);
1881
+ } catch (e) {
1882
+ logger.debug("MuxAdapter", "Typing handler error:", e);
1883
+ }
1884
+ }
1885
+ }
1886
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1887
+ dispatchComposingIndicator(indicator) {
1888
+ for (const handler of this.composingHandlers) {
1889
+ try {
1890
+ handler(indicator);
1891
+ } catch (e) {
1892
+ logger.debug("MuxAdapter", "Composing handler error:", e);
1893
+ }
1894
+ }
1895
+ }
1896
+ dispatchInstantSplitBundle(bundle) {
1897
+ for (const handler of this.instantSplitBundleHandlers) {
1898
+ try {
1899
+ handler(bundle);
1900
+ } catch (e) {
1901
+ logger.debug("MuxAdapter", "Instant split handler error:", e);
1902
+ }
1903
+ }
1904
+ }
1905
+ emitTransportEvent(event) {
1906
+ for (const cb of this.eventCallbacks) {
1907
+ try {
1908
+ cb(event);
1909
+ } catch {
1910
+ }
1911
+ }
1912
+ }
1913
+ };
1914
+
809
1915
  // modules/payments/L1PaymentsModule.ts
810
1916
  init_errors();
811
1917
  init_constants();
@@ -4911,6 +6017,15 @@ var PaymentsModule = class _PaymentsModule {
4911
6017
  this.unsubscribePaymentRequests = null;
4912
6018
  this.unsubscribePaymentRequestResponses?.();
4913
6019
  this.unsubscribePaymentRequestResponses = null;
6020
+ this.stopProofPolling();
6021
+ this.proofPollingJobs.clear();
6022
+ this.stopResolveUnconfirmedPolling();
6023
+ this.unsubscribeStorageEvents();
6024
+ for (const [, resolver] of this.pendingResponseResolvers) {
6025
+ clearTimeout(resolver.timeout);
6026
+ resolver.reject(new Error("Address switched"));
6027
+ }
6028
+ this.pendingResponseResolvers.clear();
4914
6029
  this.tokens.clear();
4915
6030
  this.pendingTransfers.clear();
4916
6031
  this.tombstones = [];
@@ -4959,6 +6074,13 @@ var PaymentsModule = class _PaymentsModule {
4959
6074
  try {
4960
6075
  const result = await provider.load();
4961
6076
  if (result.success && result.data) {
6077
+ const loadedMeta = result.data?._meta;
6078
+ const currentL1 = this.deps.identity.l1Address;
6079
+ const currentChain = this.deps.identity.chainPubkey;
6080
+ if (loadedMeta?.address && currentL1 && loadedMeta.address !== currentL1 && loadedMeta.address !== currentChain) {
6081
+ logger.warn("Payments", `Load: rejecting data from provider ${id} \u2014 address mismatch (got=${loadedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
6082
+ continue;
6083
+ }
4962
6084
  this.loadFromStorageData(result.data);
4963
6085
  const txfData = result.data;
4964
6086
  if (txfData._history && txfData._history.length > 0) {
@@ -5040,6 +6162,11 @@ var PaymentsModule = class _PaymentsModule {
5040
6162
  */
5041
6163
  async send(request) {
5042
6164
  this.ensureInitialized();
6165
+ let resolveSendTracker;
6166
+ const sendTracker = new Promise((r) => {
6167
+ resolveSendTracker = r;
6168
+ });
6169
+ this.pendingBackgroundTasks.push(sendTracker);
5043
6170
  const result = {
5044
6171
  id: crypto.randomUUID(),
5045
6172
  status: "pending",
@@ -5327,6 +6454,8 @@ var PaymentsModule = class _PaymentsModule {
5327
6454
  }
5328
6455
  this.deps.emitEvent("transfer:failed", result);
5329
6456
  throw error;
6457
+ } finally {
6458
+ resolveSendTracker();
5330
6459
  }
5331
6460
  }
5332
6461
  /**
@@ -6339,9 +7468,12 @@ var PaymentsModule = class _PaymentsModule {
6339
7468
  * Call this before process exit to ensure all tokens are saved.
6340
7469
  */
6341
7470
  async waitForPendingOperations() {
7471
+ logger.debug("Payments", `waitForPendingOperations: ${this.pendingBackgroundTasks.length} pending tasks`);
6342
7472
  if (this.pendingBackgroundTasks.length > 0) {
7473
+ logger.debug("Payments", "waitForPendingOperations: waiting...");
6343
7474
  await Promise.allSettled(this.pendingBackgroundTasks);
6344
7475
  this.pendingBackgroundTasks = [];
7476
+ logger.debug("Payments", "waitForPendingOperations: all tasks completed");
6345
7477
  }
6346
7478
  }
6347
7479
  /**
@@ -7583,6 +8715,13 @@ var PaymentsModule = class _PaymentsModule {
7583
8715
  try {
7584
8716
  const result = await provider.sync(localData);
7585
8717
  if (result.success && result.merged) {
8718
+ const mergedMeta = result.merged?._meta;
8719
+ const currentL1 = this.deps.identity.l1Address;
8720
+ const currentChain = this.deps.identity.chainPubkey;
8721
+ if (mergedMeta?.address && currentL1 && mergedMeta.address !== currentL1 && mergedMeta.address !== currentChain) {
8722
+ logger.warn("Payments", `Sync: rejecting data from provider ${providerId} \u2014 address mismatch (got=${mergedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
8723
+ continue;
8724
+ }
7586
8725
  const savedTokens = new Map(this.tokens);
7587
8726
  this.loadFromStorageData(result.merged);
7588
8727
  let restoredCount = 0;
@@ -8594,6 +9733,12 @@ var CommunicationsModule = class {
8594
9733
  this.unsubscribeComposing = deps.transport.onComposing?.((indicator) => {
8595
9734
  this.handleComposingIndicator(indicator);
8596
9735
  }) ?? null;
9736
+ if (deps.transport.onChatReady) {
9737
+ deps.transport.onChatReady(() => {
9738
+ const conversations = this.getConversations();
9739
+ deps.emitEvent("communications:ready", { conversationCount: conversations.size });
9740
+ });
9741
+ }
8597
9742
  }
8598
9743
  /**
8599
9744
  * Load messages from storage.
@@ -9009,9 +10154,9 @@ init_logger();
9009
10154
  init_errors();
9010
10155
  init_constants();
9011
10156
  import {
9012
- NostrClient,
9013
- NostrKeyManager,
9014
- Filter
10157
+ NostrClient as NostrClient2,
10158
+ NostrKeyManager as NostrKeyManager2,
10159
+ Filter as Filter2
9015
10160
  } from "@unicitylabs/nostr-js-sdk";
9016
10161
 
9017
10162
  // modules/groupchat/types.ts
@@ -9027,7 +10172,7 @@ var GroupVisibility = {
9027
10172
 
9028
10173
  // modules/groupchat/GroupChatModule.ts
9029
10174
  function createNip29Filter(data) {
9030
- return new Filter(data);
10175
+ return new Filter2(data);
9031
10176
  }
9032
10177
  var GroupChatModule = class {
9033
10178
  config;
@@ -9076,7 +10221,7 @@ var GroupChatModule = class {
9076
10221
  }
9077
10222
  this.deps = deps;
9078
10223
  const secretKey = Buffer.from(deps.identity.privateKey, "hex");
9079
- this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
10224
+ this.keyManager = NostrKeyManager2.fromPrivateKey(secretKey);
9080
10225
  }
9081
10226
  async load() {
9082
10227
  this.ensureInitialized();
@@ -9211,7 +10356,7 @@ var GroupChatModule = class {
9211
10356
  }
9212
10357
  this.subscriptionIds = [];
9213
10358
  const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
9214
- this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
10359
+ this.keyManager = NostrKeyManager2.fromPrivateKey(secretKey);
9215
10360
  if (this.groups.size === 0) {
9216
10361
  await this.restoreJoinedGroups();
9217
10362
  } else {
@@ -9223,13 +10368,13 @@ var GroupChatModule = class {
9223
10368
  this.ensureInitialized();
9224
10369
  if (!this.keyManager) {
9225
10370
  const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
9226
- this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
10371
+ this.keyManager = NostrKeyManager2.fromPrivateKey(secretKey);
9227
10372
  }
9228
10373
  const primaryRelay = this.config.relays[0];
9229
10374
  if (primaryRelay) {
9230
10375
  await this.checkAndClearOnRelayChange(primaryRelay);
9231
10376
  }
9232
- this.client = new NostrClient(this.keyManager);
10377
+ this.client = new NostrClient2(this.keyManager);
9233
10378
  try {
9234
10379
  await this.client.connect(...this.config.relays);
9235
10380
  this.connected = true;
@@ -9240,6 +10385,7 @@ var GroupChatModule = class {
9240
10385
  await this.subscribeToJoinedGroups();
9241
10386
  }
9242
10387
  this.deps.emitEvent("groupchat:connection", { connected: true });
10388
+ this.deps.emitEvent("groupchat:ready", { groupCount: this.groups.size });
9243
10389
  } catch (error) {
9244
10390
  logger.error("GroupChat", "Failed to connect to relays", error);
9245
10391
  this.deps.emitEvent("groupchat:connection", { connected: false });
@@ -9487,7 +10633,7 @@ var GroupChatModule = class {
9487
10633
  if (!myPubkey) return [];
9488
10634
  const groupIdsWithMembership = /* @__PURE__ */ new Set();
9489
10635
  await this.oneshotSubscription(
9490
- new Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
10636
+ new Filter2({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
9491
10637
  {
9492
10638
  onEvent: (event) => {
9493
10639
  const groupId = this.getGroupIdFromMetadataEvent(event);
@@ -9538,7 +10684,7 @@ var GroupChatModule = class {
9538
10684
  const memberCountsMap = /* @__PURE__ */ new Map();
9539
10685
  await Promise.all([
9540
10686
  this.oneshotSubscription(
9541
- new Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
10687
+ new Filter2({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
9542
10688
  {
9543
10689
  onEvent: (event) => {
9544
10690
  const group = this.parseGroupMetadata(event);
@@ -9556,7 +10702,7 @@ var GroupChatModule = class {
9556
10702
  }
9557
10703
  ),
9558
10704
  this.oneshotSubscription(
9559
- new Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
10705
+ new Filter2({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
9560
10706
  {
9561
10707
  onEvent: (event) => {
9562
10708
  const groupId = this.getGroupIdFromMetadataEvent(event);
@@ -9874,6 +11020,19 @@ var GroupChatModule = class {
9874
11020
  getMessages(groupId) {
9875
11021
  return (this.messages.get(groupId) || []).sort((a, b) => a.timestamp - b.timestamp);
9876
11022
  }
11023
+ getMessagesPage(groupId, options) {
11024
+ const limit = options?.limit ?? 20;
11025
+ const before = options?.before ?? Infinity;
11026
+ const groupMessages = this.messages.get(groupId) ?? [];
11027
+ const filtered = groupMessages.filter((m) => m.timestamp < before).sort((a, b) => b.timestamp - a.timestamp);
11028
+ const page = filtered.slice(0, limit);
11029
+ return {
11030
+ messages: page.reverse(),
11031
+ // chronological order
11032
+ hasMore: filtered.length > limit,
11033
+ oldestTimestamp: page.length > 0 ? page[0].timestamp : null
11034
+ };
11035
+ }
9877
11036
  getMembers(groupId) {
9878
11037
  return (this.members.get(groupId) || []).sort((a, b) => a.joinedAt - b.joinedAt);
9879
11038
  }
@@ -10046,7 +11205,7 @@ var GroupChatModule = class {
10046
11205
  if (!this.client) return /* @__PURE__ */ new Set();
10047
11206
  const adminPubkeys = /* @__PURE__ */ new Set();
10048
11207
  return this.oneshotSubscription(
10049
- new Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
11208
+ new Filter2({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
10050
11209
  {
10051
11210
  onEvent: (event) => {
10052
11211
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -10068,7 +11227,7 @@ var GroupChatModule = class {
10068
11227
  if (!this.client) return null;
10069
11228
  let result = null;
10070
11229
  return this.oneshotSubscription(
10071
- new Filter({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
11230
+ new Filter2({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
10072
11231
  {
10073
11232
  onEvent: (event) => {
10074
11233
  if (!result) result = this.parseGroupMetadata(event);
@@ -10105,7 +11264,7 @@ var GroupChatModule = class {
10105
11264
  if (!this.client) return [];
10106
11265
  const members = [];
10107
11266
  return this.oneshotSubscription(
10108
- new Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
11267
+ new Filter2({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
10109
11268
  {
10110
11269
  onEvent: (event) => {
10111
11270
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -10126,7 +11285,7 @@ var GroupChatModule = class {
10126
11285
  if (!this.client) return [];
10127
11286
  const adminPubkeys = [];
10128
11287
  return this.oneshotSubscription(
10129
- new Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
11288
+ new Filter2({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
10130
11289
  {
10131
11290
  onEvent: (event) => {
10132
11291
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -13841,11 +15000,18 @@ var Sphere = class _Sphere {
13841
15000
  _transport;
13842
15001
  _oracle;
13843
15002
  _priceProvider;
13844
- // Modules
15003
+ // Modules (single-instance — backward compat, delegates to active address)
13845
15004
  _payments;
13846
15005
  _communications;
13847
15006
  _groupChat = null;
13848
15007
  _market = null;
15008
+ // Per-address module instances (Phase 2: independent parallel operation)
15009
+ _addressModules = /* @__PURE__ */ new Map();
15010
+ _transportMux = null;
15011
+ // Stored configs for creating per-address modules
15012
+ _l1Config;
15013
+ _groupChatConfig;
15014
+ _marketConfig;
13849
15015
  // Events
13850
15016
  eventHandlers = /* @__PURE__ */ new Map();
13851
15017
  // Provider management
@@ -13863,6 +15029,9 @@ var Sphere = class _Sphere {
13863
15029
  if (tokenStorage) {
13864
15030
  this._tokenStorageProviders.set(tokenStorage.id, tokenStorage);
13865
15031
  }
15032
+ this._l1Config = l1Config;
15033
+ this._groupChatConfig = groupChatConfig;
15034
+ this._marketConfig = marketConfig;
13866
15035
  this._payments = createPaymentsModule({ l1: l1Config });
13867
15036
  this._communications = createCommunicationsModule();
13868
15037
  this._groupChat = groupChatConfig ? createGroupChatModule(groupChatConfig) : null;
@@ -15121,7 +16290,7 @@ var Sphere = class _Sphere {
15121
16290
  nametags.set(0, newNametag);
15122
16291
  }
15123
16292
  const nametag = this._addressNametags.get(addressId)?.get(0);
15124
- this._identity = {
16293
+ const newIdentity = {
15125
16294
  privateKey: addressInfo.privateKey,
15126
16295
  chainPubkey: addressInfo.publicKey,
15127
16296
  l1Address: addressInfo.address,
@@ -15129,20 +16298,53 @@ var Sphere = class _Sphere {
15129
16298
  ipnsName: "12D3KooW" + ipnsHash,
15130
16299
  nametag
15131
16300
  };
16301
+ if (!this._addressModules.has(index)) {
16302
+ logger.debug("Sphere", `switchToAddress(${index}): creating per-address modules (lazy init)`);
16303
+ const addressTokenProviders = /* @__PURE__ */ new Map();
16304
+ for (const [providerId, provider] of this._tokenStorageProviders.entries()) {
16305
+ if (provider.createForAddress) {
16306
+ const newProvider = provider.createForAddress();
16307
+ newProvider.setIdentity(newIdentity);
16308
+ await newProvider.initialize();
16309
+ addressTokenProviders.set(providerId, newProvider);
16310
+ } else {
16311
+ logger.warn("Sphere", `Token storage provider ${providerId} does not support createForAddress, reusing shared instance`);
16312
+ addressTokenProviders.set(providerId, provider);
16313
+ }
16314
+ }
16315
+ await this.initializeAddressModules(index, newIdentity, addressTokenProviders);
16316
+ } else {
16317
+ const moduleSet = this._addressModules.get(index);
16318
+ if (nametag !== moduleSet.identity.nametag) {
16319
+ moduleSet.identity = newIdentity;
16320
+ const addressTransport = moduleSet.transportAdapter ?? this._transport;
16321
+ moduleSet.payments.initialize({
16322
+ identity: newIdentity,
16323
+ storage: this._storage,
16324
+ tokenStorageProviders: moduleSet.tokenStorageProviders,
16325
+ transport: addressTransport,
16326
+ oracle: this._oracle,
16327
+ emitEvent: this.emitEvent.bind(this),
16328
+ chainCode: this._masterKey?.chainCode || void 0,
16329
+ price: this._priceProvider ?? void 0
16330
+ });
16331
+ }
16332
+ }
16333
+ this._identity = newIdentity;
15132
16334
  this._currentAddressIndex = index;
15133
16335
  await this._updateCachedProxyAddress();
16336
+ const activeModules = this._addressModules.get(index);
16337
+ this._payments = activeModules.payments;
16338
+ this._communications = activeModules.communications;
16339
+ this._groupChat = activeModules.groupChat;
16340
+ this._market = activeModules.market;
15134
16341
  await this._storage.set(STORAGE_KEYS_GLOBAL.CURRENT_ADDRESS_INDEX, index.toString());
15135
16342
  this._storage.setIdentity(this._identity);
15136
- await this._transport.setIdentity(this._identity);
15137
- logger.debug("Sphere", `switchToAddress(${index}): re-initializing ${this._tokenStorageProviders.size} token storage provider(s)`);
15138
- for (const [providerId, provider] of this._tokenStorageProviders.entries()) {
15139
- logger.debug("Sphere", `switchToAddress(${index}): shutdown provider=${providerId}`);
15140
- await provider.shutdown();
15141
- provider.setIdentity(this._identity);
15142
- logger.debug("Sphere", `switchToAddress(${index}): initialize provider=${providerId}`);
15143
- await provider.initialize();
16343
+ if (this._transport.setFallbackSince) {
16344
+ const fallbackTs = Math.floor(Date.now() / 1e3) - 86400;
16345
+ this._transport.setFallbackSince(fallbackTs);
15144
16346
  }
15145
- await this.reinitializeModulesForNewAddress();
16347
+ await this._transport.setIdentity(this._identity);
15146
16348
  this.emitEvent("identity:changed", {
15147
16349
  l1Address: this._identity.l1Address,
15148
16350
  directAddress: this._identity.directAddress,
@@ -15197,42 +16399,104 @@ var Sphere = class _Sphere {
15197
16399
  }
15198
16400
  }
15199
16401
  /**
15200
- * Re-initialize modules after address switch
16402
+ * Create a new set of per-address modules for the given index.
16403
+ * Each address gets its own PaymentsModule, CommunicationsModule, etc.
16404
+ * Modules are fully independent — they have their own token storage,
16405
+ * and can sync/finalize/split in background regardless of active address.
16406
+ *
16407
+ * @param index - HD address index
16408
+ * @param identity - Full identity for this address
16409
+ * @param tokenStorageProviders - Token storage providers for this address
15201
16410
  */
15202
- async reinitializeModulesForNewAddress() {
16411
+ async initializeAddressModules(index, identity, tokenStorageProviders) {
15203
16412
  const emitEvent = this.emitEvent.bind(this);
15204
- this._payments.initialize({
15205
- identity: this._identity,
16413
+ const adapter = await this.ensureTransportMux(index, identity);
16414
+ const addressTransport = adapter ?? this._transport;
16415
+ const payments = createPaymentsModule({ l1: this._l1Config });
16416
+ const communications = createCommunicationsModule();
16417
+ const groupChat = this._groupChatConfig ? createGroupChatModule(this._groupChatConfig) : null;
16418
+ const market = this._marketConfig ? createMarketModule(this._marketConfig) : null;
16419
+ payments.initialize({
16420
+ identity,
15206
16421
  storage: this._storage,
15207
- tokenStorageProviders: this._tokenStorageProviders,
15208
- transport: this._transport,
16422
+ tokenStorageProviders,
16423
+ transport: addressTransport,
15209
16424
  oracle: this._oracle,
15210
16425
  emitEvent,
15211
16426
  chainCode: this._masterKey?.chainCode || void 0,
15212
16427
  price: this._priceProvider ?? void 0
15213
16428
  });
15214
- this._communications.initialize({
15215
- identity: this._identity,
16429
+ communications.initialize({
16430
+ identity,
15216
16431
  storage: this._storage,
15217
- transport: this._transport,
16432
+ transport: addressTransport,
15218
16433
  emitEvent
15219
16434
  });
15220
- this._groupChat?.initialize({
15221
- identity: this._identity,
16435
+ groupChat?.initialize({
16436
+ identity,
15222
16437
  storage: this._storage,
15223
16438
  emitEvent
15224
16439
  });
15225
- this._market?.initialize({
15226
- identity: this._identity,
16440
+ market?.initialize({
16441
+ identity,
15227
16442
  emitEvent
15228
16443
  });
15229
- await this._payments.load();
15230
- await this._communications.load();
15231
- await this._groupChat?.load();
15232
- await this._market?.load();
15233
- this._payments.sync().catch((err) => {
15234
- logger.warn("Sphere", "Post-switch sync failed:", err);
16444
+ await payments.load();
16445
+ await communications.load();
16446
+ await groupChat?.load();
16447
+ await market?.load();
16448
+ const moduleSet = {
16449
+ index,
16450
+ identity,
16451
+ payments,
16452
+ communications,
16453
+ groupChat,
16454
+ market,
16455
+ transportAdapter: adapter,
16456
+ tokenStorageProviders: new Map(tokenStorageProviders),
16457
+ initialized: true
16458
+ };
16459
+ this._addressModules.set(index, moduleSet);
16460
+ logger.debug("Sphere", `Initialized per-address modules for address ${index} (transport: ${adapter ? "mux adapter" : "primary"})`);
16461
+ payments.sync().catch((err) => {
16462
+ logger.warn("Sphere", `Post-init sync failed for address ${index}:`, err);
15235
16463
  });
16464
+ return moduleSet;
16465
+ }
16466
+ /**
16467
+ * Ensure the transport multiplexer exists and register an address.
16468
+ * Creates the mux on first call. Returns an AddressTransportAdapter
16469
+ * that routes events for this address independently.
16470
+ * @returns AddressTransportAdapter or null if transport is not Nostr-based
16471
+ */
16472
+ async ensureTransportMux(index, identity) {
16473
+ const transport = this._transport;
16474
+ if (typeof transport.getWebSocketFactory !== "function" || typeof transport.getConfiguredRelays !== "function") {
16475
+ logger.debug("Sphere", "Transport does not support mux interface, skipping");
16476
+ return null;
16477
+ }
16478
+ const nostrTransport = transport;
16479
+ if (!this._transportMux) {
16480
+ this._transportMux = new MultiAddressTransportMux({
16481
+ relays: nostrTransport.getConfiguredRelays(),
16482
+ createWebSocket: nostrTransport.getWebSocketFactory(),
16483
+ storage: nostrTransport.getStorageAdapter() ?? void 0
16484
+ });
16485
+ await this._transportMux.connect();
16486
+ if (typeof nostrTransport.suppressSubscriptions === "function") {
16487
+ nostrTransport.suppressSubscriptions();
16488
+ }
16489
+ logger.debug("Sphere", "Transport mux created and connected");
16490
+ }
16491
+ const adapter = await this._transportMux.addAddress(index, identity, this._transport);
16492
+ return adapter;
16493
+ }
16494
+ /**
16495
+ * Get per-address modules for any address index (creates lazily if needed).
16496
+ * This allows accessing any address's modules without switching.
16497
+ */
16498
+ getAddressPayments(index) {
16499
+ return this._addressModules.get(index)?.payments;
15236
16500
  }
15237
16501
  /**
15238
16502
  * Derive address at a specific index
@@ -16160,10 +17424,33 @@ var Sphere = class _Sphere {
16160
17424
  // ===========================================================================
16161
17425
  async destroy() {
16162
17426
  this.cleanupProviderEventSubscriptions();
17427
+ for (const [idx, moduleSet] of this._addressModules.entries()) {
17428
+ try {
17429
+ moduleSet.payments.destroy();
17430
+ moduleSet.communications.destroy();
17431
+ moduleSet.groupChat?.destroy();
17432
+ moduleSet.market?.destroy();
17433
+ for (const provider of moduleSet.tokenStorageProviders.values()) {
17434
+ try {
17435
+ await provider.shutdown();
17436
+ } catch {
17437
+ }
17438
+ }
17439
+ moduleSet.tokenStorageProviders.clear();
17440
+ logger.debug("Sphere", `Destroyed modules for address ${idx}`);
17441
+ } catch (err) {
17442
+ logger.warn("Sphere", `Error destroying modules for address ${idx}:`, err);
17443
+ }
17444
+ }
17445
+ this._addressModules.clear();
16163
17446
  this._payments.destroy();
16164
17447
  this._communications.destroy();
16165
17448
  this._groupChat?.destroy();
16166
17449
  this._market?.destroy();
17450
+ if (this._transportMux) {
17451
+ await this._transportMux.disconnect();
17452
+ this._transportMux = null;
17453
+ }
16167
17454
  await this._transport.disconnect();
16168
17455
  await this._storage.disconnect();
16169
17456
  await this._oracle.disconnect();
@@ -16358,6 +17645,9 @@ var Sphere = class _Sphere {
16358
17645
  // ===========================================================================
16359
17646
  async initializeProviders() {
16360
17647
  this._storage.setIdentity(this._identity);
17648
+ if (this._transport.setFallbackSince) {
17649
+ this._transport.setFallbackSince(Math.floor(Date.now() / 1e3) - 86400);
17650
+ }
16361
17651
  await this._transport.setIdentity(this._identity);
16362
17652
  for (const provider of this._tokenStorageProviders.values()) {
16363
17653
  provider.setIdentity(this._identity);
@@ -16448,11 +17738,13 @@ var Sphere = class _Sphere {
16448
17738
  }
16449
17739
  async initializeModules() {
16450
17740
  const emitEvent = this.emitEvent.bind(this);
17741
+ const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
17742
+ const moduleTransport = adapter ?? this._transport;
16451
17743
  this._payments.initialize({
16452
17744
  identity: this._identity,
16453
17745
  storage: this._storage,
16454
17746
  tokenStorageProviders: this._tokenStorageProviders,
16455
- transport: this._transport,
17747
+ transport: moduleTransport,
16456
17748
  oracle: this._oracle,
16457
17749
  emitEvent,
16458
17750
  // Pass chain code for L1 HD derivation
@@ -16463,7 +17755,7 @@ var Sphere = class _Sphere {
16463
17755
  this._communications.initialize({
16464
17756
  identity: this._identity,
16465
17757
  storage: this._storage,
16466
- transport: this._transport,
17758
+ transport: moduleTransport,
16467
17759
  emitEvent
16468
17760
  });
16469
17761
  this._groupChat?.initialize({
@@ -16479,6 +17771,17 @@ var Sphere = class _Sphere {
16479
17771
  await this._communications.load();
16480
17772
  await this._groupChat?.load();
16481
17773
  await this._market?.load();
17774
+ this._addressModules.set(this._currentAddressIndex, {
17775
+ index: this._currentAddressIndex,
17776
+ identity: this._identity,
17777
+ payments: this._payments,
17778
+ communications: this._communications,
17779
+ groupChat: this._groupChat,
17780
+ market: this._market,
17781
+ transportAdapter: adapter,
17782
+ tokenStorageProviders: new Map(this._tokenStorageProviders),
17783
+ initialized: true
17784
+ });
16482
17785
  }
16483
17786
  // ===========================================================================
16484
17787
  // Private: Helpers
@@ -17159,7 +18462,7 @@ import {
17159
18462
  encryptNametag,
17160
18463
  decryptNametag
17161
18464
  } from "@unicitylabs/nostr-js-sdk";
17162
- import { NostrClient as NostrClient2, NostrKeyManager as NostrKeyManager2 } from "@unicitylabs/nostr-js-sdk";
18465
+ import { NostrClient as NostrClient3, NostrKeyManager as NostrKeyManager3 } from "@unicitylabs/nostr-js-sdk";
17163
18466
 
17164
18467
  // price/CoinGeckoPriceProvider.ts
17165
18468
  init_logger();
@@ -17431,8 +18734,8 @@ export {
17431
18734
  NETWORKS,
17432
18735
  NIP29_KINDS,
17433
18736
  NOSTR_EVENT_KINDS,
17434
- NostrClient2 as NostrClient,
17435
- NostrKeyManager2 as NostrKeyManager,
18737
+ NostrClient3 as NostrClient,
18738
+ NostrKeyManager3 as NostrKeyManager,
17436
18739
  PaymentsModule,
17437
18740
  SIGN_MESSAGE_PREFIX,
17438
18741
  STORAGE_KEYS,