@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.cjs
CHANGED
|
@@ -161,7 +161,7 @@ function getAddressId(directAddress) {
|
|
|
161
161
|
const last = hash.slice(-6).toLowerCase();
|
|
162
162
|
return `DIRECT_${first}_${last}`;
|
|
163
163
|
}
|
|
164
|
-
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;
|
|
164
|
+
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;
|
|
165
165
|
var init_constants = __esm({
|
|
166
166
|
"constants.ts"() {
|
|
167
167
|
"use strict";
|
|
@@ -238,6 +238,20 @@ var init_constants = __esm({
|
|
|
238
238
|
"wss://nos.lol",
|
|
239
239
|
"wss://relay.nostr.band"
|
|
240
240
|
];
|
|
241
|
+
NOSTR_EVENT_KINDS = {
|
|
242
|
+
/** NIP-04 encrypted direct message */
|
|
243
|
+
DIRECT_MESSAGE: 4,
|
|
244
|
+
/** Token transfer (Unicity custom - 31113) */
|
|
245
|
+
TOKEN_TRANSFER: 31113,
|
|
246
|
+
/** Payment request (Unicity custom - 31115) */
|
|
247
|
+
PAYMENT_REQUEST: 31115,
|
|
248
|
+
/** Payment request response (Unicity custom - 31116) */
|
|
249
|
+
PAYMENT_REQUEST_RESPONSE: 31116,
|
|
250
|
+
/** Nametag binding (NIP-78 app-specific data) */
|
|
251
|
+
NAMETAG_BINDING: 30078,
|
|
252
|
+
/** Public broadcast */
|
|
253
|
+
BROADCAST: 1
|
|
254
|
+
};
|
|
241
255
|
NIP29_KINDS = {
|
|
242
256
|
/** Chat message sent to group */
|
|
243
257
|
CHAT_MESSAGE: 9,
|
|
@@ -319,6 +333,18 @@ var init_constants = __esm({
|
|
|
319
333
|
tokenRegistryUrl: TOKEN_REGISTRY_URL
|
|
320
334
|
}
|
|
321
335
|
};
|
|
336
|
+
TIMEOUTS = {
|
|
337
|
+
/** WebSocket connection timeout */
|
|
338
|
+
WEBSOCKET_CONNECT: 1e4,
|
|
339
|
+
/** Nostr relay reconnect delay */
|
|
340
|
+
NOSTR_RECONNECT_DELAY: 3e3,
|
|
341
|
+
/** Max reconnect attempts */
|
|
342
|
+
MAX_RECONNECT_ATTEMPTS: 5,
|
|
343
|
+
/** Proof polling interval */
|
|
344
|
+
PROOF_POLL_INTERVAL: 1e3,
|
|
345
|
+
/** Sync interval */
|
|
346
|
+
SYNC_INTERVAL: 6e4
|
|
347
|
+
};
|
|
322
348
|
}
|
|
323
349
|
});
|
|
324
350
|
|
|
@@ -855,6 +881,1102 @@ module.exports = __toCommonJS(core_exports);
|
|
|
855
881
|
init_logger();
|
|
856
882
|
init_errors();
|
|
857
883
|
|
|
884
|
+
// transport/MultiAddressTransportMux.ts
|
|
885
|
+
var import_buffer = require("buffer");
|
|
886
|
+
var import_nostr_js_sdk = require("@unicitylabs/nostr-js-sdk");
|
|
887
|
+
init_logger();
|
|
888
|
+
init_errors();
|
|
889
|
+
|
|
890
|
+
// transport/websocket.ts
|
|
891
|
+
function defaultUUIDGenerator() {
|
|
892
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
893
|
+
return crypto.randomUUID();
|
|
894
|
+
}
|
|
895
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
896
|
+
const r = Math.random() * 16 | 0;
|
|
897
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
898
|
+
return v.toString(16);
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// transport/MultiAddressTransportMux.ts
|
|
903
|
+
init_constants();
|
|
904
|
+
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
905
|
+
var COMPOSING_INDICATOR_KIND = 25050;
|
|
906
|
+
var MultiAddressTransportMux = class {
|
|
907
|
+
config;
|
|
908
|
+
storage = null;
|
|
909
|
+
// Single NostrClient — one WebSocket connection for all addresses
|
|
910
|
+
nostrClient = null;
|
|
911
|
+
// KeyManager used for NostrClient creation (uses first address or temp key)
|
|
912
|
+
primaryKeyManager = null;
|
|
913
|
+
status = "disconnected";
|
|
914
|
+
// Per-address entries
|
|
915
|
+
addresses = /* @__PURE__ */ new Map();
|
|
916
|
+
// pubkey → address index (for fast routing)
|
|
917
|
+
pubkeyToIndex = /* @__PURE__ */ new Map();
|
|
918
|
+
// Subscription IDs
|
|
919
|
+
walletSubscriptionId = null;
|
|
920
|
+
chatSubscriptionId = null;
|
|
921
|
+
chatEoseFired = false;
|
|
922
|
+
chatEoseHandlers = [];
|
|
923
|
+
// Dedup
|
|
924
|
+
processedEventIds = /* @__PURE__ */ new Set();
|
|
925
|
+
// Event callbacks (mux-level, forwarded to all adapters)
|
|
926
|
+
eventCallbacks = /* @__PURE__ */ new Set();
|
|
927
|
+
constructor(config) {
|
|
928
|
+
this.config = {
|
|
929
|
+
relays: config.relays ?? [...DEFAULT_NOSTR_RELAYS],
|
|
930
|
+
timeout: config.timeout ?? TIMEOUTS.WEBSOCKET_CONNECT,
|
|
931
|
+
autoReconnect: config.autoReconnect ?? true,
|
|
932
|
+
reconnectDelay: config.reconnectDelay ?? TIMEOUTS.NOSTR_RECONNECT_DELAY,
|
|
933
|
+
maxReconnectAttempts: config.maxReconnectAttempts ?? TIMEOUTS.MAX_RECONNECT_ATTEMPTS,
|
|
934
|
+
createWebSocket: config.createWebSocket,
|
|
935
|
+
generateUUID: config.generateUUID ?? defaultUUIDGenerator
|
|
936
|
+
};
|
|
937
|
+
this.storage = config.storage ?? null;
|
|
938
|
+
}
|
|
939
|
+
// ===========================================================================
|
|
940
|
+
// Address Management
|
|
941
|
+
// ===========================================================================
|
|
942
|
+
/**
|
|
943
|
+
* Add an address to the multiplexer.
|
|
944
|
+
* Creates an AddressTransportAdapter for this address.
|
|
945
|
+
* If already connected, updates subscriptions to include the new pubkey.
|
|
946
|
+
*/
|
|
947
|
+
async addAddress(index, identity, resolveDelegate) {
|
|
948
|
+
const existing = this.addresses.get(index);
|
|
949
|
+
if (existing) {
|
|
950
|
+
existing.identity = identity;
|
|
951
|
+
existing.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(import_buffer.Buffer.from(identity.privateKey, "hex"));
|
|
952
|
+
existing.nostrPubkey = existing.keyManager.getPublicKeyHex();
|
|
953
|
+
for (const [pk, idx] of this.pubkeyToIndex) {
|
|
954
|
+
if (idx === index) this.pubkeyToIndex.delete(pk);
|
|
955
|
+
}
|
|
956
|
+
this.pubkeyToIndex.set(existing.nostrPubkey, index);
|
|
957
|
+
logger.debug("Mux", `Updated address ${index}, pubkey: ${existing.nostrPubkey.slice(0, 16)}...`);
|
|
958
|
+
await this.updateSubscriptions();
|
|
959
|
+
return existing.adapter;
|
|
960
|
+
}
|
|
961
|
+
const keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(import_buffer.Buffer.from(identity.privateKey, "hex"));
|
|
962
|
+
const nostrPubkey = keyManager.getPublicKeyHex();
|
|
963
|
+
const adapter = new AddressTransportAdapter(this, index, identity, resolveDelegate);
|
|
964
|
+
const entry = {
|
|
965
|
+
index,
|
|
966
|
+
identity,
|
|
967
|
+
keyManager,
|
|
968
|
+
nostrPubkey,
|
|
969
|
+
adapter,
|
|
970
|
+
lastEventTs: 0,
|
|
971
|
+
fallbackSince: null
|
|
972
|
+
};
|
|
973
|
+
this.addresses.set(index, entry);
|
|
974
|
+
this.pubkeyToIndex.set(nostrPubkey, index);
|
|
975
|
+
logger.debug("Mux", `Added address ${index}, pubkey: ${nostrPubkey.slice(0, 16)}..., total: ${this.addresses.size}`);
|
|
976
|
+
if (this.addresses.size === 1) {
|
|
977
|
+
this.primaryKeyManager = keyManager;
|
|
978
|
+
}
|
|
979
|
+
if (this.isConnected()) {
|
|
980
|
+
await this.updateSubscriptions();
|
|
981
|
+
}
|
|
982
|
+
return adapter;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Remove an address from the multiplexer.
|
|
986
|
+
* Stops routing events to this address.
|
|
987
|
+
*/
|
|
988
|
+
async removeAddress(index) {
|
|
989
|
+
const entry = this.addresses.get(index);
|
|
990
|
+
if (!entry) return;
|
|
991
|
+
this.pubkeyToIndex.delete(entry.nostrPubkey);
|
|
992
|
+
this.addresses.delete(index);
|
|
993
|
+
logger.debug("Mux", `Removed address ${index}, remaining: ${this.addresses.size}`);
|
|
994
|
+
if (this.isConnected() && this.addresses.size > 0) {
|
|
995
|
+
await this.updateSubscriptions();
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Get adapter for a specific address index.
|
|
1000
|
+
*/
|
|
1001
|
+
getAdapter(index) {
|
|
1002
|
+
return this.addresses.get(index)?.adapter;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Set fallback 'since' for an address (consumed once on next subscription setup).
|
|
1006
|
+
*/
|
|
1007
|
+
setFallbackSince(index, sinceSeconds) {
|
|
1008
|
+
const entry = this.addresses.get(index);
|
|
1009
|
+
if (entry) {
|
|
1010
|
+
entry.fallbackSince = sinceSeconds;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
// ===========================================================================
|
|
1014
|
+
// Connection Management (delegated from adapters)
|
|
1015
|
+
// ===========================================================================
|
|
1016
|
+
async connect() {
|
|
1017
|
+
if (this.status === "connected") return;
|
|
1018
|
+
this.status = "connecting";
|
|
1019
|
+
try {
|
|
1020
|
+
if (!this.primaryKeyManager) {
|
|
1021
|
+
const tempKey = import_buffer.Buffer.alloc(32);
|
|
1022
|
+
crypto.getRandomValues(tempKey);
|
|
1023
|
+
this.primaryKeyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(tempKey);
|
|
1024
|
+
}
|
|
1025
|
+
this.nostrClient = new import_nostr_js_sdk.NostrClient(this.primaryKeyManager, {
|
|
1026
|
+
autoReconnect: this.config.autoReconnect,
|
|
1027
|
+
reconnectIntervalMs: this.config.reconnectDelay,
|
|
1028
|
+
maxReconnectIntervalMs: this.config.reconnectDelay * 16,
|
|
1029
|
+
pingIntervalMs: 15e3
|
|
1030
|
+
});
|
|
1031
|
+
this.nostrClient.addConnectionListener({
|
|
1032
|
+
onConnect: (url) => {
|
|
1033
|
+
logger.debug("Mux", "Connected to relay:", url);
|
|
1034
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1035
|
+
},
|
|
1036
|
+
onDisconnect: (url, reason) => {
|
|
1037
|
+
logger.debug("Mux", "Disconnected from relay:", url, "reason:", reason);
|
|
1038
|
+
},
|
|
1039
|
+
onReconnecting: (url, attempt) => {
|
|
1040
|
+
logger.debug("Mux", "Reconnecting to relay:", url, "attempt:", attempt);
|
|
1041
|
+
this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
|
|
1042
|
+
},
|
|
1043
|
+
onReconnected: (url) => {
|
|
1044
|
+
logger.debug("Mux", "Reconnected to relay:", url);
|
|
1045
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
await Promise.race([
|
|
1049
|
+
this.nostrClient.connect(...this.config.relays),
|
|
1050
|
+
new Promise(
|
|
1051
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
1052
|
+
`Transport connection timed out after ${this.config.timeout}ms`
|
|
1053
|
+
)), this.config.timeout)
|
|
1054
|
+
)
|
|
1055
|
+
]);
|
|
1056
|
+
if (!this.nostrClient.isConnected()) {
|
|
1057
|
+
throw new SphereError("Failed to connect to any relay", "TRANSPORT_ERROR");
|
|
1058
|
+
}
|
|
1059
|
+
this.status = "connected";
|
|
1060
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1061
|
+
if (this.addresses.size > 0) {
|
|
1062
|
+
await this.updateSubscriptions();
|
|
1063
|
+
}
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
this.status = "error";
|
|
1066
|
+
throw error;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
async disconnect() {
|
|
1070
|
+
if (this.nostrClient) {
|
|
1071
|
+
this.nostrClient.disconnect();
|
|
1072
|
+
this.nostrClient = null;
|
|
1073
|
+
}
|
|
1074
|
+
this.walletSubscriptionId = null;
|
|
1075
|
+
this.chatSubscriptionId = null;
|
|
1076
|
+
this.chatEoseFired = false;
|
|
1077
|
+
this.status = "disconnected";
|
|
1078
|
+
this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
|
|
1079
|
+
}
|
|
1080
|
+
isConnected() {
|
|
1081
|
+
return this.status === "connected" && this.nostrClient?.isConnected() === true;
|
|
1082
|
+
}
|
|
1083
|
+
getStatus() {
|
|
1084
|
+
return this.status;
|
|
1085
|
+
}
|
|
1086
|
+
// ===========================================================================
|
|
1087
|
+
// Relay Management
|
|
1088
|
+
// ===========================================================================
|
|
1089
|
+
getRelays() {
|
|
1090
|
+
return [...this.config.relays];
|
|
1091
|
+
}
|
|
1092
|
+
getConnectedRelays() {
|
|
1093
|
+
if (!this.nostrClient) return [];
|
|
1094
|
+
return Array.from(this.nostrClient.getConnectedRelays());
|
|
1095
|
+
}
|
|
1096
|
+
async addRelay(relayUrl) {
|
|
1097
|
+
if (this.config.relays.includes(relayUrl)) return false;
|
|
1098
|
+
this.config.relays.push(relayUrl);
|
|
1099
|
+
if (this.status === "connected" && this.nostrClient) {
|
|
1100
|
+
try {
|
|
1101
|
+
await this.nostrClient.connect(relayUrl);
|
|
1102
|
+
this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: true } });
|
|
1103
|
+
return true;
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: false, error: String(error) } });
|
|
1106
|
+
return false;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return true;
|
|
1110
|
+
}
|
|
1111
|
+
async removeRelay(relayUrl) {
|
|
1112
|
+
const idx = this.config.relays.indexOf(relayUrl);
|
|
1113
|
+
if (idx === -1) return false;
|
|
1114
|
+
this.config.relays.splice(idx, 1);
|
|
1115
|
+
this.emitEvent({ type: "transport:relay_removed", timestamp: Date.now(), data: { relay: relayUrl } });
|
|
1116
|
+
return true;
|
|
1117
|
+
}
|
|
1118
|
+
hasRelay(relayUrl) {
|
|
1119
|
+
return this.config.relays.includes(relayUrl);
|
|
1120
|
+
}
|
|
1121
|
+
isRelayConnected(relayUrl) {
|
|
1122
|
+
if (!this.nostrClient) return false;
|
|
1123
|
+
return this.nostrClient.getConnectedRelays().has(relayUrl);
|
|
1124
|
+
}
|
|
1125
|
+
// ===========================================================================
|
|
1126
|
+
// Subscription Management
|
|
1127
|
+
// ===========================================================================
|
|
1128
|
+
/**
|
|
1129
|
+
* Update Nostr subscriptions to listen for events on ALL registered address pubkeys.
|
|
1130
|
+
* Called whenever addresses are added/removed.
|
|
1131
|
+
*/
|
|
1132
|
+
async updateSubscriptions() {
|
|
1133
|
+
if (!this.nostrClient || this.addresses.size === 0) return;
|
|
1134
|
+
if (this.walletSubscriptionId) {
|
|
1135
|
+
this.nostrClient.unsubscribe(this.walletSubscriptionId);
|
|
1136
|
+
this.walletSubscriptionId = null;
|
|
1137
|
+
}
|
|
1138
|
+
if (this.chatSubscriptionId) {
|
|
1139
|
+
this.nostrClient.unsubscribe(this.chatSubscriptionId);
|
|
1140
|
+
this.chatSubscriptionId = null;
|
|
1141
|
+
}
|
|
1142
|
+
const allPubkeys = [];
|
|
1143
|
+
for (const entry of this.addresses.values()) {
|
|
1144
|
+
allPubkeys.push(entry.nostrPubkey);
|
|
1145
|
+
}
|
|
1146
|
+
logger.debug("Mux", `Subscribing for ${allPubkeys.length} address(es):`, allPubkeys.map((p) => p.slice(0, 12)).join(", "));
|
|
1147
|
+
let globalSince = Math.floor(Date.now() / 1e3);
|
|
1148
|
+
for (const entry of this.addresses.values()) {
|
|
1149
|
+
const since = await this.getAddressSince(entry);
|
|
1150
|
+
if (since < globalSince) {
|
|
1151
|
+
globalSince = since;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const walletFilter = new import_nostr_js_sdk.Filter();
|
|
1155
|
+
walletFilter.kinds = [
|
|
1156
|
+
EVENT_KINDS.DIRECT_MESSAGE,
|
|
1157
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
1158
|
+
EVENT_KINDS.PAYMENT_REQUEST,
|
|
1159
|
+
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
|
|
1160
|
+
];
|
|
1161
|
+
walletFilter["#p"] = allPubkeys;
|
|
1162
|
+
walletFilter.since = globalSince;
|
|
1163
|
+
this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
|
|
1164
|
+
onEvent: (event) => {
|
|
1165
|
+
this.handleEvent({
|
|
1166
|
+
id: event.id,
|
|
1167
|
+
kind: event.kind,
|
|
1168
|
+
content: event.content,
|
|
1169
|
+
tags: event.tags,
|
|
1170
|
+
pubkey: event.pubkey,
|
|
1171
|
+
created_at: event.created_at,
|
|
1172
|
+
sig: event.sig
|
|
1173
|
+
});
|
|
1174
|
+
},
|
|
1175
|
+
onEndOfStoredEvents: () => {
|
|
1176
|
+
logger.debug("Mux", "Wallet subscription EOSE");
|
|
1177
|
+
},
|
|
1178
|
+
onError: (_subId, error) => {
|
|
1179
|
+
logger.debug("Mux", "Wallet subscription error:", error);
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
const chatFilter = new import_nostr_js_sdk.Filter();
|
|
1183
|
+
chatFilter.kinds = [import_nostr_js_sdk.EventKinds.GIFT_WRAP];
|
|
1184
|
+
chatFilter["#p"] = allPubkeys;
|
|
1185
|
+
this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
|
|
1186
|
+
onEvent: (event) => {
|
|
1187
|
+
this.handleEvent({
|
|
1188
|
+
id: event.id,
|
|
1189
|
+
kind: event.kind,
|
|
1190
|
+
content: event.content,
|
|
1191
|
+
tags: event.tags,
|
|
1192
|
+
pubkey: event.pubkey,
|
|
1193
|
+
created_at: event.created_at,
|
|
1194
|
+
sig: event.sig
|
|
1195
|
+
});
|
|
1196
|
+
},
|
|
1197
|
+
onEndOfStoredEvents: () => {
|
|
1198
|
+
logger.debug("Mux", "Chat subscription EOSE");
|
|
1199
|
+
if (!this.chatEoseFired) {
|
|
1200
|
+
this.chatEoseFired = true;
|
|
1201
|
+
for (const handler of this.chatEoseHandlers) {
|
|
1202
|
+
try {
|
|
1203
|
+
handler();
|
|
1204
|
+
} catch {
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
1209
|
+
onError: (_subId, error) => {
|
|
1210
|
+
logger.debug("Mux", "Chat subscription error:", error);
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Determine 'since' timestamp for an address entry.
|
|
1216
|
+
*/
|
|
1217
|
+
async getAddressSince(entry) {
|
|
1218
|
+
if (this.storage) {
|
|
1219
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
|
|
1220
|
+
try {
|
|
1221
|
+
const stored = await this.storage.get(storageKey);
|
|
1222
|
+
if (stored) {
|
|
1223
|
+
const ts = parseInt(stored, 10);
|
|
1224
|
+
entry.lastEventTs = ts;
|
|
1225
|
+
entry.fallbackSince = null;
|
|
1226
|
+
return ts;
|
|
1227
|
+
} else if (entry.fallbackSince !== null) {
|
|
1228
|
+
const ts = entry.fallbackSince;
|
|
1229
|
+
entry.lastEventTs = ts;
|
|
1230
|
+
entry.fallbackSince = null;
|
|
1231
|
+
return ts;
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return Math.floor(Date.now() / 1e3);
|
|
1237
|
+
}
|
|
1238
|
+
// ===========================================================================
|
|
1239
|
+
// Event Routing
|
|
1240
|
+
// ===========================================================================
|
|
1241
|
+
/**
|
|
1242
|
+
* Route an incoming Nostr event to the correct address adapter.
|
|
1243
|
+
*/
|
|
1244
|
+
async handleEvent(event) {
|
|
1245
|
+
if (event.id && this.processedEventIds.has(event.id)) return;
|
|
1246
|
+
if (event.id) this.processedEventIds.add(event.id);
|
|
1247
|
+
try {
|
|
1248
|
+
if (event.kind === import_nostr_js_sdk.EventKinds.GIFT_WRAP) {
|
|
1249
|
+
await this.routeGiftWrap(event);
|
|
1250
|
+
} else {
|
|
1251
|
+
const recipientPubkey = this.extractRecipientPubkey(event);
|
|
1252
|
+
if (!recipientPubkey) {
|
|
1253
|
+
logger.debug("Mux", "Event has no #p tag, dropping:", event.id?.slice(0, 12));
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
const addressIndex = this.pubkeyToIndex.get(recipientPubkey);
|
|
1257
|
+
if (addressIndex === void 0) {
|
|
1258
|
+
logger.debug("Mux", "Event for unknown pubkey:", recipientPubkey.slice(0, 16), "dropping");
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const entry = this.addresses.get(addressIndex);
|
|
1262
|
+
if (!entry) return;
|
|
1263
|
+
await this.dispatchWalletEvent(entry, event);
|
|
1264
|
+
}
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
logger.debug("Mux", "Failed to handle event:", event.id?.slice(0, 12), error);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Extract recipient pubkey from event's #p tag.
|
|
1271
|
+
* Returns the first #p value that matches a known address pubkey,
|
|
1272
|
+
* or the first #p value if none match.
|
|
1273
|
+
*/
|
|
1274
|
+
extractRecipientPubkey(event) {
|
|
1275
|
+
const pTags = event.tags?.filter((t) => t[0] === "p");
|
|
1276
|
+
if (!pTags || pTags.length === 0) return null;
|
|
1277
|
+
for (const tag of pTags) {
|
|
1278
|
+
if (tag[1] && this.pubkeyToIndex.has(tag[1])) {
|
|
1279
|
+
return tag[1];
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return pTags[0]?.[1] ?? null;
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Route a gift wrap event by trying decryption with each address keyManager.
|
|
1286
|
+
*/
|
|
1287
|
+
async routeGiftWrap(event) {
|
|
1288
|
+
for (const entry of this.addresses.values()) {
|
|
1289
|
+
try {
|
|
1290
|
+
const pm = import_nostr_js_sdk.NIP17.unwrap(event, entry.keyManager);
|
|
1291
|
+
logger.debug("Mux", `Gift wrap decrypted by address ${entry.index}, sender: ${pm.senderPubkey?.slice(0, 16)}`);
|
|
1292
|
+
if (pm.senderPubkey === entry.nostrPubkey) {
|
|
1293
|
+
try {
|
|
1294
|
+
const parsed = JSON.parse(pm.content);
|
|
1295
|
+
if (parsed?.selfWrap && parsed.recipientPubkey) {
|
|
1296
|
+
const message2 = {
|
|
1297
|
+
id: parsed.originalId || pm.eventId,
|
|
1298
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1299
|
+
senderNametag: parsed.senderNametag,
|
|
1300
|
+
recipientTransportPubkey: parsed.recipientPubkey,
|
|
1301
|
+
content: parsed.text ?? "",
|
|
1302
|
+
timestamp: pm.timestamp * 1e3,
|
|
1303
|
+
encrypted: true,
|
|
1304
|
+
isSelfWrap: true
|
|
1305
|
+
};
|
|
1306
|
+
entry.adapter.dispatchMessage(message2);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
} catch {
|
|
1310
|
+
}
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
if ((0, import_nostr_js_sdk.isReadReceipt)(pm)) {
|
|
1314
|
+
if (pm.replyToEventId) {
|
|
1315
|
+
const receipt = {
|
|
1316
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1317
|
+
messageEventId: pm.replyToEventId,
|
|
1318
|
+
timestamp: pm.timestamp * 1e3
|
|
1319
|
+
};
|
|
1320
|
+
entry.adapter.dispatchReadReceipt(receipt);
|
|
1321
|
+
}
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (pm.kind === COMPOSING_INDICATOR_KIND) {
|
|
1325
|
+
let senderNametag2;
|
|
1326
|
+
let expiresIn = 3e4;
|
|
1327
|
+
try {
|
|
1328
|
+
const parsed = JSON.parse(pm.content);
|
|
1329
|
+
senderNametag2 = parsed.senderNametag || void 0;
|
|
1330
|
+
expiresIn = parsed.expiresIn ?? 3e4;
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
entry.adapter.dispatchComposingIndicator({
|
|
1334
|
+
senderPubkey: pm.senderPubkey,
|
|
1335
|
+
senderNametag: senderNametag2,
|
|
1336
|
+
expiresIn
|
|
1337
|
+
});
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
try {
|
|
1341
|
+
const parsed = JSON.parse(pm.content);
|
|
1342
|
+
if (parsed?.type === "typing") {
|
|
1343
|
+
const indicator = {
|
|
1344
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1345
|
+
senderNametag: parsed.senderNametag,
|
|
1346
|
+
timestamp: pm.timestamp * 1e3
|
|
1347
|
+
};
|
|
1348
|
+
entry.adapter.dispatchTypingIndicator(indicator);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
} catch {
|
|
1352
|
+
}
|
|
1353
|
+
if (!(0, import_nostr_js_sdk.isChatMessage)(pm)) return;
|
|
1354
|
+
let content = pm.content;
|
|
1355
|
+
let senderNametag;
|
|
1356
|
+
try {
|
|
1357
|
+
const parsed = JSON.parse(content);
|
|
1358
|
+
if (typeof parsed === "object" && parsed.text !== void 0) {
|
|
1359
|
+
content = parsed.text;
|
|
1360
|
+
senderNametag = parsed.senderNametag || void 0;
|
|
1361
|
+
}
|
|
1362
|
+
} catch {
|
|
1363
|
+
}
|
|
1364
|
+
const message = {
|
|
1365
|
+
id: event.id,
|
|
1366
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1367
|
+
senderNametag,
|
|
1368
|
+
content,
|
|
1369
|
+
timestamp: pm.timestamp * 1e3,
|
|
1370
|
+
encrypted: true
|
|
1371
|
+
};
|
|
1372
|
+
entry.adapter.dispatchMessage(message);
|
|
1373
|
+
return;
|
|
1374
|
+
} catch {
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
logger.debug("Mux", "Gift wrap could not be decrypted by any address");
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Dispatch a wallet event (non-gift-wrap) to the correct address adapter.
|
|
1382
|
+
*/
|
|
1383
|
+
async dispatchWalletEvent(entry, event) {
|
|
1384
|
+
switch (event.kind) {
|
|
1385
|
+
case EVENT_KINDS.DIRECT_MESSAGE:
|
|
1386
|
+
break;
|
|
1387
|
+
case EVENT_KINDS.TOKEN_TRANSFER:
|
|
1388
|
+
await this.handleTokenTransfer(entry, event);
|
|
1389
|
+
break;
|
|
1390
|
+
case EVENT_KINDS.PAYMENT_REQUEST:
|
|
1391
|
+
await this.handlePaymentRequest(entry, event);
|
|
1392
|
+
break;
|
|
1393
|
+
case EVENT_KINDS.PAYMENT_REQUEST_RESPONSE:
|
|
1394
|
+
await this.handlePaymentRequestResponse(entry, event);
|
|
1395
|
+
break;
|
|
1396
|
+
}
|
|
1397
|
+
if (event.created_at) {
|
|
1398
|
+
this.updateLastEventTimestamp(entry, event.created_at);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
async handleTokenTransfer(entry, event) {
|
|
1402
|
+
try {
|
|
1403
|
+
const content = await this.decryptContent(entry, event.content, event.pubkey);
|
|
1404
|
+
const payload = JSON.parse(content);
|
|
1405
|
+
const transfer = {
|
|
1406
|
+
id: event.id,
|
|
1407
|
+
senderTransportPubkey: event.pubkey,
|
|
1408
|
+
payload,
|
|
1409
|
+
timestamp: event.created_at * 1e3
|
|
1410
|
+
};
|
|
1411
|
+
entry.adapter.dispatchTokenTransfer(transfer);
|
|
1412
|
+
} catch (err) {
|
|
1413
|
+
logger.debug("Mux", `Token transfer decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
async handlePaymentRequest(entry, event) {
|
|
1417
|
+
try {
|
|
1418
|
+
const content = await this.decryptContent(entry, event.content, event.pubkey);
|
|
1419
|
+
const requestData = JSON.parse(content);
|
|
1420
|
+
const request = {
|
|
1421
|
+
id: event.id,
|
|
1422
|
+
senderTransportPubkey: event.pubkey,
|
|
1423
|
+
request: {
|
|
1424
|
+
requestId: requestData.requestId,
|
|
1425
|
+
amount: requestData.amount,
|
|
1426
|
+
coinId: requestData.coinId,
|
|
1427
|
+
message: requestData.message,
|
|
1428
|
+
recipientNametag: requestData.recipientNametag,
|
|
1429
|
+
metadata: requestData.metadata
|
|
1430
|
+
},
|
|
1431
|
+
timestamp: event.created_at * 1e3
|
|
1432
|
+
};
|
|
1433
|
+
entry.adapter.dispatchPaymentRequest(request);
|
|
1434
|
+
} catch (err) {
|
|
1435
|
+
logger.debug("Mux", `Payment request decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
async handlePaymentRequestResponse(entry, event) {
|
|
1439
|
+
try {
|
|
1440
|
+
const content = await this.decryptContent(entry, event.content, event.pubkey);
|
|
1441
|
+
const responseData = JSON.parse(content);
|
|
1442
|
+
const response = {
|
|
1443
|
+
id: event.id,
|
|
1444
|
+
responderTransportPubkey: event.pubkey,
|
|
1445
|
+
response: {
|
|
1446
|
+
requestId: responseData.requestId,
|
|
1447
|
+
responseType: responseData.responseType,
|
|
1448
|
+
message: responseData.message,
|
|
1449
|
+
transferId: responseData.transferId
|
|
1450
|
+
},
|
|
1451
|
+
timestamp: event.created_at * 1e3
|
|
1452
|
+
};
|
|
1453
|
+
entry.adapter.dispatchPaymentRequestResponse(response);
|
|
1454
|
+
} catch (err) {
|
|
1455
|
+
logger.debug("Mux", `Payment response decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
// ===========================================================================
|
|
1459
|
+
// Crypto Helpers
|
|
1460
|
+
// ===========================================================================
|
|
1461
|
+
async decryptContent(entry, content, senderPubkey) {
|
|
1462
|
+
const decrypted = await import_nostr_js_sdk.NIP04.decryptHex(
|
|
1463
|
+
content,
|
|
1464
|
+
entry.keyManager.getPrivateKeyHex(),
|
|
1465
|
+
senderPubkey
|
|
1466
|
+
);
|
|
1467
|
+
return this.stripContentPrefix(decrypted);
|
|
1468
|
+
}
|
|
1469
|
+
stripContentPrefix(content) {
|
|
1470
|
+
const prefixes = ["payment_request:", "token_transfer:", "payment_response:"];
|
|
1471
|
+
for (const prefix of prefixes) {
|
|
1472
|
+
if (content.startsWith(prefix)) return content.slice(prefix.length);
|
|
1473
|
+
}
|
|
1474
|
+
return content;
|
|
1475
|
+
}
|
|
1476
|
+
// ===========================================================================
|
|
1477
|
+
// Sending (called by adapters)
|
|
1478
|
+
// ===========================================================================
|
|
1479
|
+
/**
|
|
1480
|
+
* Create an encrypted event using a specific address's keyManager.
|
|
1481
|
+
* Used by AddressTransportAdapter for sending.
|
|
1482
|
+
*/
|
|
1483
|
+
async createAndPublishEncryptedEvent(addressIndex, kind, content, tags) {
|
|
1484
|
+
const entry = this.addresses.get(addressIndex);
|
|
1485
|
+
if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
|
|
1486
|
+
if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
|
|
1487
|
+
const recipientTag = tags.find((t) => t[0] === "p");
|
|
1488
|
+
if (!recipientTag?.[1]) throw new SphereError("No recipient pubkey in tags", "VALIDATION_ERROR");
|
|
1489
|
+
const encrypted = await import_nostr_js_sdk.NIP04.encryptHex(
|
|
1490
|
+
content,
|
|
1491
|
+
entry.keyManager.getPrivateKeyHex(),
|
|
1492
|
+
recipientTag[1]
|
|
1493
|
+
);
|
|
1494
|
+
const signedEvent = import_nostr_js_sdk.Event.create(entry.keyManager, { kind, content: encrypted, tags });
|
|
1495
|
+
const nostrEvent = import_nostr_js_sdk.Event.fromJSON({
|
|
1496
|
+
id: signedEvent.id,
|
|
1497
|
+
kind: signedEvent.kind,
|
|
1498
|
+
content: signedEvent.content,
|
|
1499
|
+
tags: signedEvent.tags,
|
|
1500
|
+
pubkey: signedEvent.pubkey,
|
|
1501
|
+
created_at: signedEvent.created_at,
|
|
1502
|
+
sig: signedEvent.sig
|
|
1503
|
+
});
|
|
1504
|
+
await this.nostrClient.publishEvent(nostrEvent);
|
|
1505
|
+
return signedEvent.id;
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Create and publish a NIP-17 gift wrap message for a specific address.
|
|
1509
|
+
*/
|
|
1510
|
+
async sendGiftWrap(addressIndex, recipientPubkey, content) {
|
|
1511
|
+
const entry = this.addresses.get(addressIndex);
|
|
1512
|
+
if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
|
|
1513
|
+
if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
|
|
1514
|
+
const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
|
|
1515
|
+
const giftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(entry.keyManager, nostrRecipient, content);
|
|
1516
|
+
const giftWrapEvent = import_nostr_js_sdk.Event.fromJSON(giftWrap);
|
|
1517
|
+
await this.nostrClient.publishEvent(giftWrapEvent);
|
|
1518
|
+
const selfPubkey = entry.keyManager.getPublicKeyHex();
|
|
1519
|
+
const senderNametag = entry.identity.nametag;
|
|
1520
|
+
const selfWrapContent = JSON.stringify({
|
|
1521
|
+
selfWrap: true,
|
|
1522
|
+
originalId: giftWrap.id,
|
|
1523
|
+
recipientPubkey,
|
|
1524
|
+
senderNametag,
|
|
1525
|
+
text: content
|
|
1526
|
+
});
|
|
1527
|
+
const selfGiftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(entry.keyManager, selfPubkey, selfWrapContent);
|
|
1528
|
+
const selfGiftWrapEvent = import_nostr_js_sdk.Event.fromJSON(selfGiftWrap);
|
|
1529
|
+
this.nostrClient.publishEvent(selfGiftWrapEvent).catch((err) => {
|
|
1530
|
+
logger.debug("Mux", "Self-wrap publish failed:", err);
|
|
1531
|
+
});
|
|
1532
|
+
return giftWrap.id;
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Publish a raw event (e.g., identity binding, broadcast).
|
|
1536
|
+
*/
|
|
1537
|
+
async publishRawEvent(addressIndex, kind, content, tags) {
|
|
1538
|
+
const entry = this.addresses.get(addressIndex);
|
|
1539
|
+
if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
|
|
1540
|
+
if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
|
|
1541
|
+
const signedEvent = import_nostr_js_sdk.Event.create(entry.keyManager, { kind, content, tags });
|
|
1542
|
+
const nostrEvent = import_nostr_js_sdk.Event.fromJSON({
|
|
1543
|
+
id: signedEvent.id,
|
|
1544
|
+
kind: signedEvent.kind,
|
|
1545
|
+
content: signedEvent.content,
|
|
1546
|
+
tags: signedEvent.tags,
|
|
1547
|
+
pubkey: signedEvent.pubkey,
|
|
1548
|
+
created_at: signedEvent.created_at,
|
|
1549
|
+
sig: signedEvent.sig
|
|
1550
|
+
});
|
|
1551
|
+
await this.nostrClient.publishEvent(nostrEvent);
|
|
1552
|
+
return signedEvent.id;
|
|
1553
|
+
}
|
|
1554
|
+
// ===========================================================================
|
|
1555
|
+
// Resolve Methods (delegates to inner — these are stateless relay queries)
|
|
1556
|
+
// ===========================================================================
|
|
1557
|
+
/**
|
|
1558
|
+
* Get the NostrClient for resolve operations.
|
|
1559
|
+
* Adapters use this for resolve*, publishIdentityBinding, etc.
|
|
1560
|
+
*/
|
|
1561
|
+
getNostrClient() {
|
|
1562
|
+
return this.nostrClient;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Get keyManager for a specific address (used by adapters for resolve/binding).
|
|
1566
|
+
*/
|
|
1567
|
+
getKeyManager(addressIndex) {
|
|
1568
|
+
return this.addresses.get(addressIndex)?.keyManager ?? null;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Get identity for a specific address.
|
|
1572
|
+
*/
|
|
1573
|
+
getIdentity(addressIndex) {
|
|
1574
|
+
return this.addresses.get(addressIndex)?.identity ?? null;
|
|
1575
|
+
}
|
|
1576
|
+
// ===========================================================================
|
|
1577
|
+
// Event timestamp persistence
|
|
1578
|
+
// ===========================================================================
|
|
1579
|
+
updateLastEventTimestamp(entry, createdAt) {
|
|
1580
|
+
if (!this.storage) return;
|
|
1581
|
+
if (createdAt <= entry.lastEventTs) return;
|
|
1582
|
+
entry.lastEventTs = createdAt;
|
|
1583
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
|
|
1584
|
+
this.storage.set(storageKey, createdAt.toString()).catch((err) => {
|
|
1585
|
+
logger.debug("Mux", "Failed to save last event timestamp:", err);
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
// ===========================================================================
|
|
1589
|
+
// Mux-level event system
|
|
1590
|
+
// ===========================================================================
|
|
1591
|
+
onTransportEvent(callback) {
|
|
1592
|
+
this.eventCallbacks.add(callback);
|
|
1593
|
+
return () => this.eventCallbacks.delete(callback);
|
|
1594
|
+
}
|
|
1595
|
+
onChatReady(handler) {
|
|
1596
|
+
if (this.chatEoseFired) {
|
|
1597
|
+
try {
|
|
1598
|
+
handler();
|
|
1599
|
+
} catch {
|
|
1600
|
+
}
|
|
1601
|
+
return () => {
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
this.chatEoseHandlers.push(handler);
|
|
1605
|
+
return () => {
|
|
1606
|
+
const idx = this.chatEoseHandlers.indexOf(handler);
|
|
1607
|
+
if (idx >= 0) this.chatEoseHandlers.splice(idx, 1);
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
emitEvent(event) {
|
|
1611
|
+
for (const cb of this.eventCallbacks) {
|
|
1612
|
+
try {
|
|
1613
|
+
cb(event);
|
|
1614
|
+
} catch {
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
for (const entry of this.addresses.values()) {
|
|
1618
|
+
entry.adapter.emitTransportEvent(event);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
// ===========================================================================
|
|
1622
|
+
// Dedup Management
|
|
1623
|
+
// ===========================================================================
|
|
1624
|
+
/**
|
|
1625
|
+
* Clear processed event IDs (e.g., on address change or periodic cleanup).
|
|
1626
|
+
*/
|
|
1627
|
+
clearProcessedEvents() {
|
|
1628
|
+
this.processedEventIds.clear();
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Get the storage adapter (for adapters that need it).
|
|
1632
|
+
*/
|
|
1633
|
+
getStorage() {
|
|
1634
|
+
return this.storage;
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Get the UUID generator.
|
|
1638
|
+
*/
|
|
1639
|
+
getUUIDGenerator() {
|
|
1640
|
+
return this.config.generateUUID;
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
var AddressTransportAdapter = class {
|
|
1644
|
+
id;
|
|
1645
|
+
name;
|
|
1646
|
+
type = "p2p";
|
|
1647
|
+
description;
|
|
1648
|
+
mux;
|
|
1649
|
+
addressIndex;
|
|
1650
|
+
identity;
|
|
1651
|
+
resolveDelegate;
|
|
1652
|
+
// Per-address handler sets
|
|
1653
|
+
messageHandlers = /* @__PURE__ */ new Set();
|
|
1654
|
+
transferHandlers = /* @__PURE__ */ new Set();
|
|
1655
|
+
paymentRequestHandlers = /* @__PURE__ */ new Set();
|
|
1656
|
+
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1657
|
+
readReceiptHandlers = /* @__PURE__ */ new Set();
|
|
1658
|
+
typingIndicatorHandlers = /* @__PURE__ */ new Set();
|
|
1659
|
+
composingHandlers = /* @__PURE__ */ new Set();
|
|
1660
|
+
instantSplitBundleHandlers = /* @__PURE__ */ new Set();
|
|
1661
|
+
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1662
|
+
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1663
|
+
pendingMessages = [];
|
|
1664
|
+
chatEoseHandlers = [];
|
|
1665
|
+
constructor(mux, addressIndex, identity, resolveDelegate) {
|
|
1666
|
+
this.mux = mux;
|
|
1667
|
+
this.addressIndex = addressIndex;
|
|
1668
|
+
this.identity = identity;
|
|
1669
|
+
this.resolveDelegate = resolveDelegate ?? null;
|
|
1670
|
+
this.id = `nostr-addr-${addressIndex}`;
|
|
1671
|
+
this.name = `Nostr Transport (address ${addressIndex})`;
|
|
1672
|
+
this.description = `P2P messaging for address index ${addressIndex}`;
|
|
1673
|
+
}
|
|
1674
|
+
// ===========================================================================
|
|
1675
|
+
// BaseProvider — delegates to mux
|
|
1676
|
+
// ===========================================================================
|
|
1677
|
+
async connect() {
|
|
1678
|
+
await this.mux.connect();
|
|
1679
|
+
}
|
|
1680
|
+
async disconnect() {
|
|
1681
|
+
}
|
|
1682
|
+
isConnected() {
|
|
1683
|
+
return this.mux.isConnected();
|
|
1684
|
+
}
|
|
1685
|
+
getStatus() {
|
|
1686
|
+
return this.mux.getStatus();
|
|
1687
|
+
}
|
|
1688
|
+
// ===========================================================================
|
|
1689
|
+
// Identity (no-op — mux manages identity via addAddress)
|
|
1690
|
+
// ===========================================================================
|
|
1691
|
+
async setIdentity(identity) {
|
|
1692
|
+
this.identity = identity;
|
|
1693
|
+
await this.mux.addAddress(this.addressIndex, identity);
|
|
1694
|
+
}
|
|
1695
|
+
// ===========================================================================
|
|
1696
|
+
// Sending — delegates to mux with this address's keyManager
|
|
1697
|
+
// ===========================================================================
|
|
1698
|
+
async sendMessage(recipientPubkey, content) {
|
|
1699
|
+
const senderNametag = this.identity.nametag;
|
|
1700
|
+
const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
|
|
1701
|
+
return this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, wrappedContent);
|
|
1702
|
+
}
|
|
1703
|
+
async sendTokenTransfer(recipientPubkey, payload) {
|
|
1704
|
+
const content = "token_transfer:" + JSON.stringify(payload);
|
|
1705
|
+
const uniqueD = `token-transfer-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1706
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1707
|
+
this.addressIndex,
|
|
1708
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
1709
|
+
content,
|
|
1710
|
+
[["p", recipientPubkey], ["d", uniqueD], ["type", "token_transfer"]]
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
async sendPaymentRequest(recipientPubkey, payload) {
|
|
1714
|
+
const requestId2 = this.mux.getUUIDGenerator()();
|
|
1715
|
+
const amount = typeof payload.amount === "bigint" ? payload.amount.toString() : payload.amount;
|
|
1716
|
+
const requestContent = {
|
|
1717
|
+
requestId: requestId2,
|
|
1718
|
+
amount,
|
|
1719
|
+
coinId: payload.coinId,
|
|
1720
|
+
message: payload.message,
|
|
1721
|
+
recipientNametag: payload.recipientNametag,
|
|
1722
|
+
deadline: Date.now() + 5 * 60 * 1e3
|
|
1723
|
+
};
|
|
1724
|
+
const content = "payment_request:" + JSON.stringify(requestContent);
|
|
1725
|
+
const tags = [
|
|
1726
|
+
["p", recipientPubkey],
|
|
1727
|
+
["type", "payment_request"],
|
|
1728
|
+
["amount", amount]
|
|
1729
|
+
];
|
|
1730
|
+
if (payload.recipientNametag) {
|
|
1731
|
+
tags.push(["recipient", payload.recipientNametag]);
|
|
1732
|
+
}
|
|
1733
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1734
|
+
this.addressIndex,
|
|
1735
|
+
EVENT_KINDS.PAYMENT_REQUEST,
|
|
1736
|
+
content,
|
|
1737
|
+
tags
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
async sendPaymentRequestResponse(recipientPubkey, response) {
|
|
1741
|
+
const content = "payment_response:" + JSON.stringify(response);
|
|
1742
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1743
|
+
this.addressIndex,
|
|
1744
|
+
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE,
|
|
1745
|
+
content,
|
|
1746
|
+
[["p", recipientPubkey], ["type", "payment_response"]]
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
async sendReadReceipt(recipientPubkey, messageEventId) {
|
|
1750
|
+
const content = JSON.stringify({ type: "read_receipt", messageEventId });
|
|
1751
|
+
await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
|
|
1752
|
+
}
|
|
1753
|
+
async sendTypingIndicator(recipientPubkey) {
|
|
1754
|
+
const content = JSON.stringify({
|
|
1755
|
+
type: "typing",
|
|
1756
|
+
senderNametag: this.identity.nametag
|
|
1757
|
+
});
|
|
1758
|
+
await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
|
|
1759
|
+
}
|
|
1760
|
+
async sendComposingIndicator(recipientPubkey, content) {
|
|
1761
|
+
await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
|
|
1762
|
+
}
|
|
1763
|
+
async sendInstantSplitBundle(recipientPubkey, bundle) {
|
|
1764
|
+
const content = "token_transfer:" + JSON.stringify({
|
|
1765
|
+
type: "instant_split",
|
|
1766
|
+
...bundle
|
|
1767
|
+
});
|
|
1768
|
+
const uniqueD = `instant-split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1769
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1770
|
+
this.addressIndex,
|
|
1771
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
1772
|
+
content,
|
|
1773
|
+
[["p", recipientPubkey], ["d", uniqueD], ["type", "instant_split"]]
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
// ===========================================================================
|
|
1777
|
+
// Subscription handlers — per-address
|
|
1778
|
+
// ===========================================================================
|
|
1779
|
+
onMessage(handler) {
|
|
1780
|
+
this.messageHandlers.add(handler);
|
|
1781
|
+
if (this.pendingMessages.length > 0) {
|
|
1782
|
+
const pending2 = this.pendingMessages;
|
|
1783
|
+
this.pendingMessages = [];
|
|
1784
|
+
for (const msg of pending2) {
|
|
1785
|
+
try {
|
|
1786
|
+
handler(msg);
|
|
1787
|
+
} catch {
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return () => this.messageHandlers.delete(handler);
|
|
1792
|
+
}
|
|
1793
|
+
onTokenTransfer(handler) {
|
|
1794
|
+
this.transferHandlers.add(handler);
|
|
1795
|
+
return () => this.transferHandlers.delete(handler);
|
|
1796
|
+
}
|
|
1797
|
+
onPaymentRequest(handler) {
|
|
1798
|
+
this.paymentRequestHandlers.add(handler);
|
|
1799
|
+
return () => this.paymentRequestHandlers.delete(handler);
|
|
1800
|
+
}
|
|
1801
|
+
onPaymentRequestResponse(handler) {
|
|
1802
|
+
this.paymentRequestResponseHandlers.add(handler);
|
|
1803
|
+
return () => this.paymentRequestResponseHandlers.delete(handler);
|
|
1804
|
+
}
|
|
1805
|
+
onReadReceipt(handler) {
|
|
1806
|
+
this.readReceiptHandlers.add(handler);
|
|
1807
|
+
return () => this.readReceiptHandlers.delete(handler);
|
|
1808
|
+
}
|
|
1809
|
+
onTypingIndicator(handler) {
|
|
1810
|
+
this.typingIndicatorHandlers.add(handler);
|
|
1811
|
+
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1812
|
+
}
|
|
1813
|
+
onComposing(handler) {
|
|
1814
|
+
this.composingHandlers.add(handler);
|
|
1815
|
+
return () => this.composingHandlers.delete(handler);
|
|
1816
|
+
}
|
|
1817
|
+
onInstantSplitReceived(handler) {
|
|
1818
|
+
this.instantSplitBundleHandlers.add(handler);
|
|
1819
|
+
return () => this.instantSplitBundleHandlers.delete(handler);
|
|
1820
|
+
}
|
|
1821
|
+
subscribeToBroadcast(tags, handler) {
|
|
1822
|
+
const key = tags.sort().join(":");
|
|
1823
|
+
if (!this.broadcastHandlers.has(key)) {
|
|
1824
|
+
this.broadcastHandlers.set(key, /* @__PURE__ */ new Set());
|
|
1825
|
+
}
|
|
1826
|
+
this.broadcastHandlers.get(key).add(handler);
|
|
1827
|
+
return () => this.broadcastHandlers.get(key)?.delete(handler);
|
|
1828
|
+
}
|
|
1829
|
+
async publishBroadcast(content, tags) {
|
|
1830
|
+
const eventTags = tags ? tags.map((t) => ["t", t]) : [];
|
|
1831
|
+
return this.mux.publishRawEvent(this.addressIndex, 30023, content, eventTags);
|
|
1832
|
+
}
|
|
1833
|
+
// ===========================================================================
|
|
1834
|
+
// Resolve methods — delegate to original NostrTransportProvider
|
|
1835
|
+
// These are stateless relay queries, shared across all addresses
|
|
1836
|
+
// ===========================================================================
|
|
1837
|
+
async resolve(identifier) {
|
|
1838
|
+
return this.resolveDelegate?.resolve?.(identifier) ?? null;
|
|
1839
|
+
}
|
|
1840
|
+
async resolveNametag(nametag) {
|
|
1841
|
+
return this.resolveDelegate?.resolveNametag?.(nametag) ?? null;
|
|
1842
|
+
}
|
|
1843
|
+
async resolveNametagInfo(nametag) {
|
|
1844
|
+
return this.resolveDelegate?.resolveNametagInfo?.(nametag) ?? null;
|
|
1845
|
+
}
|
|
1846
|
+
async resolveAddressInfo(address) {
|
|
1847
|
+
return this.resolveDelegate?.resolveAddressInfo?.(address) ?? null;
|
|
1848
|
+
}
|
|
1849
|
+
async resolveTransportPubkeyInfo(transportPubkey) {
|
|
1850
|
+
return this.resolveDelegate?.resolveTransportPubkeyInfo?.(transportPubkey) ?? null;
|
|
1851
|
+
}
|
|
1852
|
+
async discoverAddresses(transportPubkeys) {
|
|
1853
|
+
return this.resolveDelegate?.discoverAddresses?.(transportPubkeys) ?? [];
|
|
1854
|
+
}
|
|
1855
|
+
async recoverNametag() {
|
|
1856
|
+
return this.resolveDelegate?.recoverNametag?.() ?? null;
|
|
1857
|
+
}
|
|
1858
|
+
async publishIdentityBinding(chainPubkey, l1Address, directAddress, nametag) {
|
|
1859
|
+
return this.resolveDelegate?.publishIdentityBinding?.(chainPubkey, l1Address, directAddress, nametag) ?? false;
|
|
1860
|
+
}
|
|
1861
|
+
// ===========================================================================
|
|
1862
|
+
// Relay Management — delegates to mux
|
|
1863
|
+
// ===========================================================================
|
|
1864
|
+
getRelays() {
|
|
1865
|
+
return this.mux.getRelays();
|
|
1866
|
+
}
|
|
1867
|
+
getConnectedRelays() {
|
|
1868
|
+
return this.mux.getConnectedRelays();
|
|
1869
|
+
}
|
|
1870
|
+
async addRelay(relayUrl) {
|
|
1871
|
+
return this.mux.addRelay(relayUrl);
|
|
1872
|
+
}
|
|
1873
|
+
async removeRelay(relayUrl) {
|
|
1874
|
+
return this.mux.removeRelay(relayUrl);
|
|
1875
|
+
}
|
|
1876
|
+
hasRelay(relayUrl) {
|
|
1877
|
+
return this.mux.hasRelay(relayUrl);
|
|
1878
|
+
}
|
|
1879
|
+
isRelayConnected(relayUrl) {
|
|
1880
|
+
return this.mux.isRelayConnected(relayUrl);
|
|
1881
|
+
}
|
|
1882
|
+
setFallbackSince(sinceSeconds) {
|
|
1883
|
+
this.mux.setFallbackSince(this.addressIndex, sinceSeconds);
|
|
1884
|
+
}
|
|
1885
|
+
async fetchPendingEvents() {
|
|
1886
|
+
}
|
|
1887
|
+
onChatReady(handler) {
|
|
1888
|
+
return this.mux.onChatReady(handler);
|
|
1889
|
+
}
|
|
1890
|
+
// ===========================================================================
|
|
1891
|
+
// Dispatch methods — called by MultiAddressTransportMux to route events
|
|
1892
|
+
// ===========================================================================
|
|
1893
|
+
dispatchMessage(message) {
|
|
1894
|
+
if (this.messageHandlers.size === 0) {
|
|
1895
|
+
this.pendingMessages.push(message);
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
for (const handler of this.messageHandlers) {
|
|
1899
|
+
try {
|
|
1900
|
+
handler(message);
|
|
1901
|
+
} catch (e) {
|
|
1902
|
+
logger.debug("MuxAdapter", "Message handler error:", e);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
dispatchTokenTransfer(transfer) {
|
|
1907
|
+
for (const handler of this.transferHandlers) {
|
|
1908
|
+
try {
|
|
1909
|
+
handler(transfer);
|
|
1910
|
+
} catch (e) {
|
|
1911
|
+
logger.debug("MuxAdapter", "Transfer handler error:", e);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
dispatchPaymentRequest(request) {
|
|
1916
|
+
for (const handler of this.paymentRequestHandlers) {
|
|
1917
|
+
try {
|
|
1918
|
+
handler(request);
|
|
1919
|
+
} catch (e) {
|
|
1920
|
+
logger.debug("MuxAdapter", "Payment request handler error:", e);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
dispatchPaymentRequestResponse(response) {
|
|
1925
|
+
for (const handler of this.paymentRequestResponseHandlers) {
|
|
1926
|
+
try {
|
|
1927
|
+
handler(response);
|
|
1928
|
+
} catch (e) {
|
|
1929
|
+
logger.debug("MuxAdapter", "Payment response handler error:", e);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
dispatchReadReceipt(receipt) {
|
|
1934
|
+
for (const handler of this.readReceiptHandlers) {
|
|
1935
|
+
try {
|
|
1936
|
+
handler(receipt);
|
|
1937
|
+
} catch (e) {
|
|
1938
|
+
logger.debug("MuxAdapter", "Read receipt handler error:", e);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
dispatchTypingIndicator(indicator) {
|
|
1943
|
+
for (const handler of this.typingIndicatorHandlers) {
|
|
1944
|
+
try {
|
|
1945
|
+
handler(indicator);
|
|
1946
|
+
} catch (e) {
|
|
1947
|
+
logger.debug("MuxAdapter", "Typing handler error:", e);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1952
|
+
dispatchComposingIndicator(indicator) {
|
|
1953
|
+
for (const handler of this.composingHandlers) {
|
|
1954
|
+
try {
|
|
1955
|
+
handler(indicator);
|
|
1956
|
+
} catch (e) {
|
|
1957
|
+
logger.debug("MuxAdapter", "Composing handler error:", e);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
dispatchInstantSplitBundle(bundle) {
|
|
1962
|
+
for (const handler of this.instantSplitBundleHandlers) {
|
|
1963
|
+
try {
|
|
1964
|
+
handler(bundle);
|
|
1965
|
+
} catch (e) {
|
|
1966
|
+
logger.debug("MuxAdapter", "Instant split handler error:", e);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
emitTransportEvent(event) {
|
|
1971
|
+
for (const cb of this.eventCallbacks) {
|
|
1972
|
+
try {
|
|
1973
|
+
cb(event);
|
|
1974
|
+
} catch {
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
};
|
|
1979
|
+
|
|
858
1980
|
// modules/payments/L1PaymentsModule.ts
|
|
859
1981
|
init_errors();
|
|
860
1982
|
init_constants();
|
|
@@ -2656,7 +3778,7 @@ var import_MintCommitment = require("@unicitylabs/state-transition-sdk/lib/trans
|
|
|
2656
3778
|
var import_HashAlgorithm2 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
2657
3779
|
var import_UnmaskedPredicate2 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
|
|
2658
3780
|
var import_InclusionProofUtils2 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
|
|
2659
|
-
var
|
|
3781
|
+
var import_nostr_js_sdk2 = require("@unicitylabs/nostr-js-sdk");
|
|
2660
3782
|
var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
2661
3783
|
var NametagMinter = class {
|
|
2662
3784
|
client;
|
|
@@ -2680,7 +3802,7 @@ var NametagMinter = class {
|
|
|
2680
3802
|
async isNametagAvailable(nametag) {
|
|
2681
3803
|
try {
|
|
2682
3804
|
const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
|
|
2683
|
-
const cleanNametag = (0,
|
|
3805
|
+
const cleanNametag = (0, import_nostr_js_sdk2.normalizeNametag)(stripped);
|
|
2684
3806
|
const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
|
|
2685
3807
|
const isMinted = await this.client.isMinted(this.trustBase, nametagTokenId);
|
|
2686
3808
|
return !isMinted;
|
|
@@ -2698,7 +3820,7 @@ var NametagMinter = class {
|
|
|
2698
3820
|
*/
|
|
2699
3821
|
async mintNametag(nametag, ownerAddress) {
|
|
2700
3822
|
const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
|
|
2701
|
-
const cleanNametag = (0,
|
|
3823
|
+
const cleanNametag = (0, import_nostr_js_sdk2.normalizeNametag)(stripped);
|
|
2702
3824
|
this.log(`Starting mint for nametag: ${cleanNametag}`);
|
|
2703
3825
|
try {
|
|
2704
3826
|
const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
|
|
@@ -4718,6 +5840,15 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4718
5840
|
this.unsubscribePaymentRequests = null;
|
|
4719
5841
|
this.unsubscribePaymentRequestResponses?.();
|
|
4720
5842
|
this.unsubscribePaymentRequestResponses = null;
|
|
5843
|
+
this.stopProofPolling();
|
|
5844
|
+
this.proofPollingJobs.clear();
|
|
5845
|
+
this.stopResolveUnconfirmedPolling();
|
|
5846
|
+
this.unsubscribeStorageEvents();
|
|
5847
|
+
for (const [, resolver] of this.pendingResponseResolvers) {
|
|
5848
|
+
clearTimeout(resolver.timeout);
|
|
5849
|
+
resolver.reject(new Error("Address switched"));
|
|
5850
|
+
}
|
|
5851
|
+
this.pendingResponseResolvers.clear();
|
|
4721
5852
|
this.tokens.clear();
|
|
4722
5853
|
this.pendingTransfers.clear();
|
|
4723
5854
|
this.tombstones = [];
|
|
@@ -4766,6 +5897,13 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4766
5897
|
try {
|
|
4767
5898
|
const result = await provider.load();
|
|
4768
5899
|
if (result.success && result.data) {
|
|
5900
|
+
const loadedMeta = result.data?._meta;
|
|
5901
|
+
const currentL1 = this.deps.identity.l1Address;
|
|
5902
|
+
const currentChain = this.deps.identity.chainPubkey;
|
|
5903
|
+
if (loadedMeta?.address && currentL1 && loadedMeta.address !== currentL1 && loadedMeta.address !== currentChain) {
|
|
5904
|
+
logger.warn("Payments", `Load: rejecting data from provider ${id} \u2014 address mismatch (got=${loadedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
|
|
5905
|
+
continue;
|
|
5906
|
+
}
|
|
4769
5907
|
this.loadFromStorageData(result.data);
|
|
4770
5908
|
const txfData = result.data;
|
|
4771
5909
|
if (txfData._history && txfData._history.length > 0) {
|
|
@@ -4847,6 +5985,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4847
5985
|
*/
|
|
4848
5986
|
async send(request) {
|
|
4849
5987
|
this.ensureInitialized();
|
|
5988
|
+
let resolveSendTracker;
|
|
5989
|
+
const sendTracker = new Promise((r) => {
|
|
5990
|
+
resolveSendTracker = r;
|
|
5991
|
+
});
|
|
5992
|
+
this.pendingBackgroundTasks.push(sendTracker);
|
|
4850
5993
|
const result = {
|
|
4851
5994
|
id: crypto.randomUUID(),
|
|
4852
5995
|
status: "pending",
|
|
@@ -5134,6 +6277,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5134
6277
|
}
|
|
5135
6278
|
this.deps.emitEvent("transfer:failed", result);
|
|
5136
6279
|
throw error;
|
|
6280
|
+
} finally {
|
|
6281
|
+
resolveSendTracker();
|
|
5137
6282
|
}
|
|
5138
6283
|
}
|
|
5139
6284
|
/**
|
|
@@ -6146,9 +7291,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6146
7291
|
* Call this before process exit to ensure all tokens are saved.
|
|
6147
7292
|
*/
|
|
6148
7293
|
async waitForPendingOperations() {
|
|
7294
|
+
logger.debug("Payments", `waitForPendingOperations: ${this.pendingBackgroundTasks.length} pending tasks`);
|
|
6149
7295
|
if (this.pendingBackgroundTasks.length > 0) {
|
|
7296
|
+
logger.debug("Payments", "waitForPendingOperations: waiting...");
|
|
6150
7297
|
await Promise.allSettled(this.pendingBackgroundTasks);
|
|
6151
7298
|
this.pendingBackgroundTasks = [];
|
|
7299
|
+
logger.debug("Payments", "waitForPendingOperations: all tasks completed");
|
|
6152
7300
|
}
|
|
6153
7301
|
}
|
|
6154
7302
|
/**
|
|
@@ -7390,6 +8538,13 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7390
8538
|
try {
|
|
7391
8539
|
const result = await provider.sync(localData);
|
|
7392
8540
|
if (result.success && result.merged) {
|
|
8541
|
+
const mergedMeta = result.merged?._meta;
|
|
8542
|
+
const currentL1 = this.deps.identity.l1Address;
|
|
8543
|
+
const currentChain = this.deps.identity.chainPubkey;
|
|
8544
|
+
if (mergedMeta?.address && currentL1 && mergedMeta.address !== currentL1 && mergedMeta.address !== currentChain) {
|
|
8545
|
+
logger.warn("Payments", `Sync: rejecting data from provider ${providerId} \u2014 address mismatch (got=${mergedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
|
|
8546
|
+
continue;
|
|
8547
|
+
}
|
|
7393
8548
|
const savedTokens = new Map(this.tokens);
|
|
7394
8549
|
this.loadFromStorageData(result.merged);
|
|
7395
8550
|
let restoredCount = 0;
|
|
@@ -8401,6 +9556,12 @@ var CommunicationsModule = class {
|
|
|
8401
9556
|
this.unsubscribeComposing = deps.transport.onComposing?.((indicator) => {
|
|
8402
9557
|
this.handleComposingIndicator(indicator);
|
|
8403
9558
|
}) ?? null;
|
|
9559
|
+
if (deps.transport.onChatReady) {
|
|
9560
|
+
deps.transport.onChatReady(() => {
|
|
9561
|
+
const conversations = this.getConversations();
|
|
9562
|
+
deps.emitEvent("communications:ready", { conversationCount: conversations.size });
|
|
9563
|
+
});
|
|
9564
|
+
}
|
|
8404
9565
|
}
|
|
8405
9566
|
/**
|
|
8406
9567
|
* Load messages from storage.
|
|
@@ -8812,7 +9973,7 @@ function createCommunicationsModule(config) {
|
|
|
8812
9973
|
}
|
|
8813
9974
|
|
|
8814
9975
|
// modules/groupchat/GroupChatModule.ts
|
|
8815
|
-
var
|
|
9976
|
+
var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
|
|
8816
9977
|
init_logger();
|
|
8817
9978
|
init_errors();
|
|
8818
9979
|
init_constants();
|
|
@@ -8830,7 +9991,7 @@ var GroupVisibility = {
|
|
|
8830
9991
|
|
|
8831
9992
|
// modules/groupchat/GroupChatModule.ts
|
|
8832
9993
|
function createNip29Filter(data) {
|
|
8833
|
-
return new
|
|
9994
|
+
return new import_nostr_js_sdk3.Filter(data);
|
|
8834
9995
|
}
|
|
8835
9996
|
var GroupChatModule = class {
|
|
8836
9997
|
config;
|
|
@@ -8879,7 +10040,7 @@ var GroupChatModule = class {
|
|
|
8879
10040
|
}
|
|
8880
10041
|
this.deps = deps;
|
|
8881
10042
|
const secretKey = Buffer.from(deps.identity.privateKey, "hex");
|
|
8882
|
-
this.keyManager =
|
|
10043
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
8883
10044
|
}
|
|
8884
10045
|
async load() {
|
|
8885
10046
|
this.ensureInitialized();
|
|
@@ -9014,7 +10175,7 @@ var GroupChatModule = class {
|
|
|
9014
10175
|
}
|
|
9015
10176
|
this.subscriptionIds = [];
|
|
9016
10177
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9017
|
-
this.keyManager =
|
|
10178
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9018
10179
|
if (this.groups.size === 0) {
|
|
9019
10180
|
await this.restoreJoinedGroups();
|
|
9020
10181
|
} else {
|
|
@@ -9026,13 +10187,13 @@ var GroupChatModule = class {
|
|
|
9026
10187
|
this.ensureInitialized();
|
|
9027
10188
|
if (!this.keyManager) {
|
|
9028
10189
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9029
|
-
this.keyManager =
|
|
10190
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9030
10191
|
}
|
|
9031
10192
|
const primaryRelay = this.config.relays[0];
|
|
9032
10193
|
if (primaryRelay) {
|
|
9033
10194
|
await this.checkAndClearOnRelayChange(primaryRelay);
|
|
9034
10195
|
}
|
|
9035
|
-
this.client = new
|
|
10196
|
+
this.client = new import_nostr_js_sdk3.NostrClient(this.keyManager);
|
|
9036
10197
|
try {
|
|
9037
10198
|
await this.client.connect(...this.config.relays);
|
|
9038
10199
|
this.connected = true;
|
|
@@ -9043,6 +10204,7 @@ var GroupChatModule = class {
|
|
|
9043
10204
|
await this.subscribeToJoinedGroups();
|
|
9044
10205
|
}
|
|
9045
10206
|
this.deps.emitEvent("groupchat:connection", { connected: true });
|
|
10207
|
+
this.deps.emitEvent("groupchat:ready", { groupCount: this.groups.size });
|
|
9046
10208
|
} catch (error) {
|
|
9047
10209
|
logger.error("GroupChat", "Failed to connect to relays", error);
|
|
9048
10210
|
this.deps.emitEvent("groupchat:connection", { connected: false });
|
|
@@ -9290,7 +10452,7 @@ var GroupChatModule = class {
|
|
|
9290
10452
|
if (!myPubkey) return [];
|
|
9291
10453
|
const groupIdsWithMembership = /* @__PURE__ */ new Set();
|
|
9292
10454
|
await this.oneshotSubscription(
|
|
9293
|
-
new
|
|
10455
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9294
10456
|
{
|
|
9295
10457
|
onEvent: (event) => {
|
|
9296
10458
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -9341,7 +10503,7 @@ var GroupChatModule = class {
|
|
|
9341
10503
|
const memberCountsMap = /* @__PURE__ */ new Map();
|
|
9342
10504
|
await Promise.all([
|
|
9343
10505
|
this.oneshotSubscription(
|
|
9344
|
-
new
|
|
10506
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
|
|
9345
10507
|
{
|
|
9346
10508
|
onEvent: (event) => {
|
|
9347
10509
|
const group = this.parseGroupMetadata(event);
|
|
@@ -9359,7 +10521,7 @@ var GroupChatModule = class {
|
|
|
9359
10521
|
}
|
|
9360
10522
|
),
|
|
9361
10523
|
this.oneshotSubscription(
|
|
9362
|
-
new
|
|
10524
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9363
10525
|
{
|
|
9364
10526
|
onEvent: (event) => {
|
|
9365
10527
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -9677,6 +10839,19 @@ var GroupChatModule = class {
|
|
|
9677
10839
|
getMessages(groupId) {
|
|
9678
10840
|
return (this.messages.get(groupId) || []).sort((a, b) => a.timestamp - b.timestamp);
|
|
9679
10841
|
}
|
|
10842
|
+
getMessagesPage(groupId, options) {
|
|
10843
|
+
const limit = options?.limit ?? 20;
|
|
10844
|
+
const before = options?.before ?? Infinity;
|
|
10845
|
+
const groupMessages = this.messages.get(groupId) ?? [];
|
|
10846
|
+
const filtered = groupMessages.filter((m) => m.timestamp < before).sort((a, b) => b.timestamp - a.timestamp);
|
|
10847
|
+
const page = filtered.slice(0, limit);
|
|
10848
|
+
return {
|
|
10849
|
+
messages: page.reverse(),
|
|
10850
|
+
// chronological order
|
|
10851
|
+
hasMore: filtered.length > limit,
|
|
10852
|
+
oldestTimestamp: page.length > 0 ? page[0].timestamp : null
|
|
10853
|
+
};
|
|
10854
|
+
}
|
|
9680
10855
|
getMembers(groupId) {
|
|
9681
10856
|
return (this.members.get(groupId) || []).sort((a, b) => a.joinedAt - b.joinedAt);
|
|
9682
10857
|
}
|
|
@@ -9849,7 +11024,7 @@ var GroupChatModule = class {
|
|
|
9849
11024
|
if (!this.client) return /* @__PURE__ */ new Set();
|
|
9850
11025
|
const adminPubkeys = /* @__PURE__ */ new Set();
|
|
9851
11026
|
return this.oneshotSubscription(
|
|
9852
|
-
new
|
|
11027
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
|
|
9853
11028
|
{
|
|
9854
11029
|
onEvent: (event) => {
|
|
9855
11030
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -9871,7 +11046,7 @@ var GroupChatModule = class {
|
|
|
9871
11046
|
if (!this.client) return null;
|
|
9872
11047
|
let result = null;
|
|
9873
11048
|
return this.oneshotSubscription(
|
|
9874
|
-
new
|
|
11049
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
|
|
9875
11050
|
{
|
|
9876
11051
|
onEvent: (event) => {
|
|
9877
11052
|
if (!result) result = this.parseGroupMetadata(event);
|
|
@@ -9908,7 +11083,7 @@ var GroupChatModule = class {
|
|
|
9908
11083
|
if (!this.client) return [];
|
|
9909
11084
|
const members = [];
|
|
9910
11085
|
return this.oneshotSubscription(
|
|
9911
|
-
new
|
|
11086
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
|
|
9912
11087
|
{
|
|
9913
11088
|
onEvent: (event) => {
|
|
9914
11089
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -9929,7 +11104,7 @@ var GroupChatModule = class {
|
|
|
9929
11104
|
if (!this.client) return [];
|
|
9930
11105
|
const adminPubkeys = [];
|
|
9931
11106
|
return this.oneshotSubscription(
|
|
9932
|
-
new
|
|
11107
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
|
|
9933
11108
|
{
|
|
9934
11109
|
onEvent: (event) => {
|
|
9935
11110
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -13683,9 +14858,9 @@ var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign
|
|
|
13683
14858
|
var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
13684
14859
|
var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
13685
14860
|
var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
13686
|
-
var
|
|
14861
|
+
var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
|
|
13687
14862
|
function isValidNametag(nametag) {
|
|
13688
|
-
if ((0,
|
|
14863
|
+
if ((0, import_nostr_js_sdk4.isPhoneNumber)(nametag)) return true;
|
|
13689
14864
|
return /^[a-z0-9_-]{3,20}$/.test(nametag);
|
|
13690
14865
|
}
|
|
13691
14866
|
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
@@ -13729,11 +14904,18 @@ var Sphere = class _Sphere {
|
|
|
13729
14904
|
_transport;
|
|
13730
14905
|
_oracle;
|
|
13731
14906
|
_priceProvider;
|
|
13732
|
-
// Modules
|
|
14907
|
+
// Modules (single-instance — backward compat, delegates to active address)
|
|
13733
14908
|
_payments;
|
|
13734
14909
|
_communications;
|
|
13735
14910
|
_groupChat = null;
|
|
13736
14911
|
_market = null;
|
|
14912
|
+
// Per-address module instances (Phase 2: independent parallel operation)
|
|
14913
|
+
_addressModules = /* @__PURE__ */ new Map();
|
|
14914
|
+
_transportMux = null;
|
|
14915
|
+
// Stored configs for creating per-address modules
|
|
14916
|
+
_l1Config;
|
|
14917
|
+
_groupChatConfig;
|
|
14918
|
+
_marketConfig;
|
|
13737
14919
|
// Events
|
|
13738
14920
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
13739
14921
|
// Provider management
|
|
@@ -13751,6 +14933,9 @@ var Sphere = class _Sphere {
|
|
|
13751
14933
|
if (tokenStorage) {
|
|
13752
14934
|
this._tokenStorageProviders.set(tokenStorage.id, tokenStorage);
|
|
13753
14935
|
}
|
|
14936
|
+
this._l1Config = l1Config;
|
|
14937
|
+
this._groupChatConfig = groupChatConfig;
|
|
14938
|
+
this._marketConfig = marketConfig;
|
|
13754
14939
|
this._payments = createPaymentsModule({ l1: l1Config });
|
|
13755
14940
|
this._communications = createCommunicationsModule();
|
|
13756
14941
|
this._groupChat = groupChatConfig ? createGroupChatModule(groupChatConfig) : null;
|
|
@@ -15009,7 +16194,7 @@ var Sphere = class _Sphere {
|
|
|
15009
16194
|
nametags.set(0, newNametag);
|
|
15010
16195
|
}
|
|
15011
16196
|
const nametag = this._addressNametags.get(addressId)?.get(0);
|
|
15012
|
-
|
|
16197
|
+
const newIdentity = {
|
|
15013
16198
|
privateKey: addressInfo.privateKey,
|
|
15014
16199
|
chainPubkey: addressInfo.publicKey,
|
|
15015
16200
|
l1Address: addressInfo.address,
|
|
@@ -15017,20 +16202,53 @@ var Sphere = class _Sphere {
|
|
|
15017
16202
|
ipnsName: "12D3KooW" + ipnsHash,
|
|
15018
16203
|
nametag
|
|
15019
16204
|
};
|
|
16205
|
+
if (!this._addressModules.has(index)) {
|
|
16206
|
+
logger.debug("Sphere", `switchToAddress(${index}): creating per-address modules (lazy init)`);
|
|
16207
|
+
const addressTokenProviders = /* @__PURE__ */ new Map();
|
|
16208
|
+
for (const [providerId, provider] of this._tokenStorageProviders.entries()) {
|
|
16209
|
+
if (provider.createForAddress) {
|
|
16210
|
+
const newProvider = provider.createForAddress();
|
|
16211
|
+
newProvider.setIdentity(newIdentity);
|
|
16212
|
+
await newProvider.initialize();
|
|
16213
|
+
addressTokenProviders.set(providerId, newProvider);
|
|
16214
|
+
} else {
|
|
16215
|
+
logger.warn("Sphere", `Token storage provider ${providerId} does not support createForAddress, reusing shared instance`);
|
|
16216
|
+
addressTokenProviders.set(providerId, provider);
|
|
16217
|
+
}
|
|
16218
|
+
}
|
|
16219
|
+
await this.initializeAddressModules(index, newIdentity, addressTokenProviders);
|
|
16220
|
+
} else {
|
|
16221
|
+
const moduleSet = this._addressModules.get(index);
|
|
16222
|
+
if (nametag !== moduleSet.identity.nametag) {
|
|
16223
|
+
moduleSet.identity = newIdentity;
|
|
16224
|
+
const addressTransport = moduleSet.transportAdapter ?? this._transport;
|
|
16225
|
+
moduleSet.payments.initialize({
|
|
16226
|
+
identity: newIdentity,
|
|
16227
|
+
storage: this._storage,
|
|
16228
|
+
tokenStorageProviders: moduleSet.tokenStorageProviders,
|
|
16229
|
+
transport: addressTransport,
|
|
16230
|
+
oracle: this._oracle,
|
|
16231
|
+
emitEvent: this.emitEvent.bind(this),
|
|
16232
|
+
chainCode: this._masterKey?.chainCode || void 0,
|
|
16233
|
+
price: this._priceProvider ?? void 0
|
|
16234
|
+
});
|
|
16235
|
+
}
|
|
16236
|
+
}
|
|
16237
|
+
this._identity = newIdentity;
|
|
15020
16238
|
this._currentAddressIndex = index;
|
|
15021
16239
|
await this._updateCachedProxyAddress();
|
|
16240
|
+
const activeModules = this._addressModules.get(index);
|
|
16241
|
+
this._payments = activeModules.payments;
|
|
16242
|
+
this._communications = activeModules.communications;
|
|
16243
|
+
this._groupChat = activeModules.groupChat;
|
|
16244
|
+
this._market = activeModules.market;
|
|
15022
16245
|
await this._storage.set(STORAGE_KEYS_GLOBAL.CURRENT_ADDRESS_INDEX, index.toString());
|
|
15023
16246
|
this._storage.setIdentity(this._identity);
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
logger.debug("Sphere", `switchToAddress(${index}): shutdown provider=${providerId}`);
|
|
15028
|
-
await provider.shutdown();
|
|
15029
|
-
provider.setIdentity(this._identity);
|
|
15030
|
-
logger.debug("Sphere", `switchToAddress(${index}): initialize provider=${providerId}`);
|
|
15031
|
-
await provider.initialize();
|
|
16247
|
+
if (this._transport.setFallbackSince) {
|
|
16248
|
+
const fallbackTs = Math.floor(Date.now() / 1e3) - 86400;
|
|
16249
|
+
this._transport.setFallbackSince(fallbackTs);
|
|
15032
16250
|
}
|
|
15033
|
-
await this.
|
|
16251
|
+
await this._transport.setIdentity(this._identity);
|
|
15034
16252
|
this.emitEvent("identity:changed", {
|
|
15035
16253
|
l1Address: this._identity.l1Address,
|
|
15036
16254
|
directAddress: this._identity.directAddress,
|
|
@@ -15085,42 +16303,104 @@ var Sphere = class _Sphere {
|
|
|
15085
16303
|
}
|
|
15086
16304
|
}
|
|
15087
16305
|
/**
|
|
15088
|
-
*
|
|
16306
|
+
* Create a new set of per-address modules for the given index.
|
|
16307
|
+
* Each address gets its own PaymentsModule, CommunicationsModule, etc.
|
|
16308
|
+
* Modules are fully independent — they have their own token storage,
|
|
16309
|
+
* and can sync/finalize/split in background regardless of active address.
|
|
16310
|
+
*
|
|
16311
|
+
* @param index - HD address index
|
|
16312
|
+
* @param identity - Full identity for this address
|
|
16313
|
+
* @param tokenStorageProviders - Token storage providers for this address
|
|
15089
16314
|
*/
|
|
15090
|
-
async
|
|
16315
|
+
async initializeAddressModules(index, identity, tokenStorageProviders) {
|
|
15091
16316
|
const emitEvent = this.emitEvent.bind(this);
|
|
15092
|
-
this.
|
|
15093
|
-
|
|
16317
|
+
const adapter = await this.ensureTransportMux(index, identity);
|
|
16318
|
+
const addressTransport = adapter ?? this._transport;
|
|
16319
|
+
const payments = createPaymentsModule({ l1: this._l1Config });
|
|
16320
|
+
const communications = createCommunicationsModule();
|
|
16321
|
+
const groupChat = this._groupChatConfig ? createGroupChatModule(this._groupChatConfig) : null;
|
|
16322
|
+
const market = this._marketConfig ? createMarketModule(this._marketConfig) : null;
|
|
16323
|
+
payments.initialize({
|
|
16324
|
+
identity,
|
|
15094
16325
|
storage: this._storage,
|
|
15095
|
-
tokenStorageProviders
|
|
15096
|
-
transport:
|
|
16326
|
+
tokenStorageProviders,
|
|
16327
|
+
transport: addressTransport,
|
|
15097
16328
|
oracle: this._oracle,
|
|
15098
16329
|
emitEvent,
|
|
15099
16330
|
chainCode: this._masterKey?.chainCode || void 0,
|
|
15100
16331
|
price: this._priceProvider ?? void 0
|
|
15101
16332
|
});
|
|
15102
|
-
|
|
15103
|
-
identity
|
|
16333
|
+
communications.initialize({
|
|
16334
|
+
identity,
|
|
15104
16335
|
storage: this._storage,
|
|
15105
|
-
transport:
|
|
16336
|
+
transport: addressTransport,
|
|
15106
16337
|
emitEvent
|
|
15107
16338
|
});
|
|
15108
|
-
|
|
15109
|
-
identity
|
|
16339
|
+
groupChat?.initialize({
|
|
16340
|
+
identity,
|
|
15110
16341
|
storage: this._storage,
|
|
15111
16342
|
emitEvent
|
|
15112
16343
|
});
|
|
15113
|
-
|
|
15114
|
-
identity
|
|
16344
|
+
market?.initialize({
|
|
16345
|
+
identity,
|
|
15115
16346
|
emitEvent
|
|
15116
16347
|
});
|
|
15117
|
-
await
|
|
15118
|
-
await
|
|
15119
|
-
await
|
|
15120
|
-
await
|
|
15121
|
-
|
|
15122
|
-
|
|
16348
|
+
await payments.load();
|
|
16349
|
+
await communications.load();
|
|
16350
|
+
await groupChat?.load();
|
|
16351
|
+
await market?.load();
|
|
16352
|
+
const moduleSet = {
|
|
16353
|
+
index,
|
|
16354
|
+
identity,
|
|
16355
|
+
payments,
|
|
16356
|
+
communications,
|
|
16357
|
+
groupChat,
|
|
16358
|
+
market,
|
|
16359
|
+
transportAdapter: adapter,
|
|
16360
|
+
tokenStorageProviders: new Map(tokenStorageProviders),
|
|
16361
|
+
initialized: true
|
|
16362
|
+
};
|
|
16363
|
+
this._addressModules.set(index, moduleSet);
|
|
16364
|
+
logger.debug("Sphere", `Initialized per-address modules for address ${index} (transport: ${adapter ? "mux adapter" : "primary"})`);
|
|
16365
|
+
payments.sync().catch((err) => {
|
|
16366
|
+
logger.warn("Sphere", `Post-init sync failed for address ${index}:`, err);
|
|
15123
16367
|
});
|
|
16368
|
+
return moduleSet;
|
|
16369
|
+
}
|
|
16370
|
+
/**
|
|
16371
|
+
* Ensure the transport multiplexer exists and register an address.
|
|
16372
|
+
* Creates the mux on first call. Returns an AddressTransportAdapter
|
|
16373
|
+
* that routes events for this address independently.
|
|
16374
|
+
* @returns AddressTransportAdapter or null if transport is not Nostr-based
|
|
16375
|
+
*/
|
|
16376
|
+
async ensureTransportMux(index, identity) {
|
|
16377
|
+
const transport = this._transport;
|
|
16378
|
+
if (typeof transport.getWebSocketFactory !== "function" || typeof transport.getConfiguredRelays !== "function") {
|
|
16379
|
+
logger.debug("Sphere", "Transport does not support mux interface, skipping");
|
|
16380
|
+
return null;
|
|
16381
|
+
}
|
|
16382
|
+
const nostrTransport = transport;
|
|
16383
|
+
if (!this._transportMux) {
|
|
16384
|
+
this._transportMux = new MultiAddressTransportMux({
|
|
16385
|
+
relays: nostrTransport.getConfiguredRelays(),
|
|
16386
|
+
createWebSocket: nostrTransport.getWebSocketFactory(),
|
|
16387
|
+
storage: nostrTransport.getStorageAdapter() ?? void 0
|
|
16388
|
+
});
|
|
16389
|
+
await this._transportMux.connect();
|
|
16390
|
+
if (typeof nostrTransport.suppressSubscriptions === "function") {
|
|
16391
|
+
nostrTransport.suppressSubscriptions();
|
|
16392
|
+
}
|
|
16393
|
+
logger.debug("Sphere", "Transport mux created and connected");
|
|
16394
|
+
}
|
|
16395
|
+
const adapter = await this._transportMux.addAddress(index, identity, this._transport);
|
|
16396
|
+
return adapter;
|
|
16397
|
+
}
|
|
16398
|
+
/**
|
|
16399
|
+
* Get per-address modules for any address index (creates lazily if needed).
|
|
16400
|
+
* This allows accessing any address's modules without switching.
|
|
16401
|
+
*/
|
|
16402
|
+
getAddressPayments(index) {
|
|
16403
|
+
return this._addressModules.get(index)?.payments;
|
|
15124
16404
|
}
|
|
15125
16405
|
/**
|
|
15126
16406
|
* Derive address at a specific index
|
|
@@ -16041,17 +17321,40 @@ var Sphere = class _Sphere {
|
|
|
16041
17321
|
*/
|
|
16042
17322
|
cleanNametag(raw) {
|
|
16043
17323
|
const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
16044
|
-
return (0,
|
|
17324
|
+
return (0, import_nostr_js_sdk4.normalizeNametag)(stripped);
|
|
16045
17325
|
}
|
|
16046
17326
|
// ===========================================================================
|
|
16047
17327
|
// Public Methods - Lifecycle
|
|
16048
17328
|
// ===========================================================================
|
|
16049
17329
|
async destroy() {
|
|
16050
17330
|
this.cleanupProviderEventSubscriptions();
|
|
17331
|
+
for (const [idx, moduleSet] of this._addressModules.entries()) {
|
|
17332
|
+
try {
|
|
17333
|
+
moduleSet.payments.destroy();
|
|
17334
|
+
moduleSet.communications.destroy();
|
|
17335
|
+
moduleSet.groupChat?.destroy();
|
|
17336
|
+
moduleSet.market?.destroy();
|
|
17337
|
+
for (const provider of moduleSet.tokenStorageProviders.values()) {
|
|
17338
|
+
try {
|
|
17339
|
+
await provider.shutdown();
|
|
17340
|
+
} catch {
|
|
17341
|
+
}
|
|
17342
|
+
}
|
|
17343
|
+
moduleSet.tokenStorageProviders.clear();
|
|
17344
|
+
logger.debug("Sphere", `Destroyed modules for address ${idx}`);
|
|
17345
|
+
} catch (err) {
|
|
17346
|
+
logger.warn("Sphere", `Error destroying modules for address ${idx}:`, err);
|
|
17347
|
+
}
|
|
17348
|
+
}
|
|
17349
|
+
this._addressModules.clear();
|
|
16051
17350
|
this._payments.destroy();
|
|
16052
17351
|
this._communications.destroy();
|
|
16053
17352
|
this._groupChat?.destroy();
|
|
16054
17353
|
this._market?.destroy();
|
|
17354
|
+
if (this._transportMux) {
|
|
17355
|
+
await this._transportMux.disconnect();
|
|
17356
|
+
this._transportMux = null;
|
|
17357
|
+
}
|
|
16055
17358
|
await this._transport.disconnect();
|
|
16056
17359
|
await this._storage.disconnect();
|
|
16057
17360
|
await this._oracle.disconnect();
|
|
@@ -16246,6 +17549,9 @@ var Sphere = class _Sphere {
|
|
|
16246
17549
|
// ===========================================================================
|
|
16247
17550
|
async initializeProviders() {
|
|
16248
17551
|
this._storage.setIdentity(this._identity);
|
|
17552
|
+
if (this._transport.setFallbackSince) {
|
|
17553
|
+
this._transport.setFallbackSince(Math.floor(Date.now() / 1e3) - 86400);
|
|
17554
|
+
}
|
|
16249
17555
|
await this._transport.setIdentity(this._identity);
|
|
16250
17556
|
for (const provider of this._tokenStorageProviders.values()) {
|
|
16251
17557
|
provider.setIdentity(this._identity);
|
|
@@ -16336,11 +17642,13 @@ var Sphere = class _Sphere {
|
|
|
16336
17642
|
}
|
|
16337
17643
|
async initializeModules() {
|
|
16338
17644
|
const emitEvent = this.emitEvent.bind(this);
|
|
17645
|
+
const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
|
|
17646
|
+
const moduleTransport = adapter ?? this._transport;
|
|
16339
17647
|
this._payments.initialize({
|
|
16340
17648
|
identity: this._identity,
|
|
16341
17649
|
storage: this._storage,
|
|
16342
17650
|
tokenStorageProviders: this._tokenStorageProviders,
|
|
16343
|
-
transport:
|
|
17651
|
+
transport: moduleTransport,
|
|
16344
17652
|
oracle: this._oracle,
|
|
16345
17653
|
emitEvent,
|
|
16346
17654
|
// Pass chain code for L1 HD derivation
|
|
@@ -16351,7 +17659,7 @@ var Sphere = class _Sphere {
|
|
|
16351
17659
|
this._communications.initialize({
|
|
16352
17660
|
identity: this._identity,
|
|
16353
17661
|
storage: this._storage,
|
|
16354
|
-
transport:
|
|
17662
|
+
transport: moduleTransport,
|
|
16355
17663
|
emitEvent
|
|
16356
17664
|
});
|
|
16357
17665
|
this._groupChat?.initialize({
|
|
@@ -16367,6 +17675,17 @@ var Sphere = class _Sphere {
|
|
|
16367
17675
|
await this._communications.load();
|
|
16368
17676
|
await this._groupChat?.load();
|
|
16369
17677
|
await this._market?.load();
|
|
17678
|
+
this._addressModules.set(this._currentAddressIndex, {
|
|
17679
|
+
index: this._currentAddressIndex,
|
|
17680
|
+
identity: this._identity,
|
|
17681
|
+
payments: this._payments,
|
|
17682
|
+
communications: this._communications,
|
|
17683
|
+
groupChat: this._groupChat,
|
|
17684
|
+
market: this._market,
|
|
17685
|
+
transportAdapter: adapter,
|
|
17686
|
+
tokenStorageProviders: new Map(this._tokenStorageProviders),
|
|
17687
|
+
initialized: true
|
|
17688
|
+
});
|
|
16370
17689
|
}
|
|
16371
17690
|
// ===========================================================================
|
|
16372
17691
|
// Private: Helpers
|