@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/index.cjs
CHANGED
|
@@ -845,8 +845,8 @@ __export(index_exports, {
|
|
|
845
845
|
NETWORKS: () => NETWORKS,
|
|
846
846
|
NIP29_KINDS: () => NIP29_KINDS,
|
|
847
847
|
NOSTR_EVENT_KINDS: () => NOSTR_EVENT_KINDS,
|
|
848
|
-
NostrClient: () =>
|
|
849
|
-
NostrKeyManager: () =>
|
|
848
|
+
NostrClient: () => import_nostr_js_sdk6.NostrClient,
|
|
849
|
+
NostrKeyManager: () => import_nostr_js_sdk6.NostrKeyManager,
|
|
850
850
|
PaymentsModule: () => PaymentsModule,
|
|
851
851
|
SIGN_MESSAGE_PREFIX: () => SIGN_MESSAGE_PREFIX,
|
|
852
852
|
STORAGE_KEYS: () => STORAGE_KEYS,
|
|
@@ -862,7 +862,7 @@ __export(index_exports, {
|
|
|
862
862
|
TokenRegistry: () => TokenRegistry,
|
|
863
863
|
TokenValidator: () => TokenValidator,
|
|
864
864
|
archivedKeyFromTokenId: () => archivedKeyFromTokenId,
|
|
865
|
-
areSameNametag: () =>
|
|
865
|
+
areSameNametag: () => import_nostr_js_sdk5.areSameNametag,
|
|
866
866
|
base58Decode: () => base58Decode,
|
|
867
867
|
base58Encode: () => base58Encode2,
|
|
868
868
|
buildTxfStorageData: () => buildTxfStorageData,
|
|
@@ -884,7 +884,7 @@ __export(index_exports, {
|
|
|
884
884
|
createTokenValidator: () => createTokenValidator,
|
|
885
885
|
decodeBech32: () => decodeBech32,
|
|
886
886
|
decryptCMasterKey: () => decryptCMasterKey,
|
|
887
|
-
decryptNametag: () =>
|
|
887
|
+
decryptNametag: () => import_nostr_js_sdk5.decryptNametag,
|
|
888
888
|
decryptPrivateKey: () => decryptPrivateKey,
|
|
889
889
|
decryptTextFormatKey: () => decryptTextFormatKey,
|
|
890
890
|
deriveAddressInfo: () => deriveAddressInfo,
|
|
@@ -892,7 +892,7 @@ __export(index_exports, {
|
|
|
892
892
|
deriveKeyAtPath: () => deriveKeyAtPath,
|
|
893
893
|
doubleSha256: () => doubleSha256,
|
|
894
894
|
encodeBech32: () => encodeBech32,
|
|
895
|
-
encryptNametag: () =>
|
|
895
|
+
encryptNametag: () => import_nostr_js_sdk5.encryptNametag,
|
|
896
896
|
extractFromText: () => extractFromText,
|
|
897
897
|
findPattern: () => findPattern,
|
|
898
898
|
forkedKeyFromTokenIdAndState: () => forkedKeyFromTokenIdAndState,
|
|
@@ -917,8 +917,8 @@ __export(index_exports, {
|
|
|
917
917
|
hasUncommittedTransactions: () => hasUncommittedTransactions,
|
|
918
918
|
hasValidTxfData: () => hasValidTxfData,
|
|
919
919
|
hash160: () => hash160,
|
|
920
|
-
hashAddressForTag: () =>
|
|
921
|
-
hashNametag: () =>
|
|
920
|
+
hashAddressForTag: () => import_nostr_js_sdk5.hashAddressForTag,
|
|
921
|
+
hashNametag: () => import_nostr_js_sdk5.hashNametag,
|
|
922
922
|
hashSignMessage: () => hashSignMessage,
|
|
923
923
|
hexToBytes: () => hexToBytes,
|
|
924
924
|
identityFromMnemonicSync: () => identityFromMnemonicSync,
|
|
@@ -932,7 +932,7 @@ __export(index_exports, {
|
|
|
932
932
|
isKnownToken: () => isKnownToken,
|
|
933
933
|
isPaymentSessionTerminal: () => isPaymentSessionTerminal,
|
|
934
934
|
isPaymentSessionTimedOut: () => isPaymentSessionTimedOut,
|
|
935
|
-
isPhoneNumber: () =>
|
|
935
|
+
isPhoneNumber: () => import_nostr_js_sdk5.isPhoneNumber,
|
|
936
936
|
isSQLiteDatabase: () => isSQLiteDatabase,
|
|
937
937
|
isSphereError: () => isSphereError,
|
|
938
938
|
isTextWalletEncrypted: () => isTextWalletEncrypted,
|
|
@@ -947,7 +947,7 @@ __export(index_exports, {
|
|
|
947
947
|
loadSphere: () => loadSphere,
|
|
948
948
|
logger: () => logger,
|
|
949
949
|
mnemonicToSeedSync: () => mnemonicToSeedSync2,
|
|
950
|
-
normalizeNametag: () =>
|
|
950
|
+
normalizeNametag: () => import_nostr_js_sdk5.normalizeNametag,
|
|
951
951
|
normalizeSdkTokenToStorage: () => normalizeSdkTokenToStorage,
|
|
952
952
|
objectToTxf: () => objectToTxf,
|
|
953
953
|
parseAndDecryptWalletDat: () => parseAndDecryptWalletDat,
|
|
@@ -979,6 +979,1102 @@ module.exports = __toCommonJS(index_exports);
|
|
|
979
979
|
init_logger();
|
|
980
980
|
init_errors();
|
|
981
981
|
|
|
982
|
+
// transport/MultiAddressTransportMux.ts
|
|
983
|
+
var import_buffer = require("buffer");
|
|
984
|
+
var import_nostr_js_sdk = require("@unicitylabs/nostr-js-sdk");
|
|
985
|
+
init_logger();
|
|
986
|
+
init_errors();
|
|
987
|
+
|
|
988
|
+
// transport/websocket.ts
|
|
989
|
+
function defaultUUIDGenerator() {
|
|
990
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
991
|
+
return crypto.randomUUID();
|
|
992
|
+
}
|
|
993
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
994
|
+
const r = Math.random() * 16 | 0;
|
|
995
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
996
|
+
return v.toString(16);
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// transport/MultiAddressTransportMux.ts
|
|
1001
|
+
init_constants();
|
|
1002
|
+
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
1003
|
+
var COMPOSING_INDICATOR_KIND = 25050;
|
|
1004
|
+
var MultiAddressTransportMux = class {
|
|
1005
|
+
config;
|
|
1006
|
+
storage = null;
|
|
1007
|
+
// Single NostrClient — one WebSocket connection for all addresses
|
|
1008
|
+
nostrClient = null;
|
|
1009
|
+
// KeyManager used for NostrClient creation (uses first address or temp key)
|
|
1010
|
+
primaryKeyManager = null;
|
|
1011
|
+
status = "disconnected";
|
|
1012
|
+
// Per-address entries
|
|
1013
|
+
addresses = /* @__PURE__ */ new Map();
|
|
1014
|
+
// pubkey → address index (for fast routing)
|
|
1015
|
+
pubkeyToIndex = /* @__PURE__ */ new Map();
|
|
1016
|
+
// Subscription IDs
|
|
1017
|
+
walletSubscriptionId = null;
|
|
1018
|
+
chatSubscriptionId = null;
|
|
1019
|
+
chatEoseFired = false;
|
|
1020
|
+
chatEoseHandlers = [];
|
|
1021
|
+
// Dedup
|
|
1022
|
+
processedEventIds = /* @__PURE__ */ new Set();
|
|
1023
|
+
// Event callbacks (mux-level, forwarded to all adapters)
|
|
1024
|
+
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1025
|
+
constructor(config) {
|
|
1026
|
+
this.config = {
|
|
1027
|
+
relays: config.relays ?? [...DEFAULT_NOSTR_RELAYS],
|
|
1028
|
+
timeout: config.timeout ?? TIMEOUTS.WEBSOCKET_CONNECT,
|
|
1029
|
+
autoReconnect: config.autoReconnect ?? true,
|
|
1030
|
+
reconnectDelay: config.reconnectDelay ?? TIMEOUTS.NOSTR_RECONNECT_DELAY,
|
|
1031
|
+
maxReconnectAttempts: config.maxReconnectAttempts ?? TIMEOUTS.MAX_RECONNECT_ATTEMPTS,
|
|
1032
|
+
createWebSocket: config.createWebSocket,
|
|
1033
|
+
generateUUID: config.generateUUID ?? defaultUUIDGenerator
|
|
1034
|
+
};
|
|
1035
|
+
this.storage = config.storage ?? null;
|
|
1036
|
+
}
|
|
1037
|
+
// ===========================================================================
|
|
1038
|
+
// Address Management
|
|
1039
|
+
// ===========================================================================
|
|
1040
|
+
/**
|
|
1041
|
+
* Add an address to the multiplexer.
|
|
1042
|
+
* Creates an AddressTransportAdapter for this address.
|
|
1043
|
+
* If already connected, updates subscriptions to include the new pubkey.
|
|
1044
|
+
*/
|
|
1045
|
+
async addAddress(index, identity, resolveDelegate) {
|
|
1046
|
+
const existing = this.addresses.get(index);
|
|
1047
|
+
if (existing) {
|
|
1048
|
+
existing.identity = identity;
|
|
1049
|
+
existing.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(import_buffer.Buffer.from(identity.privateKey, "hex"));
|
|
1050
|
+
existing.nostrPubkey = existing.keyManager.getPublicKeyHex();
|
|
1051
|
+
for (const [pk, idx] of this.pubkeyToIndex) {
|
|
1052
|
+
if (idx === index) this.pubkeyToIndex.delete(pk);
|
|
1053
|
+
}
|
|
1054
|
+
this.pubkeyToIndex.set(existing.nostrPubkey, index);
|
|
1055
|
+
logger.debug("Mux", `Updated address ${index}, pubkey: ${existing.nostrPubkey.slice(0, 16)}...`);
|
|
1056
|
+
await this.updateSubscriptions();
|
|
1057
|
+
return existing.adapter;
|
|
1058
|
+
}
|
|
1059
|
+
const keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(import_buffer.Buffer.from(identity.privateKey, "hex"));
|
|
1060
|
+
const nostrPubkey = keyManager.getPublicKeyHex();
|
|
1061
|
+
const adapter = new AddressTransportAdapter(this, index, identity, resolveDelegate);
|
|
1062
|
+
const entry = {
|
|
1063
|
+
index,
|
|
1064
|
+
identity,
|
|
1065
|
+
keyManager,
|
|
1066
|
+
nostrPubkey,
|
|
1067
|
+
adapter,
|
|
1068
|
+
lastEventTs: 0,
|
|
1069
|
+
fallbackSince: null
|
|
1070
|
+
};
|
|
1071
|
+
this.addresses.set(index, entry);
|
|
1072
|
+
this.pubkeyToIndex.set(nostrPubkey, index);
|
|
1073
|
+
logger.debug("Mux", `Added address ${index}, pubkey: ${nostrPubkey.slice(0, 16)}..., total: ${this.addresses.size}`);
|
|
1074
|
+
if (this.addresses.size === 1) {
|
|
1075
|
+
this.primaryKeyManager = keyManager;
|
|
1076
|
+
}
|
|
1077
|
+
if (this.isConnected()) {
|
|
1078
|
+
await this.updateSubscriptions();
|
|
1079
|
+
}
|
|
1080
|
+
return adapter;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Remove an address from the multiplexer.
|
|
1084
|
+
* Stops routing events to this address.
|
|
1085
|
+
*/
|
|
1086
|
+
async removeAddress(index) {
|
|
1087
|
+
const entry = this.addresses.get(index);
|
|
1088
|
+
if (!entry) return;
|
|
1089
|
+
this.pubkeyToIndex.delete(entry.nostrPubkey);
|
|
1090
|
+
this.addresses.delete(index);
|
|
1091
|
+
logger.debug("Mux", `Removed address ${index}, remaining: ${this.addresses.size}`);
|
|
1092
|
+
if (this.isConnected() && this.addresses.size > 0) {
|
|
1093
|
+
await this.updateSubscriptions();
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Get adapter for a specific address index.
|
|
1098
|
+
*/
|
|
1099
|
+
getAdapter(index) {
|
|
1100
|
+
return this.addresses.get(index)?.adapter;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Set fallback 'since' for an address (consumed once on next subscription setup).
|
|
1104
|
+
*/
|
|
1105
|
+
setFallbackSince(index, sinceSeconds) {
|
|
1106
|
+
const entry = this.addresses.get(index);
|
|
1107
|
+
if (entry) {
|
|
1108
|
+
entry.fallbackSince = sinceSeconds;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
// ===========================================================================
|
|
1112
|
+
// Connection Management (delegated from adapters)
|
|
1113
|
+
// ===========================================================================
|
|
1114
|
+
async connect() {
|
|
1115
|
+
if (this.status === "connected") return;
|
|
1116
|
+
this.status = "connecting";
|
|
1117
|
+
try {
|
|
1118
|
+
if (!this.primaryKeyManager) {
|
|
1119
|
+
const tempKey = import_buffer.Buffer.alloc(32);
|
|
1120
|
+
crypto.getRandomValues(tempKey);
|
|
1121
|
+
this.primaryKeyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(tempKey);
|
|
1122
|
+
}
|
|
1123
|
+
this.nostrClient = new import_nostr_js_sdk.NostrClient(this.primaryKeyManager, {
|
|
1124
|
+
autoReconnect: this.config.autoReconnect,
|
|
1125
|
+
reconnectIntervalMs: this.config.reconnectDelay,
|
|
1126
|
+
maxReconnectIntervalMs: this.config.reconnectDelay * 16,
|
|
1127
|
+
pingIntervalMs: 15e3
|
|
1128
|
+
});
|
|
1129
|
+
this.nostrClient.addConnectionListener({
|
|
1130
|
+
onConnect: (url) => {
|
|
1131
|
+
logger.debug("Mux", "Connected to relay:", url);
|
|
1132
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1133
|
+
},
|
|
1134
|
+
onDisconnect: (url, reason) => {
|
|
1135
|
+
logger.debug("Mux", "Disconnected from relay:", url, "reason:", reason);
|
|
1136
|
+
},
|
|
1137
|
+
onReconnecting: (url, attempt) => {
|
|
1138
|
+
logger.debug("Mux", "Reconnecting to relay:", url, "attempt:", attempt);
|
|
1139
|
+
this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
|
|
1140
|
+
},
|
|
1141
|
+
onReconnected: (url) => {
|
|
1142
|
+
logger.debug("Mux", "Reconnected to relay:", url);
|
|
1143
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
await Promise.race([
|
|
1147
|
+
this.nostrClient.connect(...this.config.relays),
|
|
1148
|
+
new Promise(
|
|
1149
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
1150
|
+
`Transport connection timed out after ${this.config.timeout}ms`
|
|
1151
|
+
)), this.config.timeout)
|
|
1152
|
+
)
|
|
1153
|
+
]);
|
|
1154
|
+
if (!this.nostrClient.isConnected()) {
|
|
1155
|
+
throw new SphereError("Failed to connect to any relay", "TRANSPORT_ERROR");
|
|
1156
|
+
}
|
|
1157
|
+
this.status = "connected";
|
|
1158
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1159
|
+
if (this.addresses.size > 0) {
|
|
1160
|
+
await this.updateSubscriptions();
|
|
1161
|
+
}
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
this.status = "error";
|
|
1164
|
+
throw error;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
async disconnect() {
|
|
1168
|
+
if (this.nostrClient) {
|
|
1169
|
+
this.nostrClient.disconnect();
|
|
1170
|
+
this.nostrClient = null;
|
|
1171
|
+
}
|
|
1172
|
+
this.walletSubscriptionId = null;
|
|
1173
|
+
this.chatSubscriptionId = null;
|
|
1174
|
+
this.chatEoseFired = false;
|
|
1175
|
+
this.status = "disconnected";
|
|
1176
|
+
this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
|
|
1177
|
+
}
|
|
1178
|
+
isConnected() {
|
|
1179
|
+
return this.status === "connected" && this.nostrClient?.isConnected() === true;
|
|
1180
|
+
}
|
|
1181
|
+
getStatus() {
|
|
1182
|
+
return this.status;
|
|
1183
|
+
}
|
|
1184
|
+
// ===========================================================================
|
|
1185
|
+
// Relay Management
|
|
1186
|
+
// ===========================================================================
|
|
1187
|
+
getRelays() {
|
|
1188
|
+
return [...this.config.relays];
|
|
1189
|
+
}
|
|
1190
|
+
getConnectedRelays() {
|
|
1191
|
+
if (!this.nostrClient) return [];
|
|
1192
|
+
return Array.from(this.nostrClient.getConnectedRelays());
|
|
1193
|
+
}
|
|
1194
|
+
async addRelay(relayUrl) {
|
|
1195
|
+
if (this.config.relays.includes(relayUrl)) return false;
|
|
1196
|
+
this.config.relays.push(relayUrl);
|
|
1197
|
+
if (this.status === "connected" && this.nostrClient) {
|
|
1198
|
+
try {
|
|
1199
|
+
await this.nostrClient.connect(relayUrl);
|
|
1200
|
+
this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: true } });
|
|
1201
|
+
return true;
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
this.emitEvent({ type: "transport:relay_added", timestamp: Date.now(), data: { relay: relayUrl, connected: false, error: String(error) } });
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return true;
|
|
1208
|
+
}
|
|
1209
|
+
async removeRelay(relayUrl) {
|
|
1210
|
+
const idx = this.config.relays.indexOf(relayUrl);
|
|
1211
|
+
if (idx === -1) return false;
|
|
1212
|
+
this.config.relays.splice(idx, 1);
|
|
1213
|
+
this.emitEvent({ type: "transport:relay_removed", timestamp: Date.now(), data: { relay: relayUrl } });
|
|
1214
|
+
return true;
|
|
1215
|
+
}
|
|
1216
|
+
hasRelay(relayUrl) {
|
|
1217
|
+
return this.config.relays.includes(relayUrl);
|
|
1218
|
+
}
|
|
1219
|
+
isRelayConnected(relayUrl) {
|
|
1220
|
+
if (!this.nostrClient) return false;
|
|
1221
|
+
return this.nostrClient.getConnectedRelays().has(relayUrl);
|
|
1222
|
+
}
|
|
1223
|
+
// ===========================================================================
|
|
1224
|
+
// Subscription Management
|
|
1225
|
+
// ===========================================================================
|
|
1226
|
+
/**
|
|
1227
|
+
* Update Nostr subscriptions to listen for events on ALL registered address pubkeys.
|
|
1228
|
+
* Called whenever addresses are added/removed.
|
|
1229
|
+
*/
|
|
1230
|
+
async updateSubscriptions() {
|
|
1231
|
+
if (!this.nostrClient || this.addresses.size === 0) return;
|
|
1232
|
+
if (this.walletSubscriptionId) {
|
|
1233
|
+
this.nostrClient.unsubscribe(this.walletSubscriptionId);
|
|
1234
|
+
this.walletSubscriptionId = null;
|
|
1235
|
+
}
|
|
1236
|
+
if (this.chatSubscriptionId) {
|
|
1237
|
+
this.nostrClient.unsubscribe(this.chatSubscriptionId);
|
|
1238
|
+
this.chatSubscriptionId = null;
|
|
1239
|
+
}
|
|
1240
|
+
const allPubkeys = [];
|
|
1241
|
+
for (const entry of this.addresses.values()) {
|
|
1242
|
+
allPubkeys.push(entry.nostrPubkey);
|
|
1243
|
+
}
|
|
1244
|
+
logger.debug("Mux", `Subscribing for ${allPubkeys.length} address(es):`, allPubkeys.map((p) => p.slice(0, 12)).join(", "));
|
|
1245
|
+
let globalSince = Math.floor(Date.now() / 1e3);
|
|
1246
|
+
for (const entry of this.addresses.values()) {
|
|
1247
|
+
const since = await this.getAddressSince(entry);
|
|
1248
|
+
if (since < globalSince) {
|
|
1249
|
+
globalSince = since;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
const walletFilter = new import_nostr_js_sdk.Filter();
|
|
1253
|
+
walletFilter.kinds = [
|
|
1254
|
+
EVENT_KINDS.DIRECT_MESSAGE,
|
|
1255
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
1256
|
+
EVENT_KINDS.PAYMENT_REQUEST,
|
|
1257
|
+
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
|
|
1258
|
+
];
|
|
1259
|
+
walletFilter["#p"] = allPubkeys;
|
|
1260
|
+
walletFilter.since = globalSince;
|
|
1261
|
+
this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
|
|
1262
|
+
onEvent: (event) => {
|
|
1263
|
+
this.handleEvent({
|
|
1264
|
+
id: event.id,
|
|
1265
|
+
kind: event.kind,
|
|
1266
|
+
content: event.content,
|
|
1267
|
+
tags: event.tags,
|
|
1268
|
+
pubkey: event.pubkey,
|
|
1269
|
+
created_at: event.created_at,
|
|
1270
|
+
sig: event.sig
|
|
1271
|
+
});
|
|
1272
|
+
},
|
|
1273
|
+
onEndOfStoredEvents: () => {
|
|
1274
|
+
logger.debug("Mux", "Wallet subscription EOSE");
|
|
1275
|
+
},
|
|
1276
|
+
onError: (_subId, error) => {
|
|
1277
|
+
logger.debug("Mux", "Wallet subscription error:", error);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
const chatFilter = new import_nostr_js_sdk.Filter();
|
|
1281
|
+
chatFilter.kinds = [import_nostr_js_sdk.EventKinds.GIFT_WRAP];
|
|
1282
|
+
chatFilter["#p"] = allPubkeys;
|
|
1283
|
+
this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
|
|
1284
|
+
onEvent: (event) => {
|
|
1285
|
+
this.handleEvent({
|
|
1286
|
+
id: event.id,
|
|
1287
|
+
kind: event.kind,
|
|
1288
|
+
content: event.content,
|
|
1289
|
+
tags: event.tags,
|
|
1290
|
+
pubkey: event.pubkey,
|
|
1291
|
+
created_at: event.created_at,
|
|
1292
|
+
sig: event.sig
|
|
1293
|
+
});
|
|
1294
|
+
},
|
|
1295
|
+
onEndOfStoredEvents: () => {
|
|
1296
|
+
logger.debug("Mux", "Chat subscription EOSE");
|
|
1297
|
+
if (!this.chatEoseFired) {
|
|
1298
|
+
this.chatEoseFired = true;
|
|
1299
|
+
for (const handler of this.chatEoseHandlers) {
|
|
1300
|
+
try {
|
|
1301
|
+
handler();
|
|
1302
|
+
} catch {
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
onError: (_subId, error) => {
|
|
1308
|
+
logger.debug("Mux", "Chat subscription error:", error);
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Determine 'since' timestamp for an address entry.
|
|
1314
|
+
*/
|
|
1315
|
+
async getAddressSince(entry) {
|
|
1316
|
+
if (this.storage) {
|
|
1317
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
|
|
1318
|
+
try {
|
|
1319
|
+
const stored = await this.storage.get(storageKey);
|
|
1320
|
+
if (stored) {
|
|
1321
|
+
const ts = parseInt(stored, 10);
|
|
1322
|
+
entry.lastEventTs = ts;
|
|
1323
|
+
entry.fallbackSince = null;
|
|
1324
|
+
return ts;
|
|
1325
|
+
} else if (entry.fallbackSince !== null) {
|
|
1326
|
+
const ts = entry.fallbackSince;
|
|
1327
|
+
entry.lastEventTs = ts;
|
|
1328
|
+
entry.fallbackSince = null;
|
|
1329
|
+
return ts;
|
|
1330
|
+
}
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return Math.floor(Date.now() / 1e3);
|
|
1335
|
+
}
|
|
1336
|
+
// ===========================================================================
|
|
1337
|
+
// Event Routing
|
|
1338
|
+
// ===========================================================================
|
|
1339
|
+
/**
|
|
1340
|
+
* Route an incoming Nostr event to the correct address adapter.
|
|
1341
|
+
*/
|
|
1342
|
+
async handleEvent(event) {
|
|
1343
|
+
if (event.id && this.processedEventIds.has(event.id)) return;
|
|
1344
|
+
if (event.id) this.processedEventIds.add(event.id);
|
|
1345
|
+
try {
|
|
1346
|
+
if (event.kind === import_nostr_js_sdk.EventKinds.GIFT_WRAP) {
|
|
1347
|
+
await this.routeGiftWrap(event);
|
|
1348
|
+
} else {
|
|
1349
|
+
const recipientPubkey = this.extractRecipientPubkey(event);
|
|
1350
|
+
if (!recipientPubkey) {
|
|
1351
|
+
logger.debug("Mux", "Event has no #p tag, dropping:", event.id?.slice(0, 12));
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
const addressIndex = this.pubkeyToIndex.get(recipientPubkey);
|
|
1355
|
+
if (addressIndex === void 0) {
|
|
1356
|
+
logger.debug("Mux", "Event for unknown pubkey:", recipientPubkey.slice(0, 16), "dropping");
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
const entry = this.addresses.get(addressIndex);
|
|
1360
|
+
if (!entry) return;
|
|
1361
|
+
await this.dispatchWalletEvent(entry, event);
|
|
1362
|
+
}
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
logger.debug("Mux", "Failed to handle event:", event.id?.slice(0, 12), error);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Extract recipient pubkey from event's #p tag.
|
|
1369
|
+
* Returns the first #p value that matches a known address pubkey,
|
|
1370
|
+
* or the first #p value if none match.
|
|
1371
|
+
*/
|
|
1372
|
+
extractRecipientPubkey(event) {
|
|
1373
|
+
const pTags = event.tags?.filter((t) => t[0] === "p");
|
|
1374
|
+
if (!pTags || pTags.length === 0) return null;
|
|
1375
|
+
for (const tag of pTags) {
|
|
1376
|
+
if (tag[1] && this.pubkeyToIndex.has(tag[1])) {
|
|
1377
|
+
return tag[1];
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return pTags[0]?.[1] ?? null;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Route a gift wrap event by trying decryption with each address keyManager.
|
|
1384
|
+
*/
|
|
1385
|
+
async routeGiftWrap(event) {
|
|
1386
|
+
for (const entry of this.addresses.values()) {
|
|
1387
|
+
try {
|
|
1388
|
+
const pm = import_nostr_js_sdk.NIP17.unwrap(event, entry.keyManager);
|
|
1389
|
+
logger.debug("Mux", `Gift wrap decrypted by address ${entry.index}, sender: ${pm.senderPubkey?.slice(0, 16)}`);
|
|
1390
|
+
if (pm.senderPubkey === entry.nostrPubkey) {
|
|
1391
|
+
try {
|
|
1392
|
+
const parsed = JSON.parse(pm.content);
|
|
1393
|
+
if (parsed?.selfWrap && parsed.recipientPubkey) {
|
|
1394
|
+
const message2 = {
|
|
1395
|
+
id: parsed.originalId || pm.eventId,
|
|
1396
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1397
|
+
senderNametag: parsed.senderNametag,
|
|
1398
|
+
recipientTransportPubkey: parsed.recipientPubkey,
|
|
1399
|
+
content: parsed.text ?? "",
|
|
1400
|
+
timestamp: pm.timestamp * 1e3,
|
|
1401
|
+
encrypted: true,
|
|
1402
|
+
isSelfWrap: true
|
|
1403
|
+
};
|
|
1404
|
+
entry.adapter.dispatchMessage(message2);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
} catch {
|
|
1408
|
+
}
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
if ((0, import_nostr_js_sdk.isReadReceipt)(pm)) {
|
|
1412
|
+
if (pm.replyToEventId) {
|
|
1413
|
+
const receipt = {
|
|
1414
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1415
|
+
messageEventId: pm.replyToEventId,
|
|
1416
|
+
timestamp: pm.timestamp * 1e3
|
|
1417
|
+
};
|
|
1418
|
+
entry.adapter.dispatchReadReceipt(receipt);
|
|
1419
|
+
}
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
if (pm.kind === COMPOSING_INDICATOR_KIND) {
|
|
1423
|
+
let senderNametag2;
|
|
1424
|
+
let expiresIn = 3e4;
|
|
1425
|
+
try {
|
|
1426
|
+
const parsed = JSON.parse(pm.content);
|
|
1427
|
+
senderNametag2 = parsed.senderNametag || void 0;
|
|
1428
|
+
expiresIn = parsed.expiresIn ?? 3e4;
|
|
1429
|
+
} catch {
|
|
1430
|
+
}
|
|
1431
|
+
entry.adapter.dispatchComposingIndicator({
|
|
1432
|
+
senderPubkey: pm.senderPubkey,
|
|
1433
|
+
senderNametag: senderNametag2,
|
|
1434
|
+
expiresIn
|
|
1435
|
+
});
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
try {
|
|
1439
|
+
const parsed = JSON.parse(pm.content);
|
|
1440
|
+
if (parsed?.type === "typing") {
|
|
1441
|
+
const indicator = {
|
|
1442
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1443
|
+
senderNametag: parsed.senderNametag,
|
|
1444
|
+
timestamp: pm.timestamp * 1e3
|
|
1445
|
+
};
|
|
1446
|
+
entry.adapter.dispatchTypingIndicator(indicator);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
} catch {
|
|
1450
|
+
}
|
|
1451
|
+
if (!(0, import_nostr_js_sdk.isChatMessage)(pm)) return;
|
|
1452
|
+
let content = pm.content;
|
|
1453
|
+
let senderNametag;
|
|
1454
|
+
try {
|
|
1455
|
+
const parsed = JSON.parse(content);
|
|
1456
|
+
if (typeof parsed === "object" && parsed.text !== void 0) {
|
|
1457
|
+
content = parsed.text;
|
|
1458
|
+
senderNametag = parsed.senderNametag || void 0;
|
|
1459
|
+
}
|
|
1460
|
+
} catch {
|
|
1461
|
+
}
|
|
1462
|
+
const message = {
|
|
1463
|
+
id: event.id,
|
|
1464
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1465
|
+
senderNametag,
|
|
1466
|
+
content,
|
|
1467
|
+
timestamp: pm.timestamp * 1e3,
|
|
1468
|
+
encrypted: true
|
|
1469
|
+
};
|
|
1470
|
+
entry.adapter.dispatchMessage(message);
|
|
1471
|
+
return;
|
|
1472
|
+
} catch {
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
logger.debug("Mux", "Gift wrap could not be decrypted by any address");
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Dispatch a wallet event (non-gift-wrap) to the correct address adapter.
|
|
1480
|
+
*/
|
|
1481
|
+
async dispatchWalletEvent(entry, event) {
|
|
1482
|
+
switch (event.kind) {
|
|
1483
|
+
case EVENT_KINDS.DIRECT_MESSAGE:
|
|
1484
|
+
break;
|
|
1485
|
+
case EVENT_KINDS.TOKEN_TRANSFER:
|
|
1486
|
+
await this.handleTokenTransfer(entry, event);
|
|
1487
|
+
break;
|
|
1488
|
+
case EVENT_KINDS.PAYMENT_REQUEST:
|
|
1489
|
+
await this.handlePaymentRequest(entry, event);
|
|
1490
|
+
break;
|
|
1491
|
+
case EVENT_KINDS.PAYMENT_REQUEST_RESPONSE:
|
|
1492
|
+
await this.handlePaymentRequestResponse(entry, event);
|
|
1493
|
+
break;
|
|
1494
|
+
}
|
|
1495
|
+
if (event.created_at) {
|
|
1496
|
+
this.updateLastEventTimestamp(entry, event.created_at);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
async handleTokenTransfer(entry, event) {
|
|
1500
|
+
try {
|
|
1501
|
+
const content = await this.decryptContent(entry, event.content, event.pubkey);
|
|
1502
|
+
const payload = JSON.parse(content);
|
|
1503
|
+
const transfer = {
|
|
1504
|
+
id: event.id,
|
|
1505
|
+
senderTransportPubkey: event.pubkey,
|
|
1506
|
+
payload,
|
|
1507
|
+
timestamp: event.created_at * 1e3
|
|
1508
|
+
};
|
|
1509
|
+
entry.adapter.dispatchTokenTransfer(transfer);
|
|
1510
|
+
} catch (err) {
|
|
1511
|
+
logger.debug("Mux", `Token transfer decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
async handlePaymentRequest(entry, event) {
|
|
1515
|
+
try {
|
|
1516
|
+
const content = await this.decryptContent(entry, event.content, event.pubkey);
|
|
1517
|
+
const requestData = JSON.parse(content);
|
|
1518
|
+
const request = {
|
|
1519
|
+
id: event.id,
|
|
1520
|
+
senderTransportPubkey: event.pubkey,
|
|
1521
|
+
request: {
|
|
1522
|
+
requestId: requestData.requestId,
|
|
1523
|
+
amount: requestData.amount,
|
|
1524
|
+
coinId: requestData.coinId,
|
|
1525
|
+
message: requestData.message,
|
|
1526
|
+
recipientNametag: requestData.recipientNametag,
|
|
1527
|
+
metadata: requestData.metadata
|
|
1528
|
+
},
|
|
1529
|
+
timestamp: event.created_at * 1e3
|
|
1530
|
+
};
|
|
1531
|
+
entry.adapter.dispatchPaymentRequest(request);
|
|
1532
|
+
} catch (err) {
|
|
1533
|
+
logger.debug("Mux", `Payment request decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
async handlePaymentRequestResponse(entry, event) {
|
|
1537
|
+
try {
|
|
1538
|
+
const content = await this.decryptContent(entry, event.content, event.pubkey);
|
|
1539
|
+
const responseData = JSON.parse(content);
|
|
1540
|
+
const response = {
|
|
1541
|
+
id: event.id,
|
|
1542
|
+
responderTransportPubkey: event.pubkey,
|
|
1543
|
+
response: {
|
|
1544
|
+
requestId: responseData.requestId,
|
|
1545
|
+
responseType: responseData.responseType,
|
|
1546
|
+
message: responseData.message,
|
|
1547
|
+
transferId: responseData.transferId
|
|
1548
|
+
},
|
|
1549
|
+
timestamp: event.created_at * 1e3
|
|
1550
|
+
};
|
|
1551
|
+
entry.adapter.dispatchPaymentRequestResponse(response);
|
|
1552
|
+
} catch (err) {
|
|
1553
|
+
logger.debug("Mux", `Payment response decrypt failed for address ${entry.index}:`, err?.message?.slice(0, 50));
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
// ===========================================================================
|
|
1557
|
+
// Crypto Helpers
|
|
1558
|
+
// ===========================================================================
|
|
1559
|
+
async decryptContent(entry, content, senderPubkey) {
|
|
1560
|
+
const decrypted = await import_nostr_js_sdk.NIP04.decryptHex(
|
|
1561
|
+
content,
|
|
1562
|
+
entry.keyManager.getPrivateKeyHex(),
|
|
1563
|
+
senderPubkey
|
|
1564
|
+
);
|
|
1565
|
+
return this.stripContentPrefix(decrypted);
|
|
1566
|
+
}
|
|
1567
|
+
stripContentPrefix(content) {
|
|
1568
|
+
const prefixes = ["payment_request:", "token_transfer:", "payment_response:"];
|
|
1569
|
+
for (const prefix of prefixes) {
|
|
1570
|
+
if (content.startsWith(prefix)) return content.slice(prefix.length);
|
|
1571
|
+
}
|
|
1572
|
+
return content;
|
|
1573
|
+
}
|
|
1574
|
+
// ===========================================================================
|
|
1575
|
+
// Sending (called by adapters)
|
|
1576
|
+
// ===========================================================================
|
|
1577
|
+
/**
|
|
1578
|
+
* Create an encrypted event using a specific address's keyManager.
|
|
1579
|
+
* Used by AddressTransportAdapter for sending.
|
|
1580
|
+
*/
|
|
1581
|
+
async createAndPublishEncryptedEvent(addressIndex, kind, content, tags) {
|
|
1582
|
+
const entry = this.addresses.get(addressIndex);
|
|
1583
|
+
if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
|
|
1584
|
+
if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
|
|
1585
|
+
const recipientTag = tags.find((t) => t[0] === "p");
|
|
1586
|
+
if (!recipientTag?.[1]) throw new SphereError("No recipient pubkey in tags", "VALIDATION_ERROR");
|
|
1587
|
+
const encrypted = await import_nostr_js_sdk.NIP04.encryptHex(
|
|
1588
|
+
content,
|
|
1589
|
+
entry.keyManager.getPrivateKeyHex(),
|
|
1590
|
+
recipientTag[1]
|
|
1591
|
+
);
|
|
1592
|
+
const signedEvent = import_nostr_js_sdk.Event.create(entry.keyManager, { kind, content: encrypted, tags });
|
|
1593
|
+
const nostrEvent = import_nostr_js_sdk.Event.fromJSON({
|
|
1594
|
+
id: signedEvent.id,
|
|
1595
|
+
kind: signedEvent.kind,
|
|
1596
|
+
content: signedEvent.content,
|
|
1597
|
+
tags: signedEvent.tags,
|
|
1598
|
+
pubkey: signedEvent.pubkey,
|
|
1599
|
+
created_at: signedEvent.created_at,
|
|
1600
|
+
sig: signedEvent.sig
|
|
1601
|
+
});
|
|
1602
|
+
await this.nostrClient.publishEvent(nostrEvent);
|
|
1603
|
+
return signedEvent.id;
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Create and publish a NIP-17 gift wrap message for a specific address.
|
|
1607
|
+
*/
|
|
1608
|
+
async sendGiftWrap(addressIndex, recipientPubkey, content) {
|
|
1609
|
+
const entry = this.addresses.get(addressIndex);
|
|
1610
|
+
if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
|
|
1611
|
+
if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
|
|
1612
|
+
const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
|
|
1613
|
+
const giftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(entry.keyManager, nostrRecipient, content);
|
|
1614
|
+
const giftWrapEvent = import_nostr_js_sdk.Event.fromJSON(giftWrap);
|
|
1615
|
+
await this.nostrClient.publishEvent(giftWrapEvent);
|
|
1616
|
+
const selfPubkey = entry.keyManager.getPublicKeyHex();
|
|
1617
|
+
const senderNametag = entry.identity.nametag;
|
|
1618
|
+
const selfWrapContent = JSON.stringify({
|
|
1619
|
+
selfWrap: true,
|
|
1620
|
+
originalId: giftWrap.id,
|
|
1621
|
+
recipientPubkey,
|
|
1622
|
+
senderNametag,
|
|
1623
|
+
text: content
|
|
1624
|
+
});
|
|
1625
|
+
const selfGiftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(entry.keyManager, selfPubkey, selfWrapContent);
|
|
1626
|
+
const selfGiftWrapEvent = import_nostr_js_sdk.Event.fromJSON(selfGiftWrap);
|
|
1627
|
+
this.nostrClient.publishEvent(selfGiftWrapEvent).catch((err) => {
|
|
1628
|
+
logger.debug("Mux", "Self-wrap publish failed:", err);
|
|
1629
|
+
});
|
|
1630
|
+
return giftWrap.id;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Publish a raw event (e.g., identity binding, broadcast).
|
|
1634
|
+
*/
|
|
1635
|
+
async publishRawEvent(addressIndex, kind, content, tags) {
|
|
1636
|
+
const entry = this.addresses.get(addressIndex);
|
|
1637
|
+
if (!entry) throw new SphereError("Address not registered in mux", "NOT_INITIALIZED");
|
|
1638
|
+
if (!this.nostrClient) throw new SphereError("Not connected", "NOT_INITIALIZED");
|
|
1639
|
+
const signedEvent = import_nostr_js_sdk.Event.create(entry.keyManager, { kind, content, tags });
|
|
1640
|
+
const nostrEvent = import_nostr_js_sdk.Event.fromJSON({
|
|
1641
|
+
id: signedEvent.id,
|
|
1642
|
+
kind: signedEvent.kind,
|
|
1643
|
+
content: signedEvent.content,
|
|
1644
|
+
tags: signedEvent.tags,
|
|
1645
|
+
pubkey: signedEvent.pubkey,
|
|
1646
|
+
created_at: signedEvent.created_at,
|
|
1647
|
+
sig: signedEvent.sig
|
|
1648
|
+
});
|
|
1649
|
+
await this.nostrClient.publishEvent(nostrEvent);
|
|
1650
|
+
return signedEvent.id;
|
|
1651
|
+
}
|
|
1652
|
+
// ===========================================================================
|
|
1653
|
+
// Resolve Methods (delegates to inner — these are stateless relay queries)
|
|
1654
|
+
// ===========================================================================
|
|
1655
|
+
/**
|
|
1656
|
+
* Get the NostrClient for resolve operations.
|
|
1657
|
+
* Adapters use this for resolve*, publishIdentityBinding, etc.
|
|
1658
|
+
*/
|
|
1659
|
+
getNostrClient() {
|
|
1660
|
+
return this.nostrClient;
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Get keyManager for a specific address (used by adapters for resolve/binding).
|
|
1664
|
+
*/
|
|
1665
|
+
getKeyManager(addressIndex) {
|
|
1666
|
+
return this.addresses.get(addressIndex)?.keyManager ?? null;
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Get identity for a specific address.
|
|
1670
|
+
*/
|
|
1671
|
+
getIdentity(addressIndex) {
|
|
1672
|
+
return this.addresses.get(addressIndex)?.identity ?? null;
|
|
1673
|
+
}
|
|
1674
|
+
// ===========================================================================
|
|
1675
|
+
// Event timestamp persistence
|
|
1676
|
+
// ===========================================================================
|
|
1677
|
+
updateLastEventTimestamp(entry, createdAt) {
|
|
1678
|
+
if (!this.storage) return;
|
|
1679
|
+
if (createdAt <= entry.lastEventTs) return;
|
|
1680
|
+
entry.lastEventTs = createdAt;
|
|
1681
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${entry.nostrPubkey.slice(0, 16)}`;
|
|
1682
|
+
this.storage.set(storageKey, createdAt.toString()).catch((err) => {
|
|
1683
|
+
logger.debug("Mux", "Failed to save last event timestamp:", err);
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
// ===========================================================================
|
|
1687
|
+
// Mux-level event system
|
|
1688
|
+
// ===========================================================================
|
|
1689
|
+
onTransportEvent(callback) {
|
|
1690
|
+
this.eventCallbacks.add(callback);
|
|
1691
|
+
return () => this.eventCallbacks.delete(callback);
|
|
1692
|
+
}
|
|
1693
|
+
onChatReady(handler) {
|
|
1694
|
+
if (this.chatEoseFired) {
|
|
1695
|
+
try {
|
|
1696
|
+
handler();
|
|
1697
|
+
} catch {
|
|
1698
|
+
}
|
|
1699
|
+
return () => {
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
this.chatEoseHandlers.push(handler);
|
|
1703
|
+
return () => {
|
|
1704
|
+
const idx = this.chatEoseHandlers.indexOf(handler);
|
|
1705
|
+
if (idx >= 0) this.chatEoseHandlers.splice(idx, 1);
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
emitEvent(event) {
|
|
1709
|
+
for (const cb of this.eventCallbacks) {
|
|
1710
|
+
try {
|
|
1711
|
+
cb(event);
|
|
1712
|
+
} catch {
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
for (const entry of this.addresses.values()) {
|
|
1716
|
+
entry.adapter.emitTransportEvent(event);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
// ===========================================================================
|
|
1720
|
+
// Dedup Management
|
|
1721
|
+
// ===========================================================================
|
|
1722
|
+
/**
|
|
1723
|
+
* Clear processed event IDs (e.g., on address change or periodic cleanup).
|
|
1724
|
+
*/
|
|
1725
|
+
clearProcessedEvents() {
|
|
1726
|
+
this.processedEventIds.clear();
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Get the storage adapter (for adapters that need it).
|
|
1730
|
+
*/
|
|
1731
|
+
getStorage() {
|
|
1732
|
+
return this.storage;
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Get the UUID generator.
|
|
1736
|
+
*/
|
|
1737
|
+
getUUIDGenerator() {
|
|
1738
|
+
return this.config.generateUUID;
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
var AddressTransportAdapter = class {
|
|
1742
|
+
id;
|
|
1743
|
+
name;
|
|
1744
|
+
type = "p2p";
|
|
1745
|
+
description;
|
|
1746
|
+
mux;
|
|
1747
|
+
addressIndex;
|
|
1748
|
+
identity;
|
|
1749
|
+
resolveDelegate;
|
|
1750
|
+
// Per-address handler sets
|
|
1751
|
+
messageHandlers = /* @__PURE__ */ new Set();
|
|
1752
|
+
transferHandlers = /* @__PURE__ */ new Set();
|
|
1753
|
+
paymentRequestHandlers = /* @__PURE__ */ new Set();
|
|
1754
|
+
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1755
|
+
readReceiptHandlers = /* @__PURE__ */ new Set();
|
|
1756
|
+
typingIndicatorHandlers = /* @__PURE__ */ new Set();
|
|
1757
|
+
composingHandlers = /* @__PURE__ */ new Set();
|
|
1758
|
+
instantSplitBundleHandlers = /* @__PURE__ */ new Set();
|
|
1759
|
+
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1760
|
+
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1761
|
+
pendingMessages = [];
|
|
1762
|
+
chatEoseHandlers = [];
|
|
1763
|
+
constructor(mux, addressIndex, identity, resolveDelegate) {
|
|
1764
|
+
this.mux = mux;
|
|
1765
|
+
this.addressIndex = addressIndex;
|
|
1766
|
+
this.identity = identity;
|
|
1767
|
+
this.resolveDelegate = resolveDelegate ?? null;
|
|
1768
|
+
this.id = `nostr-addr-${addressIndex}`;
|
|
1769
|
+
this.name = `Nostr Transport (address ${addressIndex})`;
|
|
1770
|
+
this.description = `P2P messaging for address index ${addressIndex}`;
|
|
1771
|
+
}
|
|
1772
|
+
// ===========================================================================
|
|
1773
|
+
// BaseProvider — delegates to mux
|
|
1774
|
+
// ===========================================================================
|
|
1775
|
+
async connect() {
|
|
1776
|
+
await this.mux.connect();
|
|
1777
|
+
}
|
|
1778
|
+
async disconnect() {
|
|
1779
|
+
}
|
|
1780
|
+
isConnected() {
|
|
1781
|
+
return this.mux.isConnected();
|
|
1782
|
+
}
|
|
1783
|
+
getStatus() {
|
|
1784
|
+
return this.mux.getStatus();
|
|
1785
|
+
}
|
|
1786
|
+
// ===========================================================================
|
|
1787
|
+
// Identity (no-op — mux manages identity via addAddress)
|
|
1788
|
+
// ===========================================================================
|
|
1789
|
+
async setIdentity(identity) {
|
|
1790
|
+
this.identity = identity;
|
|
1791
|
+
await this.mux.addAddress(this.addressIndex, identity);
|
|
1792
|
+
}
|
|
1793
|
+
// ===========================================================================
|
|
1794
|
+
// Sending — delegates to mux with this address's keyManager
|
|
1795
|
+
// ===========================================================================
|
|
1796
|
+
async sendMessage(recipientPubkey, content) {
|
|
1797
|
+
const senderNametag = this.identity.nametag;
|
|
1798
|
+
const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
|
|
1799
|
+
return this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, wrappedContent);
|
|
1800
|
+
}
|
|
1801
|
+
async sendTokenTransfer(recipientPubkey, payload) {
|
|
1802
|
+
const content = "token_transfer:" + JSON.stringify(payload);
|
|
1803
|
+
const uniqueD = `token-transfer-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1804
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1805
|
+
this.addressIndex,
|
|
1806
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
1807
|
+
content,
|
|
1808
|
+
[["p", recipientPubkey], ["d", uniqueD], ["type", "token_transfer"]]
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
async sendPaymentRequest(recipientPubkey, payload) {
|
|
1812
|
+
const requestId2 = this.mux.getUUIDGenerator()();
|
|
1813
|
+
const amount = typeof payload.amount === "bigint" ? payload.amount.toString() : payload.amount;
|
|
1814
|
+
const requestContent = {
|
|
1815
|
+
requestId: requestId2,
|
|
1816
|
+
amount,
|
|
1817
|
+
coinId: payload.coinId,
|
|
1818
|
+
message: payload.message,
|
|
1819
|
+
recipientNametag: payload.recipientNametag,
|
|
1820
|
+
deadline: Date.now() + 5 * 60 * 1e3
|
|
1821
|
+
};
|
|
1822
|
+
const content = "payment_request:" + JSON.stringify(requestContent);
|
|
1823
|
+
const tags = [
|
|
1824
|
+
["p", recipientPubkey],
|
|
1825
|
+
["type", "payment_request"],
|
|
1826
|
+
["amount", amount]
|
|
1827
|
+
];
|
|
1828
|
+
if (payload.recipientNametag) {
|
|
1829
|
+
tags.push(["recipient", payload.recipientNametag]);
|
|
1830
|
+
}
|
|
1831
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1832
|
+
this.addressIndex,
|
|
1833
|
+
EVENT_KINDS.PAYMENT_REQUEST,
|
|
1834
|
+
content,
|
|
1835
|
+
tags
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
async sendPaymentRequestResponse(recipientPubkey, response) {
|
|
1839
|
+
const content = "payment_response:" + JSON.stringify(response);
|
|
1840
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1841
|
+
this.addressIndex,
|
|
1842
|
+
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE,
|
|
1843
|
+
content,
|
|
1844
|
+
[["p", recipientPubkey], ["type", "payment_response"]]
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
async sendReadReceipt(recipientPubkey, messageEventId) {
|
|
1848
|
+
const content = JSON.stringify({ type: "read_receipt", messageEventId });
|
|
1849
|
+
await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
|
|
1850
|
+
}
|
|
1851
|
+
async sendTypingIndicator(recipientPubkey) {
|
|
1852
|
+
const content = JSON.stringify({
|
|
1853
|
+
type: "typing",
|
|
1854
|
+
senderNametag: this.identity.nametag
|
|
1855
|
+
});
|
|
1856
|
+
await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
|
|
1857
|
+
}
|
|
1858
|
+
async sendComposingIndicator(recipientPubkey, content) {
|
|
1859
|
+
await this.mux.sendGiftWrap(this.addressIndex, recipientPubkey, content);
|
|
1860
|
+
}
|
|
1861
|
+
async sendInstantSplitBundle(recipientPubkey, bundle) {
|
|
1862
|
+
const content = "token_transfer:" + JSON.stringify({
|
|
1863
|
+
type: "instant_split",
|
|
1864
|
+
...bundle
|
|
1865
|
+
});
|
|
1866
|
+
const uniqueD = `instant-split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1867
|
+
return this.mux.createAndPublishEncryptedEvent(
|
|
1868
|
+
this.addressIndex,
|
|
1869
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
1870
|
+
content,
|
|
1871
|
+
[["p", recipientPubkey], ["d", uniqueD], ["type", "instant_split"]]
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1874
|
+
// ===========================================================================
|
|
1875
|
+
// Subscription handlers — per-address
|
|
1876
|
+
// ===========================================================================
|
|
1877
|
+
onMessage(handler) {
|
|
1878
|
+
this.messageHandlers.add(handler);
|
|
1879
|
+
if (this.pendingMessages.length > 0) {
|
|
1880
|
+
const pending2 = this.pendingMessages;
|
|
1881
|
+
this.pendingMessages = [];
|
|
1882
|
+
for (const msg of pending2) {
|
|
1883
|
+
try {
|
|
1884
|
+
handler(msg);
|
|
1885
|
+
} catch {
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return () => this.messageHandlers.delete(handler);
|
|
1890
|
+
}
|
|
1891
|
+
onTokenTransfer(handler) {
|
|
1892
|
+
this.transferHandlers.add(handler);
|
|
1893
|
+
return () => this.transferHandlers.delete(handler);
|
|
1894
|
+
}
|
|
1895
|
+
onPaymentRequest(handler) {
|
|
1896
|
+
this.paymentRequestHandlers.add(handler);
|
|
1897
|
+
return () => this.paymentRequestHandlers.delete(handler);
|
|
1898
|
+
}
|
|
1899
|
+
onPaymentRequestResponse(handler) {
|
|
1900
|
+
this.paymentRequestResponseHandlers.add(handler);
|
|
1901
|
+
return () => this.paymentRequestResponseHandlers.delete(handler);
|
|
1902
|
+
}
|
|
1903
|
+
onReadReceipt(handler) {
|
|
1904
|
+
this.readReceiptHandlers.add(handler);
|
|
1905
|
+
return () => this.readReceiptHandlers.delete(handler);
|
|
1906
|
+
}
|
|
1907
|
+
onTypingIndicator(handler) {
|
|
1908
|
+
this.typingIndicatorHandlers.add(handler);
|
|
1909
|
+
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1910
|
+
}
|
|
1911
|
+
onComposing(handler) {
|
|
1912
|
+
this.composingHandlers.add(handler);
|
|
1913
|
+
return () => this.composingHandlers.delete(handler);
|
|
1914
|
+
}
|
|
1915
|
+
onInstantSplitReceived(handler) {
|
|
1916
|
+
this.instantSplitBundleHandlers.add(handler);
|
|
1917
|
+
return () => this.instantSplitBundleHandlers.delete(handler);
|
|
1918
|
+
}
|
|
1919
|
+
subscribeToBroadcast(tags, handler) {
|
|
1920
|
+
const key = tags.sort().join(":");
|
|
1921
|
+
if (!this.broadcastHandlers.has(key)) {
|
|
1922
|
+
this.broadcastHandlers.set(key, /* @__PURE__ */ new Set());
|
|
1923
|
+
}
|
|
1924
|
+
this.broadcastHandlers.get(key).add(handler);
|
|
1925
|
+
return () => this.broadcastHandlers.get(key)?.delete(handler);
|
|
1926
|
+
}
|
|
1927
|
+
async publishBroadcast(content, tags) {
|
|
1928
|
+
const eventTags = tags ? tags.map((t) => ["t", t]) : [];
|
|
1929
|
+
return this.mux.publishRawEvent(this.addressIndex, 30023, content, eventTags);
|
|
1930
|
+
}
|
|
1931
|
+
// ===========================================================================
|
|
1932
|
+
// Resolve methods — delegate to original NostrTransportProvider
|
|
1933
|
+
// These are stateless relay queries, shared across all addresses
|
|
1934
|
+
// ===========================================================================
|
|
1935
|
+
async resolve(identifier) {
|
|
1936
|
+
return this.resolveDelegate?.resolve?.(identifier) ?? null;
|
|
1937
|
+
}
|
|
1938
|
+
async resolveNametag(nametag) {
|
|
1939
|
+
return this.resolveDelegate?.resolveNametag?.(nametag) ?? null;
|
|
1940
|
+
}
|
|
1941
|
+
async resolveNametagInfo(nametag) {
|
|
1942
|
+
return this.resolveDelegate?.resolveNametagInfo?.(nametag) ?? null;
|
|
1943
|
+
}
|
|
1944
|
+
async resolveAddressInfo(address) {
|
|
1945
|
+
return this.resolveDelegate?.resolveAddressInfo?.(address) ?? null;
|
|
1946
|
+
}
|
|
1947
|
+
async resolveTransportPubkeyInfo(transportPubkey) {
|
|
1948
|
+
return this.resolveDelegate?.resolveTransportPubkeyInfo?.(transportPubkey) ?? null;
|
|
1949
|
+
}
|
|
1950
|
+
async discoverAddresses(transportPubkeys) {
|
|
1951
|
+
return this.resolveDelegate?.discoverAddresses?.(transportPubkeys) ?? [];
|
|
1952
|
+
}
|
|
1953
|
+
async recoverNametag() {
|
|
1954
|
+
return this.resolveDelegate?.recoverNametag?.() ?? null;
|
|
1955
|
+
}
|
|
1956
|
+
async publishIdentityBinding(chainPubkey, l1Address, directAddress, nametag) {
|
|
1957
|
+
return this.resolveDelegate?.publishIdentityBinding?.(chainPubkey, l1Address, directAddress, nametag) ?? false;
|
|
1958
|
+
}
|
|
1959
|
+
// ===========================================================================
|
|
1960
|
+
// Relay Management — delegates to mux
|
|
1961
|
+
// ===========================================================================
|
|
1962
|
+
getRelays() {
|
|
1963
|
+
return this.mux.getRelays();
|
|
1964
|
+
}
|
|
1965
|
+
getConnectedRelays() {
|
|
1966
|
+
return this.mux.getConnectedRelays();
|
|
1967
|
+
}
|
|
1968
|
+
async addRelay(relayUrl) {
|
|
1969
|
+
return this.mux.addRelay(relayUrl);
|
|
1970
|
+
}
|
|
1971
|
+
async removeRelay(relayUrl) {
|
|
1972
|
+
return this.mux.removeRelay(relayUrl);
|
|
1973
|
+
}
|
|
1974
|
+
hasRelay(relayUrl) {
|
|
1975
|
+
return this.mux.hasRelay(relayUrl);
|
|
1976
|
+
}
|
|
1977
|
+
isRelayConnected(relayUrl) {
|
|
1978
|
+
return this.mux.isRelayConnected(relayUrl);
|
|
1979
|
+
}
|
|
1980
|
+
setFallbackSince(sinceSeconds) {
|
|
1981
|
+
this.mux.setFallbackSince(this.addressIndex, sinceSeconds);
|
|
1982
|
+
}
|
|
1983
|
+
async fetchPendingEvents() {
|
|
1984
|
+
}
|
|
1985
|
+
onChatReady(handler) {
|
|
1986
|
+
return this.mux.onChatReady(handler);
|
|
1987
|
+
}
|
|
1988
|
+
// ===========================================================================
|
|
1989
|
+
// Dispatch methods — called by MultiAddressTransportMux to route events
|
|
1990
|
+
// ===========================================================================
|
|
1991
|
+
dispatchMessage(message) {
|
|
1992
|
+
if (this.messageHandlers.size === 0) {
|
|
1993
|
+
this.pendingMessages.push(message);
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
for (const handler of this.messageHandlers) {
|
|
1997
|
+
try {
|
|
1998
|
+
handler(message);
|
|
1999
|
+
} catch (e) {
|
|
2000
|
+
logger.debug("MuxAdapter", "Message handler error:", e);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
dispatchTokenTransfer(transfer) {
|
|
2005
|
+
for (const handler of this.transferHandlers) {
|
|
2006
|
+
try {
|
|
2007
|
+
handler(transfer);
|
|
2008
|
+
} catch (e) {
|
|
2009
|
+
logger.debug("MuxAdapter", "Transfer handler error:", e);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
dispatchPaymentRequest(request) {
|
|
2014
|
+
for (const handler of this.paymentRequestHandlers) {
|
|
2015
|
+
try {
|
|
2016
|
+
handler(request);
|
|
2017
|
+
} catch (e) {
|
|
2018
|
+
logger.debug("MuxAdapter", "Payment request handler error:", e);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
dispatchPaymentRequestResponse(response) {
|
|
2023
|
+
for (const handler of this.paymentRequestResponseHandlers) {
|
|
2024
|
+
try {
|
|
2025
|
+
handler(response);
|
|
2026
|
+
} catch (e) {
|
|
2027
|
+
logger.debug("MuxAdapter", "Payment response handler error:", e);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
dispatchReadReceipt(receipt) {
|
|
2032
|
+
for (const handler of this.readReceiptHandlers) {
|
|
2033
|
+
try {
|
|
2034
|
+
handler(receipt);
|
|
2035
|
+
} catch (e) {
|
|
2036
|
+
logger.debug("MuxAdapter", "Read receipt handler error:", e);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
dispatchTypingIndicator(indicator) {
|
|
2041
|
+
for (const handler of this.typingIndicatorHandlers) {
|
|
2042
|
+
try {
|
|
2043
|
+
handler(indicator);
|
|
2044
|
+
} catch (e) {
|
|
2045
|
+
logger.debug("MuxAdapter", "Typing handler error:", e);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2050
|
+
dispatchComposingIndicator(indicator) {
|
|
2051
|
+
for (const handler of this.composingHandlers) {
|
|
2052
|
+
try {
|
|
2053
|
+
handler(indicator);
|
|
2054
|
+
} catch (e) {
|
|
2055
|
+
logger.debug("MuxAdapter", "Composing handler error:", e);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
dispatchInstantSplitBundle(bundle) {
|
|
2060
|
+
for (const handler of this.instantSplitBundleHandlers) {
|
|
2061
|
+
try {
|
|
2062
|
+
handler(bundle);
|
|
2063
|
+
} catch (e) {
|
|
2064
|
+
logger.debug("MuxAdapter", "Instant split handler error:", e);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
emitTransportEvent(event) {
|
|
2069
|
+
for (const cb of this.eventCallbacks) {
|
|
2070
|
+
try {
|
|
2071
|
+
cb(event);
|
|
2072
|
+
} catch {
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
|
|
982
2078
|
// modules/payments/L1PaymentsModule.ts
|
|
983
2079
|
init_errors();
|
|
984
2080
|
init_constants();
|
|
@@ -2934,7 +4030,7 @@ var import_MintCommitment = require("@unicitylabs/state-transition-sdk/lib/trans
|
|
|
2934
4030
|
var import_HashAlgorithm2 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
2935
4031
|
var import_UnmaskedPredicate2 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
|
|
2936
4032
|
var import_InclusionProofUtils2 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
|
|
2937
|
-
var
|
|
4033
|
+
var import_nostr_js_sdk2 = require("@unicitylabs/nostr-js-sdk");
|
|
2938
4034
|
var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
2939
4035
|
var NametagMinter = class {
|
|
2940
4036
|
client;
|
|
@@ -2958,7 +4054,7 @@ var NametagMinter = class {
|
|
|
2958
4054
|
async isNametagAvailable(nametag) {
|
|
2959
4055
|
try {
|
|
2960
4056
|
const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
|
|
2961
|
-
const cleanNametag = (0,
|
|
4057
|
+
const cleanNametag = (0, import_nostr_js_sdk2.normalizeNametag)(stripped);
|
|
2962
4058
|
const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
|
|
2963
4059
|
const isMinted = await this.client.isMinted(this.trustBase, nametagTokenId);
|
|
2964
4060
|
return !isMinted;
|
|
@@ -2976,7 +4072,7 @@ var NametagMinter = class {
|
|
|
2976
4072
|
*/
|
|
2977
4073
|
async mintNametag(nametag, ownerAddress) {
|
|
2978
4074
|
const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
|
|
2979
|
-
const cleanNametag = (0,
|
|
4075
|
+
const cleanNametag = (0, import_nostr_js_sdk2.normalizeNametag)(stripped);
|
|
2980
4076
|
this.log(`Starting mint for nametag: ${cleanNametag}`);
|
|
2981
4077
|
try {
|
|
2982
4078
|
const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
|
|
@@ -5084,6 +6180,15 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5084
6180
|
this.unsubscribePaymentRequests = null;
|
|
5085
6181
|
this.unsubscribePaymentRequestResponses?.();
|
|
5086
6182
|
this.unsubscribePaymentRequestResponses = null;
|
|
6183
|
+
this.stopProofPolling();
|
|
6184
|
+
this.proofPollingJobs.clear();
|
|
6185
|
+
this.stopResolveUnconfirmedPolling();
|
|
6186
|
+
this.unsubscribeStorageEvents();
|
|
6187
|
+
for (const [, resolver] of this.pendingResponseResolvers) {
|
|
6188
|
+
clearTimeout(resolver.timeout);
|
|
6189
|
+
resolver.reject(new Error("Address switched"));
|
|
6190
|
+
}
|
|
6191
|
+
this.pendingResponseResolvers.clear();
|
|
5087
6192
|
this.tokens.clear();
|
|
5088
6193
|
this.pendingTransfers.clear();
|
|
5089
6194
|
this.tombstones = [];
|
|
@@ -5132,6 +6237,13 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5132
6237
|
try {
|
|
5133
6238
|
const result = await provider.load();
|
|
5134
6239
|
if (result.success && result.data) {
|
|
6240
|
+
const loadedMeta = result.data?._meta;
|
|
6241
|
+
const currentL1 = this.deps.identity.l1Address;
|
|
6242
|
+
const currentChain = this.deps.identity.chainPubkey;
|
|
6243
|
+
if (loadedMeta?.address && currentL1 && loadedMeta.address !== currentL1 && loadedMeta.address !== currentChain) {
|
|
6244
|
+
logger.warn("Payments", `Load: rejecting data from provider ${id} \u2014 address mismatch (got=${loadedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
|
|
6245
|
+
continue;
|
|
6246
|
+
}
|
|
5135
6247
|
this.loadFromStorageData(result.data);
|
|
5136
6248
|
const txfData = result.data;
|
|
5137
6249
|
if (txfData._history && txfData._history.length > 0) {
|
|
@@ -5213,6 +6325,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5213
6325
|
*/
|
|
5214
6326
|
async send(request) {
|
|
5215
6327
|
this.ensureInitialized();
|
|
6328
|
+
let resolveSendTracker;
|
|
6329
|
+
const sendTracker = new Promise((r) => {
|
|
6330
|
+
resolveSendTracker = r;
|
|
6331
|
+
});
|
|
6332
|
+
this.pendingBackgroundTasks.push(sendTracker);
|
|
5216
6333
|
const result = {
|
|
5217
6334
|
id: crypto.randomUUID(),
|
|
5218
6335
|
status: "pending",
|
|
@@ -5500,6 +6617,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5500
6617
|
}
|
|
5501
6618
|
this.deps.emitEvent("transfer:failed", result);
|
|
5502
6619
|
throw error;
|
|
6620
|
+
} finally {
|
|
6621
|
+
resolveSendTracker();
|
|
5503
6622
|
}
|
|
5504
6623
|
}
|
|
5505
6624
|
/**
|
|
@@ -6512,9 +7631,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6512
7631
|
* Call this before process exit to ensure all tokens are saved.
|
|
6513
7632
|
*/
|
|
6514
7633
|
async waitForPendingOperations() {
|
|
7634
|
+
logger.debug("Payments", `waitForPendingOperations: ${this.pendingBackgroundTasks.length} pending tasks`);
|
|
6515
7635
|
if (this.pendingBackgroundTasks.length > 0) {
|
|
7636
|
+
logger.debug("Payments", "waitForPendingOperations: waiting...");
|
|
6516
7637
|
await Promise.allSettled(this.pendingBackgroundTasks);
|
|
6517
7638
|
this.pendingBackgroundTasks = [];
|
|
7639
|
+
logger.debug("Payments", "waitForPendingOperations: all tasks completed");
|
|
6518
7640
|
}
|
|
6519
7641
|
}
|
|
6520
7642
|
/**
|
|
@@ -7756,6 +8878,13 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7756
8878
|
try {
|
|
7757
8879
|
const result = await provider.sync(localData);
|
|
7758
8880
|
if (result.success && result.merged) {
|
|
8881
|
+
const mergedMeta = result.merged?._meta;
|
|
8882
|
+
const currentL1 = this.deps.identity.l1Address;
|
|
8883
|
+
const currentChain = this.deps.identity.chainPubkey;
|
|
8884
|
+
if (mergedMeta?.address && currentL1 && mergedMeta.address !== currentL1 && mergedMeta.address !== currentChain) {
|
|
8885
|
+
logger.warn("Payments", `Sync: rejecting data from provider ${providerId} \u2014 address mismatch (got=${mergedMeta.address.slice(0, 20)}... expected=${currentL1.slice(0, 20)}...)`);
|
|
8886
|
+
continue;
|
|
8887
|
+
}
|
|
7759
8888
|
const savedTokens = new Map(this.tokens);
|
|
7760
8889
|
this.loadFromStorageData(result.merged);
|
|
7761
8890
|
let restoredCount = 0;
|
|
@@ -8767,6 +9896,12 @@ var CommunicationsModule = class {
|
|
|
8767
9896
|
this.unsubscribeComposing = deps.transport.onComposing?.((indicator) => {
|
|
8768
9897
|
this.handleComposingIndicator(indicator);
|
|
8769
9898
|
}) ?? null;
|
|
9899
|
+
if (deps.transport.onChatReady) {
|
|
9900
|
+
deps.transport.onChatReady(() => {
|
|
9901
|
+
const conversations = this.getConversations();
|
|
9902
|
+
deps.emitEvent("communications:ready", { conversationCount: conversations.size });
|
|
9903
|
+
});
|
|
9904
|
+
}
|
|
8770
9905
|
}
|
|
8771
9906
|
/**
|
|
8772
9907
|
* Load messages from storage.
|
|
@@ -9178,7 +10313,7 @@ function createCommunicationsModule(config) {
|
|
|
9178
10313
|
}
|
|
9179
10314
|
|
|
9180
10315
|
// modules/groupchat/GroupChatModule.ts
|
|
9181
|
-
var
|
|
10316
|
+
var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
|
|
9182
10317
|
init_logger();
|
|
9183
10318
|
init_errors();
|
|
9184
10319
|
init_constants();
|
|
@@ -9196,7 +10331,7 @@ var GroupVisibility = {
|
|
|
9196
10331
|
|
|
9197
10332
|
// modules/groupchat/GroupChatModule.ts
|
|
9198
10333
|
function createNip29Filter(data) {
|
|
9199
|
-
return new
|
|
10334
|
+
return new import_nostr_js_sdk3.Filter(data);
|
|
9200
10335
|
}
|
|
9201
10336
|
var GroupChatModule = class {
|
|
9202
10337
|
config;
|
|
@@ -9245,7 +10380,7 @@ var GroupChatModule = class {
|
|
|
9245
10380
|
}
|
|
9246
10381
|
this.deps = deps;
|
|
9247
10382
|
const secretKey = Buffer.from(deps.identity.privateKey, "hex");
|
|
9248
|
-
this.keyManager =
|
|
10383
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9249
10384
|
}
|
|
9250
10385
|
async load() {
|
|
9251
10386
|
this.ensureInitialized();
|
|
@@ -9380,7 +10515,7 @@ var GroupChatModule = class {
|
|
|
9380
10515
|
}
|
|
9381
10516
|
this.subscriptionIds = [];
|
|
9382
10517
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9383
|
-
this.keyManager =
|
|
10518
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9384
10519
|
if (this.groups.size === 0) {
|
|
9385
10520
|
await this.restoreJoinedGroups();
|
|
9386
10521
|
} else {
|
|
@@ -9392,13 +10527,13 @@ var GroupChatModule = class {
|
|
|
9392
10527
|
this.ensureInitialized();
|
|
9393
10528
|
if (!this.keyManager) {
|
|
9394
10529
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9395
|
-
this.keyManager =
|
|
10530
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9396
10531
|
}
|
|
9397
10532
|
const primaryRelay = this.config.relays[0];
|
|
9398
10533
|
if (primaryRelay) {
|
|
9399
10534
|
await this.checkAndClearOnRelayChange(primaryRelay);
|
|
9400
10535
|
}
|
|
9401
|
-
this.client = new
|
|
10536
|
+
this.client = new import_nostr_js_sdk3.NostrClient(this.keyManager);
|
|
9402
10537
|
try {
|
|
9403
10538
|
await this.client.connect(...this.config.relays);
|
|
9404
10539
|
this.connected = true;
|
|
@@ -9409,6 +10544,7 @@ var GroupChatModule = class {
|
|
|
9409
10544
|
await this.subscribeToJoinedGroups();
|
|
9410
10545
|
}
|
|
9411
10546
|
this.deps.emitEvent("groupchat:connection", { connected: true });
|
|
10547
|
+
this.deps.emitEvent("groupchat:ready", { groupCount: this.groups.size });
|
|
9412
10548
|
} catch (error) {
|
|
9413
10549
|
logger.error("GroupChat", "Failed to connect to relays", error);
|
|
9414
10550
|
this.deps.emitEvent("groupchat:connection", { connected: false });
|
|
@@ -9656,7 +10792,7 @@ var GroupChatModule = class {
|
|
|
9656
10792
|
if (!myPubkey) return [];
|
|
9657
10793
|
const groupIdsWithMembership = /* @__PURE__ */ new Set();
|
|
9658
10794
|
await this.oneshotSubscription(
|
|
9659
|
-
new
|
|
10795
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9660
10796
|
{
|
|
9661
10797
|
onEvent: (event) => {
|
|
9662
10798
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -9707,7 +10843,7 @@ var GroupChatModule = class {
|
|
|
9707
10843
|
const memberCountsMap = /* @__PURE__ */ new Map();
|
|
9708
10844
|
await Promise.all([
|
|
9709
10845
|
this.oneshotSubscription(
|
|
9710
|
-
new
|
|
10846
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
|
|
9711
10847
|
{
|
|
9712
10848
|
onEvent: (event) => {
|
|
9713
10849
|
const group = this.parseGroupMetadata(event);
|
|
@@ -9725,7 +10861,7 @@ var GroupChatModule = class {
|
|
|
9725
10861
|
}
|
|
9726
10862
|
),
|
|
9727
10863
|
this.oneshotSubscription(
|
|
9728
|
-
new
|
|
10864
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9729
10865
|
{
|
|
9730
10866
|
onEvent: (event) => {
|
|
9731
10867
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -10043,6 +11179,19 @@ var GroupChatModule = class {
|
|
|
10043
11179
|
getMessages(groupId) {
|
|
10044
11180
|
return (this.messages.get(groupId) || []).sort((a, b) => a.timestamp - b.timestamp);
|
|
10045
11181
|
}
|
|
11182
|
+
getMessagesPage(groupId, options) {
|
|
11183
|
+
const limit = options?.limit ?? 20;
|
|
11184
|
+
const before = options?.before ?? Infinity;
|
|
11185
|
+
const groupMessages = this.messages.get(groupId) ?? [];
|
|
11186
|
+
const filtered = groupMessages.filter((m) => m.timestamp < before).sort((a, b) => b.timestamp - a.timestamp);
|
|
11187
|
+
const page = filtered.slice(0, limit);
|
|
11188
|
+
return {
|
|
11189
|
+
messages: page.reverse(),
|
|
11190
|
+
// chronological order
|
|
11191
|
+
hasMore: filtered.length > limit,
|
|
11192
|
+
oldestTimestamp: page.length > 0 ? page[0].timestamp : null
|
|
11193
|
+
};
|
|
11194
|
+
}
|
|
10046
11195
|
getMembers(groupId) {
|
|
10047
11196
|
return (this.members.get(groupId) || []).sort((a, b) => a.joinedAt - b.joinedAt);
|
|
10048
11197
|
}
|
|
@@ -10215,7 +11364,7 @@ var GroupChatModule = class {
|
|
|
10215
11364
|
if (!this.client) return /* @__PURE__ */ new Set();
|
|
10216
11365
|
const adminPubkeys = /* @__PURE__ */ new Set();
|
|
10217
11366
|
return this.oneshotSubscription(
|
|
10218
|
-
new
|
|
11367
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
|
|
10219
11368
|
{
|
|
10220
11369
|
onEvent: (event) => {
|
|
10221
11370
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -10237,7 +11386,7 @@ var GroupChatModule = class {
|
|
|
10237
11386
|
if (!this.client) return null;
|
|
10238
11387
|
let result = null;
|
|
10239
11388
|
return this.oneshotSubscription(
|
|
10240
|
-
new
|
|
11389
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
|
|
10241
11390
|
{
|
|
10242
11391
|
onEvent: (event) => {
|
|
10243
11392
|
if (!result) result = this.parseGroupMetadata(event);
|
|
@@ -10274,7 +11423,7 @@ var GroupChatModule = class {
|
|
|
10274
11423
|
if (!this.client) return [];
|
|
10275
11424
|
const members = [];
|
|
10276
11425
|
return this.oneshotSubscription(
|
|
10277
|
-
new
|
|
11426
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
|
|
10278
11427
|
{
|
|
10279
11428
|
onEvent: (event) => {
|
|
10280
11429
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -10295,7 +11444,7 @@ var GroupChatModule = class {
|
|
|
10295
11444
|
if (!this.client) return [];
|
|
10296
11445
|
const adminPubkeys = [];
|
|
10297
11446
|
return this.oneshotSubscription(
|
|
10298
|
-
new
|
|
11447
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
|
|
10299
11448
|
{
|
|
10300
11449
|
onEvent: (event) => {
|
|
10301
11450
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -13964,9 +15113,9 @@ var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign
|
|
|
13964
15113
|
var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
13965
15114
|
var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
13966
15115
|
var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
13967
|
-
var
|
|
15116
|
+
var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
|
|
13968
15117
|
function isValidNametag(nametag) {
|
|
13969
|
-
if ((0,
|
|
15118
|
+
if ((0, import_nostr_js_sdk4.isPhoneNumber)(nametag)) return true;
|
|
13970
15119
|
return /^[a-z0-9_-]{3,20}$/.test(nametag);
|
|
13971
15120
|
}
|
|
13972
15121
|
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
@@ -14010,11 +15159,18 @@ var Sphere = class _Sphere {
|
|
|
14010
15159
|
_transport;
|
|
14011
15160
|
_oracle;
|
|
14012
15161
|
_priceProvider;
|
|
14013
|
-
// Modules
|
|
15162
|
+
// Modules (single-instance — backward compat, delegates to active address)
|
|
14014
15163
|
_payments;
|
|
14015
15164
|
_communications;
|
|
14016
15165
|
_groupChat = null;
|
|
14017
15166
|
_market = null;
|
|
15167
|
+
// Per-address module instances (Phase 2: independent parallel operation)
|
|
15168
|
+
_addressModules = /* @__PURE__ */ new Map();
|
|
15169
|
+
_transportMux = null;
|
|
15170
|
+
// Stored configs for creating per-address modules
|
|
15171
|
+
_l1Config;
|
|
15172
|
+
_groupChatConfig;
|
|
15173
|
+
_marketConfig;
|
|
14018
15174
|
// Events
|
|
14019
15175
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
14020
15176
|
// Provider management
|
|
@@ -14032,6 +15188,9 @@ var Sphere = class _Sphere {
|
|
|
14032
15188
|
if (tokenStorage) {
|
|
14033
15189
|
this._tokenStorageProviders.set(tokenStorage.id, tokenStorage);
|
|
14034
15190
|
}
|
|
15191
|
+
this._l1Config = l1Config;
|
|
15192
|
+
this._groupChatConfig = groupChatConfig;
|
|
15193
|
+
this._marketConfig = marketConfig;
|
|
14035
15194
|
this._payments = createPaymentsModule({ l1: l1Config });
|
|
14036
15195
|
this._communications = createCommunicationsModule();
|
|
14037
15196
|
this._groupChat = groupChatConfig ? createGroupChatModule(groupChatConfig) : null;
|
|
@@ -15290,7 +16449,7 @@ var Sphere = class _Sphere {
|
|
|
15290
16449
|
nametags.set(0, newNametag);
|
|
15291
16450
|
}
|
|
15292
16451
|
const nametag = this._addressNametags.get(addressId)?.get(0);
|
|
15293
|
-
|
|
16452
|
+
const newIdentity = {
|
|
15294
16453
|
privateKey: addressInfo.privateKey,
|
|
15295
16454
|
chainPubkey: addressInfo.publicKey,
|
|
15296
16455
|
l1Address: addressInfo.address,
|
|
@@ -15298,20 +16457,53 @@ var Sphere = class _Sphere {
|
|
|
15298
16457
|
ipnsName: "12D3KooW" + ipnsHash,
|
|
15299
16458
|
nametag
|
|
15300
16459
|
};
|
|
16460
|
+
if (!this._addressModules.has(index)) {
|
|
16461
|
+
logger.debug("Sphere", `switchToAddress(${index}): creating per-address modules (lazy init)`);
|
|
16462
|
+
const addressTokenProviders = /* @__PURE__ */ new Map();
|
|
16463
|
+
for (const [providerId, provider] of this._tokenStorageProviders.entries()) {
|
|
16464
|
+
if (provider.createForAddress) {
|
|
16465
|
+
const newProvider = provider.createForAddress();
|
|
16466
|
+
newProvider.setIdentity(newIdentity);
|
|
16467
|
+
await newProvider.initialize();
|
|
16468
|
+
addressTokenProviders.set(providerId, newProvider);
|
|
16469
|
+
} else {
|
|
16470
|
+
logger.warn("Sphere", `Token storage provider ${providerId} does not support createForAddress, reusing shared instance`);
|
|
16471
|
+
addressTokenProviders.set(providerId, provider);
|
|
16472
|
+
}
|
|
16473
|
+
}
|
|
16474
|
+
await this.initializeAddressModules(index, newIdentity, addressTokenProviders);
|
|
16475
|
+
} else {
|
|
16476
|
+
const moduleSet = this._addressModules.get(index);
|
|
16477
|
+
if (nametag !== moduleSet.identity.nametag) {
|
|
16478
|
+
moduleSet.identity = newIdentity;
|
|
16479
|
+
const addressTransport = moduleSet.transportAdapter ?? this._transport;
|
|
16480
|
+
moduleSet.payments.initialize({
|
|
16481
|
+
identity: newIdentity,
|
|
16482
|
+
storage: this._storage,
|
|
16483
|
+
tokenStorageProviders: moduleSet.tokenStorageProviders,
|
|
16484
|
+
transport: addressTransport,
|
|
16485
|
+
oracle: this._oracle,
|
|
16486
|
+
emitEvent: this.emitEvent.bind(this),
|
|
16487
|
+
chainCode: this._masterKey?.chainCode || void 0,
|
|
16488
|
+
price: this._priceProvider ?? void 0
|
|
16489
|
+
});
|
|
16490
|
+
}
|
|
16491
|
+
}
|
|
16492
|
+
this._identity = newIdentity;
|
|
15301
16493
|
this._currentAddressIndex = index;
|
|
15302
16494
|
await this._updateCachedProxyAddress();
|
|
16495
|
+
const activeModules = this._addressModules.get(index);
|
|
16496
|
+
this._payments = activeModules.payments;
|
|
16497
|
+
this._communications = activeModules.communications;
|
|
16498
|
+
this._groupChat = activeModules.groupChat;
|
|
16499
|
+
this._market = activeModules.market;
|
|
15303
16500
|
await this._storage.set(STORAGE_KEYS_GLOBAL.CURRENT_ADDRESS_INDEX, index.toString());
|
|
15304
16501
|
this._storage.setIdentity(this._identity);
|
|
15305
|
-
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
logger.debug("Sphere", `switchToAddress(${index}): shutdown provider=${providerId}`);
|
|
15309
|
-
await provider.shutdown();
|
|
15310
|
-
provider.setIdentity(this._identity);
|
|
15311
|
-
logger.debug("Sphere", `switchToAddress(${index}): initialize provider=${providerId}`);
|
|
15312
|
-
await provider.initialize();
|
|
16502
|
+
if (this._transport.setFallbackSince) {
|
|
16503
|
+
const fallbackTs = Math.floor(Date.now() / 1e3) - 86400;
|
|
16504
|
+
this._transport.setFallbackSince(fallbackTs);
|
|
15313
16505
|
}
|
|
15314
|
-
await this.
|
|
16506
|
+
await this._transport.setIdentity(this._identity);
|
|
15315
16507
|
this.emitEvent("identity:changed", {
|
|
15316
16508
|
l1Address: this._identity.l1Address,
|
|
15317
16509
|
directAddress: this._identity.directAddress,
|
|
@@ -15366,42 +16558,104 @@ var Sphere = class _Sphere {
|
|
|
15366
16558
|
}
|
|
15367
16559
|
}
|
|
15368
16560
|
/**
|
|
15369
|
-
*
|
|
16561
|
+
* Create a new set of per-address modules for the given index.
|
|
16562
|
+
* Each address gets its own PaymentsModule, CommunicationsModule, etc.
|
|
16563
|
+
* Modules are fully independent — they have their own token storage,
|
|
16564
|
+
* and can sync/finalize/split in background regardless of active address.
|
|
16565
|
+
*
|
|
16566
|
+
* @param index - HD address index
|
|
16567
|
+
* @param identity - Full identity for this address
|
|
16568
|
+
* @param tokenStorageProviders - Token storage providers for this address
|
|
15370
16569
|
*/
|
|
15371
|
-
async
|
|
16570
|
+
async initializeAddressModules(index, identity, tokenStorageProviders) {
|
|
15372
16571
|
const emitEvent = this.emitEvent.bind(this);
|
|
15373
|
-
this.
|
|
15374
|
-
|
|
16572
|
+
const adapter = await this.ensureTransportMux(index, identity);
|
|
16573
|
+
const addressTransport = adapter ?? this._transport;
|
|
16574
|
+
const payments = createPaymentsModule({ l1: this._l1Config });
|
|
16575
|
+
const communications = createCommunicationsModule();
|
|
16576
|
+
const groupChat = this._groupChatConfig ? createGroupChatModule(this._groupChatConfig) : null;
|
|
16577
|
+
const market = this._marketConfig ? createMarketModule(this._marketConfig) : null;
|
|
16578
|
+
payments.initialize({
|
|
16579
|
+
identity,
|
|
15375
16580
|
storage: this._storage,
|
|
15376
|
-
tokenStorageProviders
|
|
15377
|
-
transport:
|
|
16581
|
+
tokenStorageProviders,
|
|
16582
|
+
transport: addressTransport,
|
|
15378
16583
|
oracle: this._oracle,
|
|
15379
16584
|
emitEvent,
|
|
15380
16585
|
chainCode: this._masterKey?.chainCode || void 0,
|
|
15381
16586
|
price: this._priceProvider ?? void 0
|
|
15382
16587
|
});
|
|
15383
|
-
|
|
15384
|
-
identity
|
|
16588
|
+
communications.initialize({
|
|
16589
|
+
identity,
|
|
15385
16590
|
storage: this._storage,
|
|
15386
|
-
transport:
|
|
16591
|
+
transport: addressTransport,
|
|
15387
16592
|
emitEvent
|
|
15388
16593
|
});
|
|
15389
|
-
|
|
15390
|
-
identity
|
|
16594
|
+
groupChat?.initialize({
|
|
16595
|
+
identity,
|
|
15391
16596
|
storage: this._storage,
|
|
15392
16597
|
emitEvent
|
|
15393
16598
|
});
|
|
15394
|
-
|
|
15395
|
-
identity
|
|
16599
|
+
market?.initialize({
|
|
16600
|
+
identity,
|
|
15396
16601
|
emitEvent
|
|
15397
16602
|
});
|
|
15398
|
-
await
|
|
15399
|
-
await
|
|
15400
|
-
await
|
|
15401
|
-
await
|
|
15402
|
-
|
|
15403
|
-
|
|
16603
|
+
await payments.load();
|
|
16604
|
+
await communications.load();
|
|
16605
|
+
await groupChat?.load();
|
|
16606
|
+
await market?.load();
|
|
16607
|
+
const moduleSet = {
|
|
16608
|
+
index,
|
|
16609
|
+
identity,
|
|
16610
|
+
payments,
|
|
16611
|
+
communications,
|
|
16612
|
+
groupChat,
|
|
16613
|
+
market,
|
|
16614
|
+
transportAdapter: adapter,
|
|
16615
|
+
tokenStorageProviders: new Map(tokenStorageProviders),
|
|
16616
|
+
initialized: true
|
|
16617
|
+
};
|
|
16618
|
+
this._addressModules.set(index, moduleSet);
|
|
16619
|
+
logger.debug("Sphere", `Initialized per-address modules for address ${index} (transport: ${adapter ? "mux adapter" : "primary"})`);
|
|
16620
|
+
payments.sync().catch((err) => {
|
|
16621
|
+
logger.warn("Sphere", `Post-init sync failed for address ${index}:`, err);
|
|
15404
16622
|
});
|
|
16623
|
+
return moduleSet;
|
|
16624
|
+
}
|
|
16625
|
+
/**
|
|
16626
|
+
* Ensure the transport multiplexer exists and register an address.
|
|
16627
|
+
* Creates the mux on first call. Returns an AddressTransportAdapter
|
|
16628
|
+
* that routes events for this address independently.
|
|
16629
|
+
* @returns AddressTransportAdapter or null if transport is not Nostr-based
|
|
16630
|
+
*/
|
|
16631
|
+
async ensureTransportMux(index, identity) {
|
|
16632
|
+
const transport = this._transport;
|
|
16633
|
+
if (typeof transport.getWebSocketFactory !== "function" || typeof transport.getConfiguredRelays !== "function") {
|
|
16634
|
+
logger.debug("Sphere", "Transport does not support mux interface, skipping");
|
|
16635
|
+
return null;
|
|
16636
|
+
}
|
|
16637
|
+
const nostrTransport = transport;
|
|
16638
|
+
if (!this._transportMux) {
|
|
16639
|
+
this._transportMux = new MultiAddressTransportMux({
|
|
16640
|
+
relays: nostrTransport.getConfiguredRelays(),
|
|
16641
|
+
createWebSocket: nostrTransport.getWebSocketFactory(),
|
|
16642
|
+
storage: nostrTransport.getStorageAdapter() ?? void 0
|
|
16643
|
+
});
|
|
16644
|
+
await this._transportMux.connect();
|
|
16645
|
+
if (typeof nostrTransport.suppressSubscriptions === "function") {
|
|
16646
|
+
nostrTransport.suppressSubscriptions();
|
|
16647
|
+
}
|
|
16648
|
+
logger.debug("Sphere", "Transport mux created and connected");
|
|
16649
|
+
}
|
|
16650
|
+
const adapter = await this._transportMux.addAddress(index, identity, this._transport);
|
|
16651
|
+
return adapter;
|
|
16652
|
+
}
|
|
16653
|
+
/**
|
|
16654
|
+
* Get per-address modules for any address index (creates lazily if needed).
|
|
16655
|
+
* This allows accessing any address's modules without switching.
|
|
16656
|
+
*/
|
|
16657
|
+
getAddressPayments(index) {
|
|
16658
|
+
return this._addressModules.get(index)?.payments;
|
|
15405
16659
|
}
|
|
15406
16660
|
/**
|
|
15407
16661
|
* Derive address at a specific index
|
|
@@ -16322,17 +17576,40 @@ var Sphere = class _Sphere {
|
|
|
16322
17576
|
*/
|
|
16323
17577
|
cleanNametag(raw) {
|
|
16324
17578
|
const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
16325
|
-
return (0,
|
|
17579
|
+
return (0, import_nostr_js_sdk4.normalizeNametag)(stripped);
|
|
16326
17580
|
}
|
|
16327
17581
|
// ===========================================================================
|
|
16328
17582
|
// Public Methods - Lifecycle
|
|
16329
17583
|
// ===========================================================================
|
|
16330
17584
|
async destroy() {
|
|
16331
17585
|
this.cleanupProviderEventSubscriptions();
|
|
17586
|
+
for (const [idx, moduleSet] of this._addressModules.entries()) {
|
|
17587
|
+
try {
|
|
17588
|
+
moduleSet.payments.destroy();
|
|
17589
|
+
moduleSet.communications.destroy();
|
|
17590
|
+
moduleSet.groupChat?.destroy();
|
|
17591
|
+
moduleSet.market?.destroy();
|
|
17592
|
+
for (const provider of moduleSet.tokenStorageProviders.values()) {
|
|
17593
|
+
try {
|
|
17594
|
+
await provider.shutdown();
|
|
17595
|
+
} catch {
|
|
17596
|
+
}
|
|
17597
|
+
}
|
|
17598
|
+
moduleSet.tokenStorageProviders.clear();
|
|
17599
|
+
logger.debug("Sphere", `Destroyed modules for address ${idx}`);
|
|
17600
|
+
} catch (err) {
|
|
17601
|
+
logger.warn("Sphere", `Error destroying modules for address ${idx}:`, err);
|
|
17602
|
+
}
|
|
17603
|
+
}
|
|
17604
|
+
this._addressModules.clear();
|
|
16332
17605
|
this._payments.destroy();
|
|
16333
17606
|
this._communications.destroy();
|
|
16334
17607
|
this._groupChat?.destroy();
|
|
16335
17608
|
this._market?.destroy();
|
|
17609
|
+
if (this._transportMux) {
|
|
17610
|
+
await this._transportMux.disconnect();
|
|
17611
|
+
this._transportMux = null;
|
|
17612
|
+
}
|
|
16336
17613
|
await this._transport.disconnect();
|
|
16337
17614
|
await this._storage.disconnect();
|
|
16338
17615
|
await this._oracle.disconnect();
|
|
@@ -16527,6 +17804,9 @@ var Sphere = class _Sphere {
|
|
|
16527
17804
|
// ===========================================================================
|
|
16528
17805
|
async initializeProviders() {
|
|
16529
17806
|
this._storage.setIdentity(this._identity);
|
|
17807
|
+
if (this._transport.setFallbackSince) {
|
|
17808
|
+
this._transport.setFallbackSince(Math.floor(Date.now() / 1e3) - 86400);
|
|
17809
|
+
}
|
|
16530
17810
|
await this._transport.setIdentity(this._identity);
|
|
16531
17811
|
for (const provider of this._tokenStorageProviders.values()) {
|
|
16532
17812
|
provider.setIdentity(this._identity);
|
|
@@ -16617,11 +17897,13 @@ var Sphere = class _Sphere {
|
|
|
16617
17897
|
}
|
|
16618
17898
|
async initializeModules() {
|
|
16619
17899
|
const emitEvent = this.emitEvent.bind(this);
|
|
17900
|
+
const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
|
|
17901
|
+
const moduleTransport = adapter ?? this._transport;
|
|
16620
17902
|
this._payments.initialize({
|
|
16621
17903
|
identity: this._identity,
|
|
16622
17904
|
storage: this._storage,
|
|
16623
17905
|
tokenStorageProviders: this._tokenStorageProviders,
|
|
16624
|
-
transport:
|
|
17906
|
+
transport: moduleTransport,
|
|
16625
17907
|
oracle: this._oracle,
|
|
16626
17908
|
emitEvent,
|
|
16627
17909
|
// Pass chain code for L1 HD derivation
|
|
@@ -16632,7 +17914,7 @@ var Sphere = class _Sphere {
|
|
|
16632
17914
|
this._communications.initialize({
|
|
16633
17915
|
identity: this._identity,
|
|
16634
17916
|
storage: this._storage,
|
|
16635
|
-
transport:
|
|
17917
|
+
transport: moduleTransport,
|
|
16636
17918
|
emitEvent
|
|
16637
17919
|
});
|
|
16638
17920
|
this._groupChat?.initialize({
|
|
@@ -16648,6 +17930,17 @@ var Sphere = class _Sphere {
|
|
|
16648
17930
|
await this._communications.load();
|
|
16649
17931
|
await this._groupChat?.load();
|
|
16650
17932
|
await this._market?.load();
|
|
17933
|
+
this._addressModules.set(this._currentAddressIndex, {
|
|
17934
|
+
index: this._currentAddressIndex,
|
|
17935
|
+
identity: this._identity,
|
|
17936
|
+
payments: this._payments,
|
|
17937
|
+
communications: this._communications,
|
|
17938
|
+
groupChat: this._groupChat,
|
|
17939
|
+
market: this._market,
|
|
17940
|
+
transportAdapter: adapter,
|
|
17941
|
+
tokenStorageProviders: new Map(this._tokenStorageProviders),
|
|
17942
|
+
initialized: true
|
|
17943
|
+
});
|
|
16651
17944
|
}
|
|
16652
17945
|
// ===========================================================================
|
|
16653
17946
|
// Private: Helpers
|
|
@@ -17319,8 +18612,8 @@ function createTokenValidator(options) {
|
|
|
17319
18612
|
}
|
|
17320
18613
|
|
|
17321
18614
|
// index.ts
|
|
17322
|
-
var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
|
|
17323
18615
|
var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
|
|
18616
|
+
var import_nostr_js_sdk6 = require("@unicitylabs/nostr-js-sdk");
|
|
17324
18617
|
|
|
17325
18618
|
// price/CoinGeckoPriceProvider.ts
|
|
17326
18619
|
init_logger();
|