@unicitylabs/sphere-sdk 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.cjs +1351 -52
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +342 -3
- package/dist/core/index.d.ts +342 -3
- package/dist/core/index.js +1357 -48
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +109 -11
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +109 -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 +105 -11
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +43 -0
- package/dist/impl/nodejs/index.d.ts +43 -0
- package/dist/impl/nodejs/index.js +105 -11
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +1334 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -2
- package/dist/index.d.ts +43 -2
- package/dist/index.js +1333 -50
- package/dist/index.js.map +1 -1
- package/dist/l1/index.d.cts +717 -0
- package/dist/l1/index.d.ts +717 -0
- package/package.json +1 -1
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;
|
|
@@ -9015,9 +10154,9 @@ init_logger();
|
|
|
9015
10154
|
init_errors();
|
|
9016
10155
|
init_constants();
|
|
9017
10156
|
import {
|
|
9018
|
-
NostrClient,
|
|
9019
|
-
NostrKeyManager,
|
|
9020
|
-
Filter
|
|
10157
|
+
NostrClient as NostrClient2,
|
|
10158
|
+
NostrKeyManager as NostrKeyManager2,
|
|
10159
|
+
Filter as Filter2
|
|
9021
10160
|
} from "@unicitylabs/nostr-js-sdk";
|
|
9022
10161
|
|
|
9023
10162
|
// modules/groupchat/types.ts
|
|
@@ -9033,7 +10172,7 @@ var GroupVisibility = {
|
|
|
9033
10172
|
|
|
9034
10173
|
// modules/groupchat/GroupChatModule.ts
|
|
9035
10174
|
function createNip29Filter(data) {
|
|
9036
|
-
return new
|
|
10175
|
+
return new Filter2(data);
|
|
9037
10176
|
}
|
|
9038
10177
|
var GroupChatModule = class {
|
|
9039
10178
|
config;
|
|
@@ -9082,7 +10221,7 @@ var GroupChatModule = class {
|
|
|
9082
10221
|
}
|
|
9083
10222
|
this.deps = deps;
|
|
9084
10223
|
const secretKey = Buffer.from(deps.identity.privateKey, "hex");
|
|
9085
|
-
this.keyManager =
|
|
10224
|
+
this.keyManager = NostrKeyManager2.fromPrivateKey(secretKey);
|
|
9086
10225
|
}
|
|
9087
10226
|
async load() {
|
|
9088
10227
|
this.ensureInitialized();
|
|
@@ -9217,7 +10356,7 @@ var GroupChatModule = class {
|
|
|
9217
10356
|
}
|
|
9218
10357
|
this.subscriptionIds = [];
|
|
9219
10358
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9220
|
-
this.keyManager =
|
|
10359
|
+
this.keyManager = NostrKeyManager2.fromPrivateKey(secretKey);
|
|
9221
10360
|
if (this.groups.size === 0) {
|
|
9222
10361
|
await this.restoreJoinedGroups();
|
|
9223
10362
|
} else {
|
|
@@ -9229,13 +10368,13 @@ var GroupChatModule = class {
|
|
|
9229
10368
|
this.ensureInitialized();
|
|
9230
10369
|
if (!this.keyManager) {
|
|
9231
10370
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9232
|
-
this.keyManager =
|
|
10371
|
+
this.keyManager = NostrKeyManager2.fromPrivateKey(secretKey);
|
|
9233
10372
|
}
|
|
9234
10373
|
const primaryRelay = this.config.relays[0];
|
|
9235
10374
|
if (primaryRelay) {
|
|
9236
10375
|
await this.checkAndClearOnRelayChange(primaryRelay);
|
|
9237
10376
|
}
|
|
9238
|
-
this.client = new
|
|
10377
|
+
this.client = new NostrClient2(this.keyManager);
|
|
9239
10378
|
try {
|
|
9240
10379
|
await this.client.connect(...this.config.relays);
|
|
9241
10380
|
this.connected = true;
|
|
@@ -9494,7 +10633,7 @@ var GroupChatModule = class {
|
|
|
9494
10633
|
if (!myPubkey) return [];
|
|
9495
10634
|
const groupIdsWithMembership = /* @__PURE__ */ new Set();
|
|
9496
10635
|
await this.oneshotSubscription(
|
|
9497
|
-
new
|
|
10636
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9498
10637
|
{
|
|
9499
10638
|
onEvent: (event) => {
|
|
9500
10639
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -9545,7 +10684,7 @@ var GroupChatModule = class {
|
|
|
9545
10684
|
const memberCountsMap = /* @__PURE__ */ new Map();
|
|
9546
10685
|
await Promise.all([
|
|
9547
10686
|
this.oneshotSubscription(
|
|
9548
|
-
new
|
|
10687
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
|
|
9549
10688
|
{
|
|
9550
10689
|
onEvent: (event) => {
|
|
9551
10690
|
const group = this.parseGroupMetadata(event);
|
|
@@ -9563,7 +10702,7 @@ var GroupChatModule = class {
|
|
|
9563
10702
|
}
|
|
9564
10703
|
),
|
|
9565
10704
|
this.oneshotSubscription(
|
|
9566
|
-
new
|
|
10705
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9567
10706
|
{
|
|
9568
10707
|
onEvent: (event) => {
|
|
9569
10708
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -10066,7 +11205,7 @@ var GroupChatModule = class {
|
|
|
10066
11205
|
if (!this.client) return /* @__PURE__ */ new Set();
|
|
10067
11206
|
const adminPubkeys = /* @__PURE__ */ new Set();
|
|
10068
11207
|
return this.oneshotSubscription(
|
|
10069
|
-
new
|
|
11208
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
|
|
10070
11209
|
{
|
|
10071
11210
|
onEvent: (event) => {
|
|
10072
11211
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -10088,7 +11227,7 @@ var GroupChatModule = class {
|
|
|
10088
11227
|
if (!this.client) return null;
|
|
10089
11228
|
let result = null;
|
|
10090
11229
|
return this.oneshotSubscription(
|
|
10091
|
-
new
|
|
11230
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
|
|
10092
11231
|
{
|
|
10093
11232
|
onEvent: (event) => {
|
|
10094
11233
|
if (!result) result = this.parseGroupMetadata(event);
|
|
@@ -10125,7 +11264,7 @@ var GroupChatModule = class {
|
|
|
10125
11264
|
if (!this.client) return [];
|
|
10126
11265
|
const members = [];
|
|
10127
11266
|
return this.oneshotSubscription(
|
|
10128
|
-
new
|
|
11267
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
|
|
10129
11268
|
{
|
|
10130
11269
|
onEvent: (event) => {
|
|
10131
11270
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -10146,7 +11285,7 @@ var GroupChatModule = class {
|
|
|
10146
11285
|
if (!this.client) return [];
|
|
10147
11286
|
const adminPubkeys = [];
|
|
10148
11287
|
return this.oneshotSubscription(
|
|
10149
|
-
new
|
|
11288
|
+
new Filter2({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
|
|
10150
11289
|
{
|
|
10151
11290
|
onEvent: (event) => {
|
|
10152
11291
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -13861,11 +15000,18 @@ var Sphere = class _Sphere {
|
|
|
13861
15000
|
_transport;
|
|
13862
15001
|
_oracle;
|
|
13863
15002
|
_priceProvider;
|
|
13864
|
-
// Modules
|
|
15003
|
+
// Modules (single-instance — backward compat, delegates to active address)
|
|
13865
15004
|
_payments;
|
|
13866
15005
|
_communications;
|
|
13867
15006
|
_groupChat = null;
|
|
13868
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;
|
|
13869
15015
|
// Events
|
|
13870
15016
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
13871
15017
|
// Provider management
|
|
@@ -13883,6 +15029,9 @@ var Sphere = class _Sphere {
|
|
|
13883
15029
|
if (tokenStorage) {
|
|
13884
15030
|
this._tokenStorageProviders.set(tokenStorage.id, tokenStorage);
|
|
13885
15031
|
}
|
|
15032
|
+
this._l1Config = l1Config;
|
|
15033
|
+
this._groupChatConfig = groupChatConfig;
|
|
15034
|
+
this._marketConfig = marketConfig;
|
|
13886
15035
|
this._payments = createPaymentsModule({ l1: l1Config });
|
|
13887
15036
|
this._communications = createCommunicationsModule();
|
|
13888
15037
|
this._groupChat = groupChatConfig ? createGroupChatModule(groupChatConfig) : null;
|
|
@@ -15141,7 +16290,7 @@ var Sphere = class _Sphere {
|
|
|
15141
16290
|
nametags.set(0, newNametag);
|
|
15142
16291
|
}
|
|
15143
16292
|
const nametag = this._addressNametags.get(addressId)?.get(0);
|
|
15144
|
-
|
|
16293
|
+
const newIdentity = {
|
|
15145
16294
|
privateKey: addressInfo.privateKey,
|
|
15146
16295
|
chainPubkey: addressInfo.publicKey,
|
|
15147
16296
|
l1Address: addressInfo.address,
|
|
@@ -15149,20 +16298,53 @@ var Sphere = class _Sphere {
|
|
|
15149
16298
|
ipnsName: "12D3KooW" + ipnsHash,
|
|
15150
16299
|
nametag
|
|
15151
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;
|
|
15152
16334
|
this._currentAddressIndex = index;
|
|
15153
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;
|
|
15154
16341
|
await this._storage.set(STORAGE_KEYS_GLOBAL.CURRENT_ADDRESS_INDEX, index.toString());
|
|
15155
16342
|
this._storage.setIdentity(this._identity);
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
logger.debug("Sphere", `switchToAddress(${index}): shutdown provider=${providerId}`);
|
|
15160
|
-
await provider.shutdown();
|
|
15161
|
-
provider.setIdentity(this._identity);
|
|
15162
|
-
logger.debug("Sphere", `switchToAddress(${index}): initialize provider=${providerId}`);
|
|
15163
|
-
await provider.initialize();
|
|
16343
|
+
if (this._transport.setFallbackSince) {
|
|
16344
|
+
const fallbackTs = Math.floor(Date.now() / 1e3) - 86400;
|
|
16345
|
+
this._transport.setFallbackSince(fallbackTs);
|
|
15164
16346
|
}
|
|
15165
|
-
await this.
|
|
16347
|
+
await this._transport.setIdentity(this._identity);
|
|
15166
16348
|
this.emitEvent("identity:changed", {
|
|
15167
16349
|
l1Address: this._identity.l1Address,
|
|
15168
16350
|
directAddress: this._identity.directAddress,
|
|
@@ -15217,42 +16399,104 @@ var Sphere = class _Sphere {
|
|
|
15217
16399
|
}
|
|
15218
16400
|
}
|
|
15219
16401
|
/**
|
|
15220
|
-
*
|
|
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
|
|
15221
16410
|
*/
|
|
15222
|
-
async
|
|
16411
|
+
async initializeAddressModules(index, identity, tokenStorageProviders) {
|
|
15223
16412
|
const emitEvent = this.emitEvent.bind(this);
|
|
15224
|
-
this.
|
|
15225
|
-
|
|
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,
|
|
15226
16421
|
storage: this._storage,
|
|
15227
|
-
tokenStorageProviders
|
|
15228
|
-
transport:
|
|
16422
|
+
tokenStorageProviders,
|
|
16423
|
+
transport: addressTransport,
|
|
15229
16424
|
oracle: this._oracle,
|
|
15230
16425
|
emitEvent,
|
|
15231
16426
|
chainCode: this._masterKey?.chainCode || void 0,
|
|
15232
16427
|
price: this._priceProvider ?? void 0
|
|
15233
16428
|
});
|
|
15234
|
-
|
|
15235
|
-
identity
|
|
16429
|
+
communications.initialize({
|
|
16430
|
+
identity,
|
|
15236
16431
|
storage: this._storage,
|
|
15237
|
-
transport:
|
|
16432
|
+
transport: addressTransport,
|
|
15238
16433
|
emitEvent
|
|
15239
16434
|
});
|
|
15240
|
-
|
|
15241
|
-
identity
|
|
16435
|
+
groupChat?.initialize({
|
|
16436
|
+
identity,
|
|
15242
16437
|
storage: this._storage,
|
|
15243
16438
|
emitEvent
|
|
15244
16439
|
});
|
|
15245
|
-
|
|
15246
|
-
identity
|
|
16440
|
+
market?.initialize({
|
|
16441
|
+
identity,
|
|
15247
16442
|
emitEvent
|
|
15248
16443
|
});
|
|
15249
|
-
await
|
|
15250
|
-
await
|
|
15251
|
-
await
|
|
15252
|
-
await
|
|
15253
|
-
|
|
15254
|
-
|
|
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);
|
|
15255
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;
|
|
15256
16500
|
}
|
|
15257
16501
|
/**
|
|
15258
16502
|
* Derive address at a specific index
|
|
@@ -16180,10 +17424,33 @@ var Sphere = class _Sphere {
|
|
|
16180
17424
|
// ===========================================================================
|
|
16181
17425
|
async destroy() {
|
|
16182
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();
|
|
16183
17446
|
this._payments.destroy();
|
|
16184
17447
|
this._communications.destroy();
|
|
16185
17448
|
this._groupChat?.destroy();
|
|
16186
17449
|
this._market?.destroy();
|
|
17450
|
+
if (this._transportMux) {
|
|
17451
|
+
await this._transportMux.disconnect();
|
|
17452
|
+
this._transportMux = null;
|
|
17453
|
+
}
|
|
16187
17454
|
await this._transport.disconnect();
|
|
16188
17455
|
await this._storage.disconnect();
|
|
16189
17456
|
await this._oracle.disconnect();
|
|
@@ -16378,6 +17645,9 @@ var Sphere = class _Sphere {
|
|
|
16378
17645
|
// ===========================================================================
|
|
16379
17646
|
async initializeProviders() {
|
|
16380
17647
|
this._storage.setIdentity(this._identity);
|
|
17648
|
+
if (this._transport.setFallbackSince) {
|
|
17649
|
+
this._transport.setFallbackSince(Math.floor(Date.now() / 1e3) - 86400);
|
|
17650
|
+
}
|
|
16381
17651
|
await this._transport.setIdentity(this._identity);
|
|
16382
17652
|
for (const provider of this._tokenStorageProviders.values()) {
|
|
16383
17653
|
provider.setIdentity(this._identity);
|
|
@@ -16468,11 +17738,13 @@ var Sphere = class _Sphere {
|
|
|
16468
17738
|
}
|
|
16469
17739
|
async initializeModules() {
|
|
16470
17740
|
const emitEvent = this.emitEvent.bind(this);
|
|
17741
|
+
const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
|
|
17742
|
+
const moduleTransport = adapter ?? this._transport;
|
|
16471
17743
|
this._payments.initialize({
|
|
16472
17744
|
identity: this._identity,
|
|
16473
17745
|
storage: this._storage,
|
|
16474
17746
|
tokenStorageProviders: this._tokenStorageProviders,
|
|
16475
|
-
transport:
|
|
17747
|
+
transport: moduleTransport,
|
|
16476
17748
|
oracle: this._oracle,
|
|
16477
17749
|
emitEvent,
|
|
16478
17750
|
// Pass chain code for L1 HD derivation
|
|
@@ -16483,7 +17755,7 @@ var Sphere = class _Sphere {
|
|
|
16483
17755
|
this._communications.initialize({
|
|
16484
17756
|
identity: this._identity,
|
|
16485
17757
|
storage: this._storage,
|
|
16486
|
-
transport:
|
|
17758
|
+
transport: moduleTransport,
|
|
16487
17759
|
emitEvent
|
|
16488
17760
|
});
|
|
16489
17761
|
this._groupChat?.initialize({
|
|
@@ -16499,6 +17771,17 @@ var Sphere = class _Sphere {
|
|
|
16499
17771
|
await this._communications.load();
|
|
16500
17772
|
await this._groupChat?.load();
|
|
16501
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
|
+
});
|
|
16502
17785
|
}
|
|
16503
17786
|
// ===========================================================================
|
|
16504
17787
|
// Private: Helpers
|
|
@@ -17179,7 +18462,7 @@ import {
|
|
|
17179
18462
|
encryptNametag,
|
|
17180
18463
|
decryptNametag
|
|
17181
18464
|
} from "@unicitylabs/nostr-js-sdk";
|
|
17182
|
-
import { NostrClient as
|
|
18465
|
+
import { NostrClient as NostrClient3, NostrKeyManager as NostrKeyManager3 } from "@unicitylabs/nostr-js-sdk";
|
|
17183
18466
|
|
|
17184
18467
|
// price/CoinGeckoPriceProvider.ts
|
|
17185
18468
|
init_logger();
|
|
@@ -17451,8 +18734,8 @@ export {
|
|
|
17451
18734
|
NETWORKS,
|
|
17452
18735
|
NIP29_KINDS,
|
|
17453
18736
|
NOSTR_EVENT_KINDS,
|
|
17454
|
-
|
|
17455
|
-
|
|
18737
|
+
NostrClient3 as NostrClient,
|
|
18738
|
+
NostrKeyManager3 as NostrKeyManager,
|
|
17456
18739
|
PaymentsModule,
|
|
17457
18740
|
SIGN_MESSAGE_PREFIX,
|
|
17458
18741
|
STORAGE_KEYS,
|