@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/core/index.cjs +1371 -52
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +369 -4
- package/dist/core/index.d.ts +369 -4
- package/dist/core/index.js +1377 -48
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +137 -11
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +137 -11
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +38 -10
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +38 -10
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +133 -11
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +54 -0
- package/dist/impl/nodejs/index.d.ts +54 -0
- package/dist/impl/nodejs/index.js +133 -11
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +1354 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -3
- package/dist/index.d.ts +70 -3
- package/dist/index.js +1353 -50
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/core/index.js
CHANGED
|
@@ -145,7 +145,7 @@ function getAddressId(directAddress) {
|
|
|
145
145
|
const last = hash.slice(-6).toLowerCase();
|
|
146
146
|
return `DIRECT_${first}_${last}`;
|
|
147
147
|
}
|
|
148
|
-
var DEFAULT_ENCRYPTION_KEY, STORAGE_KEYS_GLOBAL, STORAGE_KEYS_ADDRESS, STORAGE_KEYS, DEFAULT_NOSTR_RELAYS, NIP29_KINDS, DEFAULT_AGGREGATOR_URL, DEV_AGGREGATOR_URL, TEST_AGGREGATOR_URL, DEFAULT_IPFS_GATEWAYS, DEFAULT_BASE_PATH, DEFAULT_DERIVATION_PATH, DEFAULT_ELECTRUM_URL, TEST_ELECTRUM_URL, TOKEN_REGISTRY_URL, TOKEN_REGISTRY_REFRESH_INTERVAL, TEST_NOSTR_RELAYS, DEFAULT_GROUP_RELAYS, NETWORKS;
|
|
148
|
+
var DEFAULT_ENCRYPTION_KEY, STORAGE_KEYS_GLOBAL, STORAGE_KEYS_ADDRESS, STORAGE_KEYS, DEFAULT_NOSTR_RELAYS, NOSTR_EVENT_KINDS, NIP29_KINDS, DEFAULT_AGGREGATOR_URL, DEV_AGGREGATOR_URL, TEST_AGGREGATOR_URL, DEFAULT_IPFS_GATEWAYS, DEFAULT_BASE_PATH, DEFAULT_DERIVATION_PATH, DEFAULT_ELECTRUM_URL, TEST_ELECTRUM_URL, TOKEN_REGISTRY_URL, TOKEN_REGISTRY_REFRESH_INTERVAL, TEST_NOSTR_RELAYS, DEFAULT_GROUP_RELAYS, NETWORKS, TIMEOUTS;
|
|
149
149
|
var init_constants = __esm({
|
|
150
150
|
"constants.ts"() {
|
|
151
151
|
"use strict";
|
|
@@ -222,6 +222,20 @@ var init_constants = __esm({
|
|
|
222
222
|
"wss://nos.lol",
|
|
223
223
|
"wss://relay.nostr.band"
|
|
224
224
|
];
|
|
225
|
+
NOSTR_EVENT_KINDS = {
|
|
226
|
+
/** NIP-04 encrypted direct message */
|
|
227
|
+
DIRECT_MESSAGE: 4,
|
|
228
|
+
/** Token transfer (Unicity custom - 31113) */
|
|
229
|
+
TOKEN_TRANSFER: 31113,
|
|
230
|
+
/** Payment request (Unicity custom - 31115) */
|
|
231
|
+
PAYMENT_REQUEST: 31115,
|
|
232
|
+
/** Payment request response (Unicity custom - 31116) */
|
|
233
|
+
PAYMENT_REQUEST_RESPONSE: 31116,
|
|
234
|
+
/** Nametag binding (NIP-78 app-specific data) */
|
|
235
|
+
NAMETAG_BINDING: 30078,
|
|
236
|
+
/** Public broadcast */
|
|
237
|
+
BROADCAST: 1
|
|
238
|
+
};
|
|
225
239
|
NIP29_KINDS = {
|
|
226
240
|
/** Chat message sent to group */
|
|
227
241
|
CHAT_MESSAGE: 9,
|
|
@@ -303,6 +317,18 @@ var init_constants = __esm({
|
|
|
303
317
|
tokenRegistryUrl: TOKEN_REGISTRY_URL
|
|
304
318
|
}
|
|
305
319
|
};
|
|
320
|
+
TIMEOUTS = {
|
|
321
|
+
/** WebSocket connection timeout */
|
|
322
|
+
WEBSOCKET_CONNECT: 1e4,
|
|
323
|
+
/** Nostr relay reconnect delay */
|
|
324
|
+
NOSTR_RECONNECT_DELAY: 3e3,
|
|
325
|
+
/** Max reconnect attempts */
|
|
326
|
+
MAX_RECONNECT_ATTEMPTS: 5,
|
|
327
|
+
/** Proof polling interval */
|
|
328
|
+
PROOF_POLL_INTERVAL: 1e3,
|
|
329
|
+
/** Sync interval */
|
|
330
|
+
SYNC_INTERVAL: 6e4
|
|
331
|
+
};
|
|
306
332
|
}
|
|
307
333
|
});
|
|
308
334
|
|
|
@@ -753,6 +779,1112 @@ var init_network = __esm({
|
|
|
753
779
|
init_logger();
|
|
754
780
|
init_errors();
|
|
755
781
|
|
|
782
|
+
// transport/MultiAddressTransportMux.ts
|
|
783
|
+
init_logger();
|
|
784
|
+
init_errors();
|
|
785
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
786
|
+
import {
|
|
787
|
+
NostrKeyManager,
|
|
788
|
+
NIP04,
|
|
789
|
+
NIP17,
|
|
790
|
+
Event as NostrEventClass,
|
|
791
|
+
EventKinds,
|
|
792
|
+
NostrClient,
|
|
793
|
+
Filter,
|
|
794
|
+
isChatMessage,
|
|
795
|
+
isReadReceipt
|
|
796
|
+
} from "@unicitylabs/nostr-js-sdk";
|
|
797
|
+
|
|
798
|
+
// transport/websocket.ts
|
|
799
|
+
function defaultUUIDGenerator() {
|
|
800
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
801
|
+
return crypto.randomUUID();
|
|
802
|
+
}
|
|
803
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
804
|
+
const r = Math.random() * 16 | 0;
|
|
805
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
806
|
+
return v.toString(16);
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// transport/MultiAddressTransportMux.ts
|
|
811
|
+
init_constants();
|
|
812
|
+
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
813
|
+
var COMPOSING_INDICATOR_KIND = 25050;
|
|
814
|
+
var MultiAddressTransportMux = class {
|
|
815
|
+
config;
|
|
816
|
+
storage = null;
|
|
817
|
+
// Single NostrClient — one WebSocket connection for all addresses
|
|
818
|
+
nostrClient = null;
|
|
819
|
+
// KeyManager used for NostrClient creation (uses first address or temp key)
|
|
820
|
+
primaryKeyManager = null;
|
|
821
|
+
status = "disconnected";
|
|
822
|
+
// Per-address entries
|
|
823
|
+
addresses = /* @__PURE__ */ new Map();
|
|
824
|
+
// pubkey → address index (for fast routing)
|
|
825
|
+
pubkeyToIndex = /* @__PURE__ */ new Map();
|
|
826
|
+
// Subscription IDs
|
|
827
|
+
walletSubscriptionId = null;
|
|
828
|
+
chatSubscriptionId = null;
|
|
829
|
+
chatEoseFired = false;
|
|
830
|
+
chatEoseHandlers = [];
|
|
831
|
+
// Dedup
|
|
832
|
+
processedEventIds = /* @__PURE__ */ new Set();
|
|
833
|
+
// Event callbacks (mux-level, forwarded to all adapters)
|
|
834
|
+
eventCallbacks = /* @__PURE__ */ new Set();
|
|
835
|
+
constructor(config) {
|
|
836
|
+
this.config = {
|
|
837
|
+
relays: config.relays ?? [...DEFAULT_NOSTR_RELAYS],
|
|
838
|
+
timeout: config.timeout ?? TIMEOUTS.WEBSOCKET_CONNECT,
|
|
839
|
+
autoReconnect: config.autoReconnect ?? true,
|
|
840
|
+
reconnectDelay: config.reconnectDelay ?? TIMEOUTS.NOSTR_RECONNECT_DELAY,
|
|
841
|
+
maxReconnectAttempts: config.maxReconnectAttempts ?? TIMEOUTS.MAX_RECONNECT_ATTEMPTS,
|
|
842
|
+
createWebSocket: config.createWebSocket,
|
|
843
|
+
generateUUID: config.generateUUID ?? defaultUUIDGenerator
|
|
844
|
+
};
|
|
845
|
+
this.storage = config.storage ?? null;
|
|
846
|
+
}
|
|
847
|
+
// ===========================================================================
|
|
848
|
+
// Address Management
|
|
849
|
+
// ===========================================================================
|
|
850
|
+
/**
|
|
851
|
+
* Add an address to the multiplexer.
|
|
852
|
+
* Creates an AddressTransportAdapter for this address.
|
|
853
|
+
* If already connected, updates subscriptions to include the new pubkey.
|
|
854
|
+
*/
|
|
855
|
+
async addAddress(index, identity, resolveDelegate) {
|
|
856
|
+
const existing = this.addresses.get(index);
|
|
857
|
+
if (existing) {
|
|
858
|
+
existing.identity = identity;
|
|
859
|
+
existing.keyManager = NostrKeyManager.fromPrivateKey(Buffer2.from(identity.privateKey, "hex"));
|
|
860
|
+
existing.nostrPubkey = existing.keyManager.getPublicKeyHex();
|
|
861
|
+
for (const [pk, idx] of this.pubkeyToIndex) {
|
|
862
|
+
if (idx === index) this.pubkeyToIndex.delete(pk);
|
|
863
|
+
}
|
|
864
|
+
this.pubkeyToIndex.set(existing.nostrPubkey, index);
|
|
865
|
+
logger.debug("Mux", `Updated address ${index}, pubkey: ${existing.nostrPubkey.slice(0, 16)}...`);
|
|
866
|
+
await this.updateSubscriptions();
|
|
867
|
+
return existing.adapter;
|
|
868
|
+
}
|
|
869
|
+
const keyManager = NostrKeyManager.fromPrivateKey(Buffer2.from(identity.privateKey, "hex"));
|
|
870
|
+
const nostrPubkey = keyManager.getPublicKeyHex();
|
|
871
|
+
const adapter = new AddressTransportAdapter(this, index, identity, resolveDelegate);
|
|
872
|
+
const entry = {
|
|
873
|
+
index,
|
|
874
|
+
identity,
|
|
875
|
+
keyManager,
|
|
876
|
+
nostrPubkey,
|
|
877
|
+
adapter,
|
|
878
|
+
lastEventTs: 0,
|
|
879
|
+
fallbackSince: null
|
|
880
|
+
};
|
|
881
|
+
this.addresses.set(index, entry);
|
|
882
|
+
this.pubkeyToIndex.set(nostrPubkey, index);
|
|
883
|
+
logger.debug("Mux", `Added address ${index}, pubkey: ${nostrPubkey.slice(0, 16)}..., total: ${this.addresses.size}`);
|
|
884
|
+
if (this.addresses.size === 1) {
|
|
885
|
+
this.primaryKeyManager = keyManager;
|
|
886
|
+
}
|
|
887
|
+
if (this.isConnected()) {
|
|
888
|
+
await this.updateSubscriptions();
|
|
889
|
+
}
|
|
890
|
+
return adapter;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Remove an address from the multiplexer.
|
|
894
|
+
* Stops routing events to this address.
|
|
895
|
+
*/
|
|
896
|
+
async removeAddress(index) {
|
|
897
|
+
const entry = this.addresses.get(index);
|
|
898
|
+
if (!entry) return;
|
|
899
|
+
this.pubkeyToIndex.delete(entry.nostrPubkey);
|
|
900
|
+
this.addresses.delete(index);
|
|
901
|
+
logger.debug("Mux", `Removed address ${index}, remaining: ${this.addresses.size}`);
|
|
902
|
+
if (this.isConnected() && this.addresses.size > 0) {
|
|
903
|
+
await this.updateSubscriptions();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Get adapter for a specific address index.
|
|
908
|
+
*/
|
|
909
|
+
getAdapter(index) {
|
|
910
|
+
return this.addresses.get(index)?.adapter;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Set fallback 'since' for an address (consumed once on next subscription setup).
|
|
914
|
+
*/
|
|
915
|
+
setFallbackSince(index, sinceSeconds) {
|
|
916
|
+
const entry = this.addresses.get(index);
|
|
917
|
+
if (entry) {
|
|
918
|
+
entry.fallbackSince = sinceSeconds;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
// ===========================================================================
|
|
922
|
+
// Connection Management (delegated from adapters)
|
|
923
|
+
// ===========================================================================
|
|
924
|
+
async connect() {
|
|
925
|
+
if (this.status === "connected") return;
|
|
926
|
+
this.status = "connecting";
|
|
927
|
+
try {
|
|
928
|
+
if (!this.primaryKeyManager) {
|
|
929
|
+
const tempKey = Buffer2.alloc(32);
|
|
930
|
+
crypto.getRandomValues(tempKey);
|
|
931
|
+
this.primaryKeyManager = NostrKeyManager.fromPrivateKey(tempKey);
|
|
932
|
+
}
|
|
933
|
+
this.nostrClient = new NostrClient(this.primaryKeyManager, {
|
|
934
|
+
autoReconnect: this.config.autoReconnect,
|
|
935
|
+
reconnectIntervalMs: this.config.reconnectDelay,
|
|
936
|
+
maxReconnectIntervalMs: this.config.reconnectDelay * 16,
|
|
937
|
+
pingIntervalMs: 15e3
|
|
938
|
+
});
|
|
939
|
+
this.nostrClient.addConnectionListener({
|
|
940
|
+
onConnect: (url) => {
|
|
941
|
+
logger.debug("Mux", "Connected to relay:", url);
|
|
942
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
943
|
+
},
|
|
944
|
+
onDisconnect: (url, reason) => {
|
|
945
|
+
logger.debug("Mux", "Disconnected from relay:", url, "reason:", reason);
|
|
946
|
+
},
|
|
947
|
+
onReconnecting: (url, attempt) => {
|
|
948
|
+
logger.debug("Mux", "Reconnecting to relay:", url, "attempt:", attempt);
|
|
949
|
+
this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
|
|
950
|
+
},
|
|
951
|
+
onReconnected: (url) => {
|
|
952
|
+
logger.debug("Mux", "Reconnected to relay:", url);
|
|
953
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
await Promise.race([
|
|
957
|
+
this.nostrClient.connect(...this.config.relays),
|
|
958
|
+
new Promise(
|
|
959
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
960
|
+
`Transport connection timed out after ${this.config.timeout}ms`
|
|
961
|
+
)), this.config.timeout)
|
|
962
|
+
)
|
|
963
|
+
]);
|
|
964
|
+
if (!this.nostrClient.isConnected()) {
|
|
965
|
+
throw new SphereError("Failed to connect to any relay", "TRANSPORT_ERROR");
|
|
966
|
+
}
|
|
967
|
+
this.status = "connected";
|
|
968
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
969
|
+
if (this.addresses.size > 0) {
|
|
970
|
+
await this.updateSubscriptions();
|
|
971
|
+
}
|
|
972
|
+
} catch (error) {
|
|
973
|
+
this.status = "error";
|
|
974
|
+
throw error;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
async disconnect() {
|
|
978
|
+
if (this.nostrClient) {
|
|
979
|
+
this.nostrClient.disconnect();
|
|
980
|
+
this.nostrClient = null;
|
|
981
|
+
}
|
|
982
|
+
this.walletSubscriptionId = null;
|
|
983
|
+
this.chatSubscriptionId = null;
|
|
984
|
+
this.chatEoseFired = false;
|
|
985
|
+
this.status = "disconnected";
|
|
986
|
+
this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
|
|
987
|
+
}
|
|
988
|
+
isConnected() {
|
|
989
|
+
return this.status === "connected" && this.nostrClient?.isConnected() === true;
|
|
990
|
+
}
|
|
991
|
+
getStatus() {
|
|
992
|
+
return this.status;
|
|
993
|
+
}
|
|
994
|
+
// ===========================================================================
|
|
995
|
+
// Relay Management
|
|
996
|
+
// ===========================================================================
|
|
997
|
+
getRelays() {
|
|
998
|
+
return [...this.config.relays];
|
|
999
|
+
}
|
|
1000
|
+
getConnectedRelays() {
|
|
1001
|
+
if (!this.nostrClient) return [];
|
|
1002
|
+
return Array.from(this.nostrClient.getConnectedRelays());
|
|
1003
|
+
}
|
|
1004
|
+
async addRelay(relayUrl) {
|
|
1005
|
+
if (this.config.relays.includes(relayUrl)) return false;
|
|
1006
|
+
this.config.relays.push(relayUrl);
|
|
1007
|
+
if (this.status === "connected" && this.nostrClient) {
|
|
1008
|
+
try {
|
|
1009
|
+
await this.nostrClient.connect(relayUrl);
|
|
1010
|
+
this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: true } });
|
|
1011
|
+
return true;
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: false, error: String(error) } });
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return true;
|
|
1018
|
+
}
|
|
1019
|
+
async removeRelay(relayUrl) {
|
|
1020
|
+
const idx = this.config.relays.indexOf(relayUrl);
|
|
1021
|
+
if (idx === -1) return false;
|
|
1022
|
+
this.config.relays.splice(idx, 1);
|
|
1023
|
+
this.emitEvent({ type: "transport:relay_removed", timestamp: Date.now(), data: { relay: relayUrl } });
|
|
1024
|
+
return true;
|
|
1025
|
+
}
|
|
1026
|
+
hasRelay(relayUrl) {
|
|
1027
|
+
return this.config.relays.includes(relayUrl);
|
|
1028
|
+
}
|
|
1029
|
+
isRelayConnected(relayUrl) {
|
|
1030
|
+
if (!this.nostrClient) return false;
|
|
1031
|
+
return this.nostrClient.getConnectedRelays().has(relayUrl);
|
|
1032
|
+
}
|
|
1033
|
+
// ===========================================================================
|
|
1034
|
+
// Subscription Management
|
|
1035
|
+
// ===========================================================================
|
|
1036
|
+
/**
|
|
1037
|
+
* Update Nostr subscriptions to listen for events on ALL registered address pubkeys.
|
|
1038
|
+
* Called whenever addresses are added/removed.
|
|
1039
|
+
*/
|
|
1040
|
+
async updateSubscriptions() {
|
|
1041
|
+
if (!this.nostrClient || this.addresses.size === 0) return;
|
|
1042
|
+
if (this.walletSubscriptionId) {
|
|
1043
|
+
this.nostrClient.unsubscribe(this.walletSubscriptionId);
|
|
1044
|
+
this.walletSubscriptionId = null;
|
|
1045
|
+
}
|
|
1046
|
+
if (this.chatSubscriptionId) {
|
|
1047
|
+
this.nostrClient.unsubscribe(this.chatSubscriptionId);
|
|
1048
|
+
this.chatSubscriptionId = null;
|
|
1049
|
+
}
|
|
1050
|
+
const allPubkeys = [];
|
|
1051
|
+
for (const entry of this.addresses.values()) {
|
|
1052
|
+
allPubkeys.push(entry.nostrPubkey);
|
|
1053
|
+
}
|
|
1054
|
+
logger.debug("Mux", `Subscribing for ${allPubkeys.length} address(es):`, allPubkeys.map((p) => p.slice(0, 12)).join(", "));
|
|
1055
|
+
let globalSince = Math.floor(Date.now() / 1e3);
|
|
1056
|
+
for (const entry of this.addresses.values()) {
|
|
1057
|
+
const since = await this.getAddressSince(entry);
|
|
1058
|
+
if (since < globalSince) {
|
|
1059
|
+
globalSince = since;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const walletFilter = new Filter();
|
|
1063
|
+
walletFilter.kinds = [
|
|
1064
|
+
EVENT_KINDS.DIRECT_MESSAGE,
|
|
1065
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
1066
|
+
EVENT_KINDS.PAYMENT_REQUEST,
|
|
1067
|
+
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
|
|
1068
|
+
];
|
|
1069
|
+
walletFilter["#p"] = allPubkeys;
|
|
1070
|
+
walletFilter.since = globalSince;
|
|
1071
|
+
this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
|
|
1072
|
+
onEvent: (event) => {
|
|
1073
|
+
this.handleEvent({
|
|
1074
|
+
id: event.id,
|
|
1075
|
+
kind: event.kind,
|
|
1076
|
+
content: event.content,
|
|
1077
|
+
tags: event.tags,
|
|
1078
|
+
pubkey: event.pubkey,
|
|
1079
|
+
created_at: event.created_at,
|
|
1080
|
+
sig: event.sig
|
|
1081
|
+
});
|
|
1082
|
+
},
|
|
1083
|
+
onEndOfStoredEvents: () => {
|
|
1084
|
+
logger.debug("Mux", "Wallet subscription EOSE");
|
|
1085
|
+
},
|
|
1086
|
+
onError: (_subId, error) => {
|
|
1087
|
+
logger.debug("Mux", "Wallet subscription error:", error);
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
const chatFilter = new Filter();
|
|
1091
|
+
chatFilter.kinds = [EventKinds.GIFT_WRAP];
|
|
1092
|
+
chatFilter["#p"] = allPubkeys;
|
|
1093
|
+
this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
|
|
1094
|
+
onEvent: (event) => {
|
|
1095
|
+
this.handleEvent({
|
|
1096
|
+
id: event.id,
|
|
1097
|
+
kind: event.kind,
|
|
1098
|
+
content: event.content,
|
|
1099
|
+
tags: event.tags,
|
|
1100
|
+
pubkey: event.pubkey,
|
|
1101
|
+
created_at: event.created_at,
|
|
1102
|
+
sig: event.sig
|
|
1103
|
+
});
|
|
1104
|
+
},
|
|
1105
|
+
onEndOfStoredEvents: () => {
|
|
1106
|
+
logger.debug("Mux", "Chat subscription EOSE");
|
|
1107
|
+
if (!this.chatEoseFired) {
|
|
1108
|
+
this.chatEoseFired = true;
|
|
1109
|
+
for (const handler of this.chatEoseHandlers) {
|
|
1110
|
+
try {
|
|
1111
|
+
handler();
|
|
1112
|
+
} catch {
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
onError: (_subId, error) => {
|
|
1118
|
+
logger.debug("Mux", "Chat subscription error:", error);
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Determine 'since' timestamp for an address entry.
|
|
1124
|
+
*/
|
|
1125
|
+
async getAddressSince(entry) {
|
|
1126
|
+
if (this.storage) {
|
|
1127
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
|
|
1128
|
+
try {
|
|
1129
|
+
const stored = await this.storage.get(storageKey);
|
|
1130
|
+
if (stored) {
|
|
1131
|
+
const ts = parseInt(stored, 10);
|
|
1132
|
+
entry.lastEventTs = ts;
|
|
1133
|
+
entry.fallbackSince = null;
|
|
1134
|
+
return ts;
|
|
1135
|
+
} else if (entry.fallbackSince !== null) {
|
|
1136
|
+
const ts = entry.fallbackSince;
|
|
1137
|
+
entry.lastEventTs = ts;
|
|
1138
|
+
entry.fallbackSince = null;
|
|
1139
|
+
return ts;
|
|
1140
|
+
}
|
|
1141
|
+
} catch {
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return Math.floor(Date.now() / 1e3);
|
|
1145
|
+
}
|
|
1146
|
+
// ===========================================================================
|
|
1147
|
+
// Event Routing
|
|
1148
|
+
// ===========================================================================
|
|
1149
|
+
/**
|
|
1150
|
+
* Route an incoming Nostr event to the correct address adapter.
|
|
1151
|
+
*/
|
|
1152
|
+
async handleEvent(event) {
|
|
1153
|
+
if (event.id && this.processedEventIds.has(event.id)) return;
|
|
1154
|
+
if (event.id) this.processedEventIds.add(event.id);
|
|
1155
|
+
try {
|
|
1156
|
+
if (event.kind === EventKinds.GIFT_WRAP) {
|
|
1157
|
+
await this.routeGiftWrap(event);
|
|
1158
|
+
} else {
|
|
1159
|
+
const recipientPubkey = this.extractRecipientPubkey(event);
|
|
1160
|
+
if (!recipientPubkey) {
|
|
1161
|
+
logger.debug("Mux", "Event has no #p tag, dropping:", event.id?.slice(0, 12));
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const addressIndex = this.pubkeyToIndex.get(recipientPubkey);
|
|
1165
|
+
if (addressIndex === void 0) {
|
|
1166
|
+
logger.debug("Mux", "Event for unknown pubkey:", recipientPubkey.slice(0, 16), "dropping");
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
const entry = this.addresses.get(addressIndex);
|
|
1170
|
+
if (!entry) return;
|
|
1171
|
+
await this.dispatchWalletEvent(entry, event);
|
|
1172
|
+
}
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
logger.debug("Mux", "Failed to handle event:", event.id?.slice(0, 12), error);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Extract recipient pubkey from event's #p tag.
|
|
1179
|
+
* Returns the first #p value that matches a known address pubkey,
|
|
1180
|
+
* or the first #p value if none match.
|
|
1181
|
+
*/
|
|
1182
|
+
extractRecipientPubkey(event) {
|
|
1183
|
+
const pTags = event.tags?.filter((t) => t[0] === "p");
|
|
1184
|
+
if (!pTags || pTags.length === 0) return null;
|
|
1185
|
+
for (const tag of pTags) {
|
|
1186
|
+
if (tag[1] && this.pubkeyToIndex.has(tag[1])) {
|
|
1187
|
+
return tag[1];
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return pTags[0]?.[1] ?? null;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Route a gift wrap event by trying decryption with each address keyManager.
|
|
1194
|
+
*/
|
|
1195
|
+
async routeGiftWrap(event) {
|
|
1196
|
+
for (const entry of this.addresses.values()) {
|
|
1197
|
+
try {
|
|
1198
|
+
const pm = NIP17.unwrap(event, entry.keyManager);
|
|
1199
|
+
logger.debug("Mux", `Gift wrap decrypted by address ${entry.index}, sender: ${pm.senderPubkey?.slice(0, 16)}`);
|
|
1200
|
+
if (pm.senderPubkey === entry.nostrPubkey) {
|
|
1201
|
+
try {
|
|
1202
|
+
const parsed = JSON.parse(pm.content);
|
|
1203
|
+
if (parsed?.selfWrap && parsed.recipientPubkey) {
|
|
1204
|
+
const message2 = {
|
|
1205
|
+
id: parsed.originalId || pm.eventId,
|
|
1206
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1207
|
+
senderNametag: parsed.senderNametag,
|
|
1208
|
+
recipientTransportPubkey: parsed.recipientPubkey,
|
|
1209
|
+
content: parsed.text ?? "",
|
|
1210
|
+
timestamp: pm.timestamp * 1e3,
|
|
1211
|
+
encrypted: true,
|
|
1212
|
+
isSelfWrap: true
|
|
1213
|
+
};
|
|
1214
|
+
entry.adapter.dispatchMessage(message2);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
} catch {
|
|
1218
|
+
}
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
if (isReadReceipt(pm)) {
|
|
1222
|
+
if (pm.replyToEventId) {
|
|
1223
|
+
const receipt = {
|
|
1224
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1225
|
+
messageEventId: pm.replyToEventId,
|
|
1226
|
+
timestamp: pm.timestamp * 1e3
|
|
1227
|
+
};
|
|
1228
|
+
entry.adapter.dispatchReadReceipt(receipt);
|
|
1229
|
+
}
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (pm.kind === COMPOSING_INDICATOR_KIND) {
|
|
1233
|
+
let senderNametag2;
|
|
1234
|
+
let expiresIn = 3e4;
|
|
1235
|
+
try {
|
|
1236
|
+
const parsed = JSON.parse(pm.content);
|
|
1237
|
+
senderNametag2 = parsed.senderNametag || void 0;
|
|
1238
|
+
expiresIn = parsed.expiresIn ?? 3e4;
|
|
1239
|
+
} catch {
|
|
1240
|
+
}
|
|
1241
|
+
entry.adapter.dispatchComposingIndicator({
|
|
1242
|
+
senderPubkey: pm.senderPubkey,
|
|
1243
|
+
senderNametag: senderNametag2,
|
|
1244
|
+
expiresIn
|
|
1245
|
+
});
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
try {
|
|
1249
|
+
const parsed = JSON.parse(pm.content);
|
|
1250
|
+
if (parsed?.type === "typing") {
|
|
1251
|
+
const indicator = {
|
|
1252
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1253
|
+
senderNametag: parsed.senderNametag,
|
|
1254
|
+
timestamp: pm.timestamp * 1e3
|
|
1255
|
+
};
|
|
1256
|
+
entry.adapter.dispatchTypingIndicator(indicator);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
} catch {
|
|
1260
|
+
}
|
|
1261
|
+
if (!isChatMessage(pm)) return;
|
|
1262
|
+
let content = pm.content;
|
|
1263
|
+
let senderNametag;
|
|
1264
|
+
try {
|
|
1265
|
+
const parsed = JSON.parse(content);
|
|
1266
|
+
if (typeof parsed === "object" && parsed.text !== void 0) {
|
|
1267
|
+
content = parsed.text;
|
|
1268
|
+
senderNametag = parsed.senderNametag || void 0;
|
|
1269
|
+
}
|
|
1270
|
+
} catch {
|
|
1271
|
+
}
|
|
1272
|
+
const message = {
|
|
1273
|
+
id: event.id,
|
|
1274
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1275
|
+
senderNametag,
|
|
1276
|
+
content,
|
|
1277
|
+
timestamp: pm.timestamp * 1e3,
|
|
1278
|
+
encrypted: true
|
|
1279
|
+
};
|
|
1280
|
+
entry.adapter.dispatchMessage(message);
|
|
1281
|
+
return;
|
|
1282
|
+
} catch {
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
logger.debug("Mux", "Gift wrap could not be decrypted by any address");
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Dispatch a wallet event (non-gift-wrap) to the correct address adapter.
|
|
1290
|
+
*/
|
|
1291
|
+
async dispatchWalletEvent(entry, event) {
|
|
1292
|
+
switch (event.kind) {
|
|
1293
|
+
case EVENT_KINDS.DIRECT_MESSAGE:
|
|
1294
|
+
break;
|
|
1295
|
+
case EVENT_KINDS.TOKEN_TRANSFER:
|
|
1296
|
+
await this.handleTokenTransfer(entry, event);
|
|
1297
|
+
break;
|
|
1298
|
+
case EVENT_KINDS.PAYMENT_REQUEST:
|
|
1299
|
+
await this.handlePaymentRequest(entry, event);
|
|
1300
|
+
break;
|
|
1301
|
+
case EVENT_KINDS.PAYMENT_REQUEST_RESPONSE:
|
|
1302
|
+
await this.handlePaymentRequestResponse(entry, event);
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
if (event.created_at) {
|
|
1306
|
+
this.updateLastEventTimestamp(entry, event.created_at);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
async handleTokenTransfer(entry, event) {
|
|
1310
|
+
try {
|
|
1311
|
+
const content = await this.decryptContent(entry, event.content, event.pubkey);
|
|
1312
|
+
const payload = JSON.parse(content);
|
|
1313
|
+
const transfer = {
|
|
1314
|
+
id: event.id,
|
|
1315
|
+
senderTransportPubkey: event.pubkey,
|
|
1316
|
+
payload,
|
|
1317
|
+
timestamp: event.created_at * 1e3
|
|
1318
|
+
};
|
|
1319
|
+
entry.adapter.dispatchTokenTransfer(transfer);
|
|
1320
|
+
} catch (err) {
|
|
1321
|
+
logger.debug("Mux", `Token transfer decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
async handlePaymentRequest(entry, event) {
|
|
1325
|
+
try {
|
|
1326
|
+
const content = await this.decryptContent(entry, event.content, event.pubkey);
|
|
1327
|
+
const requestData = JSON.parse(content);
|
|
1328
|
+
const request = {
|
|
1329
|
+
id: event.id,
|
|
1330
|
+
senderTransportPubkey: event.pubkey,
|
|
1331
|
+
request: {
|
|
1332
|
+
requestId: requestData.requestId,
|
|
1333
|
+
amount: requestData.amount,
|
|
1334
|
+
coinId: requestData.coinId,
|
|
1335
|
+
message: requestData.message,
|
|
1336
|
+
recipientNametag: requestData.recipientNametag,
|
|
1337
|
+
metadata: requestData.metadata
|
|
1338
|
+
},
|
|
1339
|
+
timestamp: event.created_at * 1e3
|
|
1340
|
+
};
|
|
1341
|
+
entry.adapter.dispatchPaymentRequest(request);
|
|
1342
|
+
} catch (err) {
|
|
1343
|
+
logger.debug("Mux", `Payment request decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
async handlePaymentRequestResponse(entry, event) {
|
|
1347
|
+
try {
|
|
1348
|
+
const content = await this.decryptContent(entry, event.content, event.pubkey);
|
|
1349
|
+
const responseData = JSON.parse(content);
|
|
1350
|
+
const response = {
|
|
1351
|
+
id: event.id,
|
|
1352
|
+
responderTransportPubkey: event.pubkey,
|
|
1353
|
+
response: {
|
|
1354
|
+
requestId: responseData.requestId,
|
|
1355
|
+
responseType: responseData.responseType,
|
|
1356
|
+
message: responseData.message,
|
|
1357
|
+
transferId: responseData.transferId
|
|
1358
|
+
},
|
|
1359
|
+
timestamp: event.created_at * 1e3
|
|
1360
|
+
};
|
|
1361
|
+
entry.adapter.dispatchPaymentRequestResponse(response);
|
|
1362
|
+
} catch (err) {
|
|
1363
|
+
logger.debug("Mux", `Payment response decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
// ===========================================================================
|
|
1367
|
+
// Crypto Helpers
|
|
1368
|
+
// ===========================================================================
|
|
1369
|
+
async decryptContent(entry, content, senderPubkey) {
|
|
1370
|
+
const decrypted = await NIP04.decryptHex(
|
|
1371
|
+
content,
|
|
1372
|
+
entry.keyManager.getPrivateKeyHex(),
|
|
1373
|
+
senderPubkey
|
|
1374
|
+
);
|
|
1375
|
+
return this.stripContentPrefix(decrypted);
|
|
1376
|
+
}
|
|
1377
|
+
stripContentPrefix(content) {
|
|
1378
|
+
const prefixes = ["payment_request:", "token_transfer:", "payment_response:"];
|
|
1379
|
+
for (const prefix of prefixes) {
|
|
1380
|
+
if (content.startsWith(prefix)) return content.slice(prefix.length);
|
|
1381
|
+
}
|
|
1382
|
+
return content;
|
|
1383
|
+
}
|
|
1384
|
+
// ===========================================================================
|
|
1385
|
+
// Sending (called by adapters)
|
|
1386
|
+
// ===========================================================================
|
|
1387
|
+
/**
|
|
1388
|
+
* Create an encrypted event using a specific address's keyManager.
|
|
1389
|
+
* Used by AddressTransportAdapter for sending.
|
|
1390
|
+
*/
|
|
1391
|
+
async createAndPublishEncryptedEvent(addressIndex, kind, content, tags) {
|
|
1392
|
+
const entry = this.addresses.get(addressIndex);
|
|
1393
|
+
if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
|
|
1394
|
+
if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
|
|
1395
|
+
const recipientTag = tags.find((t) => t[0] === "p");
|
|
1396
|
+
if (!recipientTag?.[1]) throw new SphereError("No recipient pubkey in tags", "VALIDATION_ERROR");
|
|
1397
|
+
const encrypted = await NIP04.encryptHex(
|
|
1398
|
+
content,
|
|
1399
|
+
entry.keyManager.getPrivateKeyHex(),
|
|
1400
|
+
recipientTag[1]
|
|
1401
|
+
);
|
|
1402
|
+
const signedEvent = NostrEventClass.create(entry.keyManager, { kind, content: encrypted, tags });
|
|
1403
|
+
const nostrEvent = NostrEventClass.fromJSON({
|
|
1404
|
+
id: signedEvent.id,
|
|
1405
|
+
kind: signedEvent.kind,
|
|
1406
|
+
content: signedEvent.content,
|
|
1407
|
+
tags: signedEvent.tags,
|
|
1408
|
+
pubkey: signedEvent.pubkey,
|
|
1409
|
+
created_at: signedEvent.created_at,
|
|
1410
|
+
sig: signedEvent.sig
|
|
1411
|
+
});
|
|
1412
|
+
await this.nostrClient.publishEvent(nostrEvent);
|
|
1413
|
+
return signedEvent.id;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Create and publish a NIP-17 gift wrap message for a specific address.
|
|
1417
|
+
*/
|
|
1418
|
+
async sendGiftWrap(addressIndex, recipientPubkey, content) {
|
|
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 nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
|
|
1423
|
+
const giftWrap = NIP17.createGiftWrap(entry.keyManager, nostrRecipient, content);
|
|
1424
|
+
const giftWrapEvent = NostrEventClass.fromJSON(giftWrap);
|
|
1425
|
+
await this.nostrClient.publishEvent(giftWrapEvent);
|
|
1426
|
+
const selfPubkey = entry.keyManager.getPublicKeyHex();
|
|
1427
|
+
const senderNametag = entry.identity.nametag;
|
|
1428
|
+
const selfWrapContent = JSON.stringify({
|
|
1429
|
+
selfWrap: true,
|
|
1430
|
+
originalId: giftWrap.id,
|
|
1431
|
+
recipientPubkey,
|
|
1432
|
+
senderNametag,
|
|
1433
|
+
text: content
|
|
1434
|
+
});
|
|
1435
|
+
const selfGiftWrap = NIP17.createGiftWrap(entry.keyManager, selfPubkey, selfWrapContent);
|
|
1436
|
+
const selfGiftWrapEvent = NostrEventClass.fromJSON(selfGiftWrap);
|
|
1437
|
+
this.nostrClient.publishEvent(selfGiftWrapEvent).catch((err) => {
|
|
1438
|
+
logger.debug("Mux", "Self-wrap publish failed:", err);
|
|
1439
|
+
});
|
|
1440
|
+
return giftWrap.id;
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Publish a raw event (e.g., identity binding, broadcast).
|
|
1444
|
+
*/
|
|
1445
|
+
async publishRawEvent(addressIndex, kind, content, tags) {
|
|
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 signedEvent = NostrEventClass.create(entry.keyManager, { kind, content, tags });
|
|
1450
|
+
const nostrEvent = NostrEventClass.fromJSON({
|
|
1451
|
+
id: signedEvent.id,
|
|
1452
|
+
kind: signedEvent.kind,
|
|
1453
|
+
content: signedEvent.content,
|
|
1454
|
+
tags: signedEvent.tags,
|
|
1455
|
+
pubkey: signedEvent.pubkey,
|
|
1456
|
+
created_at: signedEvent.created_at,
|
|
1457
|
+
sig: signedEvent.sig
|
|
1458
|
+
});
|
|
1459
|
+
await this.nostrClient.publishEvent(nostrEvent);
|
|
1460
|
+
return signedEvent.id;
|
|
1461
|
+
}
|
|
1462
|
+
// ===========================================================================
|
|
1463
|
+
// Resolve Methods (delegates to inner — these are stateless relay queries)
|
|
1464
|
+
// ===========================================================================
|
|
1465
|
+
/**
|
|
1466
|
+
* Get the NostrClient for resolve operations.
|
|
1467
|
+
* Adapters use this for resolve*, publishIdentityBinding, etc.
|
|
1468
|
+
*/
|
|
1469
|
+
getNostrClient() {
|
|
1470
|
+
return this.nostrClient;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Get keyManager for a specific address (used by adapters for resolve/binding).
|
|
1474
|
+
*/
|
|
1475
|
+
getKeyManager(addressIndex) {
|
|
1476
|
+
return this.addresses.get(addressIndex)?.keyManager ?? null;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Get identity for a specific address.
|
|
1480
|
+
*/
|
|
1481
|
+
getIdentity(addressIndex) {
|
|
1482
|
+
return this.addresses.get(addressIndex)?.identity ?? null;
|
|
1483
|
+
}
|
|
1484
|
+
// ===========================================================================
|
|
1485
|
+
// Event timestamp persistence
|
|
1486
|
+
// ===========================================================================
|
|
1487
|
+
updateLastEventTimestamp(entry, createdAt) {
|
|
1488
|
+
if (!this.storage) return;
|
|
1489
|
+
if (createdAt <= entry.lastEventTs) return;
|
|
1490
|
+
entry.lastEventTs = createdAt;
|
|
1491
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
|
|
1492
|
+
this.storage.set(storageKey, createdAt.toString()).catch((err) => {
|
|
1493
|
+
logger.debug("Mux", "Failed to save last event timestamp:", err);
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
// ===========================================================================
|
|
1497
|
+
// Mux-level event system
|
|
1498
|
+
// ===========================================================================
|
|
1499
|
+
onTransportEvent(callback) {
|
|
1500
|
+
this.eventCallbacks.add(callback);
|
|
1501
|
+
return () => this.eventCallbacks.delete(callback);
|
|
1502
|
+
}
|
|
1503
|
+
onChatReady(handler) {
|
|
1504
|
+
if (this.chatEoseFired) {
|
|
1505
|
+
try {
|
|
1506
|
+
handler();
|
|
1507
|
+
} catch {
|
|
1508
|
+
}
|
|
1509
|
+
return () => {
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
this.chatEoseHandlers.push(handler);
|
|
1513
|
+
return () => {
|
|
1514
|
+
const idx = this.chatEoseHandlers.indexOf(handler);
|
|
1515
|
+
if (idx >= 0) this.chatEoseHandlers.splice(idx, 1);
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
emitEvent(event) {
|
|
1519
|
+
for (const cb of this.eventCallbacks) {
|
|
1520
|
+
try {
|
|
1521
|
+
cb(event);
|
|
1522
|
+
} catch {
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
for (const entry of this.addresses.values()) {
|
|
1526
|
+
entry.adapter.emitTransportEvent(event);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
// ===========================================================================
|
|
1530
|
+
// Dedup Management
|
|
1531
|
+
// ===========================================================================
|
|
1532
|
+
/**
|
|
1533
|
+
* Clear processed event IDs (e.g., on address change or periodic cleanup).
|
|
1534
|
+
*/
|
|
1535
|
+
clearProcessedEvents() {
|
|
1536
|
+
this.processedEventIds.clear();
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Get the storage adapter (for adapters that need it).
|
|
1540
|
+
*/
|
|
1541
|
+
getStorage() {
|
|
1542
|
+
return this.storage;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Get the UUID generator.
|
|
1546
|
+
*/
|
|
1547
|
+
getUUIDGenerator() {
|
|
1548
|
+
return this.config.generateUUID;
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
var AddressTransportAdapter = class {
|
|
1552
|
+
id;
|
|
1553
|
+
name;
|
|
1554
|
+
type = "p2p";
|
|
1555
|
+
description;
|
|
1556
|
+
mux;
|
|
1557
|
+
addressIndex;
|
|
1558
|
+
identity;
|
|
1559
|
+
resolveDelegate;
|
|
1560
|
+
// Per-address handler sets
|
|
1561
|
+
messageHandlers = /* @__PURE__ */ new Set();
|
|
1562
|
+
transferHandlers = /* @__PURE__ */ new Set();
|
|
1563
|
+
paymentRequestHandlers = /* @__PURE__ */ new Set();
|
|
1564
|
+
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1565
|
+
readReceiptHandlers = /* @__PURE__ */ new Set();
|
|
1566
|
+
typingIndicatorHandlers = /* @__PURE__ */ new Set();
|
|
1567
|
+
composingHandlers = /* @__PURE__ */ new Set();
|
|
1568
|
+
instantSplitBundleHandlers = /* @__PURE__ */ new Set();
|
|
1569
|
+
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1570
|
+
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1571
|
+
pendingMessages = [];
|
|
1572
|
+
chatEoseHandlers = [];
|
|
1573
|
+
constructor(mux, addressIndex, identity, resolveDelegate) {
|
|
1574
|
+
this.mux = mux;
|
|
1575
|
+
this.addressIndex = addressIndex;
|
|
1576
|
+
this.identity = identity;
|
|
1577
|
+
this.resolveDelegate = resolveDelegate ?? null;
|
|
1578
|
+
this.id = `nostr-addr-${addressIndex}`;
|
|
1579
|
+
this.name = `Nostr Transport (address ${addressIndex})`;
|
|
1580
|
+
this.description = `P2P messaging for address index ${addressIndex}`;
|
|
1581
|
+
}
|
|
1582
|
+
// ===========================================================================
|
|
1583
|
+
// BaseProvider — delegates to mux
|
|
1584
|
+
// ===========================================================================
|
|
1585
|
+
async connect() {
|
|
1586
|
+
await this.mux.connect();
|
|
1587
|
+
}
|
|
1588
|
+
async disconnect() {
|
|
1589
|
+
}
|
|
1590
|
+
isConnected() {
|
|
1591
|
+
return this.mux.isConnected();
|
|
1592
|
+
}
|
|
1593
|
+
getStatus() {
|
|
1594
|
+
return this.mux.getStatus();
|
|
1595
|
+
}
|
|
1596
|
+
// ===========================================================================
|
|
1597
|
+
// Identity (no-op — mux manages identity via addAddress)
|
|
1598
|
+
// ===========================================================================
|
|
1599
|
+
async setIdentity(identity) {
|
|
1600
|
+
this.identity = identity;
|
|
1601
|
+
await this.mux.addAddress(this.addressIndex, identity);
|
|
1602
|
+
}
|
|
1603
|
+
// ===========================================================================
|
|
1604
|
+
// Sending — delegates to mux with this address's keyManager
|
|
1605
|
+
// ===========================================================================
|
|
1606
|
+
async sendMessage(recipientPubkey, content) {
|
|
1607
|
+
const senderNametag = this.identity.nametag;
|
|
1608
|
+
const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
|
|
1609
|
+
return this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, wrappedContent);
|
|
1610
|
+
}
|
|
1611
|
+
async sendTokenTransfer(recipientPubkey, payload) {
|
|
1612
|
+
const content = "token_transfer:" + JSON.stringify(payload);
|
|
1613
|
+
const uniqueD = `token-transfer-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1614
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1615
|
+
this.addressIndex,
|
|
1616
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
1617
|
+
content,
|
|
1618
|
+
[["p", recipientPubkey], ["d", uniqueD], ["type", "token_transfer"]]
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
async sendPaymentRequest(recipientPubkey, payload) {
|
|
1622
|
+
const requestId2 = this.mux.getUUIDGenerator()();
|
|
1623
|
+
const amount = typeof payload.amount === "bigint" ? payload.amount.toString() : payload.amount;
|
|
1624
|
+
const requestContent = {
|
|
1625
|
+
requestId: requestId2,
|
|
1626
|
+
amount,
|
|
1627
|
+
coinId: payload.coinId,
|
|
1628
|
+
message: payload.message,
|
|
1629
|
+
recipientNametag: payload.recipientNametag,
|
|
1630
|
+
deadline: Date.now() + 5 * 60 * 1e3
|
|
1631
|
+
};
|
|
1632
|
+
const content = "payment_request:" + JSON.stringify(requestContent);
|
|
1633
|
+
const tags = [
|
|
1634
|
+
["p", recipientPubkey],
|
|
1635
|
+
["type", "payment_request"],
|
|
1636
|
+
["amount", amount]
|
|
1637
|
+
];
|
|
1638
|
+
if (payload.recipientNametag) {
|
|
1639
|
+
tags.push(["recipient", payload.recipientNametag]);
|
|
1640
|
+
}
|
|
1641
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1642
|
+
this.addressIndex,
|
|
1643
|
+
EVENT_KINDS.PAYMENT_REQUEST,
|
|
1644
|
+
content,
|
|
1645
|
+
tags
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
async sendPaymentRequestResponse(recipientPubkey, response) {
|
|
1649
|
+
const content = "payment_response:" + JSON.stringify(response);
|
|
1650
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1651
|
+
this.addressIndex,
|
|
1652
|
+
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE,
|
|
1653
|
+
content,
|
|
1654
|
+
[["p", recipientPubkey], ["type", "payment_response"]]
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
async sendReadReceipt(recipientPubkey, messageEventId) {
|
|
1658
|
+
const content = JSON.stringify({ type: "read_receipt", messageEventId });
|
|
1659
|
+
await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
|
|
1660
|
+
}
|
|
1661
|
+
async sendTypingIndicator(recipientPubkey) {
|
|
1662
|
+
const content = JSON.stringify({
|
|
1663
|
+
type: "typing",
|
|
1664
|
+
senderNametag: this.identity.nametag
|
|
1665
|
+
});
|
|
1666
|
+
await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
|
|
1667
|
+
}
|
|
1668
|
+
async sendComposingIndicator(recipientPubkey, content) {
|
|
1669
|
+
await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
|
|
1670
|
+
}
|
|
1671
|
+
async sendInstantSplitBundle(recipientPubkey, bundle) {
|
|
1672
|
+
const content = "token_transfer:" + JSON.stringify({
|
|
1673
|
+
type: "instant_split",
|
|
1674
|
+
...bundle
|
|
1675
|
+
});
|
|
1676
|
+
const uniqueD = `instant-split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1677
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1678
|
+
this.addressIndex,
|
|
1679
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
1680
|
+
content,
|
|
1681
|
+
[["p", recipientPubkey], ["d", uniqueD], ["type", "instant_split"]]
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
// ===========================================================================
|
|
1685
|
+
// Subscription handlers — per-address
|
|
1686
|
+
// ===========================================================================
|
|
1687
|
+
onMessage(handler) {
|
|
1688
|
+
this.messageHandlers.add(handler);
|
|
1689
|
+
if (this.pendingMessages.length > 0) {
|
|
1690
|
+
const pending2 = this.pendingMessages;
|
|
1691
|
+
this.pendingMessages = [];
|
|
1692
|
+
for (const msg of pending2) {
|
|
1693
|
+
try {
|
|
1694
|
+
handler(msg);
|
|
1695
|
+
} catch {
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
return () => this.messageHandlers.delete(handler);
|
|
1700
|
+
}
|
|
1701
|
+
onTokenTransfer(handler) {
|
|
1702
|
+
this.transferHandlers.add(handler);
|
|
1703
|
+
return () => this.transferHandlers.delete(handler);
|
|
1704
|
+
}
|
|
1705
|
+
onPaymentRequest(handler) {
|
|
1706
|
+
this.paymentRequestHandlers.add(handler);
|
|
1707
|
+
return () => this.paymentRequestHandlers.delete(handler);
|
|
1708
|
+
}
|
|
1709
|
+
onPaymentRequestResponse(handler) {
|
|
1710
|
+
this.paymentRequestResponseHandlers.add(handler);
|
|
1711
|
+
return () => this.paymentRequestResponseHandlers.delete(handler);
|
|
1712
|
+
}
|
|
1713
|
+
onReadReceipt(handler) {
|
|
1714
|
+
this.readReceiptHandlers.add(handler);
|
|
1715
|
+
return () => this.readReceiptHandlers.delete(handler);
|
|
1716
|
+
}
|
|
1717
|
+
onTypingIndicator(handler) {
|
|
1718
|
+
this.typingIndicatorHandlers.add(handler);
|
|
1719
|
+
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1720
|
+
}
|
|
1721
|
+
onComposing(handler) {
|
|
1722
|
+
this.composingHandlers.add(handler);
|
|
1723
|
+
return () => this.composingHandlers.delete(handler);
|
|
1724
|
+
}
|
|
1725
|
+
onInstantSplitReceived(handler) {
|
|
1726
|
+
this.instantSplitBundleHandlers.add(handler);
|
|
1727
|
+
return () => this.instantSplitBundleHandlers.delete(handler);
|
|
1728
|
+
}
|
|
1729
|
+
subscribeToBroadcast(tags, handler) {
|
|
1730
|
+
const key = tags.sort().join(":");
|
|
1731
|
+
if (!this.broadcastHandlers.has(key)) {
|
|
1732
|
+
this.broadcastHandlers.set(key, /* @__PURE__ */ new Set());
|
|
1733
|
+
}
|
|
1734
|
+
this.broadcastHandlers.get(key).add(handler);
|
|
1735
|
+
return () => this.broadcastHandlers.get(key)?.delete(handler);
|
|
1736
|
+
}
|
|
1737
|
+
async publishBroadcast(content, tags) {
|
|
1738
|
+
const eventTags = tags ? tags.map((t) => ["t", t]) : [];
|
|
1739
|
+
return this.mux.publishRawEvent(this.addressIndex, 30023, content, eventTags);
|
|
1740
|
+
}
|
|
1741
|
+
// ===========================================================================
|
|
1742
|
+
// Resolve methods — delegate to original NostrTransportProvider
|
|
1743
|
+
// These are stateless relay queries, shared across all addresses
|
|
1744
|
+
// ===========================================================================
|
|
1745
|
+
async resolve(identifier) {
|
|
1746
|
+
return this.resolveDelegate?.resolve?.(identifier) ?? null;
|
|
1747
|
+
}
|
|
1748
|
+
async resolveNametag(nametag) {
|
|
1749
|
+
return this.resolveDelegate?.resolveNametag?.(nametag) ?? null;
|
|
1750
|
+
}
|
|
1751
|
+
async resolveNametagInfo(nametag) {
|
|
1752
|
+
return this.resolveDelegate?.resolveNametagInfo?.(nametag) ?? null;
|
|
1753
|
+
}
|
|
1754
|
+
async resolveAddressInfo(address) {
|
|
1755
|
+
return this.resolveDelegate?.resolveAddressInfo?.(address) ?? null;
|
|
1756
|
+
}
|
|
1757
|
+
async resolveTransportPubkeyInfo(transportPubkey) {
|
|
1758
|
+
return this.resolveDelegate?.resolveTransportPubkeyInfo?.(transportPubkey) ?? null;
|
|
1759
|
+
}
|
|
1760
|
+
async discoverAddresses(transportPubkeys) {
|
|
1761
|
+
return this.resolveDelegate?.discoverAddresses?.(transportPubkeys) ?? [];
|
|
1762
|
+
}
|
|
1763
|
+
async recoverNametag() {
|
|
1764
|
+
return this.resolveDelegate?.recoverNametag?.() ?? null;
|
|
1765
|
+
}
|
|
1766
|
+
async publishIdentityBinding(chainPubkey, l1Address, directAddress, nametag) {
|
|
1767
|
+
return this.resolveDelegate?.publishIdentityBinding?.(chainPubkey, l1Address, directAddress, nametag) ?? false;
|
|
1768
|
+
}
|
|
1769
|
+
// ===========================================================================
|
|
1770
|
+
// Relay Management — delegates to mux
|
|
1771
|
+
// ===========================================================================
|
|
1772
|
+
getRelays() {
|
|
1773
|
+
return this.mux.getRelays();
|
|
1774
|
+
}
|
|
1775
|
+
getConnectedRelays() {
|
|
1776
|
+
return this.mux.getConnectedRelays();
|
|
1777
|
+
}
|
|
1778
|
+
async addRelay(relayUrl) {
|
|
1779
|
+
return this.mux.addRelay(relayUrl);
|
|
1780
|
+
}
|
|
1781
|
+
async removeRelay(relayUrl) {
|
|
1782
|
+
return this.mux.removeRelay(relayUrl);
|
|
1783
|
+
}
|
|
1784
|
+
hasRelay(relayUrl) {
|
|
1785
|
+
return this.mux.hasRelay(relayUrl);
|
|
1786
|
+
}
|
|
1787
|
+
isRelayConnected(relayUrl) {
|
|
1788
|
+
return this.mux.isRelayConnected(relayUrl);
|
|
1789
|
+
}
|
|
1790
|
+
setFallbackSince(sinceSeconds) {
|
|
1791
|
+
this.mux.setFallbackSince(this.addressIndex, sinceSeconds);
|
|
1792
|
+
}
|
|
1793
|
+
async fetchPendingEvents() {
|
|
1794
|
+
}
|
|
1795
|
+
onChatReady(handler) {
|
|
1796
|
+
return this.mux.onChatReady(handler);
|
|
1797
|
+
}
|
|
1798
|
+
// ===========================================================================
|
|
1799
|
+
// Dispatch methods — called by MultiAddressTransportMux to route events
|
|
1800
|
+
// ===========================================================================
|
|
1801
|
+
dispatchMessage(message) {
|
|
1802
|
+
if (this.messageHandlers.size === 0) {
|
|
1803
|
+
this.pendingMessages.push(message);
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
for (const handler of this.messageHandlers) {
|
|
1807
|
+
try {
|
|
1808
|
+
handler(message);
|
|
1809
|
+
} catch (e) {
|
|
1810
|
+
logger.debug("MuxAdapter", "Message handler error:", e);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
dispatchTokenTransfer(transfer) {
|
|
1815
|
+
for (const handler of this.transferHandlers) {
|
|
1816
|
+
try {
|
|
1817
|
+
handler(transfer);
|
|
1818
|
+
} catch (e) {
|
|
1819
|
+
logger.debug("MuxAdapter", "Transfer handler error:", e);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
dispatchPaymentRequest(request) {
|
|
1824
|
+
for (const handler of this.paymentRequestHandlers) {
|
|
1825
|
+
try {
|
|
1826
|
+
handler(request);
|
|
1827
|
+
} catch (e) {
|
|
1828
|
+
logger.debug("MuxAdapter", "Payment request handler error:", e);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
dispatchPaymentRequestResponse(response) {
|
|
1833
|
+
for (const handler of this.paymentRequestResponseHandlers) {
|
|
1834
|
+
try {
|
|
1835
|
+
handler(response);
|
|
1836
|
+
} catch (e) {
|
|
1837
|
+
logger.debug("MuxAdapter", "Payment response handler error:", e);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
dispatchReadReceipt(receipt) {
|
|
1842
|
+
for (const handler of this.readReceiptHandlers) {
|
|
1843
|
+
try {
|
|
1844
|
+
handler(receipt);
|
|
1845
|
+
} catch (e) {
|
|
1846
|
+
logger.debug("MuxAdapter", "Read receipt handler error:", e);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
dispatchTypingIndicator(indicator) {
|
|
1851
|
+
for (const handler of this.typingIndicatorHandlers) {
|
|
1852
|
+
try {
|
|
1853
|
+
handler(indicator);
|
|
1854
|
+
} catch (e) {
|
|
1855
|
+
logger.debug("MuxAdapter", "Typing handler error:", e);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1860
|
+
dispatchComposingIndicator(indicator) {
|
|
1861
|
+
for (const handler of this.composingHandlers) {
|
|
1862
|
+
try {
|
|
1863
|
+
handler(indicator);
|
|
1864
|
+
} catch (e) {
|
|
1865
|
+
logger.debug("MuxAdapter", "Composing handler error:", e);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
dispatchInstantSplitBundle(bundle) {
|
|
1870
|
+
for (const handler of this.instantSplitBundleHandlers) {
|
|
1871
|
+
try {
|
|
1872
|
+
handler(bundle);
|
|
1873
|
+
} catch (e) {
|
|
1874
|
+
logger.debug("MuxAdapter", "Instant split handler error:", e);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
emitTransportEvent(event) {
|
|
1879
|
+
for (const cb of this.eventCallbacks) {
|
|
1880
|
+
try {
|
|
1881
|
+
cb(event);
|
|
1882
|
+
} catch {
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1887
|
+
|
|
756
1888
|
// modules/payments/L1PaymentsModule.ts
|
|
757
1889
|
init_errors();
|
|
758
1890
|
init_constants();
|
|
@@ -4616,6 +5748,15 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4616
5748
|
this.unsubscribePaymentRequests = null;
|
|
4617
5749
|
this.unsubscribePaymentRequestResponses?.();
|
|
4618
5750
|
this.unsubscribePaymentRequestResponses = null;
|
|
5751
|
+
this.stopProofPolling();
|
|
5752
|
+
this.proofPollingJobs.clear();
|
|
5753
|
+
this.stopResolveUnconfirmedPolling();
|
|
5754
|
+
this.unsubscribeStorageEvents();
|
|
5755
|
+
for (const [, resolver] of this.pendingResponseResolvers) {
|
|
5756
|
+
clearTimeout(resolver.timeout);
|
|
5757
|
+
resolver.reject(new Error("Address switched"));
|
|
5758
|
+
}
|
|
5759
|
+
this.pendingResponseResolvers.clear();
|
|
4619
5760
|
this.tokens.clear();
|
|
4620
5761
|
this.pendingTransfers.clear();
|
|
4621
5762
|
this.tombstones = [];
|
|
@@ -4664,6 +5805,13 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4664
5805
|
try {
|
|
4665
5806
|
const result = await provider.load();
|
|
4666
5807
|
if (result.success && result.data) {
|
|
5808
|
+
const loadedMeta = result.data?._meta;
|
|
5809
|
+
const currentL1 = this.deps.identity.l1Address;
|
|
5810
|
+
const currentChain = this.deps.identity.chainPubkey;
|
|
5811
|
+
if (loadedMeta?.address && currentL1 && loadedMeta.address !== currentL1 && loadedMeta.address !== currentChain) {
|
|
5812
|
+
logger.warn("Payments", `Load: rejecting data from provider ${id} \u2014 address mismatch (got=${loadedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
|
|
5813
|
+
continue;
|
|
5814
|
+
}
|
|
4667
5815
|
this.loadFromStorageData(result.data);
|
|
4668
5816
|
const txfData = result.data;
|
|
4669
5817
|
if (txfData._history && txfData._history.length > 0) {
|
|
@@ -4745,6 +5893,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4745
5893
|
*/
|
|
4746
5894
|
async send(request) {
|
|
4747
5895
|
this.ensureInitialized();
|
|
5896
|
+
let resolveSendTracker;
|
|
5897
|
+
const sendTracker = new Promise((r) => {
|
|
5898
|
+
resolveSendTracker = r;
|
|
5899
|
+
});
|
|
5900
|
+
this.pendingBackgroundTasks.push(sendTracker);
|
|
4748
5901
|
const result = {
|
|
4749
5902
|
id: crypto.randomUUID(),
|
|
4750
5903
|
status: "pending",
|
|
@@ -5032,6 +6185,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5032
6185
|
}
|
|
5033
6186
|
this.deps.emitEvent("transfer:failed", result);
|
|
5034
6187
|
throw error;
|
|
6188
|
+
} finally {
|
|
6189
|
+
resolveSendTracker();
|
|
5035
6190
|
}
|
|
5036
6191
|
}
|
|
5037
6192
|
/**
|
|
@@ -6044,9 +7199,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6044
7199
|
* Call this before process exit to ensure all tokens are saved.
|
|
6045
7200
|
*/
|
|
6046
7201
|
async waitForPendingOperations() {
|
|
7202
|
+
logger.debug("Payments", `waitForPendingOperations: ${this.pendingBackgroundTasks.length} pending tasks`);
|
|
6047
7203
|
if (this.pendingBackgroundTasks.length > 0) {
|
|
7204
|
+
logger.debug("Payments", "waitForPendingOperations: waiting...");
|
|
6048
7205
|
await Promise.allSettled(this.pendingBackgroundTasks);
|
|
6049
7206
|
this.pendingBackgroundTasks = [];
|
|
7207
|
+
logger.debug("Payments", "waitForPendingOperations: all tasks completed");
|
|
6050
7208
|
}
|
|
6051
7209
|
}
|
|
6052
7210
|
/**
|
|
@@ -7288,6 +8446,13 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7288
8446
|
try {
|
|
7289
8447
|
const result = await provider.sync(localData);
|
|
7290
8448
|
if (result.success && result.merged) {
|
|
8449
|
+
const mergedMeta = result.merged?._meta;
|
|
8450
|
+
const currentL1 = this.deps.identity.l1Address;
|
|
8451
|
+
const currentChain = this.deps.identity.chainPubkey;
|
|
8452
|
+
if (mergedMeta?.address && currentL1 && mergedMeta.address !== currentL1 && mergedMeta.address !== currentChain) {
|
|
8453
|
+
logger.warn("Payments", `Sync: rejecting data from provider ${providerId} \u2014 address mismatch (got=${mergedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
|
|
8454
|
+
continue;
|
|
8455
|
+
}
|
|
7291
8456
|
const savedTokens = new Map(this.tokens);
|
|
7292
8457
|
this.loadFromStorageData(result.merged);
|
|
7293
8458
|
let restoredCount = 0;
|
|
@@ -8299,6 +9464,12 @@ var CommunicationsModule = class {
|
|
|
8299
9464
|
this.unsubscribeComposing = deps.transport.onComposing?.((indicator) => {
|
|
8300
9465
|
this.handleComposingIndicator(indicator);
|
|
8301
9466
|
}) ?? null;
|
|
9467
|
+
if (deps.transport.onChatReady) {
|
|
9468
|
+
deps.transport.onChatReady(() => {
|
|
9469
|
+
const conversations = this.getConversations();
|
|
9470
|
+
deps.emitEvent("communications:ready", { conversationCount: conversations.size });
|
|
9471
|
+
});
|
|
9472
|
+
}
|
|
8302
9473
|
}
|
|
8303
9474
|
/**
|
|
8304
9475
|
* Load messages from storage.
|
|
@@ -8714,9 +9885,9 @@ init_logger();
|
|
|
8714
9885
|
init_errors();
|
|
8715
9886
|
init_constants();
|
|
8716
9887
|
import {
|
|
8717
|
-
NostrClient,
|
|
8718
|
-
NostrKeyManager,
|
|
8719
|
-
Filter
|
|
9888
|
+
NostrClient as NostrClient2,
|
|
9889
|
+
NostrKeyManager as NostrKeyManager2,
|
|
9890
|
+
Filter as Filter2
|
|
8720
9891
|
} from "@unicitylabs/nostr-js-sdk";
|
|
8721
9892
|
|
|
8722
9893
|
// modules/groupchat/types.ts
|
|
@@ -8732,7 +9903,7 @@ var GroupVisibility = {
|
|
|
8732
9903
|
|
|
8733
9904
|
// modules/groupchat/GroupChatModule.ts
|
|
8734
9905
|
function createNip29Filter(data) {
|
|
8735
|
-
return new
|
|
9906
|
+
return new Filter2(data);
|
|
8736
9907
|
}
|
|
8737
9908
|
var GroupChatModule = class {
|
|
8738
9909
|
config;
|
|
@@ -8781,7 +9952,7 @@ var GroupChatModule = class {
|
|
|
8781
9952
|
}
|
|
8782
9953
|
this.deps = deps;
|
|
8783
9954
|
const secretKey = Buffer.from(deps.identity.privateKey, "hex");
|
|
8784
|
-
this.keyManager =
|
|
9955
|
+
this.keyManager = NostrKeyManager2.fromPrivateKey(secretKey);
|
|
8785
9956
|
}
|
|
8786
9957
|
async load() {
|
|
8787
9958
|
this.ensureInitialized();
|
|
@@ -8916,7 +10087,7 @@ var GroupChatModule = class {
|
|
|
8916
10087
|
}
|
|
8917
10088
|
this.subscriptionIds = [];
|
|
8918
10089
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
8919
|
-
this.keyManager =
|
|
10090
|
+
this.keyManager = NostrKeyManager2.fromPrivateKey(secretKey);
|
|
8920
10091
|
if (this.groups.size === 0) {
|
|
8921
10092
|
await this.restoreJoinedGroups();
|
|
8922
10093
|
} else {
|
|
@@ -8928,13 +10099,13 @@ var GroupChatModule = class {
|
|
|
8928
10099
|
this.ensureInitialized();
|
|
8929
10100
|
if (!this.keyManager) {
|
|
8930
10101
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
8931
|
-
this.keyManager =
|
|
10102
|
+
this.keyManager = NostrKeyManager2.fromPrivateKey(secretKey);
|
|
8932
10103
|
}
|
|
8933
10104
|
const primaryRelay = this.config.relays[0];
|
|
8934
10105
|
if (primaryRelay) {
|
|
8935
10106
|
await this.checkAndClearOnRelayChange(primaryRelay);
|
|
8936
10107
|
}
|
|
8937
|
-
this.client = new
|
|
10108
|
+
this.client = new NostrClient2(this.keyManager);
|
|
8938
10109
|
try {
|
|
8939
10110
|
await this.client.connect(...this.config.relays);
|
|
8940
10111
|
this.connected = true;
|
|
@@ -8945,6 +10116,7 @@ var GroupChatModule = class {
|
|
|
8945
10116
|
await this.subscribeToJoinedGroups();
|
|
8946
10117
|
}
|
|
8947
10118
|
this.deps.emitEvent("groupchat:connection", { connected: true });
|
|
10119
|
+
this.deps.emitEvent("groupchat:ready", { groupCount: this.groups.size });
|
|
8948
10120
|
} catch (error) {
|
|
8949
10121
|
logger.error("GroupChat", "Failed to connect to relays", error);
|
|
8950
10122
|
this.deps.emitEvent("groupchat:connection", { connected: false });
|
|
@@ -9192,7 +10364,7 @@ var GroupChatModule = class {
|
|
|
9192
10364
|
if (!myPubkey) return [];
|
|
9193
10365
|
const groupIdsWithMembership = /* @__PURE__ */ new Set();
|
|
9194
10366
|
await this.oneshotSubscription(
|
|
9195
|
-
new
|
|
10367
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9196
10368
|
{
|
|
9197
10369
|
onEvent: (event) => {
|
|
9198
10370
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -9243,7 +10415,7 @@ var GroupChatModule = class {
|
|
|
9243
10415
|
const memberCountsMap = /* @__PURE__ */ new Map();
|
|
9244
10416
|
await Promise.all([
|
|
9245
10417
|
this.oneshotSubscription(
|
|
9246
|
-
new
|
|
10418
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
|
|
9247
10419
|
{
|
|
9248
10420
|
onEvent: (event) => {
|
|
9249
10421
|
const group = this.parseGroupMetadata(event);
|
|
@@ -9261,7 +10433,7 @@ var GroupChatModule = class {
|
|
|
9261
10433
|
}
|
|
9262
10434
|
),
|
|
9263
10435
|
this.oneshotSubscription(
|
|
9264
|
-
new
|
|
10436
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9265
10437
|
{
|
|
9266
10438
|
onEvent: (event) => {
|
|
9267
10439
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -9579,6 +10751,19 @@ var GroupChatModule = class {
|
|
|
9579
10751
|
getMessages(groupId) {
|
|
9580
10752
|
return (this.messages.get(groupId) || []).sort((a, b) => a.timestamp - b.timestamp);
|
|
9581
10753
|
}
|
|
10754
|
+
getMessagesPage(groupId, options) {
|
|
10755
|
+
const limit = options?.limit ?? 20;
|
|
10756
|
+
const before = options?.before ?? Infinity;
|
|
10757
|
+
const groupMessages = this.messages.get(groupId) ?? [];
|
|
10758
|
+
const filtered = groupMessages.filter((m) => m.timestamp < before).sort((a, b) => b.timestamp - a.timestamp);
|
|
10759
|
+
const page = filtered.slice(0, limit);
|
|
10760
|
+
return {
|
|
10761
|
+
messages: page.reverse(),
|
|
10762
|
+
// chronological order
|
|
10763
|
+
hasMore: filtered.length > limit,
|
|
10764
|
+
oldestTimestamp: page.length > 0 ? page[0].timestamp : null
|
|
10765
|
+
};
|
|
10766
|
+
}
|
|
9582
10767
|
getMembers(groupId) {
|
|
9583
10768
|
return (this.members.get(groupId) || []).sort((a, b) => a.joinedAt - b.joinedAt);
|
|
9584
10769
|
}
|
|
@@ -9751,7 +10936,7 @@ var GroupChatModule = class {
|
|
|
9751
10936
|
if (!this.client) return /* @__PURE__ */ new Set();
|
|
9752
10937
|
const adminPubkeys = /* @__PURE__ */ new Set();
|
|
9753
10938
|
return this.oneshotSubscription(
|
|
9754
|
-
new
|
|
10939
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
|
|
9755
10940
|
{
|
|
9756
10941
|
onEvent: (event) => {
|
|
9757
10942
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -9773,7 +10958,7 @@ var GroupChatModule = class {
|
|
|
9773
10958
|
if (!this.client) return null;
|
|
9774
10959
|
let result = null;
|
|
9775
10960
|
return this.oneshotSubscription(
|
|
9776
|
-
new
|
|
10961
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
|
|
9777
10962
|
{
|
|
9778
10963
|
onEvent: (event) => {
|
|
9779
10964
|
if (!result) result = this.parseGroupMetadata(event);
|
|
@@ -9810,7 +10995,7 @@ var GroupChatModule = class {
|
|
|
9810
10995
|
if (!this.client) return [];
|
|
9811
10996
|
const members = [];
|
|
9812
10997
|
return this.oneshotSubscription(
|
|
9813
|
-
new
|
|
10998
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
|
|
9814
10999
|
{
|
|
9815
11000
|
onEvent: (event) => {
|
|
9816
11001
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -9831,7 +11016,7 @@ var GroupChatModule = class {
|
|
|
9831
11016
|
if (!this.client) return [];
|
|
9832
11017
|
const adminPubkeys = [];
|
|
9833
11018
|
return this.oneshotSubscription(
|
|
9834
|
-
new
|
|
11019
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
|
|
9835
11020
|
{
|
|
9836
11021
|
onEvent: (event) => {
|
|
9837
11022
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -13631,11 +14816,18 @@ var Sphere = class _Sphere {
|
|
|
13631
14816
|
_transport;
|
|
13632
14817
|
_oracle;
|
|
13633
14818
|
_priceProvider;
|
|
13634
|
-
// Modules
|
|
14819
|
+
// Modules (single-instance — backward compat, delegates to active address)
|
|
13635
14820
|
_payments;
|
|
13636
14821
|
_communications;
|
|
13637
14822
|
_groupChat = null;
|
|
13638
14823
|
_market = null;
|
|
14824
|
+
// Per-address module instances (Phase 2: independent parallel operation)
|
|
14825
|
+
_addressModules = /* @__PURE__ */ new Map();
|
|
14826
|
+
_transportMux = null;
|
|
14827
|
+
// Stored configs for creating per-address modules
|
|
14828
|
+
_l1Config;
|
|
14829
|
+
_groupChatConfig;
|
|
14830
|
+
_marketConfig;
|
|
13639
14831
|
// Events
|
|
13640
14832
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
13641
14833
|
// Provider management
|
|
@@ -13653,6 +14845,9 @@ var Sphere = class _Sphere {
|
|
|
13653
14845
|
if (tokenStorage) {
|
|
13654
14846
|
this._tokenStorageProviders.set(tokenStorage.id, tokenStorage);
|
|
13655
14847
|
}
|
|
14848
|
+
this._l1Config = l1Config;
|
|
14849
|
+
this._groupChatConfig = groupChatConfig;
|
|
14850
|
+
this._marketConfig = marketConfig;
|
|
13656
14851
|
this._payments = createPaymentsModule({ l1: l1Config });
|
|
13657
14852
|
this._communications = createCommunicationsModule();
|
|
13658
14853
|
this._groupChat = groupChatConfig ? createGroupChatModule(groupChatConfig) : null;
|
|
@@ -14911,7 +16106,7 @@ var Sphere = class _Sphere {
|
|
|
14911
16106
|
nametags.set(0, newNametag);
|
|
14912
16107
|
}
|
|
14913
16108
|
const nametag = this._addressNametags.get(addressId)?.get(0);
|
|
14914
|
-
|
|
16109
|
+
const newIdentity = {
|
|
14915
16110
|
privateKey: addressInfo.privateKey,
|
|
14916
16111
|
chainPubkey: addressInfo.publicKey,
|
|
14917
16112
|
l1Address: addressInfo.address,
|
|
@@ -14919,20 +16114,53 @@ var Sphere = class _Sphere {
|
|
|
14919
16114
|
ipnsName: "12D3KooW" + ipnsHash,
|
|
14920
16115
|
nametag
|
|
14921
16116
|
};
|
|
16117
|
+
if (!this._addressModules.has(index)) {
|
|
16118
|
+
logger.debug("Sphere", `switchToAddress(${index}): creating per-address modules (lazy init)`);
|
|
16119
|
+
const addressTokenProviders = /* @__PURE__ */ new Map();
|
|
16120
|
+
for (const [providerId, provider] of this._tokenStorageProviders.entries()) {
|
|
16121
|
+
if (provider.createForAddress) {
|
|
16122
|
+
const newProvider = provider.createForAddress();
|
|
16123
|
+
newProvider.setIdentity(newIdentity);
|
|
16124
|
+
await newProvider.initialize();
|
|
16125
|
+
addressTokenProviders.set(providerId, newProvider);
|
|
16126
|
+
} else {
|
|
16127
|
+
logger.warn("Sphere", `Token storage provider ${providerId} does not support createForAddress, reusing shared instance`);
|
|
16128
|
+
addressTokenProviders.set(providerId, provider);
|
|
16129
|
+
}
|
|
16130
|
+
}
|
|
16131
|
+
await this.initializeAddressModules(index, newIdentity, addressTokenProviders);
|
|
16132
|
+
} else {
|
|
16133
|
+
const moduleSet = this._addressModules.get(index);
|
|
16134
|
+
if (nametag !== moduleSet.identity.nametag) {
|
|
16135
|
+
moduleSet.identity = newIdentity;
|
|
16136
|
+
const addressTransport = moduleSet.transportAdapter ?? this._transport;
|
|
16137
|
+
moduleSet.payments.initialize({
|
|
16138
|
+
identity: newIdentity,
|
|
16139
|
+
storage: this._storage,
|
|
16140
|
+
tokenStorageProviders: moduleSet.tokenStorageProviders,
|
|
16141
|
+
transport: addressTransport,
|
|
16142
|
+
oracle: this._oracle,
|
|
16143
|
+
emitEvent: this.emitEvent.bind(this),
|
|
16144
|
+
chainCode: this._masterKey?.chainCode || void 0,
|
|
16145
|
+
price: this._priceProvider ?? void 0
|
|
16146
|
+
});
|
|
16147
|
+
}
|
|
16148
|
+
}
|
|
16149
|
+
this._identity = newIdentity;
|
|
14922
16150
|
this._currentAddressIndex = index;
|
|
14923
16151
|
await this._updateCachedProxyAddress();
|
|
16152
|
+
const activeModules = this._addressModules.get(index);
|
|
16153
|
+
this._payments = activeModules.payments;
|
|
16154
|
+
this._communications = activeModules.communications;
|
|
16155
|
+
this._groupChat = activeModules.groupChat;
|
|
16156
|
+
this._market = activeModules.market;
|
|
14924
16157
|
await this._storage.set(STORAGE_KEYS_GLOBAL.CURRENT_ADDRESS_INDEX, index.toString());
|
|
14925
16158
|
this._storage.setIdentity(this._identity);
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
logger.debug("Sphere", `switchToAddress(${index}): shutdown provider=${providerId}`);
|
|
14930
|
-
await provider.shutdown();
|
|
14931
|
-
provider.setIdentity(this._identity);
|
|
14932
|
-
logger.debug("Sphere", `switchToAddress(${index}): initialize provider=${providerId}`);
|
|
14933
|
-
await provider.initialize();
|
|
16159
|
+
if (this._transport.setFallbackSince) {
|
|
16160
|
+
const fallbackTs = Math.floor(Date.now() / 1e3) - 86400;
|
|
16161
|
+
this._transport.setFallbackSince(fallbackTs);
|
|
14934
16162
|
}
|
|
14935
|
-
await this.
|
|
16163
|
+
await this._transport.setIdentity(this._identity);
|
|
14936
16164
|
this.emitEvent("identity:changed", {
|
|
14937
16165
|
l1Address: this._identity.l1Address,
|
|
14938
16166
|
directAddress: this._identity.directAddress,
|
|
@@ -14987,42 +16215,104 @@ var Sphere = class _Sphere {
|
|
|
14987
16215
|
}
|
|
14988
16216
|
}
|
|
14989
16217
|
/**
|
|
14990
|
-
*
|
|
16218
|
+
* Create a new set of per-address modules for the given index.
|
|
16219
|
+
* Each address gets its own PaymentsModule, CommunicationsModule, etc.
|
|
16220
|
+
* Modules are fully independent — they have their own token storage,
|
|
16221
|
+
* and can sync/finalize/split in background regardless of active address.
|
|
16222
|
+
*
|
|
16223
|
+
* @param index - HD address index
|
|
16224
|
+
* @param identity - Full identity for this address
|
|
16225
|
+
* @param tokenStorageProviders - Token storage providers for this address
|
|
14991
16226
|
*/
|
|
14992
|
-
async
|
|
16227
|
+
async initializeAddressModules(index, identity, tokenStorageProviders) {
|
|
14993
16228
|
const emitEvent = this.emitEvent.bind(this);
|
|
14994
|
-
this.
|
|
14995
|
-
|
|
16229
|
+
const adapter = await this.ensureTransportMux(index, identity);
|
|
16230
|
+
const addressTransport = adapter ?? this._transport;
|
|
16231
|
+
const payments = createPaymentsModule({ l1: this._l1Config });
|
|
16232
|
+
const communications = createCommunicationsModule();
|
|
16233
|
+
const groupChat = this._groupChatConfig ? createGroupChatModule(this._groupChatConfig) : null;
|
|
16234
|
+
const market = this._marketConfig ? createMarketModule(this._marketConfig) : null;
|
|
16235
|
+
payments.initialize({
|
|
16236
|
+
identity,
|
|
14996
16237
|
storage: this._storage,
|
|
14997
|
-
tokenStorageProviders
|
|
14998
|
-
transport:
|
|
16238
|
+
tokenStorageProviders,
|
|
16239
|
+
transport: addressTransport,
|
|
14999
16240
|
oracle: this._oracle,
|
|
15000
16241
|
emitEvent,
|
|
15001
16242
|
chainCode: this._masterKey?.chainCode || void 0,
|
|
15002
16243
|
price: this._priceProvider ?? void 0
|
|
15003
16244
|
});
|
|
15004
|
-
|
|
15005
|
-
identity
|
|
16245
|
+
communications.initialize({
|
|
16246
|
+
identity,
|
|
15006
16247
|
storage: this._storage,
|
|
15007
|
-
transport:
|
|
16248
|
+
transport: addressTransport,
|
|
15008
16249
|
emitEvent
|
|
15009
16250
|
});
|
|
15010
|
-
|
|
15011
|
-
identity
|
|
16251
|
+
groupChat?.initialize({
|
|
16252
|
+
identity,
|
|
15012
16253
|
storage: this._storage,
|
|
15013
16254
|
emitEvent
|
|
15014
16255
|
});
|
|
15015
|
-
|
|
15016
|
-
identity
|
|
16256
|
+
market?.initialize({
|
|
16257
|
+
identity,
|
|
15017
16258
|
emitEvent
|
|
15018
16259
|
});
|
|
15019
|
-
await
|
|
15020
|
-
await
|
|
15021
|
-
await
|
|
15022
|
-
await
|
|
15023
|
-
|
|
15024
|
-
|
|
16260
|
+
await payments.load();
|
|
16261
|
+
await communications.load();
|
|
16262
|
+
await groupChat?.load();
|
|
16263
|
+
await market?.load();
|
|
16264
|
+
const moduleSet = {
|
|
16265
|
+
index,
|
|
16266
|
+
identity,
|
|
16267
|
+
payments,
|
|
16268
|
+
communications,
|
|
16269
|
+
groupChat,
|
|
16270
|
+
market,
|
|
16271
|
+
transportAdapter: adapter,
|
|
16272
|
+
tokenStorageProviders: new Map(tokenStorageProviders),
|
|
16273
|
+
initialized: true
|
|
16274
|
+
};
|
|
16275
|
+
this._addressModules.set(index, moduleSet);
|
|
16276
|
+
logger.debug("Sphere", `Initialized per-address modules for address ${index} (transport: ${adapter ? "mux adapter" : "primary"})`);
|
|
16277
|
+
payments.sync().catch((err) => {
|
|
16278
|
+
logger.warn("Sphere", `Post-init sync failed for address ${index}:`, err);
|
|
15025
16279
|
});
|
|
16280
|
+
return moduleSet;
|
|
16281
|
+
}
|
|
16282
|
+
/**
|
|
16283
|
+
* Ensure the transport multiplexer exists and register an address.
|
|
16284
|
+
* Creates the mux on first call. Returns an AddressTransportAdapter
|
|
16285
|
+
* that routes events for this address independently.
|
|
16286
|
+
* @returns AddressTransportAdapter or null if transport is not Nostr-based
|
|
16287
|
+
*/
|
|
16288
|
+
async ensureTransportMux(index, identity) {
|
|
16289
|
+
const transport = this._transport;
|
|
16290
|
+
if (typeof transport.getWebSocketFactory !== "function" || typeof transport.getConfiguredRelays !== "function") {
|
|
16291
|
+
logger.debug("Sphere", "Transport does not support mux interface, skipping");
|
|
16292
|
+
return null;
|
|
16293
|
+
}
|
|
16294
|
+
const nostrTransport = transport;
|
|
16295
|
+
if (!this._transportMux) {
|
|
16296
|
+
this._transportMux = new MultiAddressTransportMux({
|
|
16297
|
+
relays: nostrTransport.getConfiguredRelays(),
|
|
16298
|
+
createWebSocket: nostrTransport.getWebSocketFactory(),
|
|
16299
|
+
storage: nostrTransport.getStorageAdapter() ?? void 0
|
|
16300
|
+
});
|
|
16301
|
+
await this._transportMux.connect();
|
|
16302
|
+
if (typeof nostrTransport.suppressSubscriptions === "function") {
|
|
16303
|
+
nostrTransport.suppressSubscriptions();
|
|
16304
|
+
}
|
|
16305
|
+
logger.debug("Sphere", "Transport mux created and connected");
|
|
16306
|
+
}
|
|
16307
|
+
const adapter = await this._transportMux.addAddress(index, identity, this._transport);
|
|
16308
|
+
return adapter;
|
|
16309
|
+
}
|
|
16310
|
+
/**
|
|
16311
|
+
* Get per-address modules for any address index (creates lazily if needed).
|
|
16312
|
+
* This allows accessing any address's modules without switching.
|
|
16313
|
+
*/
|
|
16314
|
+
getAddressPayments(index) {
|
|
16315
|
+
return this._addressModules.get(index)?.payments;
|
|
15026
16316
|
}
|
|
15027
16317
|
/**
|
|
15028
16318
|
* Derive address at a specific index
|
|
@@ -15950,10 +17240,33 @@ var Sphere = class _Sphere {
|
|
|
15950
17240
|
// ===========================================================================
|
|
15951
17241
|
async destroy() {
|
|
15952
17242
|
this.cleanupProviderEventSubscriptions();
|
|
17243
|
+
for (const [idx, moduleSet] of this._addressModules.entries()) {
|
|
17244
|
+
try {
|
|
17245
|
+
moduleSet.payments.destroy();
|
|
17246
|
+
moduleSet.communications.destroy();
|
|
17247
|
+
moduleSet.groupChat?.destroy();
|
|
17248
|
+
moduleSet.market?.destroy();
|
|
17249
|
+
for (const provider of moduleSet.tokenStorageProviders.values()) {
|
|
17250
|
+
try {
|
|
17251
|
+
await provider.shutdown();
|
|
17252
|
+
} catch {
|
|
17253
|
+
}
|
|
17254
|
+
}
|
|
17255
|
+
moduleSet.tokenStorageProviders.clear();
|
|
17256
|
+
logger.debug("Sphere", `Destroyed modules for address ${idx}`);
|
|
17257
|
+
} catch (err) {
|
|
17258
|
+
logger.warn("Sphere", `Error destroying modules for address ${idx}:`, err);
|
|
17259
|
+
}
|
|
17260
|
+
}
|
|
17261
|
+
this._addressModules.clear();
|
|
15953
17262
|
this._payments.destroy();
|
|
15954
17263
|
this._communications.destroy();
|
|
15955
17264
|
this._groupChat?.destroy();
|
|
15956
17265
|
this._market?.destroy();
|
|
17266
|
+
if (this._transportMux) {
|
|
17267
|
+
await this._transportMux.disconnect();
|
|
17268
|
+
this._transportMux = null;
|
|
17269
|
+
}
|
|
15957
17270
|
await this._transport.disconnect();
|
|
15958
17271
|
await this._storage.disconnect();
|
|
15959
17272
|
await this._oracle.disconnect();
|
|
@@ -16148,6 +17461,9 @@ var Sphere = class _Sphere {
|
|
|
16148
17461
|
// ===========================================================================
|
|
16149
17462
|
async initializeProviders() {
|
|
16150
17463
|
this._storage.setIdentity(this._identity);
|
|
17464
|
+
if (this._transport.setFallbackSince) {
|
|
17465
|
+
this._transport.setFallbackSince(Math.floor(Date.now() / 1e3) - 86400);
|
|
17466
|
+
}
|
|
16151
17467
|
await this._transport.setIdentity(this._identity);
|
|
16152
17468
|
for (const provider of this._tokenStorageProviders.values()) {
|
|
16153
17469
|
provider.setIdentity(this._identity);
|
|
@@ -16238,11 +17554,13 @@ var Sphere = class _Sphere {
|
|
|
16238
17554
|
}
|
|
16239
17555
|
async initializeModules() {
|
|
16240
17556
|
const emitEvent = this.emitEvent.bind(this);
|
|
17557
|
+
const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
|
|
17558
|
+
const moduleTransport = adapter ?? this._transport;
|
|
16241
17559
|
this._payments.initialize({
|
|
16242
17560
|
identity: this._identity,
|
|
16243
17561
|
storage: this._storage,
|
|
16244
17562
|
tokenStorageProviders: this._tokenStorageProviders,
|
|
16245
|
-
transport:
|
|
17563
|
+
transport: moduleTransport,
|
|
16246
17564
|
oracle: this._oracle,
|
|
16247
17565
|
emitEvent,
|
|
16248
17566
|
// Pass chain code for L1 HD derivation
|
|
@@ -16253,7 +17571,7 @@ var Sphere = class _Sphere {
|
|
|
16253
17571
|
this._communications.initialize({
|
|
16254
17572
|
identity: this._identity,
|
|
16255
17573
|
storage: this._storage,
|
|
16256
|
-
transport:
|
|
17574
|
+
transport: moduleTransport,
|
|
16257
17575
|
emitEvent
|
|
16258
17576
|
});
|
|
16259
17577
|
this._groupChat?.initialize({
|
|
@@ -16269,6 +17587,17 @@ var Sphere = class _Sphere {
|
|
|
16269
17587
|
await this._communications.load();
|
|
16270
17588
|
await this._groupChat?.load();
|
|
16271
17589
|
await this._market?.load();
|
|
17590
|
+
this._addressModules.set(this._currentAddressIndex, {
|
|
17591
|
+
index: this._currentAddressIndex,
|
|
17592
|
+
identity: this._identity,
|
|
17593
|
+
payments: this._payments,
|
|
17594
|
+
communications: this._communications,
|
|
17595
|
+
groupChat: this._groupChat,
|
|
17596
|
+
market: this._market,
|
|
17597
|
+
transportAdapter: adapter,
|
|
17598
|
+
tokenStorageProviders: new Map(this._tokenStorageProviders),
|
|
17599
|
+
initialized: true
|
|
17600
|
+
});
|
|
16272
17601
|
}
|
|
16273
17602
|
// ===========================================================================
|
|
16274
17603
|
// Private: Helpers
|