@unicitylabs/sphere-sdk 0.1.2 → 0.1.4
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/README.md +54 -15
- package/dist/core/index.cjs +310 -88
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +300 -204
- package/dist/core/index.d.ts +300 -204
- package/dist/core/index.js +310 -88
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +1234 -920
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +1234 -926
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +1112 -0
- package/dist/impl/browser/ipfs.cjs.map +1 -0
- package/dist/impl/browser/ipfs.js +1079 -0
- package/dist/impl/browser/ipfs.js.map +1 -0
- package/dist/impl/nodejs/index.cjs +1081 -222
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +131 -55
- package/dist/impl/nodejs/index.d.ts +131 -55
- package/dist/impl/nodejs/index.js +1088 -225
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +310 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +663 -345
- package/dist/index.d.ts +663 -345
- package/dist/index.js +310 -88
- package/dist/index.js.map +1 -1
- package/package.json +16 -2
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// impl/browser/index.ts
|
|
@@ -22,7 +32,6 @@ var browser_exports = {};
|
|
|
22
32
|
__export(browser_exports, {
|
|
23
33
|
BrowserTrustBaseLoader: () => BrowserTrustBaseLoader,
|
|
24
34
|
IndexedDBTokenStorageProvider: () => IndexedDBTokenStorageProvider,
|
|
25
|
-
IpfsStorageProvider: () => IpfsStorageProvider,
|
|
26
35
|
LocalStorageProvider: () => LocalStorageProvider,
|
|
27
36
|
NostrTransportProvider: () => NostrTransportProvider,
|
|
28
37
|
UnicityAggregatorProvider: () => UnicityAggregatorProvider,
|
|
@@ -32,7 +41,6 @@ __export(browser_exports, {
|
|
|
32
41
|
createBrowserTrustBaseLoader: () => createBrowserTrustBaseLoader,
|
|
33
42
|
createBrowserWebSocket: () => createBrowserWebSocket,
|
|
34
43
|
createIndexedDBTokenStorageProvider: () => createIndexedDBTokenStorageProvider,
|
|
35
|
-
createIpfsStorageProvider: () => createIpfsStorageProvider,
|
|
36
44
|
createLocalStorageProvider: () => createLocalStorageProvider,
|
|
37
45
|
createNostrTransportProvider: () => createNostrTransportProvider,
|
|
38
46
|
createUnicityAggregatorProvider: () => createUnicityAggregatorProvider,
|
|
@@ -62,7 +70,7 @@ var LocalStorageProvider = class {
|
|
|
62
70
|
constructor(config) {
|
|
63
71
|
const storage = config?.storage ?? this.getStorageSafe();
|
|
64
72
|
this.config = {
|
|
65
|
-
prefix: config?.prefix ?? "
|
|
73
|
+
prefix: config?.prefix ?? "sphere_",
|
|
66
74
|
storage,
|
|
67
75
|
debug: config?.debug ?? false
|
|
68
76
|
};
|
|
@@ -99,7 +107,7 @@ var LocalStorageProvider = class {
|
|
|
99
107
|
// ===========================================================================
|
|
100
108
|
setIdentity(identity) {
|
|
101
109
|
this.identity = identity;
|
|
102
|
-
this.log("Identity set:", identity.
|
|
110
|
+
this.log("Identity set:", identity.l1Address);
|
|
103
111
|
}
|
|
104
112
|
async get(key) {
|
|
105
113
|
this.ensureConnected();
|
|
@@ -166,7 +174,7 @@ var LocalStorageProvider = class {
|
|
|
166
174
|
// Private Methods
|
|
167
175
|
// ===========================================================================
|
|
168
176
|
getFullKey(key) {
|
|
169
|
-
const addressPart = this.identity?.
|
|
177
|
+
const addressPart = this.identity?.l1Address ?? "default";
|
|
170
178
|
return `${this.config.prefix}${addressPart}_${key}`;
|
|
171
179
|
}
|
|
172
180
|
ensureConnected() {
|
|
@@ -213,676 +221,6 @@ function createLocalStorageProvider(config) {
|
|
|
213
221
|
return new LocalStorageProvider(config);
|
|
214
222
|
}
|
|
215
223
|
|
|
216
|
-
// constants.ts
|
|
217
|
-
var STORAGE_PREFIX = "sphere_";
|
|
218
|
-
var STORAGE_KEYS = {
|
|
219
|
-
/** Encrypted BIP39 mnemonic */
|
|
220
|
-
MNEMONIC: `${STORAGE_PREFIX}mnemonic`,
|
|
221
|
-
/** Encrypted master private key */
|
|
222
|
-
MASTER_KEY: `${STORAGE_PREFIX}master_key`,
|
|
223
|
-
/** BIP32 chain code */
|
|
224
|
-
CHAIN_CODE: `${STORAGE_PREFIX}chain_code`,
|
|
225
|
-
/** HD derivation path (full path like m/44'/0'/0'/0/0) */
|
|
226
|
-
DERIVATION_PATH: `${STORAGE_PREFIX}derivation_path`,
|
|
227
|
-
/** Base derivation path (like m/44'/0'/0' without chain/index) */
|
|
228
|
-
BASE_PATH: `${STORAGE_PREFIX}base_path`,
|
|
229
|
-
/** Derivation mode: bip32, wif_hmac, legacy_hmac */
|
|
230
|
-
DERIVATION_MODE: `${STORAGE_PREFIX}derivation_mode`,
|
|
231
|
-
/** Wallet source: mnemonic, file, unknown */
|
|
232
|
-
WALLET_SOURCE: `${STORAGE_PREFIX}wallet_source`,
|
|
233
|
-
/** Wallet existence flag */
|
|
234
|
-
WALLET_EXISTS: `${STORAGE_PREFIX}wallet_exists`,
|
|
235
|
-
/** Registered nametag (legacy - single address) */
|
|
236
|
-
NAMETAG: `${STORAGE_PREFIX}nametag`,
|
|
237
|
-
/** Current active address index */
|
|
238
|
-
CURRENT_ADDRESS_INDEX: `${STORAGE_PREFIX}current_address_index`,
|
|
239
|
-
/** Address nametags map (JSON: { "0": "alice", "1": "bob" }) */
|
|
240
|
-
ADDRESS_NAMETAGS: `${STORAGE_PREFIX}address_nametags`,
|
|
241
|
-
/** Token data */
|
|
242
|
-
TOKENS: `${STORAGE_PREFIX}tokens`,
|
|
243
|
-
/** Pending transfers */
|
|
244
|
-
PENDING_TRANSFERS: `${STORAGE_PREFIX}pending_transfers`,
|
|
245
|
-
/** Transfer outbox */
|
|
246
|
-
OUTBOX: `${STORAGE_PREFIX}outbox`,
|
|
247
|
-
/** Conversations */
|
|
248
|
-
CONVERSATIONS: `${STORAGE_PREFIX}conversations`,
|
|
249
|
-
/** Messages */
|
|
250
|
-
MESSAGES: `${STORAGE_PREFIX}messages`,
|
|
251
|
-
/** Transaction history */
|
|
252
|
-
TRANSACTION_HISTORY: `${STORAGE_PREFIX}transaction_history`,
|
|
253
|
-
/** Archived tokens (spent token history) */
|
|
254
|
-
ARCHIVED_TOKENS: `${STORAGE_PREFIX}archived_tokens`,
|
|
255
|
-
/** Tombstones (records of deleted/spent tokens) */
|
|
256
|
-
TOMBSTONES: `${STORAGE_PREFIX}tombstones`,
|
|
257
|
-
/** Forked tokens (alternative histories) */
|
|
258
|
-
FORKED_TOKENS: `${STORAGE_PREFIX}forked_tokens`
|
|
259
|
-
};
|
|
260
|
-
var DEFAULT_NOSTR_RELAYS = [
|
|
261
|
-
"wss://relay.unicity.network",
|
|
262
|
-
"wss://relay.damus.io",
|
|
263
|
-
"wss://nos.lol",
|
|
264
|
-
"wss://relay.nostr.band"
|
|
265
|
-
];
|
|
266
|
-
var NOSTR_EVENT_KINDS = {
|
|
267
|
-
/** NIP-04 encrypted direct message */
|
|
268
|
-
DIRECT_MESSAGE: 4,
|
|
269
|
-
/** Token transfer (Unicity custom - 31113) */
|
|
270
|
-
TOKEN_TRANSFER: 31113,
|
|
271
|
-
/** Payment request (Unicity custom - 31115) */
|
|
272
|
-
PAYMENT_REQUEST: 31115,
|
|
273
|
-
/** Payment request response (Unicity custom - 31116) */
|
|
274
|
-
PAYMENT_REQUEST_RESPONSE: 31116,
|
|
275
|
-
/** Nametag binding (NIP-78 app-specific data) */
|
|
276
|
-
NAMETAG_BINDING: 30078,
|
|
277
|
-
/** Public broadcast */
|
|
278
|
-
BROADCAST: 1
|
|
279
|
-
};
|
|
280
|
-
var DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
|
|
281
|
-
var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
|
|
282
|
-
var TEST_AGGREGATOR_URL = "https://goggregator-test.unicity.network";
|
|
283
|
-
var DEFAULT_AGGREGATOR_TIMEOUT = 3e4;
|
|
284
|
-
var DEFAULT_IPFS_GATEWAYS = [
|
|
285
|
-
"https://ipfs.unicity.network",
|
|
286
|
-
"https://dweb.link",
|
|
287
|
-
"https://ipfs.io"
|
|
288
|
-
];
|
|
289
|
-
var DEFAULT_IPFS_BOOTSTRAP_PEERS = [
|
|
290
|
-
"/dns4/unicity-ipfs2.dyndns.org/tcp/4001/p2p/12D3KooWLNi5NDPPHbrfJakAQqwBqymYTTwMQXQKEWuCrJNDdmfh",
|
|
291
|
-
"/dns4/unicity-ipfs3.dyndns.org/tcp/4001/p2p/12D3KooWQ4aujVE4ShLjdusNZBdffq3TbzrwT2DuWZY9H1Gxhwn6",
|
|
292
|
-
"/dns4/unicity-ipfs4.dyndns.org/tcp/4001/p2p/12D3KooWJ1ByPfUzUrpYvgxKU8NZrR8i6PU1tUgMEbQX9Hh2DEn1",
|
|
293
|
-
"/dns4/unicity-ipfs5.dyndns.org/tcp/4001/p2p/12D3KooWB1MdZZGHN5B8TvWXntbycfe7Cjcz7n6eZ9eykZadvmDv"
|
|
294
|
-
];
|
|
295
|
-
var DEFAULT_BASE_PATH = "m/44'/0'/0'";
|
|
296
|
-
var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
|
|
297
|
-
var DEFAULT_ELECTRUM_URL = "wss://fulcrum.alpha.unicity.network:50004";
|
|
298
|
-
var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
|
|
299
|
-
var TEST_NOSTR_RELAYS = [
|
|
300
|
-
"wss://nostr-relay.testnet.unicity.network"
|
|
301
|
-
];
|
|
302
|
-
var NETWORKS = {
|
|
303
|
-
mainnet: {
|
|
304
|
-
name: "Mainnet",
|
|
305
|
-
aggregatorUrl: DEFAULT_AGGREGATOR_URL,
|
|
306
|
-
nostrRelays: DEFAULT_NOSTR_RELAYS,
|
|
307
|
-
ipfsGateways: DEFAULT_IPFS_GATEWAYS,
|
|
308
|
-
electrumUrl: DEFAULT_ELECTRUM_URL
|
|
309
|
-
},
|
|
310
|
-
testnet: {
|
|
311
|
-
name: "Testnet",
|
|
312
|
-
aggregatorUrl: TEST_AGGREGATOR_URL,
|
|
313
|
-
nostrRelays: TEST_NOSTR_RELAYS,
|
|
314
|
-
ipfsGateways: DEFAULT_IPFS_GATEWAYS,
|
|
315
|
-
electrumUrl: TEST_ELECTRUM_URL
|
|
316
|
-
},
|
|
317
|
-
dev: {
|
|
318
|
-
name: "Development",
|
|
319
|
-
aggregatorUrl: DEV_AGGREGATOR_URL,
|
|
320
|
-
nostrRelays: TEST_NOSTR_RELAYS,
|
|
321
|
-
ipfsGateways: DEFAULT_IPFS_GATEWAYS,
|
|
322
|
-
electrumUrl: TEST_ELECTRUM_URL
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
var TIMEOUTS = {
|
|
326
|
-
/** WebSocket connection timeout */
|
|
327
|
-
WEBSOCKET_CONNECT: 1e4,
|
|
328
|
-
/** Nostr relay reconnect delay */
|
|
329
|
-
NOSTR_RECONNECT_DELAY: 3e3,
|
|
330
|
-
/** Max reconnect attempts */
|
|
331
|
-
MAX_RECONNECT_ATTEMPTS: 5,
|
|
332
|
-
/** Proof polling interval */
|
|
333
|
-
PROOF_POLL_INTERVAL: 1e3,
|
|
334
|
-
/** Sync interval */
|
|
335
|
-
SYNC_INTERVAL: 6e4
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
// impl/browser/storage/IpfsStorageProvider.ts
|
|
339
|
-
var import_helia = require("helia");
|
|
340
|
-
var import_json = require("@helia/json");
|
|
341
|
-
var import_bootstrap = require("@libp2p/bootstrap");
|
|
342
|
-
var import_keys = require("@libp2p/crypto/keys");
|
|
343
|
-
var import_peer_id = require("@libp2p/peer-id");
|
|
344
|
-
var import_hkdf = require("@noble/hashes/hkdf.js");
|
|
345
|
-
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
346
|
-
var import_cid = require("multiformats/cid");
|
|
347
|
-
var HKDF_INFO = new TextEncoder().encode("ipfs-storage-key");
|
|
348
|
-
var IpfsStorageProvider = class {
|
|
349
|
-
id = "ipfs";
|
|
350
|
-
name = "IPFS Storage";
|
|
351
|
-
type = "p2p";
|
|
352
|
-
description = "Decentralized storage via IPFS/IPNS";
|
|
353
|
-
config;
|
|
354
|
-
identity = null;
|
|
355
|
-
status = "disconnected";
|
|
356
|
-
ipnsName = null;
|
|
357
|
-
lastCid = null;
|
|
358
|
-
eventCallbacks = /* @__PURE__ */ new Set();
|
|
359
|
-
// Helia instance for browser-based IPFS
|
|
360
|
-
helia = null;
|
|
361
|
-
heliaJson = null;
|
|
362
|
-
ipnsKeyPair = null;
|
|
363
|
-
/** Get the last published CID */
|
|
364
|
-
getLastCid() {
|
|
365
|
-
return this.lastCid;
|
|
366
|
-
}
|
|
367
|
-
// Local cache for faster loads
|
|
368
|
-
localCache = null;
|
|
369
|
-
cacheTimestamp = 0;
|
|
370
|
-
constructor(config) {
|
|
371
|
-
this.config = {
|
|
372
|
-
gateways: config?.gateways ?? [...DEFAULT_IPFS_GATEWAYS],
|
|
373
|
-
bootstrapPeers: config?.bootstrapPeers ?? [...DEFAULT_IPFS_BOOTSTRAP_PEERS],
|
|
374
|
-
enableIpns: config?.enableIpns ?? true,
|
|
375
|
-
ipnsTimeout: config?.ipnsTimeout ?? 3e4,
|
|
376
|
-
fetchTimeout: config?.fetchTimeout ?? 15e3,
|
|
377
|
-
debug: config?.debug ?? false
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
// ===========================================================================
|
|
381
|
-
// BaseProvider Implementation
|
|
382
|
-
// ===========================================================================
|
|
383
|
-
async connect() {
|
|
384
|
-
if (this.status === "connected") return;
|
|
385
|
-
this.status = "connecting";
|
|
386
|
-
try {
|
|
387
|
-
await this.testGatewayConnectivity();
|
|
388
|
-
await this.initializeHelia();
|
|
389
|
-
this.status = "connected";
|
|
390
|
-
this.log("Connected to IPFS gateways and Helia initialized");
|
|
391
|
-
} catch (error) {
|
|
392
|
-
this.status = "error";
|
|
393
|
-
throw new Error(`IPFS connection failed: ${error}`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Initialize Helia browser IPFS node
|
|
398
|
-
*/
|
|
399
|
-
async initializeHelia() {
|
|
400
|
-
if (this.helia) return;
|
|
401
|
-
try {
|
|
402
|
-
this.log("Initializing Helia with bootstrap peers...");
|
|
403
|
-
this.helia = await (0, import_helia.createHelia)({
|
|
404
|
-
libp2p: {
|
|
405
|
-
peerDiscovery: [
|
|
406
|
-
(0, import_bootstrap.bootstrap)({ list: this.config.bootstrapPeers })
|
|
407
|
-
],
|
|
408
|
-
connectionManager: {
|
|
409
|
-
maxConnections: 10
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
});
|
|
413
|
-
this.heliaJson = (0, import_json.json)(this.helia);
|
|
414
|
-
const peerId = this.helia.libp2p.peerId.toString();
|
|
415
|
-
this.log("Helia initialized, browser peer ID:", peerId.slice(0, 20) + "...");
|
|
416
|
-
setTimeout(() => {
|
|
417
|
-
const connections = this.helia?.libp2p.getConnections() || [];
|
|
418
|
-
this.log(`Active Helia connections: ${connections.length}`);
|
|
419
|
-
}, 3e3);
|
|
420
|
-
} catch (error) {
|
|
421
|
-
this.log("Helia initialization failed (will use HTTP only):", error);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
async disconnect() {
|
|
425
|
-
if (this.helia) {
|
|
426
|
-
try {
|
|
427
|
-
await this.helia.stop();
|
|
428
|
-
} catch (error) {
|
|
429
|
-
this.log("Error stopping Helia:", error);
|
|
430
|
-
}
|
|
431
|
-
this.helia = null;
|
|
432
|
-
this.heliaJson = null;
|
|
433
|
-
}
|
|
434
|
-
this.status = "disconnected";
|
|
435
|
-
this.localCache = null;
|
|
436
|
-
this.ipnsKeyPair = null;
|
|
437
|
-
this.log("Disconnected from IPFS");
|
|
438
|
-
}
|
|
439
|
-
isConnected() {
|
|
440
|
-
return this.status === "connected";
|
|
441
|
-
}
|
|
442
|
-
getStatus() {
|
|
443
|
-
return this.status;
|
|
444
|
-
}
|
|
445
|
-
// ===========================================================================
|
|
446
|
-
// TokenStorageProvider Implementation
|
|
447
|
-
// ===========================================================================
|
|
448
|
-
async setIdentity(identity) {
|
|
449
|
-
this.identity = identity;
|
|
450
|
-
try {
|
|
451
|
-
const walletSecret = this.hexToBytes(identity.privateKey);
|
|
452
|
-
const derivedKey = (0, import_hkdf.hkdf)(import_sha2.sha256, walletSecret, void 0, HKDF_INFO, 32);
|
|
453
|
-
this.ipnsKeyPair = await (0, import_keys.generateKeyPairFromSeed)("Ed25519", derivedKey);
|
|
454
|
-
const peerId = (0, import_peer_id.peerIdFromPrivateKey)(this.ipnsKeyPair);
|
|
455
|
-
this.ipnsName = peerId.toString();
|
|
456
|
-
this.log("Identity set, IPNS name:", this.ipnsName);
|
|
457
|
-
} catch {
|
|
458
|
-
this.ipnsName = identity.ipnsName ?? this.deriveIpnsNameSimple(identity.privateKey);
|
|
459
|
-
this.log("Identity set with fallback IPNS name:", this.ipnsName);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
async initialize() {
|
|
463
|
-
if (!this.identity) {
|
|
464
|
-
throw new Error("Identity must be set before initialization");
|
|
465
|
-
}
|
|
466
|
-
try {
|
|
467
|
-
await this.connect();
|
|
468
|
-
return true;
|
|
469
|
-
} catch {
|
|
470
|
-
return false;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
async shutdown() {
|
|
474
|
-
await this.disconnect();
|
|
475
|
-
}
|
|
476
|
-
async save(data) {
|
|
477
|
-
this.ensureReady();
|
|
478
|
-
this.emitEvent({ type: "storage:saving", timestamp: Date.now() });
|
|
479
|
-
try {
|
|
480
|
-
const dataToSave = {
|
|
481
|
-
...data,
|
|
482
|
-
_meta: {
|
|
483
|
-
...data._meta,
|
|
484
|
-
updatedAt: Date.now(),
|
|
485
|
-
ipnsName: this.ipnsName ?? void 0
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
const cid = await this.publishToGateways(dataToSave);
|
|
489
|
-
if (this.config.enableIpns && this.ipnsName) {
|
|
490
|
-
await this.publishIpns(cid);
|
|
491
|
-
}
|
|
492
|
-
this.localCache = dataToSave;
|
|
493
|
-
this.cacheTimestamp = Date.now();
|
|
494
|
-
this.lastCid = cid;
|
|
495
|
-
this.emitEvent({ type: "storage:saved", timestamp: Date.now(), data: { cid } });
|
|
496
|
-
return {
|
|
497
|
-
success: true,
|
|
498
|
-
cid,
|
|
499
|
-
timestamp: Date.now()
|
|
500
|
-
};
|
|
501
|
-
} catch (error) {
|
|
502
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
503
|
-
this.emitEvent({ type: "storage:error", timestamp: Date.now(), error: errorMsg });
|
|
504
|
-
return {
|
|
505
|
-
success: false,
|
|
506
|
-
error: errorMsg,
|
|
507
|
-
timestamp: Date.now()
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
async load(identifier) {
|
|
512
|
-
this.ensureReady();
|
|
513
|
-
this.emitEvent({ type: "storage:loading", timestamp: Date.now() });
|
|
514
|
-
try {
|
|
515
|
-
const cacheAge = Date.now() - this.cacheTimestamp;
|
|
516
|
-
if (this.localCache && cacheAge < 6e4) {
|
|
517
|
-
this.log("Returning cached data");
|
|
518
|
-
return {
|
|
519
|
-
success: true,
|
|
520
|
-
data: this.localCache,
|
|
521
|
-
source: "cache",
|
|
522
|
-
timestamp: Date.now()
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
let cid = identifier ?? null;
|
|
526
|
-
if (!cid && this.config.enableIpns && this.ipnsName) {
|
|
527
|
-
cid = await this.resolveIpns(this.ipnsName);
|
|
528
|
-
}
|
|
529
|
-
if (!cid) {
|
|
530
|
-
return {
|
|
531
|
-
success: true,
|
|
532
|
-
data: void 0,
|
|
533
|
-
source: "remote",
|
|
534
|
-
timestamp: Date.now()
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
const data = await this.fetchFromGateways(cid);
|
|
538
|
-
this.localCache = data;
|
|
539
|
-
this.cacheTimestamp = Date.now();
|
|
540
|
-
this.lastCid = cid;
|
|
541
|
-
this.emitEvent({ type: "storage:loaded", timestamp: Date.now() });
|
|
542
|
-
return {
|
|
543
|
-
success: true,
|
|
544
|
-
data,
|
|
545
|
-
source: "remote",
|
|
546
|
-
timestamp: Date.now()
|
|
547
|
-
};
|
|
548
|
-
} catch (error) {
|
|
549
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
550
|
-
this.emitEvent({ type: "storage:error", timestamp: Date.now(), error: errorMsg });
|
|
551
|
-
if (this.localCache) {
|
|
552
|
-
return {
|
|
553
|
-
success: true,
|
|
554
|
-
data: this.localCache,
|
|
555
|
-
source: "cache",
|
|
556
|
-
timestamp: Date.now()
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
return {
|
|
560
|
-
success: false,
|
|
561
|
-
error: errorMsg,
|
|
562
|
-
source: "remote",
|
|
563
|
-
timestamp: Date.now()
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
async sync(localData) {
|
|
568
|
-
this.ensureReady();
|
|
569
|
-
this.emitEvent({ type: "sync:started", timestamp: Date.now() });
|
|
570
|
-
try {
|
|
571
|
-
const remoteResult = await this.load();
|
|
572
|
-
const remoteData = remoteResult.data;
|
|
573
|
-
if (!remoteData) {
|
|
574
|
-
await this.save(localData);
|
|
575
|
-
this.emitEvent({ type: "sync:completed", timestamp: Date.now() });
|
|
576
|
-
return {
|
|
577
|
-
success: true,
|
|
578
|
-
merged: localData,
|
|
579
|
-
added: 0,
|
|
580
|
-
removed: 0,
|
|
581
|
-
conflicts: 0
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
const mergeResult = this.mergeData(localData, remoteData);
|
|
585
|
-
await this.save(mergeResult.merged);
|
|
586
|
-
this.emitEvent({ type: "sync:completed", timestamp: Date.now() });
|
|
587
|
-
return {
|
|
588
|
-
success: true,
|
|
589
|
-
merged: mergeResult.merged,
|
|
590
|
-
added: mergeResult.added,
|
|
591
|
-
removed: mergeResult.removed,
|
|
592
|
-
conflicts: mergeResult.conflicts
|
|
593
|
-
};
|
|
594
|
-
} catch (error) {
|
|
595
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
596
|
-
this.emitEvent({ type: "sync:error", timestamp: Date.now(), error: errorMsg });
|
|
597
|
-
return {
|
|
598
|
-
success: false,
|
|
599
|
-
added: 0,
|
|
600
|
-
removed: 0,
|
|
601
|
-
conflicts: 0,
|
|
602
|
-
error: errorMsg
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
async exists() {
|
|
607
|
-
if (!this.ipnsName) return false;
|
|
608
|
-
try {
|
|
609
|
-
const cid = await this.resolveIpns(this.ipnsName);
|
|
610
|
-
return cid !== null;
|
|
611
|
-
} catch {
|
|
612
|
-
return false;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
async clear() {
|
|
616
|
-
const emptyData = {
|
|
617
|
-
_meta: {
|
|
618
|
-
version: 0,
|
|
619
|
-
address: this.identity?.address ?? "",
|
|
620
|
-
formatVersion: "2.0",
|
|
621
|
-
updatedAt: Date.now()
|
|
622
|
-
},
|
|
623
|
-
_tombstones: []
|
|
624
|
-
};
|
|
625
|
-
const result = await this.save(emptyData);
|
|
626
|
-
return result.success;
|
|
627
|
-
}
|
|
628
|
-
onEvent(callback) {
|
|
629
|
-
this.eventCallbacks.add(callback);
|
|
630
|
-
return () => this.eventCallbacks.delete(callback);
|
|
631
|
-
}
|
|
632
|
-
// ===========================================================================
|
|
633
|
-
// Private: IPFS Operations
|
|
634
|
-
// ===========================================================================
|
|
635
|
-
async testGatewayConnectivity() {
|
|
636
|
-
const gateway = this.config.gateways[0];
|
|
637
|
-
const controller = new AbortController();
|
|
638
|
-
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
639
|
-
try {
|
|
640
|
-
const response = await fetch(`${gateway}/api/v0/version`, {
|
|
641
|
-
method: "POST",
|
|
642
|
-
signal: controller.signal
|
|
643
|
-
});
|
|
644
|
-
if (!response.ok) throw new Error("Gateway not responding");
|
|
645
|
-
} finally {
|
|
646
|
-
clearTimeout(timeout);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
async publishToGateways(data) {
|
|
650
|
-
const content = JSON.stringify(data);
|
|
651
|
-
const blob = new Blob([content], { type: "application/json" });
|
|
652
|
-
const promises = [];
|
|
653
|
-
for (const gateway of this.config.gateways) {
|
|
654
|
-
promises.push(this.publishToGateway(gateway, blob));
|
|
655
|
-
}
|
|
656
|
-
if (this.heliaJson) {
|
|
657
|
-
promises.push(this.publishToHelia(data));
|
|
658
|
-
}
|
|
659
|
-
try {
|
|
660
|
-
const cid = await Promise.any(promises);
|
|
661
|
-
this.log("Published to IPFS, CID:", cid);
|
|
662
|
-
return cid;
|
|
663
|
-
} catch {
|
|
664
|
-
throw new Error("All publish attempts failed");
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
/**
|
|
668
|
-
* Publish data via Helia (browser DHT)
|
|
669
|
-
*/
|
|
670
|
-
async publishToHelia(data) {
|
|
671
|
-
if (!this.heliaJson) {
|
|
672
|
-
throw new Error("Helia not initialized");
|
|
673
|
-
}
|
|
674
|
-
const cid = await this.heliaJson.add(data);
|
|
675
|
-
this.log("Published via Helia, CID:", cid.toString());
|
|
676
|
-
return cid.toString();
|
|
677
|
-
}
|
|
678
|
-
async publishToGateway(gateway, blob) {
|
|
679
|
-
const formData = new FormData();
|
|
680
|
-
formData.append("file", blob);
|
|
681
|
-
const controller = new AbortController();
|
|
682
|
-
const timeout = setTimeout(() => controller.abort(), this.config.fetchTimeout);
|
|
683
|
-
try {
|
|
684
|
-
const response = await fetch(`${gateway}/api/v0/add?pin=true`, {
|
|
685
|
-
method: "POST",
|
|
686
|
-
body: formData,
|
|
687
|
-
signal: controller.signal
|
|
688
|
-
});
|
|
689
|
-
if (!response.ok) {
|
|
690
|
-
throw new Error(`Gateway ${gateway} returned ${response.status}`);
|
|
691
|
-
}
|
|
692
|
-
const result = await response.json();
|
|
693
|
-
return result.Hash;
|
|
694
|
-
} finally {
|
|
695
|
-
clearTimeout(timeout);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
async publishIpns(cid) {
|
|
699
|
-
if (!this.identity) return;
|
|
700
|
-
const promises = this.config.gateways.map(
|
|
701
|
-
(gateway) => this.publishIpnsToGateway(gateway, cid).catch(() => null)
|
|
702
|
-
);
|
|
703
|
-
await Promise.allSettled(promises);
|
|
704
|
-
this.log("Published IPNS:", this.ipnsName, "->", cid);
|
|
705
|
-
}
|
|
706
|
-
async publishIpnsToGateway(gateway, cid) {
|
|
707
|
-
const controller = new AbortController();
|
|
708
|
-
const timeout = setTimeout(() => controller.abort(), this.config.ipnsTimeout);
|
|
709
|
-
try {
|
|
710
|
-
const response = await fetch(
|
|
711
|
-
`${gateway}/api/v0/name/publish?arg=${cid}&key=${this.ipnsName}`,
|
|
712
|
-
{
|
|
713
|
-
method: "POST",
|
|
714
|
-
signal: controller.signal
|
|
715
|
-
}
|
|
716
|
-
);
|
|
717
|
-
if (!response.ok) {
|
|
718
|
-
throw new Error(`IPNS publish failed: ${response.status}`);
|
|
719
|
-
}
|
|
720
|
-
} finally {
|
|
721
|
-
clearTimeout(timeout);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
async resolveIpns(name) {
|
|
725
|
-
for (const gateway of this.config.gateways) {
|
|
726
|
-
try {
|
|
727
|
-
return await this.resolveIpnsFromGateway(gateway, name);
|
|
728
|
-
} catch {
|
|
729
|
-
continue;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
async resolveIpnsFromGateway(gateway, name) {
|
|
735
|
-
const controller = new AbortController();
|
|
736
|
-
const timeout = setTimeout(() => controller.abort(), this.config.fetchTimeout);
|
|
737
|
-
try {
|
|
738
|
-
const response = await fetch(`${gateway}/api/v0/name/resolve?arg=${name}`, {
|
|
739
|
-
method: "POST",
|
|
740
|
-
signal: controller.signal
|
|
741
|
-
});
|
|
742
|
-
if (!response.ok) {
|
|
743
|
-
throw new Error(`IPNS resolve failed: ${response.status}`);
|
|
744
|
-
}
|
|
745
|
-
const result = await response.json();
|
|
746
|
-
return result.Path.replace("/ipfs/", "");
|
|
747
|
-
} finally {
|
|
748
|
-
clearTimeout(timeout);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
async fetchFromGateways(cid) {
|
|
752
|
-
const promises = [];
|
|
753
|
-
for (const gateway of this.config.gateways) {
|
|
754
|
-
promises.push(this.fetchFromGateway(gateway, cid));
|
|
755
|
-
}
|
|
756
|
-
if (this.heliaJson) {
|
|
757
|
-
promises.push(this.fetchFromHelia(cid));
|
|
758
|
-
}
|
|
759
|
-
return Promise.any(promises);
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Fetch content via Helia (browser DHT)
|
|
763
|
-
*/
|
|
764
|
-
async fetchFromHelia(cidString) {
|
|
765
|
-
if (!this.heliaJson) {
|
|
766
|
-
throw new Error("Helia not initialized");
|
|
767
|
-
}
|
|
768
|
-
const cid = import_cid.CID.parse(cidString);
|
|
769
|
-
const data = await this.heliaJson.get(cid);
|
|
770
|
-
this.log("Fetched via Helia, CID:", cidString);
|
|
771
|
-
return data;
|
|
772
|
-
}
|
|
773
|
-
async fetchFromGateway(gateway, cid) {
|
|
774
|
-
const controller = new AbortController();
|
|
775
|
-
const timeout = setTimeout(() => controller.abort(), this.config.fetchTimeout);
|
|
776
|
-
try {
|
|
777
|
-
const response = await fetch(`${gateway}/ipfs/${cid}`, {
|
|
778
|
-
signal: controller.signal
|
|
779
|
-
});
|
|
780
|
-
if (!response.ok) {
|
|
781
|
-
throw new Error(`Fetch failed: ${response.status}`);
|
|
782
|
-
}
|
|
783
|
-
return response.json();
|
|
784
|
-
} finally {
|
|
785
|
-
clearTimeout(timeout);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
// ===========================================================================
|
|
789
|
-
// Private: Merge Logic
|
|
790
|
-
// ===========================================================================
|
|
791
|
-
mergeData(local, remote) {
|
|
792
|
-
const localVersion = local._meta?.version ?? 0;
|
|
793
|
-
const remoteVersion = remote._meta?.version ?? 0;
|
|
794
|
-
const baseMeta = remoteVersion > localVersion ? remote._meta : local._meta;
|
|
795
|
-
const tombstones = /* @__PURE__ */ new Map();
|
|
796
|
-
for (const t of local._tombstones ?? []) {
|
|
797
|
-
tombstones.set(t.tokenId, t);
|
|
798
|
-
}
|
|
799
|
-
for (const t of remote._tombstones ?? []) {
|
|
800
|
-
const existing = tombstones.get(t.tokenId);
|
|
801
|
-
if (!existing || t.timestamp > existing.timestamp) {
|
|
802
|
-
tombstones.set(t.tokenId, t);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
const merged = {
|
|
806
|
-
_meta: {
|
|
807
|
-
...baseMeta,
|
|
808
|
-
version: Math.max(localVersion, remoteVersion) + 1,
|
|
809
|
-
updatedAt: Date.now()
|
|
810
|
-
},
|
|
811
|
-
_tombstones: Array.from(tombstones.values())
|
|
812
|
-
};
|
|
813
|
-
let added = 0;
|
|
814
|
-
let conflicts = 0;
|
|
815
|
-
const processedKeys = /* @__PURE__ */ new Set();
|
|
816
|
-
for (const key of Object.keys(local)) {
|
|
817
|
-
if (!key.startsWith("_") || key === "_meta" || key === "_tombstones") continue;
|
|
818
|
-
processedKeys.add(key);
|
|
819
|
-
const tokenId = key.slice(1);
|
|
820
|
-
if (tombstones.has(tokenId)) continue;
|
|
821
|
-
const localToken = local[key];
|
|
822
|
-
const remoteToken = remote[key];
|
|
823
|
-
if (remoteToken) {
|
|
824
|
-
conflicts++;
|
|
825
|
-
merged[key] = localToken;
|
|
826
|
-
} else {
|
|
827
|
-
merged[key] = localToken;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
for (const key of Object.keys(remote)) {
|
|
831
|
-
if (!key.startsWith("_") || key === "_meta" || key === "_tombstones") continue;
|
|
832
|
-
if (processedKeys.has(key)) continue;
|
|
833
|
-
const tokenId = key.slice(1);
|
|
834
|
-
if (tombstones.has(tokenId)) continue;
|
|
835
|
-
merged[key] = remote[key];
|
|
836
|
-
added++;
|
|
837
|
-
}
|
|
838
|
-
return { merged, added, removed: 0, conflicts };
|
|
839
|
-
}
|
|
840
|
-
// ===========================================================================
|
|
841
|
-
// Private: Helpers
|
|
842
|
-
// ===========================================================================
|
|
843
|
-
ensureReady() {
|
|
844
|
-
if (this.status !== "connected") {
|
|
845
|
-
throw new Error("IpfsStorageProvider not connected");
|
|
846
|
-
}
|
|
847
|
-
if (!this.identity) {
|
|
848
|
-
throw new Error("Identity not set");
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
/**
|
|
852
|
-
* Simple IPNS name derivation (fallback when libp2p is unavailable)
|
|
853
|
-
*/
|
|
854
|
-
deriveIpnsNameSimple(privateKey) {
|
|
855
|
-
return `12D3KooW${privateKey.slice(0, 40)}`;
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Convert hex string to Uint8Array
|
|
859
|
-
*/
|
|
860
|
-
hexToBytes(hex) {
|
|
861
|
-
const bytes = new Uint8Array(hex.length / 2);
|
|
862
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
863
|
-
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
864
|
-
}
|
|
865
|
-
return bytes;
|
|
866
|
-
}
|
|
867
|
-
emitEvent(event) {
|
|
868
|
-
for (const callback of this.eventCallbacks) {
|
|
869
|
-
try {
|
|
870
|
-
callback(event);
|
|
871
|
-
} catch (error) {
|
|
872
|
-
console.error("[IpfsStorageProvider] Event callback error:", error);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
log(...args) {
|
|
877
|
-
if (this.config.debug) {
|
|
878
|
-
console.log("[IpfsStorageProvider]", ...args);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
};
|
|
882
|
-
function createIpfsStorageProvider(config) {
|
|
883
|
-
return new IpfsStorageProvider(config);
|
|
884
|
-
}
|
|
885
|
-
|
|
886
224
|
// impl/browser/storage/IndexedDBTokenStorageProvider.ts
|
|
887
225
|
var DB_NAME = "sphere-token-storage";
|
|
888
226
|
var DB_VERSION = 1;
|
|
@@ -902,7 +240,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
902
240
|
}
|
|
903
241
|
setIdentity(identity) {
|
|
904
242
|
this.identity = identity;
|
|
905
|
-
this.dbName = `${DB_NAME}-${identity.
|
|
243
|
+
this.dbName = `${DB_NAME}-${identity.l1Address.slice(0, 20)}`;
|
|
906
244
|
}
|
|
907
245
|
async initialize() {
|
|
908
246
|
try {
|
|
@@ -947,7 +285,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
947
285
|
const data = {
|
|
948
286
|
_meta: {
|
|
949
287
|
version: 1,
|
|
950
|
-
address: this.identity?.
|
|
288
|
+
address: this.identity?.l1Address ?? "",
|
|
951
289
|
formatVersion: "2.0",
|
|
952
290
|
updatedAt: Date.now()
|
|
953
291
|
}
|
|
@@ -1175,34 +513,750 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
1175
513
|
});
|
|
1176
514
|
}
|
|
1177
515
|
};
|
|
1178
|
-
function createIndexedDBTokenStorageProvider(config) {
|
|
1179
|
-
return new IndexedDBTokenStorageProvider(config);
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// transport/NostrTransportProvider.ts
|
|
1183
|
-
var import_buffer = require("buffer");
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
516
|
+
function createIndexedDBTokenStorageProvider(config) {
|
|
517
|
+
return new IndexedDBTokenStorageProvider(config);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// transport/NostrTransportProvider.ts
|
|
521
|
+
var import_buffer = require("buffer");
|
|
522
|
+
|
|
523
|
+
// node_modules/@noble/hashes/utils.js
|
|
524
|
+
function isBytes(a) {
|
|
525
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
526
|
+
}
|
|
527
|
+
function anumber(n, title = "") {
|
|
528
|
+
if (!Number.isSafeInteger(n) || n < 0) {
|
|
529
|
+
const prefix = title && `"${title}" `;
|
|
530
|
+
throw new Error(`${prefix}expected integer >= 0, got ${n}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function abytes(value, length, title = "") {
|
|
534
|
+
const bytes = isBytes(value);
|
|
535
|
+
const len = value?.length;
|
|
536
|
+
const needsLen = length !== void 0;
|
|
537
|
+
if (!bytes || needsLen && len !== length) {
|
|
538
|
+
const prefix = title && `"${title}" `;
|
|
539
|
+
const ofLen = needsLen ? ` of length ${length}` : "";
|
|
540
|
+
const got = bytes ? `length=${len}` : `type=${typeof value}`;
|
|
541
|
+
throw new Error(prefix + "expected Uint8Array" + ofLen + ", got " + got);
|
|
542
|
+
}
|
|
543
|
+
return value;
|
|
544
|
+
}
|
|
545
|
+
function ahash(h) {
|
|
546
|
+
if (typeof h !== "function" || typeof h.create !== "function")
|
|
547
|
+
throw new Error("Hash must wrapped by utils.createHasher");
|
|
548
|
+
anumber(h.outputLen);
|
|
549
|
+
anumber(h.blockLen);
|
|
550
|
+
}
|
|
551
|
+
function aexists(instance, checkFinished = true) {
|
|
552
|
+
if (instance.destroyed)
|
|
553
|
+
throw new Error("Hash instance has been destroyed");
|
|
554
|
+
if (checkFinished && instance.finished)
|
|
555
|
+
throw new Error("Hash#digest() has already been called");
|
|
556
|
+
}
|
|
557
|
+
function aoutput(out, instance) {
|
|
558
|
+
abytes(out, void 0, "digestInto() output");
|
|
559
|
+
const min = instance.outputLen;
|
|
560
|
+
if (out.length < min) {
|
|
561
|
+
throw new Error('"digestInto() output" expected to be of length >=' + min);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function clean(...arrays) {
|
|
565
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
566
|
+
arrays[i].fill(0);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function createView(arr) {
|
|
570
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
571
|
+
}
|
|
572
|
+
function rotr(word, shift) {
|
|
573
|
+
return word << 32 - shift | word >>> shift;
|
|
574
|
+
}
|
|
575
|
+
function createHasher(hashCons, info = {}) {
|
|
576
|
+
const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
|
|
577
|
+
const tmp = hashCons(void 0);
|
|
578
|
+
hashC.outputLen = tmp.outputLen;
|
|
579
|
+
hashC.blockLen = tmp.blockLen;
|
|
580
|
+
hashC.create = (opts) => hashCons(opts);
|
|
581
|
+
Object.assign(hashC, info);
|
|
582
|
+
return Object.freeze(hashC);
|
|
583
|
+
}
|
|
584
|
+
var oidNist = (suffix) => ({
|
|
585
|
+
oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// node_modules/@noble/hashes/hmac.js
|
|
589
|
+
var _HMAC = class {
|
|
590
|
+
oHash;
|
|
591
|
+
iHash;
|
|
592
|
+
blockLen;
|
|
593
|
+
outputLen;
|
|
594
|
+
finished = false;
|
|
595
|
+
destroyed = false;
|
|
596
|
+
constructor(hash, key) {
|
|
597
|
+
ahash(hash);
|
|
598
|
+
abytes(key, void 0, "key");
|
|
599
|
+
this.iHash = hash.create();
|
|
600
|
+
if (typeof this.iHash.update !== "function")
|
|
601
|
+
throw new Error("Expected instance of class which extends utils.Hash");
|
|
602
|
+
this.blockLen = this.iHash.blockLen;
|
|
603
|
+
this.outputLen = this.iHash.outputLen;
|
|
604
|
+
const blockLen = this.blockLen;
|
|
605
|
+
const pad = new Uint8Array(blockLen);
|
|
606
|
+
pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
|
|
607
|
+
for (let i = 0; i < pad.length; i++)
|
|
608
|
+
pad[i] ^= 54;
|
|
609
|
+
this.iHash.update(pad);
|
|
610
|
+
this.oHash = hash.create();
|
|
611
|
+
for (let i = 0; i < pad.length; i++)
|
|
612
|
+
pad[i] ^= 54 ^ 92;
|
|
613
|
+
this.oHash.update(pad);
|
|
614
|
+
clean(pad);
|
|
615
|
+
}
|
|
616
|
+
update(buf) {
|
|
617
|
+
aexists(this);
|
|
618
|
+
this.iHash.update(buf);
|
|
619
|
+
return this;
|
|
620
|
+
}
|
|
621
|
+
digestInto(out) {
|
|
622
|
+
aexists(this);
|
|
623
|
+
abytes(out, this.outputLen, "output");
|
|
624
|
+
this.finished = true;
|
|
625
|
+
this.iHash.digestInto(out);
|
|
626
|
+
this.oHash.update(out);
|
|
627
|
+
this.oHash.digestInto(out);
|
|
628
|
+
this.destroy();
|
|
629
|
+
}
|
|
630
|
+
digest() {
|
|
631
|
+
const out = new Uint8Array(this.oHash.outputLen);
|
|
632
|
+
this.digestInto(out);
|
|
633
|
+
return out;
|
|
634
|
+
}
|
|
635
|
+
_cloneInto(to) {
|
|
636
|
+
to ||= Object.create(Object.getPrototypeOf(this), {});
|
|
637
|
+
const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
|
|
638
|
+
to = to;
|
|
639
|
+
to.finished = finished;
|
|
640
|
+
to.destroyed = destroyed;
|
|
641
|
+
to.blockLen = blockLen;
|
|
642
|
+
to.outputLen = outputLen;
|
|
643
|
+
to.oHash = oHash._cloneInto(to.oHash);
|
|
644
|
+
to.iHash = iHash._cloneInto(to.iHash);
|
|
645
|
+
return to;
|
|
646
|
+
}
|
|
647
|
+
clone() {
|
|
648
|
+
return this._cloneInto();
|
|
649
|
+
}
|
|
650
|
+
destroy() {
|
|
651
|
+
this.destroyed = true;
|
|
652
|
+
this.oHash.destroy();
|
|
653
|
+
this.iHash.destroy();
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
var hmac = (hash, key, message) => new _HMAC(hash, key).update(message).digest();
|
|
657
|
+
hmac.create = (hash, key) => new _HMAC(hash, key);
|
|
658
|
+
|
|
659
|
+
// node_modules/@noble/hashes/hkdf.js
|
|
660
|
+
function extract(hash, ikm, salt) {
|
|
661
|
+
ahash(hash);
|
|
662
|
+
if (salt === void 0)
|
|
663
|
+
salt = new Uint8Array(hash.outputLen);
|
|
664
|
+
return hmac(hash, salt, ikm);
|
|
665
|
+
}
|
|
666
|
+
var HKDF_COUNTER = /* @__PURE__ */ Uint8Array.of(0);
|
|
667
|
+
var EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();
|
|
668
|
+
function expand(hash, prk, info, length = 32) {
|
|
669
|
+
ahash(hash);
|
|
670
|
+
anumber(length, "length");
|
|
671
|
+
const olen = hash.outputLen;
|
|
672
|
+
if (length > 255 * olen)
|
|
673
|
+
throw new Error("Length must be <= 255*HashLen");
|
|
674
|
+
const blocks = Math.ceil(length / olen);
|
|
675
|
+
if (info === void 0)
|
|
676
|
+
info = EMPTY_BUFFER;
|
|
677
|
+
else
|
|
678
|
+
abytes(info, void 0, "info");
|
|
679
|
+
const okm = new Uint8Array(blocks * olen);
|
|
680
|
+
const HMAC = hmac.create(hash, prk);
|
|
681
|
+
const HMACTmp = HMAC._cloneInto();
|
|
682
|
+
const T = new Uint8Array(HMAC.outputLen);
|
|
683
|
+
for (let counter = 0; counter < blocks; counter++) {
|
|
684
|
+
HKDF_COUNTER[0] = counter + 1;
|
|
685
|
+
HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T).update(info).update(HKDF_COUNTER).digestInto(T);
|
|
686
|
+
okm.set(T, olen * counter);
|
|
687
|
+
HMAC._cloneInto(HMACTmp);
|
|
688
|
+
}
|
|
689
|
+
HMAC.destroy();
|
|
690
|
+
HMACTmp.destroy();
|
|
691
|
+
clean(T, HKDF_COUNTER);
|
|
692
|
+
return okm.slice(0, length);
|
|
693
|
+
}
|
|
694
|
+
var hkdf = (hash, ikm, salt, info, length) => expand(hash, extract(hash, ikm, salt), info, length);
|
|
695
|
+
|
|
696
|
+
// node_modules/@noble/hashes/_md.js
|
|
697
|
+
function Chi(a, b, c) {
|
|
698
|
+
return a & b ^ ~a & c;
|
|
699
|
+
}
|
|
700
|
+
function Maj(a, b, c) {
|
|
701
|
+
return a & b ^ a & c ^ b & c;
|
|
702
|
+
}
|
|
703
|
+
var HashMD = class {
|
|
704
|
+
blockLen;
|
|
705
|
+
outputLen;
|
|
706
|
+
padOffset;
|
|
707
|
+
isLE;
|
|
708
|
+
// For partial updates less than block size
|
|
709
|
+
buffer;
|
|
710
|
+
view;
|
|
711
|
+
finished = false;
|
|
712
|
+
length = 0;
|
|
713
|
+
pos = 0;
|
|
714
|
+
destroyed = false;
|
|
715
|
+
constructor(blockLen, outputLen, padOffset, isLE) {
|
|
716
|
+
this.blockLen = blockLen;
|
|
717
|
+
this.outputLen = outputLen;
|
|
718
|
+
this.padOffset = padOffset;
|
|
719
|
+
this.isLE = isLE;
|
|
720
|
+
this.buffer = new Uint8Array(blockLen);
|
|
721
|
+
this.view = createView(this.buffer);
|
|
722
|
+
}
|
|
723
|
+
update(data) {
|
|
724
|
+
aexists(this);
|
|
725
|
+
abytes(data);
|
|
726
|
+
const { view, buffer, blockLen } = this;
|
|
727
|
+
const len = data.length;
|
|
728
|
+
for (let pos = 0; pos < len; ) {
|
|
729
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
730
|
+
if (take === blockLen) {
|
|
731
|
+
const dataView = createView(data);
|
|
732
|
+
for (; blockLen <= len - pos; pos += blockLen)
|
|
733
|
+
this.process(dataView, pos);
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
737
|
+
this.pos += take;
|
|
738
|
+
pos += take;
|
|
739
|
+
if (this.pos === blockLen) {
|
|
740
|
+
this.process(view, 0);
|
|
741
|
+
this.pos = 0;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
this.length += data.length;
|
|
745
|
+
this.roundClean();
|
|
746
|
+
return this;
|
|
747
|
+
}
|
|
748
|
+
digestInto(out) {
|
|
749
|
+
aexists(this);
|
|
750
|
+
aoutput(out, this);
|
|
751
|
+
this.finished = true;
|
|
752
|
+
const { buffer, view, blockLen, isLE } = this;
|
|
753
|
+
let { pos } = this;
|
|
754
|
+
buffer[pos++] = 128;
|
|
755
|
+
clean(this.buffer.subarray(pos));
|
|
756
|
+
if (this.padOffset > blockLen - pos) {
|
|
757
|
+
this.process(view, 0);
|
|
758
|
+
pos = 0;
|
|
759
|
+
}
|
|
760
|
+
for (let i = pos; i < blockLen; i++)
|
|
761
|
+
buffer[i] = 0;
|
|
762
|
+
view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);
|
|
763
|
+
this.process(view, 0);
|
|
764
|
+
const oview = createView(out);
|
|
765
|
+
const len = this.outputLen;
|
|
766
|
+
if (len % 4)
|
|
767
|
+
throw new Error("_sha2: outputLen must be aligned to 32bit");
|
|
768
|
+
const outLen = len / 4;
|
|
769
|
+
const state = this.get();
|
|
770
|
+
if (outLen > state.length)
|
|
771
|
+
throw new Error("_sha2: outputLen bigger than state");
|
|
772
|
+
for (let i = 0; i < outLen; i++)
|
|
773
|
+
oview.setUint32(4 * i, state[i], isLE);
|
|
774
|
+
}
|
|
775
|
+
digest() {
|
|
776
|
+
const { buffer, outputLen } = this;
|
|
777
|
+
this.digestInto(buffer);
|
|
778
|
+
const res = buffer.slice(0, outputLen);
|
|
779
|
+
this.destroy();
|
|
780
|
+
return res;
|
|
781
|
+
}
|
|
782
|
+
_cloneInto(to) {
|
|
783
|
+
to ||= new this.constructor();
|
|
784
|
+
to.set(...this.get());
|
|
785
|
+
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
786
|
+
to.destroyed = destroyed;
|
|
787
|
+
to.finished = finished;
|
|
788
|
+
to.length = length;
|
|
789
|
+
to.pos = pos;
|
|
790
|
+
if (length % blockLen)
|
|
791
|
+
to.buffer.set(buffer);
|
|
792
|
+
return to;
|
|
793
|
+
}
|
|
794
|
+
clone() {
|
|
795
|
+
return this._cloneInto();
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
799
|
+
1779033703,
|
|
800
|
+
3144134277,
|
|
801
|
+
1013904242,
|
|
802
|
+
2773480762,
|
|
803
|
+
1359893119,
|
|
804
|
+
2600822924,
|
|
805
|
+
528734635,
|
|
806
|
+
1541459225
|
|
807
|
+
]);
|
|
808
|
+
|
|
809
|
+
// node_modules/@noble/hashes/sha2.js
|
|
810
|
+
var SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
811
|
+
1116352408,
|
|
812
|
+
1899447441,
|
|
813
|
+
3049323471,
|
|
814
|
+
3921009573,
|
|
815
|
+
961987163,
|
|
816
|
+
1508970993,
|
|
817
|
+
2453635748,
|
|
818
|
+
2870763221,
|
|
819
|
+
3624381080,
|
|
820
|
+
310598401,
|
|
821
|
+
607225278,
|
|
822
|
+
1426881987,
|
|
823
|
+
1925078388,
|
|
824
|
+
2162078206,
|
|
825
|
+
2614888103,
|
|
826
|
+
3248222580,
|
|
827
|
+
3835390401,
|
|
828
|
+
4022224774,
|
|
829
|
+
264347078,
|
|
830
|
+
604807628,
|
|
831
|
+
770255983,
|
|
832
|
+
1249150122,
|
|
833
|
+
1555081692,
|
|
834
|
+
1996064986,
|
|
835
|
+
2554220882,
|
|
836
|
+
2821834349,
|
|
837
|
+
2952996808,
|
|
838
|
+
3210313671,
|
|
839
|
+
3336571891,
|
|
840
|
+
3584528711,
|
|
841
|
+
113926993,
|
|
842
|
+
338241895,
|
|
843
|
+
666307205,
|
|
844
|
+
773529912,
|
|
845
|
+
1294757372,
|
|
846
|
+
1396182291,
|
|
847
|
+
1695183700,
|
|
848
|
+
1986661051,
|
|
849
|
+
2177026350,
|
|
850
|
+
2456956037,
|
|
851
|
+
2730485921,
|
|
852
|
+
2820302411,
|
|
853
|
+
3259730800,
|
|
854
|
+
3345764771,
|
|
855
|
+
3516065817,
|
|
856
|
+
3600352804,
|
|
857
|
+
4094571909,
|
|
858
|
+
275423344,
|
|
859
|
+
430227734,
|
|
860
|
+
506948616,
|
|
861
|
+
659060556,
|
|
862
|
+
883997877,
|
|
863
|
+
958139571,
|
|
864
|
+
1322822218,
|
|
865
|
+
1537002063,
|
|
866
|
+
1747873779,
|
|
867
|
+
1955562222,
|
|
868
|
+
2024104815,
|
|
869
|
+
2227730452,
|
|
870
|
+
2361852424,
|
|
871
|
+
2428436474,
|
|
872
|
+
2756734187,
|
|
873
|
+
3204031479,
|
|
874
|
+
3329325298
|
|
875
|
+
]);
|
|
876
|
+
var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
877
|
+
var SHA2_32B = class extends HashMD {
|
|
878
|
+
constructor(outputLen) {
|
|
879
|
+
super(64, outputLen, 8, false);
|
|
880
|
+
}
|
|
881
|
+
get() {
|
|
882
|
+
const { A, B, C, D, E, F, G, H } = this;
|
|
883
|
+
return [A, B, C, D, E, F, G, H];
|
|
884
|
+
}
|
|
885
|
+
// prettier-ignore
|
|
886
|
+
set(A, B, C, D, E, F, G, H) {
|
|
887
|
+
this.A = A | 0;
|
|
888
|
+
this.B = B | 0;
|
|
889
|
+
this.C = C | 0;
|
|
890
|
+
this.D = D | 0;
|
|
891
|
+
this.E = E | 0;
|
|
892
|
+
this.F = F | 0;
|
|
893
|
+
this.G = G | 0;
|
|
894
|
+
this.H = H | 0;
|
|
895
|
+
}
|
|
896
|
+
process(view, offset) {
|
|
897
|
+
for (let i = 0; i < 16; i++, offset += 4)
|
|
898
|
+
SHA256_W[i] = view.getUint32(offset, false);
|
|
899
|
+
for (let i = 16; i < 64; i++) {
|
|
900
|
+
const W15 = SHA256_W[i - 15];
|
|
901
|
+
const W2 = SHA256_W[i - 2];
|
|
902
|
+
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
|
|
903
|
+
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
|
|
904
|
+
SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
|
|
905
|
+
}
|
|
906
|
+
let { A, B, C, D, E, F, G, H } = this;
|
|
907
|
+
for (let i = 0; i < 64; i++) {
|
|
908
|
+
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
|
909
|
+
const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
|
|
910
|
+
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
|
|
911
|
+
const T2 = sigma0 + Maj(A, B, C) | 0;
|
|
912
|
+
H = G;
|
|
913
|
+
G = F;
|
|
914
|
+
F = E;
|
|
915
|
+
E = D + T1 | 0;
|
|
916
|
+
D = C;
|
|
917
|
+
C = B;
|
|
918
|
+
B = A;
|
|
919
|
+
A = T1 + T2 | 0;
|
|
920
|
+
}
|
|
921
|
+
A = A + this.A | 0;
|
|
922
|
+
B = B + this.B | 0;
|
|
923
|
+
C = C + this.C | 0;
|
|
924
|
+
D = D + this.D | 0;
|
|
925
|
+
E = E + this.E | 0;
|
|
926
|
+
F = F + this.F | 0;
|
|
927
|
+
G = G + this.G | 0;
|
|
928
|
+
H = H + this.H | 0;
|
|
929
|
+
this.set(A, B, C, D, E, F, G, H);
|
|
930
|
+
}
|
|
931
|
+
roundClean() {
|
|
932
|
+
clean(SHA256_W);
|
|
933
|
+
}
|
|
934
|
+
destroy() {
|
|
935
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
|
936
|
+
clean(this.buffer);
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
var _SHA256 = class extends SHA2_32B {
|
|
940
|
+
// We cannot use array here since array allows indexing by variable
|
|
941
|
+
// which means optimizer/compiler cannot use registers.
|
|
942
|
+
A = SHA256_IV[0] | 0;
|
|
943
|
+
B = SHA256_IV[1] | 0;
|
|
944
|
+
C = SHA256_IV[2] | 0;
|
|
945
|
+
D = SHA256_IV[3] | 0;
|
|
946
|
+
E = SHA256_IV[4] | 0;
|
|
947
|
+
F = SHA256_IV[5] | 0;
|
|
948
|
+
G = SHA256_IV[6] | 0;
|
|
949
|
+
H = SHA256_IV[7] | 0;
|
|
950
|
+
constructor() {
|
|
951
|
+
super(32);
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
var sha256 = /* @__PURE__ */ createHasher(
|
|
955
|
+
() => new _SHA256(),
|
|
956
|
+
/* @__PURE__ */ oidNist(1)
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
// transport/NostrTransportProvider.ts
|
|
960
|
+
var import_nostr_js_sdk = require("@unicitylabs/nostr-js-sdk");
|
|
961
|
+
|
|
962
|
+
// core/crypto.ts
|
|
963
|
+
var bip39 = __toESM(require("bip39"), 1);
|
|
964
|
+
var import_crypto_js = __toESM(require("crypto-js"), 1);
|
|
965
|
+
var import_elliptic = __toESM(require("elliptic"), 1);
|
|
966
|
+
|
|
967
|
+
// core/bech32.ts
|
|
968
|
+
var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
969
|
+
var GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
970
|
+
function convertBits(data, fromBits, toBits, pad) {
|
|
971
|
+
let acc = 0;
|
|
972
|
+
let bits = 0;
|
|
973
|
+
const ret = [];
|
|
974
|
+
const maxv = (1 << toBits) - 1;
|
|
975
|
+
for (let i = 0; i < data.length; i++) {
|
|
976
|
+
const value = data[i];
|
|
977
|
+
if (value < 0 || value >> fromBits !== 0) return null;
|
|
978
|
+
acc = acc << fromBits | value;
|
|
979
|
+
bits += fromBits;
|
|
980
|
+
while (bits >= toBits) {
|
|
981
|
+
bits -= toBits;
|
|
982
|
+
ret.push(acc >> bits & maxv);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (pad) {
|
|
986
|
+
if (bits > 0) {
|
|
987
|
+
ret.push(acc << toBits - bits & maxv);
|
|
988
|
+
}
|
|
989
|
+
} else if (bits >= fromBits || acc << toBits - bits & maxv) {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
return ret;
|
|
993
|
+
}
|
|
994
|
+
function hrpExpand(hrp) {
|
|
995
|
+
const ret = [];
|
|
996
|
+
for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) >> 5);
|
|
997
|
+
ret.push(0);
|
|
998
|
+
for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) & 31);
|
|
999
|
+
return ret;
|
|
1000
|
+
}
|
|
1001
|
+
function bech32Polymod(values) {
|
|
1002
|
+
let chk = 1;
|
|
1003
|
+
for (let p = 0; p < values.length; p++) {
|
|
1004
|
+
const top = chk >> 25;
|
|
1005
|
+
chk = (chk & 33554431) << 5 ^ values[p];
|
|
1006
|
+
for (let i = 0; i < 5; i++) {
|
|
1007
|
+
if (top >> i & 1) chk ^= GENERATOR[i];
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return chk;
|
|
1011
|
+
}
|
|
1012
|
+
function bech32Checksum(hrp, data) {
|
|
1013
|
+
const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
|
|
1014
|
+
const mod = bech32Polymod(values) ^ 1;
|
|
1015
|
+
const ret = [];
|
|
1016
|
+
for (let p = 0; p < 6; p++) {
|
|
1017
|
+
ret.push(mod >> 5 * (5 - p) & 31);
|
|
1018
|
+
}
|
|
1019
|
+
return ret;
|
|
1020
|
+
}
|
|
1021
|
+
function encodeBech32(hrp, version, program) {
|
|
1022
|
+
if (version < 0 || version > 16) {
|
|
1023
|
+
throw new Error("Invalid witness version");
|
|
1024
|
+
}
|
|
1025
|
+
const converted = convertBits(Array.from(program), 8, 5, true);
|
|
1026
|
+
if (!converted) {
|
|
1027
|
+
throw new Error("Failed to convert bits");
|
|
1028
|
+
}
|
|
1029
|
+
const data = [version].concat(converted);
|
|
1030
|
+
const checksum = bech32Checksum(hrp, data);
|
|
1031
|
+
const combined = data.concat(checksum);
|
|
1032
|
+
let out = hrp + "1";
|
|
1033
|
+
for (let i = 0; i < combined.length; i++) {
|
|
1034
|
+
out += CHARSET[combined[i]];
|
|
1035
|
+
}
|
|
1036
|
+
return out;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// core/crypto.ts
|
|
1040
|
+
var ec = new import_elliptic.default.ec("secp256k1");
|
|
1041
|
+
var CURVE_ORDER = BigInt(
|
|
1042
|
+
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
|
1043
|
+
);
|
|
1044
|
+
function getPublicKey(privateKey, compressed = true) {
|
|
1045
|
+
const keyPair = ec.keyFromPrivate(privateKey, "hex");
|
|
1046
|
+
return keyPair.getPublic(compressed, "hex");
|
|
1047
|
+
}
|
|
1048
|
+
function sha2562(data, inputEncoding = "hex") {
|
|
1049
|
+
const parsed = inputEncoding === "hex" ? import_crypto_js.default.enc.Hex.parse(data) : import_crypto_js.default.enc.Utf8.parse(data);
|
|
1050
|
+
return import_crypto_js.default.SHA256(parsed).toString();
|
|
1051
|
+
}
|
|
1052
|
+
function ripemd160(data, inputEncoding = "hex") {
|
|
1053
|
+
const parsed = inputEncoding === "hex" ? import_crypto_js.default.enc.Hex.parse(data) : import_crypto_js.default.enc.Utf8.parse(data);
|
|
1054
|
+
return import_crypto_js.default.RIPEMD160(parsed).toString();
|
|
1055
|
+
}
|
|
1056
|
+
function hash160(data) {
|
|
1057
|
+
const sha = sha2562(data, "hex");
|
|
1058
|
+
return ripemd160(sha, "hex");
|
|
1059
|
+
}
|
|
1060
|
+
function hash160ToBytes(hash160Hex) {
|
|
1061
|
+
const matches = hash160Hex.match(/../g);
|
|
1062
|
+
if (!matches) return new Uint8Array(0);
|
|
1063
|
+
return Uint8Array.from(matches.map((x) => parseInt(x, 16)));
|
|
1064
|
+
}
|
|
1065
|
+
function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
|
|
1066
|
+
const pubKeyHash = hash160(publicKey);
|
|
1067
|
+
const programBytes = hash160ToBytes(pubKeyHash);
|
|
1068
|
+
return encodeBech32(prefix, witnessVersion, programBytes);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// transport/websocket.ts
|
|
1072
|
+
var WebSocketReadyState = {
|
|
1073
|
+
CONNECTING: 0,
|
|
1074
|
+
OPEN: 1,
|
|
1075
|
+
CLOSING: 2,
|
|
1076
|
+
CLOSED: 3
|
|
1077
|
+
};
|
|
1078
|
+
function defaultUUIDGenerator() {
|
|
1079
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1080
|
+
return crypto.randomUUID();
|
|
1081
|
+
}
|
|
1082
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1083
|
+
const r = Math.random() * 16 | 0;
|
|
1084
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
1085
|
+
return v.toString(16);
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// constants.ts
|
|
1090
|
+
var STORAGE_PREFIX = "sphere_";
|
|
1091
|
+
var STORAGE_KEYS = {
|
|
1092
|
+
/** Encrypted BIP39 mnemonic */
|
|
1093
|
+
MNEMONIC: `${STORAGE_PREFIX}mnemonic`,
|
|
1094
|
+
/** Encrypted master private key */
|
|
1095
|
+
MASTER_KEY: `${STORAGE_PREFIX}master_key`,
|
|
1096
|
+
/** BIP32 chain code */
|
|
1097
|
+
CHAIN_CODE: `${STORAGE_PREFIX}chain_code`,
|
|
1098
|
+
/** HD derivation path (full path like m/44'/0'/0'/0/0) */
|
|
1099
|
+
DERIVATION_PATH: `${STORAGE_PREFIX}derivation_path`,
|
|
1100
|
+
/** Base derivation path (like m/44'/0'/0' without chain/index) */
|
|
1101
|
+
BASE_PATH: `${STORAGE_PREFIX}base_path`,
|
|
1102
|
+
/** Derivation mode: bip32, wif_hmac, legacy_hmac */
|
|
1103
|
+
DERIVATION_MODE: `${STORAGE_PREFIX}derivation_mode`,
|
|
1104
|
+
/** Wallet source: mnemonic, file, unknown */
|
|
1105
|
+
WALLET_SOURCE: `${STORAGE_PREFIX}wallet_source`,
|
|
1106
|
+
/** Wallet existence flag */
|
|
1107
|
+
WALLET_EXISTS: `${STORAGE_PREFIX}wallet_exists`,
|
|
1108
|
+
/** Registered nametag (legacy - single address) */
|
|
1109
|
+
NAMETAG: `${STORAGE_PREFIX}nametag`,
|
|
1110
|
+
/** Current active address index */
|
|
1111
|
+
CURRENT_ADDRESS_INDEX: `${STORAGE_PREFIX}current_address_index`,
|
|
1112
|
+
/** Address nametags map (JSON: { "0": "alice", "1": "bob" }) */
|
|
1113
|
+
ADDRESS_NAMETAGS: `${STORAGE_PREFIX}address_nametags`,
|
|
1114
|
+
/** Token data */
|
|
1115
|
+
TOKENS: `${STORAGE_PREFIX}tokens`,
|
|
1116
|
+
/** Pending transfers */
|
|
1117
|
+
PENDING_TRANSFERS: `${STORAGE_PREFIX}pending_transfers`,
|
|
1118
|
+
/** Transfer outbox */
|
|
1119
|
+
OUTBOX: `${STORAGE_PREFIX}outbox`,
|
|
1120
|
+
/** Conversations */
|
|
1121
|
+
CONVERSATIONS: `${STORAGE_PREFIX}conversations`,
|
|
1122
|
+
/** Messages */
|
|
1123
|
+
MESSAGES: `${STORAGE_PREFIX}messages`,
|
|
1124
|
+
/** Transaction history */
|
|
1125
|
+
TRANSACTION_HISTORY: `${STORAGE_PREFIX}transaction_history`,
|
|
1126
|
+
/** Archived tokens (spent token history) */
|
|
1127
|
+
ARCHIVED_TOKENS: `${STORAGE_PREFIX}archived_tokens`,
|
|
1128
|
+
/** Tombstones (records of deleted/spent tokens) */
|
|
1129
|
+
TOMBSTONES: `${STORAGE_PREFIX}tombstones`,
|
|
1130
|
+
/** Forked tokens (alternative histories) */
|
|
1131
|
+
FORKED_TOKENS: `${STORAGE_PREFIX}forked_tokens`
|
|
1132
|
+
};
|
|
1133
|
+
var DEFAULT_NOSTR_RELAYS = [
|
|
1134
|
+
"wss://relay.unicity.network",
|
|
1135
|
+
"wss://relay.damus.io",
|
|
1136
|
+
"wss://nos.lol",
|
|
1137
|
+
"wss://relay.nostr.band"
|
|
1138
|
+
];
|
|
1139
|
+
var NOSTR_EVENT_KINDS = {
|
|
1140
|
+
/** NIP-04 encrypted direct message */
|
|
1141
|
+
DIRECT_MESSAGE: 4,
|
|
1142
|
+
/** Token transfer (Unicity custom - 31113) */
|
|
1143
|
+
TOKEN_TRANSFER: 31113,
|
|
1144
|
+
/** Payment request (Unicity custom - 31115) */
|
|
1145
|
+
PAYMENT_REQUEST: 31115,
|
|
1146
|
+
/** Payment request response (Unicity custom - 31116) */
|
|
1147
|
+
PAYMENT_REQUEST_RESPONSE: 31116,
|
|
1148
|
+
/** Nametag binding (NIP-78 app-specific data) */
|
|
1149
|
+
NAMETAG_BINDING: 30078,
|
|
1150
|
+
/** Public broadcast */
|
|
1151
|
+
BROADCAST: 1
|
|
1152
|
+
};
|
|
1153
|
+
var DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
|
|
1154
|
+
var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
|
|
1155
|
+
var TEST_AGGREGATOR_URL = "https://goggregator-test.unicity.network";
|
|
1156
|
+
var DEFAULT_AGGREGATOR_TIMEOUT = 3e4;
|
|
1157
|
+
var DEFAULT_IPFS_GATEWAYS = [
|
|
1158
|
+
"https://ipfs.unicity.network",
|
|
1159
|
+
"https://dweb.link",
|
|
1160
|
+
"https://ipfs.io"
|
|
1161
|
+
];
|
|
1162
|
+
var DEFAULT_BASE_PATH = "m/44'/0'/0'";
|
|
1163
|
+
var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
|
|
1164
|
+
var DEFAULT_ELECTRUM_URL = "wss://fulcrum.alpha.unicity.network:50004";
|
|
1165
|
+
var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
|
|
1166
|
+
var TEST_NOSTR_RELAYS = [
|
|
1167
|
+
"wss://nostr-relay.testnet.unicity.network"
|
|
1168
|
+
];
|
|
1169
|
+
var NETWORKS = {
|
|
1170
|
+
mainnet: {
|
|
1171
|
+
name: "Mainnet",
|
|
1172
|
+
aggregatorUrl: DEFAULT_AGGREGATOR_URL,
|
|
1173
|
+
nostrRelays: DEFAULT_NOSTR_RELAYS,
|
|
1174
|
+
ipfsGateways: DEFAULT_IPFS_GATEWAYS,
|
|
1175
|
+
electrumUrl: DEFAULT_ELECTRUM_URL
|
|
1176
|
+
},
|
|
1177
|
+
testnet: {
|
|
1178
|
+
name: "Testnet",
|
|
1179
|
+
aggregatorUrl: TEST_AGGREGATOR_URL,
|
|
1180
|
+
nostrRelays: TEST_NOSTR_RELAYS,
|
|
1181
|
+
ipfsGateways: DEFAULT_IPFS_GATEWAYS,
|
|
1182
|
+
electrumUrl: TEST_ELECTRUM_URL
|
|
1183
|
+
},
|
|
1184
|
+
dev: {
|
|
1185
|
+
name: "Development",
|
|
1186
|
+
aggregatorUrl: DEV_AGGREGATOR_URL,
|
|
1187
|
+
nostrRelays: TEST_NOSTR_RELAYS,
|
|
1188
|
+
ipfsGateways: DEFAULT_IPFS_GATEWAYS,
|
|
1189
|
+
electrumUrl: TEST_ELECTRUM_URL
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
var TIMEOUTS = {
|
|
1193
|
+
/** WebSocket connection timeout */
|
|
1194
|
+
WEBSOCKET_CONNECT: 1e4,
|
|
1195
|
+
/** Nostr relay reconnect delay */
|
|
1196
|
+
NOSTR_RECONNECT_DELAY: 3e3,
|
|
1197
|
+
/** Max reconnect attempts */
|
|
1198
|
+
MAX_RECONNECT_ATTEMPTS: 5,
|
|
1199
|
+
/** Proof polling interval */
|
|
1200
|
+
PROOF_POLL_INTERVAL: 1e3,
|
|
1201
|
+
/** Sync interval */
|
|
1202
|
+
SYNC_INTERVAL: 6e4
|
|
1192
1203
|
};
|
|
1193
|
-
function defaultUUIDGenerator() {
|
|
1194
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1195
|
-
return crypto.randomUUID();
|
|
1196
|
-
}
|
|
1197
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1198
|
-
const r = Math.random() * 16 | 0;
|
|
1199
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
1200
|
-
return v.toString(16);
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
1204
|
|
|
1204
1205
|
// transport/NostrTransportProvider.ts
|
|
1205
1206
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
1207
|
+
function deriveNametagEncryptionKey(privateKeyHex) {
|
|
1208
|
+
const privateKeyBytes = import_buffer.Buffer.from(privateKeyHex, "hex");
|
|
1209
|
+
const saltInput = new TextEncoder().encode("sphere-nametag-salt");
|
|
1210
|
+
const salt = sha256(saltInput);
|
|
1211
|
+
const info = new TextEncoder().encode("nametag-encryption");
|
|
1212
|
+
return hkdf(sha256, privateKeyBytes, salt, info, 32);
|
|
1213
|
+
}
|
|
1214
|
+
async function encryptNametag(nametag, privateKeyHex) {
|
|
1215
|
+
const key = deriveNametagEncryptionKey(privateKeyHex);
|
|
1216
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1217
|
+
const encoder = new TextEncoder();
|
|
1218
|
+
const data = encoder.encode(nametag);
|
|
1219
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
1220
|
+
"raw",
|
|
1221
|
+
new Uint8Array(key).buffer,
|
|
1222
|
+
{ name: "AES-GCM" },
|
|
1223
|
+
false,
|
|
1224
|
+
["encrypt"]
|
|
1225
|
+
);
|
|
1226
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
1227
|
+
{ name: "AES-GCM", iv: new Uint8Array(iv).buffer },
|
|
1228
|
+
cryptoKey,
|
|
1229
|
+
new Uint8Array(data).buffer
|
|
1230
|
+
);
|
|
1231
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
1232
|
+
combined.set(iv, 0);
|
|
1233
|
+
combined.set(new Uint8Array(encrypted), iv.length);
|
|
1234
|
+
return import_buffer.Buffer.from(combined).toString("base64");
|
|
1235
|
+
}
|
|
1236
|
+
async function decryptNametag(encryptedBase64, privateKeyHex) {
|
|
1237
|
+
try {
|
|
1238
|
+
const key = deriveNametagEncryptionKey(privateKeyHex);
|
|
1239
|
+
const combined = import_buffer.Buffer.from(encryptedBase64, "base64");
|
|
1240
|
+
const iv = combined.slice(0, 12);
|
|
1241
|
+
const ciphertext = combined.slice(12);
|
|
1242
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
1243
|
+
"raw",
|
|
1244
|
+
new Uint8Array(key).buffer,
|
|
1245
|
+
{ name: "AES-GCM" },
|
|
1246
|
+
false,
|
|
1247
|
+
["decrypt"]
|
|
1248
|
+
);
|
|
1249
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
1250
|
+
{ name: "AES-GCM", iv: new Uint8Array(iv).buffer },
|
|
1251
|
+
cryptoKey,
|
|
1252
|
+
new Uint8Array(ciphertext).buffer
|
|
1253
|
+
);
|
|
1254
|
+
const decoder = new TextDecoder();
|
|
1255
|
+
return decoder.decode(decrypted);
|
|
1256
|
+
} catch {
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1206
1260
|
var NostrTransportProvider = class {
|
|
1207
1261
|
id = "nostr";
|
|
1208
1262
|
name = "Nostr Transport";
|
|
@@ -1212,9 +1266,10 @@ var NostrTransportProvider = class {
|
|
|
1212
1266
|
identity = null;
|
|
1213
1267
|
keyManager = null;
|
|
1214
1268
|
status = "disconnected";
|
|
1215
|
-
//
|
|
1216
|
-
|
|
1217
|
-
|
|
1269
|
+
// NostrClient from nostr-js-sdk handles all WebSocket management,
|
|
1270
|
+
// keepalive pings, reconnection, and NIP-42 authentication
|
|
1271
|
+
nostrClient = null;
|
|
1272
|
+
mainSubscriptionId = null;
|
|
1218
1273
|
// Event handlers
|
|
1219
1274
|
messageHandlers = /* @__PURE__ */ new Set();
|
|
1220
1275
|
transferHandlers = /* @__PURE__ */ new Set();
|
|
@@ -1222,9 +1277,6 @@ var NostrTransportProvider = class {
|
|
|
1222
1277
|
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1223
1278
|
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1224
1279
|
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1225
|
-
// Subscriptions
|
|
1226
|
-
subscriptions = /* @__PURE__ */ new Map();
|
|
1227
|
-
// subId -> relays
|
|
1228
1280
|
constructor(config) {
|
|
1229
1281
|
this.config = {
|
|
1230
1282
|
relays: config.relays ?? [...DEFAULT_NOSTR_RELAYS],
|
|
@@ -1244,16 +1296,43 @@ var NostrTransportProvider = class {
|
|
|
1244
1296
|
if (this.status === "connected") return;
|
|
1245
1297
|
this.status = "connecting";
|
|
1246
1298
|
try {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1299
|
+
if (!this.keyManager) {
|
|
1300
|
+
const tempKey = import_buffer.Buffer.alloc(32);
|
|
1301
|
+
crypto.getRandomValues(tempKey);
|
|
1302
|
+
this.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(tempKey);
|
|
1303
|
+
}
|
|
1304
|
+
this.nostrClient = new import_nostr_js_sdk.NostrClient(this.keyManager, {
|
|
1305
|
+
autoReconnect: this.config.autoReconnect,
|
|
1306
|
+
reconnectIntervalMs: this.config.reconnectDelay,
|
|
1307
|
+
maxReconnectIntervalMs: this.config.reconnectDelay * 16,
|
|
1308
|
+
// exponential backoff cap
|
|
1309
|
+
pingIntervalMs: 15e3
|
|
1310
|
+
// 15 second keepalive pings (more aggressive to prevent drops)
|
|
1311
|
+
});
|
|
1312
|
+
this.nostrClient.addConnectionListener({
|
|
1313
|
+
onConnect: (url) => {
|
|
1314
|
+
this.log("NostrClient connected to relay:", url);
|
|
1315
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1316
|
+
},
|
|
1317
|
+
onDisconnect: (url, reason) => {
|
|
1318
|
+
this.log("NostrClient disconnected from relay:", url, "reason:", reason);
|
|
1319
|
+
},
|
|
1320
|
+
onReconnecting: (url, attempt) => {
|
|
1321
|
+
this.log("NostrClient reconnecting to relay:", url, "attempt:", attempt);
|
|
1322
|
+
this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
|
|
1323
|
+
},
|
|
1324
|
+
onReconnected: (url) => {
|
|
1325
|
+
this.log("NostrClient reconnected to relay:", url);
|
|
1326
|
+
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
await this.nostrClient.connect(...this.config.relays);
|
|
1330
|
+
if (!this.nostrClient.isConnected()) {
|
|
1252
1331
|
throw new Error("Failed to connect to any relay");
|
|
1253
1332
|
}
|
|
1254
1333
|
this.status = "connected";
|
|
1255
1334
|
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1256
|
-
this.log("Connected to", this.
|
|
1335
|
+
this.log("Connected to", this.nostrClient.getConnectedRelays().size, "relays");
|
|
1257
1336
|
if (this.identity) {
|
|
1258
1337
|
this.subscribeToEvents();
|
|
1259
1338
|
}
|
|
@@ -1263,17 +1342,19 @@ var NostrTransportProvider = class {
|
|
|
1263
1342
|
}
|
|
1264
1343
|
}
|
|
1265
1344
|
async disconnect() {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
this.
|
|
1345
|
+
if (this.nostrClient) {
|
|
1346
|
+
this.nostrClient.disconnect();
|
|
1347
|
+
this.nostrClient = null;
|
|
1269
1348
|
}
|
|
1270
|
-
this.
|
|
1349
|
+
this.mainSubscriptionId = null;
|
|
1350
|
+
this.walletSubscriptionId = null;
|
|
1351
|
+
this.chatSubscriptionId = null;
|
|
1271
1352
|
this.status = "disconnected";
|
|
1272
1353
|
this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
|
|
1273
1354
|
this.log("Disconnected from all relays");
|
|
1274
1355
|
}
|
|
1275
1356
|
isConnected() {
|
|
1276
|
-
return this.status === "connected" && this.
|
|
1357
|
+
return this.status === "connected" && this.nostrClient?.isConnected() === true;
|
|
1277
1358
|
}
|
|
1278
1359
|
getStatus() {
|
|
1279
1360
|
return this.status;
|
|
@@ -1291,7 +1372,8 @@ var NostrTransportProvider = class {
|
|
|
1291
1372
|
* Get list of currently connected relay URLs
|
|
1292
1373
|
*/
|
|
1293
1374
|
getConnectedRelays() {
|
|
1294
|
-
|
|
1375
|
+
if (!this.nostrClient) return [];
|
|
1376
|
+
return Array.from(this.nostrClient.getConnectedRelays());
|
|
1295
1377
|
}
|
|
1296
1378
|
/**
|
|
1297
1379
|
* Add a new relay dynamically
|
|
@@ -1303,9 +1385,9 @@ var NostrTransportProvider = class {
|
|
|
1303
1385
|
return false;
|
|
1304
1386
|
}
|
|
1305
1387
|
this.config.relays.push(relayUrl);
|
|
1306
|
-
if (this.status === "connected") {
|
|
1388
|
+
if (this.status === "connected" && this.nostrClient) {
|
|
1307
1389
|
try {
|
|
1308
|
-
await this.
|
|
1390
|
+
await this.nostrClient.connect(relayUrl);
|
|
1309
1391
|
this.log("Added and connected to relay:", relayUrl);
|
|
1310
1392
|
this.emitEvent({
|
|
1311
1393
|
type: "transport:relay_added",
|
|
@@ -1333,6 +1415,8 @@ var NostrTransportProvider = class {
|
|
|
1333
1415
|
/**
|
|
1334
1416
|
* Remove a relay dynamically
|
|
1335
1417
|
* Will disconnect from the relay if connected
|
|
1418
|
+
* NOTE: NostrClient doesn't support removing individual relays at runtime.
|
|
1419
|
+
* We remove from config so it won't be used on next connect().
|
|
1336
1420
|
*/
|
|
1337
1421
|
async removeRelay(relayUrl) {
|
|
1338
1422
|
const index = this.config.relays.indexOf(relayUrl);
|
|
@@ -1341,19 +1425,13 @@ var NostrTransportProvider = class {
|
|
|
1341
1425
|
return false;
|
|
1342
1426
|
}
|
|
1343
1427
|
this.config.relays.splice(index, 1);
|
|
1344
|
-
|
|
1345
|
-
if (ws) {
|
|
1346
|
-
ws.close();
|
|
1347
|
-
this.connections.delete(relayUrl);
|
|
1348
|
-
this.reconnectAttempts.delete(relayUrl);
|
|
1349
|
-
this.log("Removed and disconnected from relay:", relayUrl);
|
|
1350
|
-
}
|
|
1428
|
+
this.log("Removed relay from config:", relayUrl);
|
|
1351
1429
|
this.emitEvent({
|
|
1352
1430
|
type: "transport:relay_removed",
|
|
1353
1431
|
timestamp: Date.now(),
|
|
1354
1432
|
data: { relay: relayUrl }
|
|
1355
1433
|
});
|
|
1356
|
-
if (this.
|
|
1434
|
+
if (this.nostrClient && !this.nostrClient.isConnected() && this.status === "connected") {
|
|
1357
1435
|
this.status = "error";
|
|
1358
1436
|
this.emitEvent({
|
|
1359
1437
|
type: "transport:error",
|
|
@@ -1373,8 +1451,8 @@ var NostrTransportProvider = class {
|
|
|
1373
1451
|
* Check if a relay is currently connected
|
|
1374
1452
|
*/
|
|
1375
1453
|
isRelayConnected(relayUrl) {
|
|
1376
|
-
|
|
1377
|
-
return
|
|
1454
|
+
if (!this.nostrClient) return false;
|
|
1455
|
+
return this.nostrClient.getConnectedRelays().has(relayUrl);
|
|
1378
1456
|
}
|
|
1379
1457
|
// ===========================================================================
|
|
1380
1458
|
// TransportProvider Implementation
|
|
@@ -1385,7 +1463,37 @@ var NostrTransportProvider = class {
|
|
|
1385
1463
|
this.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(secretKey);
|
|
1386
1464
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
1387
1465
|
this.log("Identity set, Nostr pubkey:", nostrPubkey.slice(0, 16) + "...");
|
|
1388
|
-
if (this.
|
|
1466
|
+
if (this.nostrClient && this.status === "connected") {
|
|
1467
|
+
this.log("Identity changed while connected - recreating NostrClient");
|
|
1468
|
+
const oldClient = this.nostrClient;
|
|
1469
|
+
this.nostrClient = new import_nostr_js_sdk.NostrClient(this.keyManager, {
|
|
1470
|
+
autoReconnect: this.config.autoReconnect,
|
|
1471
|
+
reconnectIntervalMs: this.config.reconnectDelay,
|
|
1472
|
+
maxReconnectIntervalMs: this.config.reconnectDelay * 16,
|
|
1473
|
+
pingIntervalMs: 15e3
|
|
1474
|
+
// 15 second keepalive pings
|
|
1475
|
+
});
|
|
1476
|
+
this.nostrClient.addConnectionListener({
|
|
1477
|
+
onConnect: (url) => {
|
|
1478
|
+
this.log("NostrClient connected to relay:", url);
|
|
1479
|
+
},
|
|
1480
|
+
onDisconnect: (url, reason) => {
|
|
1481
|
+
this.log("NostrClient disconnected from relay:", url, "reason:", reason);
|
|
1482
|
+
},
|
|
1483
|
+
onReconnecting: (url, attempt) => {
|
|
1484
|
+
this.log("NostrClient reconnecting to relay:", url, "attempt:", attempt);
|
|
1485
|
+
},
|
|
1486
|
+
onReconnected: (url) => {
|
|
1487
|
+
this.log("NostrClient reconnected to relay:", url);
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
this.nostrClient.connect(...this.config.relays).then(() => {
|
|
1491
|
+
this.subscribeToEvents();
|
|
1492
|
+
oldClient.disconnect();
|
|
1493
|
+
}).catch((err) => {
|
|
1494
|
+
this.log("Failed to reconnect with new identity:", err);
|
|
1495
|
+
});
|
|
1496
|
+
} else if (this.isConnected()) {
|
|
1389
1497
|
this.subscribeToEvents();
|
|
1390
1498
|
}
|
|
1391
1499
|
}
|
|
@@ -1401,18 +1509,17 @@ var NostrTransportProvider = class {
|
|
|
1401
1509
|
}
|
|
1402
1510
|
async sendMessage(recipientPubkey, content) {
|
|
1403
1511
|
this.ensureReady();
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
);
|
|
1409
|
-
await this.publishEvent(event);
|
|
1512
|
+
const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
|
|
1513
|
+
const senderNametag = this.identity?.nametag;
|
|
1514
|
+
const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
|
|
1515
|
+
const giftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
|
|
1516
|
+
await this.publishEvent(giftWrap);
|
|
1410
1517
|
this.emitEvent({
|
|
1411
1518
|
type: "message:sent",
|
|
1412
1519
|
timestamp: Date.now(),
|
|
1413
1520
|
data: { recipient: recipientPubkey }
|
|
1414
1521
|
});
|
|
1415
|
-
return
|
|
1522
|
+
return giftWrap.id;
|
|
1416
1523
|
}
|
|
1417
1524
|
onMessage(handler) {
|
|
1418
1525
|
this.messageHandlers.add(handler);
|
|
@@ -1529,6 +1636,118 @@ var NostrTransportProvider = class {
|
|
|
1529
1636
|
if (pubkeyTag?.[1]) return pubkeyTag[1];
|
|
1530
1637
|
return null;
|
|
1531
1638
|
}
|
|
1639
|
+
async resolveNametagInfo(nametag) {
|
|
1640
|
+
this.ensureReady();
|
|
1641
|
+
const hashedNametag = (0, import_nostr_js_sdk.hashNametag)(nametag);
|
|
1642
|
+
let events = await this.queryEvents({
|
|
1643
|
+
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
1644
|
+
"#t": [hashedNametag],
|
|
1645
|
+
limit: 1
|
|
1646
|
+
});
|
|
1647
|
+
if (events.length === 0) {
|
|
1648
|
+
events = await this.queryEvents({
|
|
1649
|
+
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
1650
|
+
"#d": [hashedNametag],
|
|
1651
|
+
limit: 1
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
if (events.length === 0) return null;
|
|
1655
|
+
const bindingEvent = events[0];
|
|
1656
|
+
try {
|
|
1657
|
+
const content = JSON.parse(bindingEvent.content);
|
|
1658
|
+
if (content.public_key && content.l1_address) {
|
|
1659
|
+
const l3Address = `PROXY:${hashedNametag}`;
|
|
1660
|
+
return {
|
|
1661
|
+
nametag,
|
|
1662
|
+
transportPubkey: bindingEvent.pubkey,
|
|
1663
|
+
chainPubkey: content.public_key,
|
|
1664
|
+
l1Address: content.l1_address,
|
|
1665
|
+
directAddress: content.direct_address || "",
|
|
1666
|
+
proxyAddress: l3Address,
|
|
1667
|
+
timestamp: bindingEvent.created_at * 1e3
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
this.log("Legacy nametag event without extended fields:", nametag);
|
|
1671
|
+
const pubkeyTag = bindingEvent.tags.find((t) => t[0] === "pubkey");
|
|
1672
|
+
const l1Tag = bindingEvent.tags.find((t) => t[0] === "l1");
|
|
1673
|
+
if (pubkeyTag?.[1] && l1Tag?.[1]) {
|
|
1674
|
+
const l3Address = `PROXY:${hashedNametag}`;
|
|
1675
|
+
return {
|
|
1676
|
+
nametag,
|
|
1677
|
+
transportPubkey: bindingEvent.pubkey,
|
|
1678
|
+
chainPubkey: pubkeyTag[1],
|
|
1679
|
+
l1Address: l1Tag[1],
|
|
1680
|
+
directAddress: "",
|
|
1681
|
+
proxyAddress: l3Address,
|
|
1682
|
+
timestamp: bindingEvent.created_at * 1e3
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
return {
|
|
1686
|
+
nametag,
|
|
1687
|
+
transportPubkey: bindingEvent.pubkey,
|
|
1688
|
+
chainPubkey: "",
|
|
1689
|
+
// Cannot derive from 32-byte Nostr pubkey
|
|
1690
|
+
l1Address: "",
|
|
1691
|
+
// Cannot derive without 33-byte pubkey
|
|
1692
|
+
directAddress: "",
|
|
1693
|
+
proxyAddress: `PROXY:${hashedNametag}`,
|
|
1694
|
+
timestamp: bindingEvent.created_at * 1e3
|
|
1695
|
+
};
|
|
1696
|
+
} catch {
|
|
1697
|
+
return {
|
|
1698
|
+
nametag,
|
|
1699
|
+
transportPubkey: bindingEvent.pubkey,
|
|
1700
|
+
chainPubkey: "",
|
|
1701
|
+
l1Address: "",
|
|
1702
|
+
directAddress: "",
|
|
1703
|
+
proxyAddress: `PROXY:${hashedNametag}`,
|
|
1704
|
+
timestamp: bindingEvent.created_at * 1e3
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Recover nametag for the current identity by searching for encrypted nametag events
|
|
1710
|
+
* Used after wallet import to recover associated nametag
|
|
1711
|
+
* @returns Decrypted nametag or null if none found
|
|
1712
|
+
*/
|
|
1713
|
+
async recoverNametag() {
|
|
1714
|
+
this.ensureReady();
|
|
1715
|
+
if (!this.identity || !this.keyManager) {
|
|
1716
|
+
throw new Error("Identity not set");
|
|
1717
|
+
}
|
|
1718
|
+
const nostrPubkey = this.getNostrPubkey();
|
|
1719
|
+
this.log("Searching for nametag events for pubkey:", nostrPubkey.slice(0, 16) + "...");
|
|
1720
|
+
const events = await this.queryEvents({
|
|
1721
|
+
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
1722
|
+
authors: [nostrPubkey],
|
|
1723
|
+
limit: 10
|
|
1724
|
+
// Get recent events in case of updates
|
|
1725
|
+
});
|
|
1726
|
+
if (events.length === 0) {
|
|
1727
|
+
this.log("No nametag events found for this pubkey");
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
events.sort((a, b) => b.created_at - a.created_at);
|
|
1731
|
+
for (const event of events) {
|
|
1732
|
+
try {
|
|
1733
|
+
const content = JSON.parse(event.content);
|
|
1734
|
+
if (content.encrypted_nametag) {
|
|
1735
|
+
const decrypted = await decryptNametag(
|
|
1736
|
+
content.encrypted_nametag,
|
|
1737
|
+
this.identity.privateKey
|
|
1738
|
+
);
|
|
1739
|
+
if (decrypted) {
|
|
1740
|
+
this.log("Recovered nametag:", decrypted);
|
|
1741
|
+
return decrypted;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
} catch {
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
this.log("Could not decrypt nametag from any event");
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1532
1751
|
async publishNametag(nametag, address) {
|
|
1533
1752
|
this.ensureReady();
|
|
1534
1753
|
const hashedNametag = (0, import_nostr_js_sdk.hashNametag)(nametag);
|
|
@@ -1539,8 +1758,11 @@ var NostrTransportProvider = class {
|
|
|
1539
1758
|
await this.publishEvent(event);
|
|
1540
1759
|
this.log("Published nametag binding:", nametag);
|
|
1541
1760
|
}
|
|
1542
|
-
async registerNametag(nametag, _publicKey) {
|
|
1761
|
+
async registerNametag(nametag, _publicKey, directAddress = "") {
|
|
1543
1762
|
this.ensureReady();
|
|
1763
|
+
if (!this.identity) {
|
|
1764
|
+
throw new Error("Identity not set");
|
|
1765
|
+
}
|
|
1544
1766
|
const nostrPubkey = this.getNostrPubkey();
|
|
1545
1767
|
const existing = await this.resolveNametag(nametag);
|
|
1546
1768
|
this.log("registerNametag:", nametag, "existing:", existing, "myPubkey:", nostrPubkey);
|
|
@@ -1548,27 +1770,42 @@ var NostrTransportProvider = class {
|
|
|
1548
1770
|
this.log("Nametag already taken:", nametag, "- owner:", existing);
|
|
1549
1771
|
return false;
|
|
1550
1772
|
}
|
|
1773
|
+
const privateKeyHex = this.identity.privateKey;
|
|
1774
|
+
const compressedPubkey = getPublicKey(privateKeyHex, true);
|
|
1775
|
+
const l1Address = publicKeyToAddress(compressedPubkey, "alpha");
|
|
1776
|
+
const encryptedNametag = await encryptNametag(nametag, privateKeyHex);
|
|
1551
1777
|
const hashedNametag = (0, import_nostr_js_sdk.hashNametag)(nametag);
|
|
1552
1778
|
const content = JSON.stringify({
|
|
1553
1779
|
nametag_hash: hashedNametag,
|
|
1554
1780
|
address: nostrPubkey,
|
|
1555
|
-
verified: Date.now()
|
|
1781
|
+
verified: Date.now(),
|
|
1782
|
+
// Extended fields for nametag recovery and address lookup
|
|
1783
|
+
encrypted_nametag: encryptedNametag,
|
|
1784
|
+
public_key: compressedPubkey,
|
|
1785
|
+
l1_address: l1Address,
|
|
1786
|
+
direct_address: directAddress
|
|
1556
1787
|
});
|
|
1557
1788
|
const event = await this.createEvent(EVENT_KINDS.NAMETAG_BINDING, content, [
|
|
1558
1789
|
["d", hashedNametag],
|
|
1559
1790
|
["nametag", hashedNametag],
|
|
1560
1791
|
["t", hashedNametag],
|
|
1561
|
-
["address", nostrPubkey]
|
|
1792
|
+
["address", nostrPubkey],
|
|
1793
|
+
// Extended tags for indexing
|
|
1794
|
+
["pubkey", compressedPubkey],
|
|
1795
|
+
["l1", l1Address]
|
|
1562
1796
|
]);
|
|
1563
1797
|
await this.publishEvent(event);
|
|
1564
|
-
this.log("Registered nametag:", nametag, "for pubkey:", nostrPubkey.slice(0, 16) + "...");
|
|
1798
|
+
this.log("Registered nametag:", nametag, "for pubkey:", nostrPubkey.slice(0, 16) + "...", "l1:", l1Address.slice(0, 12) + "...");
|
|
1565
1799
|
return true;
|
|
1566
1800
|
}
|
|
1801
|
+
// Track broadcast subscriptions
|
|
1802
|
+
broadcastSubscriptions = /* @__PURE__ */ new Map();
|
|
1803
|
+
// key -> subId
|
|
1567
1804
|
subscribeToBroadcast(tags, handler) {
|
|
1568
1805
|
const key = tags.sort().join(":");
|
|
1569
1806
|
if (!this.broadcastHandlers.has(key)) {
|
|
1570
1807
|
this.broadcastHandlers.set(key, /* @__PURE__ */ new Set());
|
|
1571
|
-
if (this.isConnected()) {
|
|
1808
|
+
if (this.isConnected() && this.nostrClient) {
|
|
1572
1809
|
this.subscribeToTags(tags);
|
|
1573
1810
|
}
|
|
1574
1811
|
}
|
|
@@ -1577,6 +1814,11 @@ var NostrTransportProvider = class {
|
|
|
1577
1814
|
this.broadcastHandlers.get(key)?.delete(handler);
|
|
1578
1815
|
if (this.broadcastHandlers.get(key)?.size === 0) {
|
|
1579
1816
|
this.broadcastHandlers.delete(key);
|
|
1817
|
+
const subId = this.broadcastSubscriptions.get(key);
|
|
1818
|
+
if (subId && this.nostrClient) {
|
|
1819
|
+
this.nostrClient.unsubscribe(subId);
|
|
1820
|
+
this.broadcastSubscriptions.delete(key);
|
|
1821
|
+
}
|
|
1580
1822
|
}
|
|
1581
1823
|
};
|
|
1582
1824
|
}
|
|
@@ -1595,81 +1837,19 @@ var NostrTransportProvider = class {
|
|
|
1595
1837
|
return () => this.eventCallbacks.delete(callback);
|
|
1596
1838
|
}
|
|
1597
1839
|
// ===========================================================================
|
|
1598
|
-
// Private: Connection Management
|
|
1599
|
-
// ===========================================================================
|
|
1600
|
-
async connectToRelay(url) {
|
|
1601
|
-
return new Promise((resolve, reject) => {
|
|
1602
|
-
const ws = this.config.createWebSocket(url);
|
|
1603
|
-
const timeout = setTimeout(() => {
|
|
1604
|
-
ws.close();
|
|
1605
|
-
reject(new Error(`Connection timeout: ${url}`));
|
|
1606
|
-
}, this.config.timeout);
|
|
1607
|
-
ws.onopen = () => {
|
|
1608
|
-
clearTimeout(timeout);
|
|
1609
|
-
this.connections.set(url, ws);
|
|
1610
|
-
this.reconnectAttempts.set(url, 0);
|
|
1611
|
-
this.log("Connected to relay:", url);
|
|
1612
|
-
resolve();
|
|
1613
|
-
};
|
|
1614
|
-
ws.onerror = (error) => {
|
|
1615
|
-
clearTimeout(timeout);
|
|
1616
|
-
this.log("Relay error:", url, error);
|
|
1617
|
-
reject(error);
|
|
1618
|
-
};
|
|
1619
|
-
ws.onclose = () => {
|
|
1620
|
-
this.connections.delete(url);
|
|
1621
|
-
if (this.config.autoReconnect && this.status === "connected") {
|
|
1622
|
-
this.scheduleReconnect(url);
|
|
1623
|
-
}
|
|
1624
|
-
};
|
|
1625
|
-
ws.onmessage = (event) => {
|
|
1626
|
-
this.handleRelayMessage(url, event.data);
|
|
1627
|
-
};
|
|
1628
|
-
});
|
|
1629
|
-
}
|
|
1630
|
-
scheduleReconnect(url) {
|
|
1631
|
-
const attempts = this.reconnectAttempts.get(url) ?? 0;
|
|
1632
|
-
if (attempts >= this.config.maxReconnectAttempts) {
|
|
1633
|
-
this.log("Max reconnect attempts reached for:", url);
|
|
1634
|
-
return;
|
|
1635
|
-
}
|
|
1636
|
-
this.reconnectAttempts.set(url, attempts + 1);
|
|
1637
|
-
const delay = this.config.reconnectDelay * Math.pow(2, attempts);
|
|
1638
|
-
this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
|
|
1639
|
-
setTimeout(() => {
|
|
1640
|
-
this.connectToRelay(url).catch(() => {
|
|
1641
|
-
});
|
|
1642
|
-
}, delay);
|
|
1643
|
-
}
|
|
1644
|
-
// ===========================================================================
|
|
1645
1840
|
// Private: Message Handling
|
|
1646
1841
|
// ===========================================================================
|
|
1647
|
-
handleRelayMessage(relay, data) {
|
|
1648
|
-
try {
|
|
1649
|
-
const message = JSON.parse(data);
|
|
1650
|
-
const [type, ...args] = message;
|
|
1651
|
-
switch (type) {
|
|
1652
|
-
case "EVENT":
|
|
1653
|
-
this.handleEvent(args[1]);
|
|
1654
|
-
break;
|
|
1655
|
-
case "EOSE":
|
|
1656
|
-
break;
|
|
1657
|
-
case "OK":
|
|
1658
|
-
break;
|
|
1659
|
-
case "NOTICE":
|
|
1660
|
-
this.log("Relay notice:", relay, args[0]);
|
|
1661
|
-
break;
|
|
1662
|
-
}
|
|
1663
|
-
} catch (error) {
|
|
1664
|
-
this.log("Failed to parse relay message:", error);
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
1842
|
async handleEvent(event) {
|
|
1843
|
+
this.log("Processing event kind:", event.kind, "id:", event.id?.slice(0, 12));
|
|
1668
1844
|
try {
|
|
1669
1845
|
switch (event.kind) {
|
|
1670
1846
|
case EVENT_KINDS.DIRECT_MESSAGE:
|
|
1671
1847
|
await this.handleDirectMessage(event);
|
|
1672
1848
|
break;
|
|
1849
|
+
case import_nostr_js_sdk.EventKinds.GIFT_WRAP:
|
|
1850
|
+
this.log("Handling gift wrap (NIP-17 DM)");
|
|
1851
|
+
await this.handleGiftWrap(event);
|
|
1852
|
+
break;
|
|
1673
1853
|
case EVENT_KINDS.TOKEN_TRANSFER:
|
|
1674
1854
|
await this.handleTokenTransfer(event);
|
|
1675
1855
|
break;
|
|
@@ -1688,23 +1868,54 @@ var NostrTransportProvider = class {
|
|
|
1688
1868
|
}
|
|
1689
1869
|
}
|
|
1690
1870
|
async handleDirectMessage(event) {
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1871
|
+
this.log("Ignoring NIP-04 kind 4 event (DMs use NIP-17):", event.id?.slice(0, 12));
|
|
1872
|
+
}
|
|
1873
|
+
async handleGiftWrap(event) {
|
|
1874
|
+
if (!this.identity || !this.keyManager) {
|
|
1875
|
+
this.log("handleGiftWrap: no identity/keyManager");
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
try {
|
|
1879
|
+
const pm = import_nostr_js_sdk.NIP17.unwrap(event, this.keyManager);
|
|
1880
|
+
this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
|
|
1881
|
+
if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
|
|
1882
|
+
this.log("Skipping own message");
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
if (pm.kind !== import_nostr_js_sdk.EventKinds.CHAT_MESSAGE) {
|
|
1886
|
+
this.log("Skipping non-chat message, kind:", pm.kind);
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
let content = pm.content;
|
|
1890
|
+
let senderNametag;
|
|
1703
1891
|
try {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1892
|
+
const parsed = JSON.parse(content);
|
|
1893
|
+
if (typeof parsed === "object" && parsed.text !== void 0) {
|
|
1894
|
+
content = parsed.text;
|
|
1895
|
+
senderNametag = parsed.senderNametag || void 0;
|
|
1896
|
+
}
|
|
1897
|
+
} catch {
|
|
1898
|
+
}
|
|
1899
|
+
this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
|
|
1900
|
+
const message = {
|
|
1901
|
+
id: pm.eventId,
|
|
1902
|
+
senderTransportPubkey: pm.senderPubkey,
|
|
1903
|
+
senderNametag,
|
|
1904
|
+
content,
|
|
1905
|
+
timestamp: pm.timestamp * 1e3,
|
|
1906
|
+
encrypted: true
|
|
1907
|
+
};
|
|
1908
|
+
this.emitEvent({ type: "message:received", timestamp: Date.now() });
|
|
1909
|
+
this.log("Dispatching to", this.messageHandlers.size, "handlers");
|
|
1910
|
+
for (const handler of this.messageHandlers) {
|
|
1911
|
+
try {
|
|
1912
|
+
handler(message);
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
this.log("Message handler error:", error);
|
|
1915
|
+
}
|
|
1707
1916
|
}
|
|
1917
|
+
} catch (err) {
|
|
1918
|
+
this.log("Gift wrap decrypt failed (expected if not for us):", err?.message?.slice(0, 50));
|
|
1708
1919
|
}
|
|
1709
1920
|
}
|
|
1710
1921
|
async handleTokenTransfer(event) {
|
|
@@ -1713,7 +1924,7 @@ var NostrTransportProvider = class {
|
|
|
1713
1924
|
const payload = JSON.parse(content);
|
|
1714
1925
|
const transfer = {
|
|
1715
1926
|
id: event.id,
|
|
1716
|
-
|
|
1927
|
+
senderTransportPubkey: event.pubkey,
|
|
1717
1928
|
payload,
|
|
1718
1929
|
timestamp: event.created_at * 1e3
|
|
1719
1930
|
};
|
|
@@ -1733,7 +1944,7 @@ var NostrTransportProvider = class {
|
|
|
1733
1944
|
const requestData = JSON.parse(content);
|
|
1734
1945
|
const request = {
|
|
1735
1946
|
id: event.id,
|
|
1736
|
-
|
|
1947
|
+
senderTransportPubkey: event.pubkey,
|
|
1737
1948
|
request: {
|
|
1738
1949
|
requestId: requestData.requestId,
|
|
1739
1950
|
amount: requestData.amount,
|
|
@@ -1763,7 +1974,7 @@ var NostrTransportProvider = class {
|
|
|
1763
1974
|
const responseData = JSON.parse(content);
|
|
1764
1975
|
const response = {
|
|
1765
1976
|
id: event.id,
|
|
1766
|
-
|
|
1977
|
+
responderTransportPubkey: event.pubkey,
|
|
1767
1978
|
response: {
|
|
1768
1979
|
requestId: responseData.requestId,
|
|
1769
1980
|
responseType: responseData.responseType,
|
|
@@ -1788,7 +1999,7 @@ var NostrTransportProvider = class {
|
|
|
1788
1999
|
const tags = event.tags.filter((t) => t[0] === "t").map((t) => t[1]);
|
|
1789
2000
|
const broadcast = {
|
|
1790
2001
|
id: event.id,
|
|
1791
|
-
|
|
2002
|
+
authorTransportPubkey: event.pubkey,
|
|
1792
2003
|
content: event.content,
|
|
1793
2004
|
tags,
|
|
1794
2005
|
timestamp: event.created_at * 1e3
|
|
@@ -1843,109 +2054,149 @@ var NostrTransportProvider = class {
|
|
|
1843
2054
|
return this.createEvent(kind, encrypted, tags);
|
|
1844
2055
|
}
|
|
1845
2056
|
async publishEvent(event) {
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
return;
|
|
1852
|
-
}
|
|
1853
|
-
ws.send(message);
|
|
1854
|
-
resolve();
|
|
1855
|
-
});
|
|
1856
|
-
});
|
|
1857
|
-
await Promise.any(publishPromises);
|
|
2057
|
+
if (!this.nostrClient) {
|
|
2058
|
+
throw new Error("NostrClient not initialized");
|
|
2059
|
+
}
|
|
2060
|
+
const sdkEvent = import_nostr_js_sdk.Event.fromJSON(event);
|
|
2061
|
+
await this.nostrClient.publishEvent(sdkEvent);
|
|
1858
2062
|
}
|
|
1859
|
-
async queryEvents(
|
|
1860
|
-
if (this.
|
|
2063
|
+
async queryEvents(filterObj) {
|
|
2064
|
+
if (!this.nostrClient || !this.nostrClient.isConnected()) {
|
|
1861
2065
|
throw new Error("No connected relays");
|
|
1862
2066
|
}
|
|
1863
|
-
const queryPromises = Array.from(this.connections.values()).map(
|
|
1864
|
-
(ws) => this.queryEventsFromRelay(ws, filter)
|
|
1865
|
-
);
|
|
1866
|
-
const results = await Promise.allSettled(queryPromises);
|
|
1867
|
-
for (const result of results) {
|
|
1868
|
-
if (result.status === "fulfilled" && result.value.length > 0) {
|
|
1869
|
-
return result.value;
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
return [];
|
|
1873
|
-
}
|
|
1874
|
-
async queryEventsFromRelay(ws, filter) {
|
|
1875
|
-
const subId = this.config.generateUUID().slice(0, 8);
|
|
1876
2067
|
const events = [];
|
|
2068
|
+
const filter = new import_nostr_js_sdk.Filter(filterObj);
|
|
1877
2069
|
return new Promise((resolve) => {
|
|
1878
2070
|
const timeout = setTimeout(() => {
|
|
1879
|
-
|
|
2071
|
+
if (subId) {
|
|
2072
|
+
this.nostrClient?.unsubscribe(subId);
|
|
2073
|
+
}
|
|
1880
2074
|
resolve(events);
|
|
1881
2075
|
}, 5e3);
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2076
|
+
const subId = this.nostrClient.subscribe(filter, {
|
|
2077
|
+
onEvent: (event) => {
|
|
2078
|
+
events.push({
|
|
2079
|
+
id: event.id,
|
|
2080
|
+
kind: event.kind,
|
|
2081
|
+
content: event.content,
|
|
2082
|
+
tags: event.tags,
|
|
2083
|
+
pubkey: event.pubkey,
|
|
2084
|
+
created_at: event.created_at,
|
|
2085
|
+
sig: event.sig
|
|
2086
|
+
});
|
|
2087
|
+
},
|
|
2088
|
+
onEndOfStoredEvents: () => {
|
|
1893
2089
|
clearTimeout(timeout);
|
|
1894
|
-
|
|
1895
|
-
this.unsubscribeFromRelay(ws, subId);
|
|
2090
|
+
this.nostrClient?.unsubscribe(subId);
|
|
1896
2091
|
resolve(events);
|
|
1897
2092
|
}
|
|
1898
|
-
};
|
|
1899
|
-
ws.send(JSON.stringify(["REQ", subId, filter]));
|
|
2093
|
+
});
|
|
1900
2094
|
});
|
|
1901
2095
|
}
|
|
1902
|
-
unsubscribeFromRelay(ws, subId) {
|
|
1903
|
-
if (ws.readyState === WebSocketReadyState.OPEN) {
|
|
1904
|
-
ws.send(JSON.stringify(["CLOSE", subId]));
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
2096
|
// ===========================================================================
|
|
1908
2097
|
// Private: Subscriptions
|
|
1909
2098
|
// ===========================================================================
|
|
2099
|
+
// Track subscription IDs for cleanup
|
|
2100
|
+
walletSubscriptionId = null;
|
|
2101
|
+
chatSubscriptionId = null;
|
|
1910
2102
|
subscribeToEvents() {
|
|
1911
|
-
|
|
1912
|
-
|
|
2103
|
+
this.log("subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
|
|
2104
|
+
if (!this.identity || !this.keyManager || !this.nostrClient) {
|
|
2105
|
+
this.log("subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
if (this.walletSubscriptionId) {
|
|
2109
|
+
this.nostrClient.unsubscribe(this.walletSubscriptionId);
|
|
2110
|
+
this.walletSubscriptionId = null;
|
|
2111
|
+
}
|
|
2112
|
+
if (this.chatSubscriptionId) {
|
|
2113
|
+
this.nostrClient.unsubscribe(this.chatSubscriptionId);
|
|
2114
|
+
this.chatSubscriptionId = null;
|
|
2115
|
+
}
|
|
2116
|
+
if (this.mainSubscriptionId) {
|
|
2117
|
+
this.nostrClient.unsubscribe(this.mainSubscriptionId);
|
|
2118
|
+
this.mainSubscriptionId = null;
|
|
2119
|
+
}
|
|
1913
2120
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2121
|
+
this.log("Subscribing with Nostr pubkey:", nostrPubkey);
|
|
2122
|
+
const walletFilter = new import_nostr_js_sdk.Filter();
|
|
2123
|
+
walletFilter.kinds = [
|
|
2124
|
+
EVENT_KINDS.DIRECT_MESSAGE,
|
|
2125
|
+
EVENT_KINDS.TOKEN_TRANSFER,
|
|
2126
|
+
EVENT_KINDS.PAYMENT_REQUEST,
|
|
2127
|
+
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
|
|
2128
|
+
];
|
|
2129
|
+
walletFilter["#p"] = [nostrPubkey];
|
|
2130
|
+
walletFilter.since = Math.floor(Date.now() / 1e3) - 86400;
|
|
2131
|
+
this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
|
|
2132
|
+
onEvent: (event) => {
|
|
2133
|
+
this.log("Received wallet event kind:", event.kind, "id:", event.id?.slice(0, 12));
|
|
2134
|
+
this.handleEvent({
|
|
2135
|
+
id: event.id,
|
|
2136
|
+
kind: event.kind,
|
|
2137
|
+
content: event.content,
|
|
2138
|
+
tags: event.tags,
|
|
2139
|
+
pubkey: event.pubkey,
|
|
2140
|
+
created_at: event.created_at,
|
|
2141
|
+
sig: event.sig
|
|
2142
|
+
});
|
|
2143
|
+
},
|
|
2144
|
+
onEndOfStoredEvents: () => {
|
|
2145
|
+
this.log("Wallet subscription ready (EOSE)");
|
|
2146
|
+
},
|
|
2147
|
+
onError: (_subId, error) => {
|
|
2148
|
+
this.log("Wallet subscription error:", error);
|
|
1929
2149
|
}
|
|
1930
|
-
}
|
|
1931
|
-
this.
|
|
1932
|
-
|
|
2150
|
+
});
|
|
2151
|
+
this.log("Wallet subscription created, subId:", this.walletSubscriptionId);
|
|
2152
|
+
const chatFilter = new import_nostr_js_sdk.Filter();
|
|
2153
|
+
chatFilter.kinds = [import_nostr_js_sdk.EventKinds.GIFT_WRAP];
|
|
2154
|
+
chatFilter["#p"] = [nostrPubkey];
|
|
2155
|
+
this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
|
|
2156
|
+
onEvent: (event) => {
|
|
2157
|
+
this.log("Received chat event kind:", event.kind, "id:", event.id?.slice(0, 12));
|
|
2158
|
+
this.handleEvent({
|
|
2159
|
+
id: event.id,
|
|
2160
|
+
kind: event.kind,
|
|
2161
|
+
content: event.content,
|
|
2162
|
+
tags: event.tags,
|
|
2163
|
+
pubkey: event.pubkey,
|
|
2164
|
+
created_at: event.created_at,
|
|
2165
|
+
sig: event.sig
|
|
2166
|
+
});
|
|
2167
|
+
},
|
|
2168
|
+
onEndOfStoredEvents: () => {
|
|
2169
|
+
this.log("Chat subscription ready (EOSE)");
|
|
2170
|
+
},
|
|
2171
|
+
onError: (_subId, error) => {
|
|
2172
|
+
this.log("Chat subscription error:", error);
|
|
2173
|
+
}
|
|
2174
|
+
});
|
|
2175
|
+
this.log("Chat subscription created, subId:", this.chatSubscriptionId);
|
|
1933
2176
|
}
|
|
1934
2177
|
subscribeToTags(tags) {
|
|
1935
|
-
|
|
1936
|
-
const
|
|
2178
|
+
if (!this.nostrClient) return;
|
|
2179
|
+
const key = tags.sort().join(":");
|
|
2180
|
+
const filter = new import_nostr_js_sdk.Filter({
|
|
1937
2181
|
kinds: [EVENT_KINDS.BROADCAST],
|
|
1938
2182
|
"#t": tags,
|
|
1939
2183
|
since: Math.floor(Date.now() / 1e3) - 3600
|
|
1940
2184
|
// Last hour
|
|
1941
|
-
};
|
|
1942
|
-
const
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2185
|
+
});
|
|
2186
|
+
const subId = this.nostrClient.subscribe(filter, {
|
|
2187
|
+
onEvent: (event) => {
|
|
2188
|
+
this.handleBroadcast({
|
|
2189
|
+
id: event.id,
|
|
2190
|
+
kind: event.kind,
|
|
2191
|
+
content: event.content,
|
|
2192
|
+
tags: event.tags,
|
|
2193
|
+
pubkey: event.pubkey,
|
|
2194
|
+
created_at: event.created_at,
|
|
2195
|
+
sig: event.sig
|
|
2196
|
+
});
|
|
1946
2197
|
}
|
|
1947
|
-
}
|
|
1948
|
-
this.
|
|
2198
|
+
});
|
|
2199
|
+
this.broadcastSubscriptions.set(key, subId);
|
|
1949
2200
|
}
|
|
1950
2201
|
// ===========================================================================
|
|
1951
2202
|
// Private: Encryption
|
|
@@ -2401,13 +2652,70 @@ var UnicityAggregatorProvider = class {
|
|
|
2401
2652
|
};
|
|
2402
2653
|
var UnicityOracleProvider = UnicityAggregatorProvider;
|
|
2403
2654
|
|
|
2655
|
+
// assets/trustbase.ts
|
|
2656
|
+
var TRUSTBASE_TESTNET = {
|
|
2657
|
+
version: 1,
|
|
2658
|
+
networkId: 3,
|
|
2659
|
+
epoch: 1,
|
|
2660
|
+
epochStartRound: 1,
|
|
2661
|
+
rootNodes: [
|
|
2662
|
+
{
|
|
2663
|
+
nodeId: "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU",
|
|
2664
|
+
sigKey: "0x039afb2acb65f5fbc272d8907f763d0a5d189aadc9b97afdcc5897ea4dd112e68b",
|
|
2665
|
+
stake: 1
|
|
2666
|
+
}
|
|
2667
|
+
],
|
|
2668
|
+
quorumThreshold: 1,
|
|
2669
|
+
stateHash: "",
|
|
2670
|
+
changeRecordHash: "",
|
|
2671
|
+
previousEntryHash: "",
|
|
2672
|
+
signatures: {
|
|
2673
|
+
"16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU": "0xf157c9fdd8a378e3ca70d354ccc4475ab2cd8de360127bc46b0aeab4b453a80f07fd9136a5843b60a8babaff23e20acc8879861f7651440a5e2829f7541b31f100"
|
|
2674
|
+
}
|
|
2675
|
+
};
|
|
2676
|
+
var TRUSTBASE_MAINNET = null;
|
|
2677
|
+
var TRUSTBASE_DEV = TRUSTBASE_TESTNET;
|
|
2678
|
+
|
|
2679
|
+
// impl/shared/trustbase-loader.ts
|
|
2680
|
+
function getEmbeddedTrustBase(network) {
|
|
2681
|
+
switch (network) {
|
|
2682
|
+
case "mainnet":
|
|
2683
|
+
return TRUSTBASE_MAINNET;
|
|
2684
|
+
case "testnet":
|
|
2685
|
+
return TRUSTBASE_TESTNET;
|
|
2686
|
+
case "dev":
|
|
2687
|
+
return TRUSTBASE_DEV;
|
|
2688
|
+
default:
|
|
2689
|
+
return TRUSTBASE_TESTNET;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
var BaseTrustBaseLoader = class {
|
|
2693
|
+
network;
|
|
2694
|
+
constructor(network = "testnet") {
|
|
2695
|
+
this.network = network;
|
|
2696
|
+
}
|
|
2697
|
+
async load() {
|
|
2698
|
+
const external = await this.loadFromExternal();
|
|
2699
|
+
if (external) {
|
|
2700
|
+
return external;
|
|
2701
|
+
}
|
|
2702
|
+
return getEmbeddedTrustBase(this.network);
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
|
|
2404
2706
|
// impl/browser/oracle/index.ts
|
|
2405
|
-
var BrowserTrustBaseLoader = class {
|
|
2707
|
+
var BrowserTrustBaseLoader = class extends BaseTrustBaseLoader {
|
|
2406
2708
|
url;
|
|
2407
|
-
constructor(
|
|
2408
|
-
|
|
2709
|
+
constructor(networkOrUrl = "testnet") {
|
|
2710
|
+
if (networkOrUrl.startsWith("/") || networkOrUrl.startsWith("http")) {
|
|
2711
|
+
super("testnet");
|
|
2712
|
+
this.url = networkOrUrl;
|
|
2713
|
+
} else {
|
|
2714
|
+
super(networkOrUrl);
|
|
2715
|
+
}
|
|
2409
2716
|
}
|
|
2410
|
-
async
|
|
2717
|
+
async loadFromExternal() {
|
|
2718
|
+
if (!this.url) return null;
|
|
2411
2719
|
try {
|
|
2412
2720
|
const response = await fetch(this.url);
|
|
2413
2721
|
if (response.ok) {
|
|
@@ -2418,14 +2726,14 @@ var BrowserTrustBaseLoader = class {
|
|
|
2418
2726
|
return null;
|
|
2419
2727
|
}
|
|
2420
2728
|
};
|
|
2421
|
-
function createBrowserTrustBaseLoader(
|
|
2422
|
-
return new BrowserTrustBaseLoader(
|
|
2729
|
+
function createBrowserTrustBaseLoader(networkOrUrl) {
|
|
2730
|
+
return new BrowserTrustBaseLoader(networkOrUrl);
|
|
2423
2731
|
}
|
|
2424
2732
|
function createUnicityAggregatorProvider(config) {
|
|
2425
|
-
const { trustBaseUrl, ...restConfig } = config;
|
|
2733
|
+
const { trustBaseUrl, network, ...restConfig } = config;
|
|
2426
2734
|
return new UnicityAggregatorProvider({
|
|
2427
2735
|
...restConfig,
|
|
2428
|
-
trustBaseLoader: createBrowserTrustBaseLoader(trustBaseUrl)
|
|
2736
|
+
trustBaseLoader: createBrowserTrustBaseLoader(trustBaseUrl ?? network ?? "testnet")
|
|
2429
2737
|
});
|
|
2430
2738
|
}
|
|
2431
2739
|
var createUnicityOracleProvider = createUnicityAggregatorProvider;
|
|
@@ -2456,18 +2764,18 @@ function downloadWalletText(sphere, options = {}) {
|
|
|
2456
2764
|
downloadTextFile(content, filename);
|
|
2457
2765
|
}
|
|
2458
2766
|
function downloadWalletJSON(sphere, options = {}) {
|
|
2459
|
-
const
|
|
2767
|
+
const json = sphere.exportToJSON({
|
|
2460
2768
|
password: options.password,
|
|
2461
2769
|
addressCount: options.addressCount,
|
|
2462
2770
|
includeMnemonic: options.includeMnemonic
|
|
2463
2771
|
});
|
|
2464
2772
|
const filename = options.filename ? `${options.filename}.json` : `sphere-wallet-${Date.now()}.json`;
|
|
2465
|
-
const jsonString = options.pretty !== false ? JSON.stringify(
|
|
2773
|
+
const jsonString = options.pretty !== false ? JSON.stringify(json, null, 2) : JSON.stringify(json);
|
|
2466
2774
|
downloadFile(jsonString, filename, "application/json");
|
|
2467
2775
|
}
|
|
2468
|
-
function downloadWalletJSONData(
|
|
2776
|
+
function downloadWalletJSONData(json, filename) {
|
|
2469
2777
|
const name = filename || `sphere-wallet-${Date.now()}.json`;
|
|
2470
|
-
downloadJSONFile(
|
|
2778
|
+
downloadJSONFile(json, name.endsWith(".json") ? name : `${name}.json`);
|
|
2471
2779
|
}
|
|
2472
2780
|
function readFileAsText(file) {
|
|
2473
2781
|
return new Promise((resolve, reject) => {
|
|
@@ -2617,11 +2925,17 @@ function createBrowserProviders(config) {
|
|
|
2617
2925
|
apiKey: oracleConfig.apiKey,
|
|
2618
2926
|
timeout: oracleConfig.timeout,
|
|
2619
2927
|
skipVerification: oracleConfig.skipVerification,
|
|
2620
|
-
debug: oracleConfig.debug
|
|
2928
|
+
debug: oracleConfig.debug,
|
|
2929
|
+
network
|
|
2621
2930
|
}),
|
|
2622
2931
|
tokenStorage: createIndexedDBTokenStorageProvider(),
|
|
2623
2932
|
l1: l1Config,
|
|
2624
2933
|
tokenSyncConfig
|
|
2625
2934
|
};
|
|
2626
2935
|
}
|
|
2936
|
+
/*! Bundled license information:
|
|
2937
|
+
|
|
2938
|
+
@noble/hashes/utils.js:
|
|
2939
|
+
(*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
2940
|
+
*/
|
|
2627
2941
|
//# sourceMappingURL=index.cjs.map
|