@unicitylabs/sphere-sdk 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.cjs +1351 -52
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +342 -3
- package/dist/core/index.d.ts +342 -3
- package/dist/core/index.js +1357 -48
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +109 -11
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +109 -11
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +38 -10
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +38 -10
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +105 -11
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +43 -0
- package/dist/impl/nodejs/index.d.ts +43 -0
- package/dist/impl/nodejs/index.js +105 -11
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +1334 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -2
- package/dist/index.d.ts +43 -2
- package/dist/index.js +1333 -50
- package/dist/index.js.map +1 -1
- package/dist/l1/index.d.cts +717 -0
- package/dist/l1/index.d.ts +717 -0
- package/package.json +1 -1
package/dist/index.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;
|
|
@@ -9184,7 +10313,7 @@ function createCommunicationsModule(config) {
|
|
|
9184
10313
|
}
|
|
9185
10314
|
|
|
9186
10315
|
// modules/groupchat/GroupChatModule.ts
|
|
9187
|
-
var
|
|
10316
|
+
var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
|
|
9188
10317
|
init_logger();
|
|
9189
10318
|
init_errors();
|
|
9190
10319
|
init_constants();
|
|
@@ -9202,7 +10331,7 @@ var GroupVisibility = {
|
|
|
9202
10331
|
|
|
9203
10332
|
// modules/groupchat/GroupChatModule.ts
|
|
9204
10333
|
function createNip29Filter(data) {
|
|
9205
|
-
return new
|
|
10334
|
+
return new import_nostr_js_sdk3.Filter(data);
|
|
9206
10335
|
}
|
|
9207
10336
|
var GroupChatModule = class {
|
|
9208
10337
|
config;
|
|
@@ -9251,7 +10380,7 @@ var GroupChatModule = class {
|
|
|
9251
10380
|
}
|
|
9252
10381
|
this.deps = deps;
|
|
9253
10382
|
const secretKey = Buffer.from(deps.identity.privateKey, "hex");
|
|
9254
|
-
this.keyManager =
|
|
10383
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9255
10384
|
}
|
|
9256
10385
|
async load() {
|
|
9257
10386
|
this.ensureInitialized();
|
|
@@ -9386,7 +10515,7 @@ var GroupChatModule = class {
|
|
|
9386
10515
|
}
|
|
9387
10516
|
this.subscriptionIds = [];
|
|
9388
10517
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9389
|
-
this.keyManager =
|
|
10518
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9390
10519
|
if (this.groups.size === 0) {
|
|
9391
10520
|
await this.restoreJoinedGroups();
|
|
9392
10521
|
} else {
|
|
@@ -9398,13 +10527,13 @@ var GroupChatModule = class {
|
|
|
9398
10527
|
this.ensureInitialized();
|
|
9399
10528
|
if (!this.keyManager) {
|
|
9400
10529
|
const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
|
|
9401
|
-
this.keyManager =
|
|
10530
|
+
this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
|
|
9402
10531
|
}
|
|
9403
10532
|
const primaryRelay = this.config.relays[0];
|
|
9404
10533
|
if (primaryRelay) {
|
|
9405
10534
|
await this.checkAndClearOnRelayChange(primaryRelay);
|
|
9406
10535
|
}
|
|
9407
|
-
this.client = new
|
|
10536
|
+
this.client = new import_nostr_js_sdk3.NostrClient(this.keyManager);
|
|
9408
10537
|
try {
|
|
9409
10538
|
await this.client.connect(...this.config.relays);
|
|
9410
10539
|
this.connected = true;
|
|
@@ -9663,7 +10792,7 @@ var GroupChatModule = class {
|
|
|
9663
10792
|
if (!myPubkey) return [];
|
|
9664
10793
|
const groupIdsWithMembership = /* @__PURE__ */ new Set();
|
|
9665
10794
|
await this.oneshotSubscription(
|
|
9666
|
-
new
|
|
10795
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9667
10796
|
{
|
|
9668
10797
|
onEvent: (event) => {
|
|
9669
10798
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -9714,7 +10843,7 @@ var GroupChatModule = class {
|
|
|
9714
10843
|
const memberCountsMap = /* @__PURE__ */ new Map();
|
|
9715
10844
|
await Promise.all([
|
|
9716
10845
|
this.oneshotSubscription(
|
|
9717
|
-
new
|
|
10846
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
|
|
9718
10847
|
{
|
|
9719
10848
|
onEvent: (event) => {
|
|
9720
10849
|
const group = this.parseGroupMetadata(event);
|
|
@@ -9732,7 +10861,7 @@ var GroupChatModule = class {
|
|
|
9732
10861
|
}
|
|
9733
10862
|
),
|
|
9734
10863
|
this.oneshotSubscription(
|
|
9735
|
-
new
|
|
10864
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS] }),
|
|
9736
10865
|
{
|
|
9737
10866
|
onEvent: (event) => {
|
|
9738
10867
|
const groupId = this.getGroupIdFromMetadataEvent(event);
|
|
@@ -10235,7 +11364,7 @@ var GroupChatModule = class {
|
|
|
10235
11364
|
if (!this.client) return /* @__PURE__ */ new Set();
|
|
10236
11365
|
const adminPubkeys = /* @__PURE__ */ new Set();
|
|
10237
11366
|
return this.oneshotSubscription(
|
|
10238
|
-
new
|
|
11367
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
|
|
10239
11368
|
{
|
|
10240
11369
|
onEvent: (event) => {
|
|
10241
11370
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -10257,7 +11386,7 @@ var GroupChatModule = class {
|
|
|
10257
11386
|
if (!this.client) return null;
|
|
10258
11387
|
let result = null;
|
|
10259
11388
|
return this.oneshotSubscription(
|
|
10260
|
-
new
|
|
11389
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
|
|
10261
11390
|
{
|
|
10262
11391
|
onEvent: (event) => {
|
|
10263
11392
|
if (!result) result = this.parseGroupMetadata(event);
|
|
@@ -10294,7 +11423,7 @@ var GroupChatModule = class {
|
|
|
10294
11423
|
if (!this.client) return [];
|
|
10295
11424
|
const members = [];
|
|
10296
11425
|
return this.oneshotSubscription(
|
|
10297
|
-
new
|
|
11426
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
|
|
10298
11427
|
{
|
|
10299
11428
|
onEvent: (event) => {
|
|
10300
11429
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -10315,7 +11444,7 @@ var GroupChatModule = class {
|
|
|
10315
11444
|
if (!this.client) return [];
|
|
10316
11445
|
const adminPubkeys = [];
|
|
10317
11446
|
return this.oneshotSubscription(
|
|
10318
|
-
new
|
|
11447
|
+
new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
|
|
10319
11448
|
{
|
|
10320
11449
|
onEvent: (event) => {
|
|
10321
11450
|
const pTags = event.tags.filter((t) => t[0] === "p");
|
|
@@ -13984,9 +15113,9 @@ var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign
|
|
|
13984
15113
|
var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
13985
15114
|
var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
13986
15115
|
var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
13987
|
-
var
|
|
15116
|
+
var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
|
|
13988
15117
|
function isValidNametag(nametag) {
|
|
13989
|
-
if ((0,
|
|
15118
|
+
if ((0, import_nostr_js_sdk4.isPhoneNumber)(nametag)) return true;
|
|
13990
15119
|
return /^[a-z0-9_-]{3,20}$/.test(nametag);
|
|
13991
15120
|
}
|
|
13992
15121
|
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
@@ -14030,11 +15159,18 @@ var Sphere = class _Sphere {
|
|
|
14030
15159
|
_transport;
|
|
14031
15160
|
_oracle;
|
|
14032
15161
|
_priceProvider;
|
|
14033
|
-
// Modules
|
|
15162
|
+
// Modules (single-instance — backward compat, delegates to active address)
|
|
14034
15163
|
_payments;
|
|
14035
15164
|
_communications;
|
|
14036
15165
|
_groupChat = null;
|
|
14037
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;
|
|
14038
15174
|
// Events
|
|
14039
15175
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
14040
15176
|
// Provider management
|
|
@@ -14052,6 +15188,9 @@ var Sphere = class _Sphere {
|
|
|
14052
15188
|
if (tokenStorage) {
|
|
14053
15189
|
this._tokenStorageProviders.set(tokenStorage.id, tokenStorage);
|
|
14054
15190
|
}
|
|
15191
|
+
this._l1Config = l1Config;
|
|
15192
|
+
this._groupChatConfig = groupChatConfig;
|
|
15193
|
+
this._marketConfig = marketConfig;
|
|
14055
15194
|
this._payments = createPaymentsModule({ l1: l1Config });
|
|
14056
15195
|
this._communications = createCommunicationsModule();
|
|
14057
15196
|
this._groupChat = groupChatConfig ? createGroupChatModule(groupChatConfig) : null;
|
|
@@ -15310,7 +16449,7 @@ var Sphere = class _Sphere {
|
|
|
15310
16449
|
nametags.set(0, newNametag);
|
|
15311
16450
|
}
|
|
15312
16451
|
const nametag = this._addressNametags.get(addressId)?.get(0);
|
|
15313
|
-
|
|
16452
|
+
const newIdentity = {
|
|
15314
16453
|
privateKey: addressInfo.privateKey,
|
|
15315
16454
|
chainPubkey: addressInfo.publicKey,
|
|
15316
16455
|
l1Address: addressInfo.address,
|
|
@@ -15318,20 +16457,53 @@ var Sphere = class _Sphere {
|
|
|
15318
16457
|
ipnsName: "12D3KooW" + ipnsHash,
|
|
15319
16458
|
nametag
|
|
15320
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;
|
|
15321
16493
|
this._currentAddressIndex = index;
|
|
15322
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;
|
|
15323
16500
|
await this._storage.set(STORAGE_KEYS_GLOBAL.CURRENT_ADDRESS_INDEX, index.toString());
|
|
15324
16501
|
this._storage.setIdentity(this._identity);
|
|
15325
|
-
|
|
15326
|
-
|
|
15327
|
-
|
|
15328
|
-
logger.debug("Sphere", `switchToAddress(${index}): shutdown provider=${providerId}`);
|
|
15329
|
-
await provider.shutdown();
|
|
15330
|
-
provider.setIdentity(this._identity);
|
|
15331
|
-
logger.debug("Sphere", `switchToAddress(${index}): initialize provider=${providerId}`);
|
|
15332
|
-
await provider.initialize();
|
|
16502
|
+
if (this._transport.setFallbackSince) {
|
|
16503
|
+
const fallbackTs = Math.floor(Date.now() / 1e3) - 86400;
|
|
16504
|
+
this._transport.setFallbackSince(fallbackTs);
|
|
15333
16505
|
}
|
|
15334
|
-
await this.
|
|
16506
|
+
await this._transport.setIdentity(this._identity);
|
|
15335
16507
|
this.emitEvent("identity:changed", {
|
|
15336
16508
|
l1Address: this._identity.l1Address,
|
|
15337
16509
|
directAddress: this._identity.directAddress,
|
|
@@ -15386,42 +16558,104 @@ var Sphere = class _Sphere {
|
|
|
15386
16558
|
}
|
|
15387
16559
|
}
|
|
15388
16560
|
/**
|
|
15389
|
-
*
|
|
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
|
|
15390
16569
|
*/
|
|
15391
|
-
async
|
|
16570
|
+
async initializeAddressModules(index, identity, tokenStorageProviders) {
|
|
15392
16571
|
const emitEvent = this.emitEvent.bind(this);
|
|
15393
|
-
this.
|
|
15394
|
-
|
|
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,
|
|
15395
16580
|
storage: this._storage,
|
|
15396
|
-
tokenStorageProviders
|
|
15397
|
-
transport:
|
|
16581
|
+
tokenStorageProviders,
|
|
16582
|
+
transport: addressTransport,
|
|
15398
16583
|
oracle: this._oracle,
|
|
15399
16584
|
emitEvent,
|
|
15400
16585
|
chainCode: this._masterKey?.chainCode || void 0,
|
|
15401
16586
|
price: this._priceProvider ?? void 0
|
|
15402
16587
|
});
|
|
15403
|
-
|
|
15404
|
-
identity
|
|
16588
|
+
communications.initialize({
|
|
16589
|
+
identity,
|
|
15405
16590
|
storage: this._storage,
|
|
15406
|
-
transport:
|
|
16591
|
+
transport: addressTransport,
|
|
15407
16592
|
emitEvent
|
|
15408
16593
|
});
|
|
15409
|
-
|
|
15410
|
-
identity
|
|
16594
|
+
groupChat?.initialize({
|
|
16595
|
+
identity,
|
|
15411
16596
|
storage: this._storage,
|
|
15412
16597
|
emitEvent
|
|
15413
16598
|
});
|
|
15414
|
-
|
|
15415
|
-
identity
|
|
16599
|
+
market?.initialize({
|
|
16600
|
+
identity,
|
|
15416
16601
|
emitEvent
|
|
15417
16602
|
});
|
|
15418
|
-
await
|
|
15419
|
-
await
|
|
15420
|
-
await
|
|
15421
|
-
await
|
|
15422
|
-
|
|
15423
|
-
|
|
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);
|
|
15424
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;
|
|
15425
16659
|
}
|
|
15426
16660
|
/**
|
|
15427
16661
|
* Derive address at a specific index
|
|
@@ -16342,17 +17576,40 @@ var Sphere = class _Sphere {
|
|
|
16342
17576
|
*/
|
|
16343
17577
|
cleanNametag(raw) {
|
|
16344
17578
|
const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
16345
|
-
return (0,
|
|
17579
|
+
return (0, import_nostr_js_sdk4.normalizeNametag)(stripped);
|
|
16346
17580
|
}
|
|
16347
17581
|
// ===========================================================================
|
|
16348
17582
|
// Public Methods - Lifecycle
|
|
16349
17583
|
// ===========================================================================
|
|
16350
17584
|
async destroy() {
|
|
16351
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();
|
|
16352
17605
|
this._payments.destroy();
|
|
16353
17606
|
this._communications.destroy();
|
|
16354
17607
|
this._groupChat?.destroy();
|
|
16355
17608
|
this._market?.destroy();
|
|
17609
|
+
if (this._transportMux) {
|
|
17610
|
+
await this._transportMux.disconnect();
|
|
17611
|
+
this._transportMux = null;
|
|
17612
|
+
}
|
|
16356
17613
|
await this._transport.disconnect();
|
|
16357
17614
|
await this._storage.disconnect();
|
|
16358
17615
|
await this._oracle.disconnect();
|
|
@@ -16547,6 +17804,9 @@ var Sphere = class _Sphere {
|
|
|
16547
17804
|
// ===========================================================================
|
|
16548
17805
|
async initializeProviders() {
|
|
16549
17806
|
this._storage.setIdentity(this._identity);
|
|
17807
|
+
if (this._transport.setFallbackSince) {
|
|
17808
|
+
this._transport.setFallbackSince(Math.floor(Date.now() / 1e3) - 86400);
|
|
17809
|
+
}
|
|
16550
17810
|
await this._transport.setIdentity(this._identity);
|
|
16551
17811
|
for (const provider of this._tokenStorageProviders.values()) {
|
|
16552
17812
|
provider.setIdentity(this._identity);
|
|
@@ -16637,11 +17897,13 @@ var Sphere = class _Sphere {
|
|
|
16637
17897
|
}
|
|
16638
17898
|
async initializeModules() {
|
|
16639
17899
|
const emitEvent = this.emitEvent.bind(this);
|
|
17900
|
+
const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
|
|
17901
|
+
const moduleTransport = adapter ?? this._transport;
|
|
16640
17902
|
this._payments.initialize({
|
|
16641
17903
|
identity: this._identity,
|
|
16642
17904
|
storage: this._storage,
|
|
16643
17905
|
tokenStorageProviders: this._tokenStorageProviders,
|
|
16644
|
-
transport:
|
|
17906
|
+
transport: moduleTransport,
|
|
16645
17907
|
oracle: this._oracle,
|
|
16646
17908
|
emitEvent,
|
|
16647
17909
|
// Pass chain code for L1 HD derivation
|
|
@@ -16652,7 +17914,7 @@ var Sphere = class _Sphere {
|
|
|
16652
17914
|
this._communications.initialize({
|
|
16653
17915
|
identity: this._identity,
|
|
16654
17916
|
storage: this._storage,
|
|
16655
|
-
transport:
|
|
17917
|
+
transport: moduleTransport,
|
|
16656
17918
|
emitEvent
|
|
16657
17919
|
});
|
|
16658
17920
|
this._groupChat?.initialize({
|
|
@@ -16668,6 +17930,17 @@ var Sphere = class _Sphere {
|
|
|
16668
17930
|
await this._communications.load();
|
|
16669
17931
|
await this._groupChat?.load();
|
|
16670
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
|
+
});
|
|
16671
17944
|
}
|
|
16672
17945
|
// ===========================================================================
|
|
16673
17946
|
// Private: Helpers
|
|
@@ -17339,8 +18612,8 @@ function createTokenValidator(options) {
|
|
|
17339
18612
|
}
|
|
17340
18613
|
|
|
17341
18614
|
// index.ts
|
|
17342
|
-
var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
|
|
17343
18615
|
var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
|
|
18616
|
+
var import_nostr_js_sdk6 = require("@unicitylabs/nostr-js-sdk");
|
|
17344
18617
|
|
|
17345
18618
|
// price/CoinGeckoPriceProvider.ts
|
|
17346
18619
|
init_logger();
|