@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/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;
|
|
@@ -8818,7 +9973,7 @@ function createCommunicationsModule(config) {
|
|
|
8818
9973
|
}
|
|
8819
9974
|
|
|
8820
9975
|
// modules/groupchat/GroupChatModule.ts
|
|
8821
|
-
var
|
|
9976
|
+
var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
|
|
8822
9977
|
init_logger();
|
|
8823
9978
|
init_errors();
|
|
8824
9979
|
init_constants();
|
|
@@ -8836,7 +9991,7 @@ var GroupVisibility = {
|
|
|
8836
9991
|
|
|
8837
9992
|
// modules/groupchat/GroupChatModule.ts
|
|
8838
9993
|
function createNip29Filter(data) {
|
|
8839
|
-
return new
|
|
9994
|
+
return new import_nostr_js_sdk3.Filter(data);
|
|
8840
9995
|
}
|
|
8841
9996
|
var GroupChatModule = class {
|
|
8842
9997
|
config;
|
|
@@ -8885,7 +10040,7 @@ var GroupChatModule = class {
|
|
|
8885
10040
|
}
|
|
8886
10041
|
this.deps = deps;
|
|
8887
10042
|
const secretKey = Buffer.from(deps.identity.privateKey, "hex");
|
|
8888
|
-
this.keyManager =
|
|
10043
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
8889
10044
|
}
|
|
8890
10045
|
async load() {
|
|
8891
10046
|
this.ensureInitialized();
|
|
@@ -9020,7 +10175,7 @@ var GroupChatModule = class {
|
|
|
9020
10175
|
}
|
|
9021
10176
|
this.subscriptionIds = [];
|
|
9022
10177
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9023
|
-
this.keyManager =
|
|
10178
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9024
10179
|
if (this.groups.size === 0) {
|
|
9025
10180
|
await this.restoreJoinedGroups();
|
|
9026
10181
|
} else {
|
|
@@ -9032,13 +10187,13 @@ var GroupChatModule = class {
|
|
|
9032
10187
|
this.ensureInitialized();
|
|
9033
10188
|
if (!this.keyManager) {
|
|
9034
10189
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9035
|
-
this.keyManager =
|
|
10190
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9036
10191
|
}
|
|
9037
10192
|
const primaryRelay = this.config.relays[0];
|
|
9038
10193
|
if (primaryRelay) {
|
|
9039
10194
|
await this.checkAndClearOnRelayChange(primaryRelay);
|
|
9040
10195
|
}
|
|
9041
|
-
this.client = new
|
|
10196
|
+
this.client = new import_nostr_js_sdk3.NostrClient(this.keyManager);
|
|
9042
10197
|
try {
|
|
9043
10198
|
await this.client.connect(...this.config.relays);
|
|
9044
10199
|
this.connected = true;
|
|
@@ -9297,7 +10452,7 @@ var GroupChatModule = class {
|
|
|
9297
10452
|
if (!myPubkey) return [];
|
|
9298
10453
|
const groupIdsWithMembership = /* @__PURE__ */ new Set();
|
|
9299
10454
|
await this.oneshotSubscription(
|
|
9300
|
-
new
|
|
10455
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9301
10456
|
{
|
|
9302
10457
|
onEvent: (event) => {
|
|
9303
10458
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -9348,7 +10503,7 @@ var GroupChatModule = class {
|
|
|
9348
10503
|
const memberCountsMap = /* @__PURE__ */ new Map();
|
|
9349
10504
|
await Promise.all([
|
|
9350
10505
|
this.oneshotSubscription(
|
|
9351
|
-
new
|
|
10506
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
|
|
9352
10507
|
{
|
|
9353
10508
|
onEvent: (event) => {
|
|
9354
10509
|
const group = this.parseGroupMetadata(event);
|
|
@@ -9366,7 +10521,7 @@ var GroupChatModule = class {
|
|
|
9366
10521
|
}
|
|
9367
10522
|
),
|
|
9368
10523
|
this.oneshotSubscription(
|
|
9369
|
-
new
|
|
10524
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9370
10525
|
{
|
|
9371
10526
|
onEvent: (event) => {
|
|
9372
10527
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -9869,7 +11024,7 @@ var GroupChatModule = class {
|
|
|
9869
11024
|
if (!this.client) return /* @__PURE__ */ new Set();
|
|
9870
11025
|
const adminPubkeys = /* @__PURE__ */ new Set();
|
|
9871
11026
|
return this.oneshotSubscription(
|
|
9872
|
-
new
|
|
11027
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
|
|
9873
11028
|
{
|
|
9874
11029
|
onEvent: (event) => {
|
|
9875
11030
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -9891,7 +11046,7 @@ var GroupChatModule = class {
|
|
|
9891
11046
|
if (!this.client) return null;
|
|
9892
11047
|
let result = null;
|
|
9893
11048
|
return this.oneshotSubscription(
|
|
9894
|
-
new
|
|
11049
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
|
|
9895
11050
|
{
|
|
9896
11051
|
onEvent: (event) => {
|
|
9897
11052
|
if (!result) result = this.parseGroupMetadata(event);
|
|
@@ -9928,7 +11083,7 @@ var GroupChatModule = class {
|
|
|
9928
11083
|
if (!this.client) return [];
|
|
9929
11084
|
const members = [];
|
|
9930
11085
|
return this.oneshotSubscription(
|
|
9931
|
-
new
|
|
11086
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
|
|
9932
11087
|
{
|
|
9933
11088
|
onEvent: (event) => {
|
|
9934
11089
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -9949,7 +11104,7 @@ var GroupChatModule = class {
|
|
|
9949
11104
|
if (!this.client) return [];
|
|
9950
11105
|
const adminPubkeys = [];
|
|
9951
11106
|
return this.oneshotSubscription(
|
|
9952
|
-
new
|
|
11107
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
|
|
9953
11108
|
{
|
|
9954
11109
|
onEvent: (event) => {
|
|
9955
11110
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -13703,9 +14858,9 @@ var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign
|
|
|
13703
14858
|
var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
13704
14859
|
var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
13705
14860
|
var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
13706
|
-
var
|
|
14861
|
+
var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
|
|
13707
14862
|
function isValidNametag(nametag) {
|
|
13708
|
-
if ((0,
|
|
14863
|
+
if ((0, import_nostr_js_sdk4.isPhoneNumber)(nametag)) return true;
|
|
13709
14864
|
return /^[a-z0-9_-]{3,20}$/.test(nametag);
|
|
13710
14865
|
}
|
|
13711
14866
|
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
@@ -13749,11 +14904,18 @@ var Sphere = class _Sphere {
|
|
|
13749
14904
|
_transport;
|
|
13750
14905
|
_oracle;
|
|
13751
14906
|
_priceProvider;
|
|
13752
|
-
// Modules
|
|
14907
|
+
// Modules (single-instance — backward compat, delegates to active address)
|
|
13753
14908
|
_payments;
|
|
13754
14909
|
_communications;
|
|
13755
14910
|
_groupChat = null;
|
|
13756
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;
|
|
13757
14919
|
// Events
|
|
13758
14920
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
13759
14921
|
// Provider management
|
|
@@ -13771,6 +14933,9 @@ var Sphere = class _Sphere {
|
|
|
13771
14933
|
if (tokenStorage) {
|
|
13772
14934
|
this._tokenStorageProviders.set(tokenStorage.id, tokenStorage);
|
|
13773
14935
|
}
|
|
14936
|
+
this._l1Config = l1Config;
|
|
14937
|
+
this._groupChatConfig = groupChatConfig;
|
|
14938
|
+
this._marketConfig = marketConfig;
|
|
13774
14939
|
this._payments = createPaymentsModule({ l1: l1Config });
|
|
13775
14940
|
this._communications = createCommunicationsModule();
|
|
13776
14941
|
this._groupChat = groupChatConfig ? createGroupChatModule(groupChatConfig) : null;
|
|
@@ -15029,7 +16194,7 @@ var Sphere = class _Sphere {
|
|
|
15029
16194
|
nametags.set(0, newNametag);
|
|
15030
16195
|
}
|
|
15031
16196
|
const nametag = this._addressNametags.get(addressId)?.get(0);
|
|
15032
|
-
|
|
16197
|
+
const newIdentity = {
|
|
15033
16198
|
privateKey: addressInfo.privateKey,
|
|
15034
16199
|
chainPubkey: addressInfo.publicKey,
|
|
15035
16200
|
l1Address: addressInfo.address,
|
|
@@ -15037,20 +16202,53 @@ var Sphere = class _Sphere {
|
|
|
15037
16202
|
ipnsName: "12D3KooW" + ipnsHash,
|
|
15038
16203
|
nametag
|
|
15039
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;
|
|
15040
16238
|
this._currentAddressIndex = index;
|
|
15041
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;
|
|
15042
16245
|
await this._storage.set(STORAGE_KEYS_GLOBAL.CURRENT_ADDRESS_INDEX, index.toString());
|
|
15043
16246
|
this._storage.setIdentity(this._identity);
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
logger.debug("Sphere", `switchToAddress(${index}): shutdown provider=${providerId}`);
|
|
15048
|
-
await provider.shutdown();
|
|
15049
|
-
provider.setIdentity(this._identity);
|
|
15050
|
-
logger.debug("Sphere", `switchToAddress(${index}): initialize provider=${providerId}`);
|
|
15051
|
-
await provider.initialize();
|
|
16247
|
+
if (this._transport.setFallbackSince) {
|
|
16248
|
+
const fallbackTs = Math.floor(Date.now() / 1e3) - 86400;
|
|
16249
|
+
this._transport.setFallbackSince(fallbackTs);
|
|
15052
16250
|
}
|
|
15053
|
-
await this.
|
|
16251
|
+
await this._transport.setIdentity(this._identity);
|
|
15054
16252
|
this.emitEvent("identity:changed", {
|
|
15055
16253
|
l1Address: this._identity.l1Address,
|
|
15056
16254
|
directAddress: this._identity.directAddress,
|
|
@@ -15105,42 +16303,104 @@ var Sphere = class _Sphere {
|
|
|
15105
16303
|
}
|
|
15106
16304
|
}
|
|
15107
16305
|
/**
|
|
15108
|
-
*
|
|
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
|
|
15109
16314
|
*/
|
|
15110
|
-
async
|
|
16315
|
+
async initializeAddressModules(index, identity, tokenStorageProviders) {
|
|
15111
16316
|
const emitEvent = this.emitEvent.bind(this);
|
|
15112
|
-
this.
|
|
15113
|
-
|
|
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,
|
|
15114
16325
|
storage: this._storage,
|
|
15115
|
-
tokenStorageProviders
|
|
15116
|
-
transport:
|
|
16326
|
+
tokenStorageProviders,
|
|
16327
|
+
transport: addressTransport,
|
|
15117
16328
|
oracle: this._oracle,
|
|
15118
16329
|
emitEvent,
|
|
15119
16330
|
chainCode: this._masterKey?.chainCode || void 0,
|
|
15120
16331
|
price: this._priceProvider ?? void 0
|
|
15121
16332
|
});
|
|
15122
|
-
|
|
15123
|
-
identity
|
|
16333
|
+
communications.initialize({
|
|
16334
|
+
identity,
|
|
15124
16335
|
storage: this._storage,
|
|
15125
|
-
transport:
|
|
16336
|
+
transport: addressTransport,
|
|
15126
16337
|
emitEvent
|
|
15127
16338
|
});
|
|
15128
|
-
|
|
15129
|
-
identity
|
|
16339
|
+
groupChat?.initialize({
|
|
16340
|
+
identity,
|
|
15130
16341
|
storage: this._storage,
|
|
15131
16342
|
emitEvent
|
|
15132
16343
|
});
|
|
15133
|
-
|
|
15134
|
-
identity
|
|
16344
|
+
market?.initialize({
|
|
16345
|
+
identity,
|
|
15135
16346
|
emitEvent
|
|
15136
16347
|
});
|
|
15137
|
-
await
|
|
15138
|
-
await
|
|
15139
|
-
await
|
|
15140
|
-
await
|
|
15141
|
-
|
|
15142
|
-
|
|
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);
|
|
15143
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;
|
|
15144
16404
|
}
|
|
15145
16405
|
/**
|
|
15146
16406
|
* Derive address at a specific index
|
|
@@ -16061,17 +17321,40 @@ var Sphere = class _Sphere {
|
|
|
16061
17321
|
*/
|
|
16062
17322
|
cleanNametag(raw) {
|
|
16063
17323
|
const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
16064
|
-
return (0,
|
|
17324
|
+
return (0, import_nostr_js_sdk4.normalizeNametag)(stripped);
|
|
16065
17325
|
}
|
|
16066
17326
|
// ===========================================================================
|
|
16067
17327
|
// Public Methods - Lifecycle
|
|
16068
17328
|
// ===========================================================================
|
|
16069
17329
|
async destroy() {
|
|
16070
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();
|
|
16071
17350
|
this._payments.destroy();
|
|
16072
17351
|
this._communications.destroy();
|
|
16073
17352
|
this._groupChat?.destroy();
|
|
16074
17353
|
this._market?.destroy();
|
|
17354
|
+
if (this._transportMux) {
|
|
17355
|
+
await this._transportMux.disconnect();
|
|
17356
|
+
this._transportMux = null;
|
|
17357
|
+
}
|
|
16075
17358
|
await this._transport.disconnect();
|
|
16076
17359
|
await this._storage.disconnect();
|
|
16077
17360
|
await this._oracle.disconnect();
|
|
@@ -16266,6 +17549,9 @@ var Sphere = class _Sphere {
|
|
|
16266
17549
|
// ===========================================================================
|
|
16267
17550
|
async initializeProviders() {
|
|
16268
17551
|
this._storage.setIdentity(this._identity);
|
|
17552
|
+
if (this._transport.setFallbackSince) {
|
|
17553
|
+
this._transport.setFallbackSince(Math.floor(Date.now() / 1e3) - 86400);
|
|
17554
|
+
}
|
|
16269
17555
|
await this._transport.setIdentity(this._identity);
|
|
16270
17556
|
for (const provider of this._tokenStorageProviders.values()) {
|
|
16271
17557
|
provider.setIdentity(this._identity);
|
|
@@ -16356,11 +17642,13 @@ var Sphere = class _Sphere {
|
|
|
16356
17642
|
}
|
|
16357
17643
|
async initializeModules() {
|
|
16358
17644
|
const emitEvent = this.emitEvent.bind(this);
|
|
17645
|
+
const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
|
|
17646
|
+
const moduleTransport = adapter ?? this._transport;
|
|
16359
17647
|
this._payments.initialize({
|
|
16360
17648
|
identity: this._identity,
|
|
16361
17649
|
storage: this._storage,
|
|
16362
17650
|
tokenStorageProviders: this._tokenStorageProviders,
|
|
16363
|
-
transport:
|
|
17651
|
+
transport: moduleTransport,
|
|
16364
17652
|
oracle: this._oracle,
|
|
16365
17653
|
emitEvent,
|
|
16366
17654
|
// Pass chain code for L1 HD derivation
|
|
@@ -16371,7 +17659,7 @@ var Sphere = class _Sphere {
|
|
|
16371
17659
|
this._communications.initialize({
|
|
16372
17660
|
identity: this._identity,
|
|
16373
17661
|
storage: this._storage,
|
|
16374
|
-
transport:
|
|
17662
|
+
transport: moduleTransport,
|
|
16375
17663
|
emitEvent
|
|
16376
17664
|
});
|
|
16377
17665
|
this._groupChat?.initialize({
|
|
@@ -16387,6 +17675,17 @@ var Sphere = class _Sphere {
|
|
|
16387
17675
|
await this._communications.load();
|
|
16388
17676
|
await this._groupChat?.load();
|
|
16389
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
|
+
});
|
|
16390
17689
|
}
|
|
16391
17690
|
// ===========================================================================
|
|
16392
17691
|
// Private: Helpers
|