shogun-core 3.2.2 → 3.3.0

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.
Files changed (32) hide show
  1. package/README.md +12 -0
  2. package/dist/browser/shogun-core.js +108133 -43791
  3. package/dist/browser/shogun-core.js.map +1 -1
  4. package/dist/ship/examples/messenger-cli.js +173 -60
  5. package/dist/ship/examples/wallet-cli.js +767 -0
  6. package/dist/ship/implementation/SHIP_00.js +478 -0
  7. package/dist/ship/implementation/SHIP_01.js +300 -695
  8. package/dist/ship/implementation/SHIP_02.js +1366 -0
  9. package/dist/ship/implementation/SHIP_03.js +855 -0
  10. package/dist/ship/interfaces/ISHIP_00.js +135 -0
  11. package/dist/ship/interfaces/ISHIP_01.js +81 -24
  12. package/dist/ship/interfaces/ISHIP_02.js +57 -0
  13. package/dist/ship/interfaces/ISHIP_03.js +61 -0
  14. package/dist/src/gundb/db.js +55 -11
  15. package/dist/src/index.js +10 -2
  16. package/dist/src/managers/CoreInitializer.js +41 -13
  17. package/dist/src/storage/storage.js +22 -9
  18. package/dist/types/ship/examples/messenger-cli.d.ts +7 -1
  19. package/dist/types/ship/examples/wallet-cli.d.ts +131 -0
  20. package/dist/types/ship/implementation/SHIP_00.d.ts +113 -0
  21. package/dist/types/ship/implementation/SHIP_01.d.ts +47 -76
  22. package/dist/types/ship/implementation/SHIP_02.d.ts +297 -0
  23. package/dist/types/ship/implementation/SHIP_03.d.ts +127 -0
  24. package/dist/types/ship/interfaces/ISHIP_00.d.ts +410 -0
  25. package/dist/types/ship/interfaces/ISHIP_01.d.ts +157 -119
  26. package/dist/types/ship/interfaces/ISHIP_02.d.ts +470 -0
  27. package/dist/types/ship/interfaces/ISHIP_03.d.ts +295 -0
  28. package/dist/types/src/gundb/db.d.ts +10 -3
  29. package/dist/types/src/index.d.ts +7 -0
  30. package/dist/types/src/interfaces/shogun.d.ts +2 -0
  31. package/dist/types/src/storage/storage.d.ts +2 -1
  32. package/package.json +23 -9
@@ -0,0 +1,1366 @@
1
+ "use strict";
2
+ /**
3
+ * SHIP-02: Ethereum HD Wallet Implementation
4
+ *
5
+ * Full port of shogun-BIP44 with SHIP-00 derivation.
6
+ * Extends SHIP-00 to provide deterministic Ethereum address derivation.
7
+ *
8
+ * Based on:
9
+ * - SHIP-00 for identity foundation (replaces mnemonic dependency)
10
+ * - BIP-32 for hierarchical deterministic wallets
11
+ * - BIP-44 for multi-account hierarchy
12
+ * - Ethers.js for Ethereum operations
13
+ * - Gun/SEA for encrypted storage
14
+ *
15
+ * Features:
16
+ * ✅ Deterministic address derivation from SHIP-00 identity (no mnemonics needed)
17
+ * ✅ BIP-44 compliant HD wallet support
18
+ * ✅ Multiple address management
19
+ * ✅ Transaction signing
20
+ * ✅ Message signing and verification
21
+ * ✅ Gun persistence with encryption
22
+ * ✅ Export/import functionality
23
+ * ✅ Address book management
24
+ *
25
+ * Note: Stealth addresses moved to SHIP-03
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.SHIP_02 = void 0;
29
+ const ethers_1 = require("ethers");
30
+ // ============================================================================
31
+ // IMPLEMENTATION
32
+ // ============================================================================
33
+ /**
34
+ * SHIP-02 Reference Implementation
35
+ *
36
+ * Provides Ethereum address derivation on top of SHIP-00 identity.
37
+ * All addresses are deterministically derived from the user's SHIP-00 keypair.
38
+ */
39
+ class SHIP_02 {
40
+ constructor(identity, config = {}) {
41
+ this.initialized = false;
42
+ // Master seed derived from SHIP-00 identity OR from mnemonic
43
+ this.masterSeed = null;
44
+ // Optional BIP-39 mnemonic (for MetaMask compatibility)
45
+ this.mnemonic = null;
46
+ // HD Wallet for derivation
47
+ this.hdWallet = null;
48
+ // Cache of derived addresses
49
+ this.addressCache = new Map();
50
+ // Cache of wallets by address
51
+ this.walletCache = new Map();
52
+ // Address index counter
53
+ this.nextIndex = 0;
54
+ // Persistence flag
55
+ this.persistToGun = false;
56
+ // RPC Provider for network operations
57
+ this.provider = null;
58
+ // Main wallet (derived from Gun user keys, not BIP-44)
59
+ this.mainWallet = null;
60
+ // Wallet paths storage (for createWallet/loadWallets API)
61
+ this.walletPaths = {};
62
+ this.identity = identity;
63
+ this.config = {
64
+ defaultCoinType: config.defaultCoinType ?? 60, // Ethereum
65
+ defaultAccount: config.defaultAccount ?? 0,
66
+ enableStealth: config.enableStealth ?? true,
67
+ customPathPrefix: config.customPathPrefix,
68
+ };
69
+ }
70
+ // ========================================================================
71
+ // INITIALIZATION
72
+ // ========================================================================
73
+ async initialize(useMnemonic = false) {
74
+ if (this.initialized) {
75
+ return;
76
+ }
77
+ try {
78
+ // Get SHIP-00 keypair
79
+ const keyPair = this.identity.getKeyPair();
80
+ if (!keyPair || !keyPair.epriv || !keyPair.epub) {
81
+ throw new Error("SHIP-00 identity not authenticated");
82
+ }
83
+ if (useMnemonic) {
84
+ // Option 1: Use BIP-39 mnemonic (MetaMask compatible)
85
+ console.log("🔐 Initializing with BIP-39 mnemonic...");
86
+ // Try to load existing mnemonic
87
+ this.mnemonic = await this.loadMnemonicFromGun();
88
+ if (!this.mnemonic) {
89
+ // Generate new mnemonic
90
+ this.mnemonic = this.generateNewMnemonic();
91
+ await this.saveMnemonicToGun(this.mnemonic);
92
+ console.log("✅ New BIP-39 mnemonic generated and saved");
93
+ }
94
+ else {
95
+ console.log("✅ Existing mnemonic loaded");
96
+ }
97
+ // Create HD wallet from mnemonic
98
+ this.hdWallet = ethers_1.ethers.HDNodeWallet.fromPhrase(this.mnemonic);
99
+ console.log("✅ HD wallet initialized from mnemonic (MetaMask compatible)");
100
+ }
101
+ else {
102
+ // Option 2: Derive from SHIP-00 identity (default, no mnemonic needed)
103
+ console.log("🔐 Deriving wallet from SHIP-00 identity...");
104
+ // Derive master seed from SHIP-00 keypair
105
+ this.masterSeed = await this.deriveMasterSeed(keyPair);
106
+ // Create HD wallet from seed
107
+ this.hdWallet = ethers_1.ethers.HDNodeWallet.fromSeed(ethers_1.ethers.getBytes("0x" + this.masterSeed));
108
+ console.log("✅ HD wallet derived from SHIP-00 (no mnemonic needed)");
109
+ }
110
+ this.initialized = true;
111
+ }
112
+ catch (error) {
113
+ throw new Error(`SHIP-02 initialization failed: ${error.message}`);
114
+ }
115
+ }
116
+ isInitialized() {
117
+ return this.initialized;
118
+ }
119
+ // ========================================================================
120
+ // BASIC DERIVATION
121
+ // ========================================================================
122
+ async deriveEthereumAddress(path = "m/44'/60'/0'/0/0") {
123
+ try {
124
+ this.ensureInitialized();
125
+ // Verify HD wallet is available
126
+ if (!this.hdWallet) {
127
+ throw new Error("HD wallet not initialized. Call initialize() first.");
128
+ }
129
+ // Derive child wallet from HD node
130
+ const childWallet = this.hdWallet.derivePath(path);
131
+ const address = childWallet.address;
132
+ const publicKey = childWallet.publicKey;
133
+ // Cache the wallet and address
134
+ this.walletCache.set(address, childWallet);
135
+ const entry = {
136
+ address,
137
+ path,
138
+ publicKey,
139
+ index: this.nextIndex++,
140
+ createdAt: Date.now(),
141
+ };
142
+ this.addressCache.set(address, entry);
143
+ return {
144
+ success: true,
145
+ address,
146
+ path,
147
+ publicKey,
148
+ };
149
+ }
150
+ catch (error) {
151
+ return {
152
+ success: false,
153
+ error: error.message,
154
+ };
155
+ }
156
+ }
157
+ async deriveMultipleAddresses(count, startIndex = 0) {
158
+ const results = [];
159
+ for (let i = 0; i < count; i++) {
160
+ const index = startIndex + i;
161
+ const path = this.buildBIP44Path(this.config.defaultCoinType, this.config.defaultAccount, 0, index);
162
+ const result = await this.deriveEthereumAddress(path);
163
+ results.push(result);
164
+ }
165
+ return results;
166
+ }
167
+ async getPrimaryAddress() {
168
+ this.ensureInitialized();
169
+ // Verify HD wallet is available
170
+ if (!this.hdWallet) {
171
+ throw new Error("HD wallet not initialized. Please call initialize() before deriving addresses.");
172
+ }
173
+ // Check if primary address already exists in cache
174
+ const primary = Array.from(this.addressCache.values()).find((entry) => entry.index === 0);
175
+ if (primary) {
176
+ return primary.address;
177
+ }
178
+ // Derive primary address (index 0)
179
+ const result = await this.deriveEthereumAddress();
180
+ if (!result.success || !result.address) {
181
+ const errorDetail = result.error || "Unknown error";
182
+ throw new Error(`Failed to derive primary address: ${errorDetail}`);
183
+ }
184
+ return result.address;
185
+ }
186
+ // ========================================================================
187
+ // BIP-44 STANDARD DERIVATION
188
+ // ========================================================================
189
+ async deriveBIP44Address(coinType = 60, account = 0, change = 0, index = 0) {
190
+ const path = this.buildBIP44Path(coinType, account, change, index);
191
+ return this.deriveEthereumAddress(path);
192
+ }
193
+ async deriveMultipleAccounts(accountCount) {
194
+ const results = [];
195
+ for (let i = 0; i < accountCount; i++) {
196
+ const result = await this.deriveBIP44Address(this.config.defaultCoinType, i, 0, 0);
197
+ results.push(result);
198
+ }
199
+ return results;
200
+ }
201
+ // ========================================================================
202
+ // STEALTH ADDRESSES - DEPRECATED
203
+ // ========================================================================
204
+ // Note: Basic stealth functionality moved to SHIP-03
205
+ // These methods are kept for backward compatibility
206
+ /**
207
+ * @deprecated Use SHIP-03 for dual-key stealth addresses
208
+ */
209
+ async generateStealthAddress(recipientPublicKey) {
210
+ console.warn("⚠️ DEPRECATED: Use SHIP-03 for stealth addresses");
211
+ return {
212
+ success: false,
213
+ error: "Stealth addresses moved to SHIP-03. Please use SHIP-03 for dual-key stealth functionality.",
214
+ };
215
+ }
216
+ /**
217
+ * @deprecated Use SHIP-03 for stealth operations
218
+ */
219
+ async deriveSharedSecret(publicKey) {
220
+ console.warn("⚠️ DEPRECATED: Use SHIP-03 for stealth operations");
221
+ throw new Error("Use SHIP-03 for stealth operations");
222
+ }
223
+ /**
224
+ * @deprecated Use SHIP-03 for stealth operations
225
+ */
226
+ async isStealthAddress(address) {
227
+ console.warn("⚠️ DEPRECATED: Use SHIP-03 for stealth operations");
228
+ return false;
229
+ }
230
+ // ========================================================================
231
+ // KEY MANAGEMENT
232
+ // ========================================================================
233
+ async getPrivateKeyForAddress(address) {
234
+ this.ensureInitialized();
235
+ const wallet = this.walletCache.get(address);
236
+ if (!wallet) {
237
+ throw new Error(`No wallet found for address: ${address}`);
238
+ }
239
+ return wallet.privateKey;
240
+ }
241
+ async getPublicKeyForAddress(address) {
242
+ this.ensureInitialized();
243
+ const entry = this.addressCache.get(address);
244
+ if (!entry) {
245
+ throw new Error(`No address found: ${address}`);
246
+ }
247
+ return entry.publicKey;
248
+ }
249
+ async getPathForAddress(address) {
250
+ const entry = this.addressCache.get(address);
251
+ return entry?.path;
252
+ }
253
+ // ========================================================================
254
+ // TRANSACTION SIGNING
255
+ // ========================================================================
256
+ async signTransaction(tx, address) {
257
+ try {
258
+ this.ensureInitialized();
259
+ const wallet = this.walletCache.get(address);
260
+ if (!wallet) {
261
+ return {
262
+ success: false,
263
+ error: `No wallet found for address: ${address}`,
264
+ };
265
+ }
266
+ // Sign the transaction
267
+ const signedTx = await wallet.signTransaction(tx);
268
+ const parsedTx = ethers_1.ethers.Transaction.from(signedTx);
269
+ const result = {
270
+ raw: signedTx,
271
+ hash: parsedTx.hash,
272
+ from: wallet.address,
273
+ to: tx.to,
274
+ value: tx.value?.toString() || "0",
275
+ data: tx.data || "0x",
276
+ chainId: tx.chainId || 1,
277
+ nonce: tx.nonce || 0,
278
+ gasLimit: tx.gasLimit?.toString() || "21000",
279
+ signature: {
280
+ r: parsedTx.signature.r,
281
+ s: parsedTx.signature.s,
282
+ v: parsedTx.signature.v,
283
+ },
284
+ };
285
+ return {
286
+ success: true,
287
+ signature: parsedTx.signature.serialized,
288
+ signedTransaction: signedTx,
289
+ txHash: parsedTx.hash,
290
+ };
291
+ }
292
+ catch (error) {
293
+ return {
294
+ success: false,
295
+ error: error.message,
296
+ };
297
+ }
298
+ }
299
+ /**
300
+ * Send transaction to Ethereum network
301
+ * Combines signing + broadcasting in one step
302
+ */
303
+ async sendTransaction(tx, address, waitForConfirmation = false) {
304
+ try {
305
+ this.ensureInitialized();
306
+ // Verify RPC provider is configured
307
+ if (!this.provider) {
308
+ return {
309
+ success: false,
310
+ error: "RPC provider not configured. Call setRpcUrl() first.",
311
+ };
312
+ }
313
+ // Get wallet for address
314
+ const wallet = this.walletCache.get(address);
315
+ if (!wallet) {
316
+ return {
317
+ success: false,
318
+ error: `No wallet found for address: ${address}`,
319
+ };
320
+ }
321
+ console.log(`📤 Sending transaction from ${address}...`);
322
+ console.log(` To: ${tx.to}`);
323
+ console.log(` Value: ${tx.value ? ethers_1.ethers.formatEther(tx.value) : "0"} ETH`);
324
+ // Connect wallet to provider
325
+ const connectedWallet = wallet.connect(this.provider);
326
+ // Send transaction (ethers handles signing + broadcasting)
327
+ const txResponse = await connectedWallet.sendTransaction(tx);
328
+ console.log(`✅ Transaction sent: ${txResponse.hash}`);
329
+ console.log(`📍 View on Etherscan: https://etherscan.io/tx/${txResponse.hash}`);
330
+ // Wait for confirmation if requested
331
+ if (waitForConfirmation) {
332
+ console.log(`⏳ Waiting for confirmation...`);
333
+ const receipt = await txResponse.wait();
334
+ console.log(`✅ Transaction confirmed in block ${receipt.blockNumber}`);
335
+ console.log(` Gas used: ${receipt.gasUsed.toString()}`);
336
+ return {
337
+ success: true,
338
+ txHash: txResponse.hash,
339
+ receipt: receipt,
340
+ };
341
+ }
342
+ return {
343
+ success: true,
344
+ txHash: txResponse.hash,
345
+ };
346
+ }
347
+ catch (error) {
348
+ console.error("❌ Transaction failed:", error);
349
+ return {
350
+ success: false,
351
+ error: error.message,
352
+ };
353
+ }
354
+ }
355
+ async signMessage(message, address) {
356
+ this.ensureInitialized();
357
+ const wallet = this.walletCache.get(address);
358
+ if (!wallet) {
359
+ throw new Error(`No wallet found for address: ${address}`);
360
+ }
361
+ if (typeof message === "string") {
362
+ return wallet.signMessage(message);
363
+ }
364
+ else {
365
+ return wallet.signMessage(message);
366
+ }
367
+ }
368
+ async verifySignature(message, signature, address) {
369
+ try {
370
+ let recoveredAddress;
371
+ if (typeof message === "string") {
372
+ recoveredAddress = ethers_1.ethers.verifyMessage(message, signature);
373
+ }
374
+ else {
375
+ recoveredAddress = ethers_1.ethers.verifyMessage(message, signature);
376
+ }
377
+ return recoveredAddress.toLowerCase() === address.toLowerCase();
378
+ }
379
+ catch {
380
+ return false;
381
+ }
382
+ }
383
+ // ========================================================================
384
+ // ADDRESS MANAGEMENT
385
+ // ========================================================================
386
+ async getAllAddresses() {
387
+ return Array.from(this.addressCache.values()).sort((a, b) => a.index - b.index);
388
+ }
389
+ async getAddressByIndex(index) {
390
+ return Array.from(this.addressCache.values()).find((entry) => entry.index === index);
391
+ }
392
+ async setAddressLabel(address, label) {
393
+ const entry = this.addressCache.get(address);
394
+ if (entry) {
395
+ entry.label = label;
396
+ this.addressCache.set(address, entry);
397
+ }
398
+ }
399
+ async exportAddressBook() {
400
+ this.ensureInitialized();
401
+ const addressBook = {
402
+ addresses: await this.getAllAddresses(),
403
+ masterPublicKey: this.hdWallet.publicKey,
404
+ derivationMethod: "bip44",
405
+ };
406
+ // Optionally save to Gun for backup
407
+ if (this.persistToGun) {
408
+ await this.saveAddressBookToGun(addressBook);
409
+ }
410
+ return addressBook;
411
+ }
412
+ async importAddressBook(addressBook) {
413
+ this.ensureInitialized();
414
+ // Verify master public key matches
415
+ if (addressBook.masterPublicKey !== this.hdWallet.publicKey) {
416
+ throw new Error("Address book master public key mismatch");
417
+ }
418
+ // Re-derive all addresses from the book
419
+ for (const entry of addressBook.addresses) {
420
+ if (entry.path.startsWith("stealth/")) {
421
+ // Skip stealth addresses (moved to SHIP-03)
422
+ console.warn(`Skipping stealth address: ${entry.address}`);
423
+ continue;
424
+ }
425
+ await this.deriveEthereumAddress(entry.path);
426
+ if (entry.label) {
427
+ await this.setAddressLabel(entry.address, entry.label);
428
+ }
429
+ }
430
+ console.log(`✅ Imported ${addressBook.addresses.length} addresses`);
431
+ }
432
+ /**
433
+ * Enable persistence to Gun database
434
+ */
435
+ enableGunPersistence() {
436
+ this.persistToGun = true;
437
+ console.log("✅ Gun persistence enabled for SHIP-02");
438
+ }
439
+ /**
440
+ * Disable persistence to Gun database
441
+ */
442
+ disableGunPersistence() {
443
+ this.persistToGun = false;
444
+ console.log("✅ Gun persistence disabled for SHIP-02");
445
+ }
446
+ /**
447
+ * Save address book to Gun (private storage)
448
+ */
449
+ async saveAddressBookToGun(addressBook) {
450
+ try {
451
+ // Access Gun through identity
452
+ const shogun = this.identity.getShogun();
453
+ const gun = shogun?.db?.gun;
454
+ if (!gun) {
455
+ console.warn("Gun not available, skipping persistence");
456
+ return;
457
+ }
458
+ const user = gun.user();
459
+ if (!user || !user.is) {
460
+ console.warn("User not authenticated on Gun");
461
+ return;
462
+ }
463
+ // Save encrypted addressbook
464
+ await user.get(SHIP_02.NODES.ADDRESS_BOOK).put(JSON.stringify(addressBook));
465
+ console.log("✅ Address book saved to Gun");
466
+ }
467
+ catch (error) {
468
+ console.error("Error saving address book to Gun:", error);
469
+ }
470
+ }
471
+ /**
472
+ * Load address book from Gun
473
+ */
474
+ async loadAddressBookFromGun() {
475
+ try {
476
+ this.ensureInitialized();
477
+ const shogun = this.identity.getShogun();
478
+ const gun = shogun?.db?.gun;
479
+ if (!gun) {
480
+ console.warn("Gun not available");
481
+ return null;
482
+ }
483
+ const user = gun.user();
484
+ if (!user || !user.is) {
485
+ console.warn("User not authenticated on Gun");
486
+ return null;
487
+ }
488
+ // Load addressbook
489
+ const data = await new Promise((resolve) => {
490
+ let resolved = false;
491
+ const timeout = setTimeout(() => {
492
+ if (!resolved) {
493
+ resolved = true;
494
+ resolve(null);
495
+ }
496
+ }, 5000);
497
+ user.get(SHIP_02.NODES.ADDRESS_BOOK).once((data) => {
498
+ if (!resolved) {
499
+ resolved = true;
500
+ clearTimeout(timeout);
501
+ resolve(data || null);
502
+ }
503
+ });
504
+ });
505
+ if (!data) {
506
+ console.log("No address book found on Gun");
507
+ return null;
508
+ }
509
+ const addressBook = JSON.parse(data);
510
+ console.log("✅ Address book loaded from Gun");
511
+ return addressBook;
512
+ }
513
+ catch (error) {
514
+ console.error("Error loading address book from Gun:", error);
515
+ return null;
516
+ }
517
+ }
518
+ /**
519
+ * Sync local cache with Gun storage
520
+ */
521
+ async syncWithGun() {
522
+ this.ensureInitialized();
523
+ // Try to load from Gun first
524
+ const remoteBook = await this.loadAddressBookFromGun();
525
+ if (remoteBook) {
526
+ // Merge remote addresses with local
527
+ await this.importAddressBook(remoteBook);
528
+ }
529
+ // Save current state back to Gun
530
+ if (this.persistToGun) {
531
+ await this.exportAddressBook();
532
+ }
533
+ console.log("✅ Synced with Gun");
534
+ }
535
+ // ========================================================================
536
+ // UTILITIES
537
+ // ========================================================================
538
+ async ownsAddress(address) {
539
+ return this.addressCache.has(address);
540
+ }
541
+ async getMasterPublicKey() {
542
+ this.ensureInitialized();
543
+ return this.hdWallet.publicKey;
544
+ }
545
+ async clearCache() {
546
+ this.addressCache.clear();
547
+ this.walletCache.clear();
548
+ this.walletPaths = {};
549
+ this.nextIndex = 0;
550
+ this.initialized = false;
551
+ this.masterSeed = null;
552
+ this.hdWallet = null;
553
+ this.mainWallet = null;
554
+ this.provider = null;
555
+ console.log("✅ SHIP-02 cache cleared");
556
+ }
557
+ // ========================================================================
558
+ // ADVANCED FEATURES (from shogun-BIP44)
559
+ // ========================================================================
560
+ // ========================================================================
561
+ // MNEMONIC MANAGEMENT (BIP-39)
562
+ // ========================================================================
563
+ /**
564
+ * Generate new BIP-39 mnemonic (12 words)
565
+ * Compatible with MetaMask and other wallets
566
+ */
567
+ generateNewMnemonic() {
568
+ const wallet = ethers_1.ethers.Wallet.createRandom();
569
+ return wallet.mnemonic?.phrase || "";
570
+ }
571
+ /**
572
+ * Get addresses that would be derived from a mnemonic (standard BIP-44)
573
+ * Useful to verify compatibility with MetaMask
574
+ */
575
+ getStandardBIP44Addresses(mnemonic, count = 5) {
576
+ const addresses = [];
577
+ for (let i = 0; i < count; i++) {
578
+ const path = `m/44'/60'/0'/0/${i}`;
579
+ const wallet = ethers_1.ethers.HDNodeWallet.fromPhrase(mnemonic, undefined, path);
580
+ addresses.push(wallet.address);
581
+ }
582
+ return addresses;
583
+ }
584
+ /**
585
+ * Get current mnemonic (if using mnemonic mode)
586
+ */
587
+ async getMnemonic() {
588
+ return this.mnemonic;
589
+ }
590
+ /**
591
+ * Get user's master mnemonic from Gun or localStorage
592
+ * Used by createWallet/loadWallets for frontend compatibility
593
+ */
594
+ async getUserMasterMnemonic() {
595
+ try {
596
+ // First check if already in memory
597
+ if (this.mnemonic) {
598
+ return this.mnemonic;
599
+ }
600
+ // Try to load from Gun
601
+ const gunMnemonic = await this.loadMnemonicFromGun();
602
+ if (gunMnemonic) {
603
+ this.mnemonic = gunMnemonic;
604
+ return gunMnemonic;
605
+ }
606
+ // Try localStorage as fallback
607
+ if (typeof localStorage !== "undefined") {
608
+ const storageKey = `shogun_master_mnemonic_${this.getStorageUserIdentifier()}`;
609
+ const encryptedMnemonic = localStorage.getItem(storageKey);
610
+ if (encryptedMnemonic) {
611
+ const decrypted = await this.decryptSensitiveData(encryptedMnemonic);
612
+ if (decrypted) {
613
+ this.mnemonic = decrypted;
614
+ // Sync back to Gun
615
+ await this.saveMnemonicToGun(decrypted);
616
+ return decrypted;
617
+ }
618
+ }
619
+ }
620
+ return null;
621
+ }
622
+ catch (error) {
623
+ console.error("Error retrieving master mnemonic:", error);
624
+ return null;
625
+ }
626
+ }
627
+ /**
628
+ * Export mnemonic (encrypted)
629
+ */
630
+ async exportMnemonic() {
631
+ if (!this.mnemonic) {
632
+ return null;
633
+ }
634
+ return await this.encryptSensitiveData(this.mnemonic);
635
+ }
636
+ /**
637
+ * Import mnemonic and re-initialize wallet
638
+ */
639
+ async importMnemonic(encryptedMnemonic) {
640
+ const decrypted = await this.decryptSensitiveData(encryptedMnemonic);
641
+ if (!decrypted) {
642
+ throw new Error("Failed to decrypt mnemonic");
643
+ }
644
+ // Validate mnemonic
645
+ const words = decrypted.trim().split(/\s+/);
646
+ if (words.length !== 12 && words.length !== 24) {
647
+ throw new Error("Invalid mnemonic (must be 12 or 24 words)");
648
+ }
649
+ this.mnemonic = decrypted;
650
+ // Re-initialize with mnemonic
651
+ this.initialized = false;
652
+ await this.initialize(true);
653
+ console.log("✅ Mnemonic imported and wallet re-initialized");
654
+ }
655
+ /**
656
+ * Save mnemonic to Gun (encrypted)
657
+ */
658
+ async saveMnemonicToGun(mnemonic) {
659
+ try {
660
+ const shogun = this.identity.getShogun();
661
+ const gun = shogun?.db?.gun;
662
+ if (!gun)
663
+ return;
664
+ const user = gun.user();
665
+ if (!user || !user.is)
666
+ return;
667
+ const encrypted = await this.encryptSensitiveData(mnemonic);
668
+ await user.get(SHIP_02.NODES.MNEMONIC).put(encrypted);
669
+ console.log("✅ Mnemonic saved to Gun (encrypted)");
670
+ }
671
+ catch (error) {
672
+ console.error("Error saving mnemonic:", error);
673
+ }
674
+ }
675
+ /**
676
+ * Load mnemonic from Gun (encrypted)
677
+ */
678
+ async loadMnemonicFromGun() {
679
+ try {
680
+ const shogun = this.identity.getShogun();
681
+ const gun = shogun?.db?.gun;
682
+ if (!gun)
683
+ return null;
684
+ const user = gun.user();
685
+ if (!user || !user.is)
686
+ return null;
687
+ const encrypted = await new Promise((resolve) => {
688
+ let resolved = false;
689
+ const timeout = setTimeout(() => {
690
+ if (!resolved) {
691
+ resolved = true;
692
+ resolve(null);
693
+ }
694
+ }, 5000);
695
+ user.get(SHIP_02.NODES.MNEMONIC).once((data) => {
696
+ if (!resolved) {
697
+ resolved = true;
698
+ clearTimeout(timeout);
699
+ resolve(data || null);
700
+ }
701
+ });
702
+ });
703
+ if (!encrypted)
704
+ return null;
705
+ return await this.decryptSensitiveData(encrypted);
706
+ }
707
+ catch (error) {
708
+ console.error("Error loading mnemonic:", error);
709
+ return null;
710
+ }
711
+ }
712
+ /**
713
+ * Get storage identifier for current user
714
+ */
715
+ getStorageUserIdentifier() {
716
+ const currentUser = this.identity.getCurrentUser();
717
+ const pub = currentUser?.pub;
718
+ if (pub) {
719
+ return pub.substring(0, 12); // Use part of the public key
720
+ }
721
+ return "guest"; // Identifier for unauthenticated users
722
+ }
723
+ /**
724
+ * Encrypt sensitive data using SEA
725
+ */
726
+ async encryptSensitiveData(text) {
727
+ try {
728
+ const shogun = this.identity.getShogun();
729
+ const crypto = shogun?.db?.crypto;
730
+ const keyPair = this.identity.getKeyPair();
731
+ if (!crypto || !keyPair) {
732
+ throw new Error("Crypto or keypair not available");
733
+ }
734
+ // Encrypt with user's keys
735
+ const encrypted = await crypto.encrypt(text, keyPair);
736
+ if (!encrypted) {
737
+ throw new Error("Encryption failed");
738
+ }
739
+ return JSON.stringify(encrypted);
740
+ }
741
+ catch (error) {
742
+ console.error("Error encrypting data:", error);
743
+ throw new Error(`Encryption failed: ${error.message}`);
744
+ }
745
+ }
746
+ /**
747
+ * Decrypt sensitive data using SEA
748
+ */
749
+ async decryptSensitiveData(encryptedText) {
750
+ try {
751
+ const shogun = this.identity.getShogun();
752
+ const crypto = shogun?.db?.crypto;
753
+ const keyPair = this.identity.getKeyPair();
754
+ if (!crypto || !keyPair) {
755
+ throw new Error("Crypto or keypair not available");
756
+ }
757
+ const encrypted = JSON.parse(encryptedText);
758
+ const decrypted = await crypto.decrypt(encrypted, keyPair);
759
+ if (!decrypted) {
760
+ throw new Error("Decryption failed");
761
+ }
762
+ return decrypted;
763
+ }
764
+ catch (error) {
765
+ console.error("Error decrypting data:", error);
766
+ return null;
767
+ }
768
+ }
769
+ /**
770
+ * Export master seed (encrypted)
771
+ * SECURITY: Handle with extreme care!
772
+ */
773
+ async exportMasterSeed() {
774
+ this.ensureInitialized();
775
+ if (!this.masterSeed) {
776
+ throw new Error("Master seed not available");
777
+ }
778
+ // Encrypt the seed
779
+ return await this.encryptSensitiveData(this.masterSeed);
780
+ }
781
+ /**
782
+ * Export all wallet data (encrypted)
783
+ */
784
+ async exportWalletData() {
785
+ this.ensureInitialized();
786
+ const data = {
787
+ addressBook: await this.exportAddressBook(),
788
+ masterPublicKey: this.hdWallet.publicKey,
789
+ timestamp: Date.now(),
790
+ };
791
+ return await this.encryptSensitiveData(JSON.stringify(data));
792
+ }
793
+ /**
794
+ * Import wallet data (encrypted)
795
+ */
796
+ async importWalletData(encryptedData) {
797
+ this.ensureInitialized();
798
+ const decrypted = await this.decryptSensitiveData(encryptedData);
799
+ if (!decrypted) {
800
+ throw new Error("Failed to decrypt wallet data");
801
+ }
802
+ const data = JSON.parse(decrypted);
803
+ // Verify master public key matches
804
+ if (data.masterPublicKey !== this.hdWallet.publicKey) {
805
+ throw new Error("Master public key mismatch - wrong identity");
806
+ }
807
+ // Import address book
808
+ await this.importAddressBook(data.addressBook);
809
+ console.log("✅ Wallet data imported successfully");
810
+ }
811
+ // ========================================================================
812
+ // PRIVATE HELPERS
813
+ // ========================================================================
814
+ /**
815
+ * Ensure system is initialized
816
+ */
817
+ ensureInitialized() {
818
+ if (!this.initialized) {
819
+ throw new Error("SHIP-02 not initialized. Call initialize() first.");
820
+ }
821
+ }
822
+ /**
823
+ * Derive master seed from SHIP-00 keypair
824
+ */
825
+ async deriveMasterSeed(keyPair) {
826
+ // Create deterministic seed from SHIP-00 keypair
827
+ // Combine public and private encryption keys
828
+ const seedMaterial = keyPair.epub + keyPair.epriv;
829
+ // Hash to create 32-byte seed
830
+ const seed = ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(seedMaterial));
831
+ // Remove 0x prefix
832
+ return seed.slice(2);
833
+ }
834
+ /**
835
+ * Build BIP-44 derivation path
836
+ */
837
+ buildBIP44Path(coinType, account, change, index) {
838
+ if (this.config.customPathPrefix) {
839
+ return `${this.config.customPathPrefix}/${account}'/${change}/${index}`;
840
+ }
841
+ return `m/44'/${coinType}'/${account}'/${change}/${index}`;
842
+ }
843
+ // ========================================================================
844
+ // RPC PROVIDER MANAGEMENT
845
+ // ========================================================================
846
+ /**
847
+ * Set RPC provider URL
848
+ */
849
+ async setRpcUrl(rpcUrl) {
850
+ try {
851
+ this.provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl);
852
+ console.log(`✅ RPC Provider configured: ${rpcUrl}`);
853
+ }
854
+ catch (error) {
855
+ console.error("Error setting RPC URL:", error);
856
+ throw new Error(`Failed to set RPC URL: ${error.message}`);
857
+ }
858
+ }
859
+ /**
860
+ * Get current RPC provider
861
+ */
862
+ getProvider() {
863
+ return this.provider;
864
+ }
865
+ /**
866
+ * Get signer (main wallet connected to provider)
867
+ */
868
+ getSigner() {
869
+ if (!this.provider) {
870
+ throw new Error("Provider not configured. Call setRpcUrl() first.");
871
+ }
872
+ const mainWallet = this.getMainWallet();
873
+ return mainWallet.connect(this.provider);
874
+ }
875
+ /**
876
+ * Set custom signer
877
+ */
878
+ async setSigner(signer) {
879
+ if (!this.provider) {
880
+ throw new Error("Provider not configured. Call setRpcUrl() first.");
881
+ }
882
+ // Note: The signer will use the configured provider
883
+ console.log(`✅ Custom signer set: ${signer.address}`);
884
+ }
885
+ // ========================================================================
886
+ // FRONTEND-FRIENDLY WALLET MANAGEMENT
887
+ // ========================================================================
888
+ /**
889
+ * Get main wallet (derived from Gun user keys, not BIP-44)
890
+ * This provides a consistent "main" wallet independent of HD derivation
891
+ */
892
+ getMainWallet() {
893
+ if (!this.mainWallet) {
894
+ const shogun = this.identity.getShogun();
895
+ const gun = shogun?.db?.gun;
896
+ if (!gun) {
897
+ throw new Error("Gun not available");
898
+ }
899
+ const user = gun.user();
900
+ if (!user || !user.is) {
901
+ throw new Error("User not authenticated");
902
+ }
903
+ // Check SEA keys availability
904
+ if (!user._ || !user._.sea || !user._.sea.priv || !user._.sea.pub) {
905
+ throw new Error("Insufficient user data to generate main wallet");
906
+ }
907
+ // Create deterministic seed from Gun user keys
908
+ const userSeed = user._.sea.priv;
909
+ const userPub = user._.sea.pub;
910
+ const userAlias = user.is.alias;
911
+ const seed = `${userSeed}|${userPub}|${userAlias}`;
912
+ // Generate private key from seed
913
+ const privateKey = this.generatePrivateKeyFromString(seed);
914
+ this.mainWallet = new ethers_1.ethers.Wallet(privateKey);
915
+ }
916
+ return this.mainWallet;
917
+ }
918
+ /**
919
+ * Get main wallet credentials
920
+ */
921
+ getMainWalletCredentials() {
922
+ const wallet = this.getMainWallet();
923
+ return {
924
+ address: wallet.address,
925
+ priv: wallet.privateKey,
926
+ };
927
+ }
928
+ /**
929
+ * Create new wallet with auto-incremented index
930
+ * Frontend-friendly API that returns ready-to-use wallet object
931
+ */
932
+ async createWallet() {
933
+ this.ensureInitialized();
934
+ const shogun = this.identity.getShogun();
935
+ const gun = shogun?.db?.gun;
936
+ const user = gun?.user();
937
+ if (!user || !user.is) {
938
+ throw new Error("User not authenticated");
939
+ }
940
+ // Get next index
941
+ const nextIndex = Object.keys(this.walletPaths).length;
942
+ const path = `m/44'/60'/0'/0/${nextIndex}`;
943
+ // Get or generate mnemonic
944
+ let mnemonic = await this.getMnemonic();
945
+ if (!mnemonic) {
946
+ // If no mnemonic, use SHIP-00 derived wallet
947
+ await this.initialize(false);
948
+ mnemonic = await this.getMnemonic();
949
+ }
950
+ // Derive wallet
951
+ let wallet;
952
+ if (mnemonic) {
953
+ wallet = ethers_1.ethers.HDNodeWallet.fromPhrase(mnemonic, path);
954
+ }
955
+ else {
956
+ // Fallback: derive from HD wallet
957
+ wallet = this.hdWallet.derivePath(path);
958
+ }
959
+ // Store path
960
+ this.walletPaths[wallet.address] = {
961
+ path,
962
+ created: Date.now(),
963
+ };
964
+ // Cache wallet
965
+ this.walletCache.set(wallet.address, wallet);
966
+ this.addressCache.set(wallet.address, {
967
+ address: wallet.address,
968
+ path,
969
+ publicKey: wallet.publicKey,
970
+ index: nextIndex,
971
+ createdAt: Date.now(),
972
+ });
973
+ // Save to Gun
974
+ if (this.persistToGun) {
975
+ await this.saveWalletPathsToGun();
976
+ }
977
+ console.log(`✅ Created wallet #${nextIndex}: ${wallet.address}`);
978
+ return {
979
+ wallet,
980
+ path,
981
+ address: wallet.address,
982
+ publicKey: wallet.publicKey,
983
+ index: nextIndex,
984
+ };
985
+ }
986
+ /**
987
+ * Load all wallets from stored paths
988
+ * Reconstructs wallet objects from mnemonic/seed and paths
989
+ */
990
+ async loadWallets() {
991
+ this.ensureInitialized();
992
+ const wallets = [];
993
+ const mnemonic = await this.getMnemonic();
994
+ for (const [address, pathData] of Object.entries(this.walletPaths)) {
995
+ let wallet;
996
+ if (mnemonic) {
997
+ // Derive from mnemonic
998
+ wallet = ethers_1.ethers.HDNodeWallet.fromPhrase(mnemonic, pathData.path);
999
+ }
1000
+ else {
1001
+ // Derive from HD wallet (SHIP-00 based)
1002
+ wallet = this.hdWallet.derivePath(pathData.path);
1003
+ }
1004
+ wallets.push({
1005
+ wallet,
1006
+ path: pathData.path,
1007
+ address: wallet.address,
1008
+ publicKey: wallet.publicKey,
1009
+ });
1010
+ // Update caches
1011
+ this.walletCache.set(wallet.address, wallet);
1012
+ }
1013
+ console.log(`✅ Loaded ${wallets.length} wallets`);
1014
+ return wallets;
1015
+ }
1016
+ // ========================================================================
1017
+ // ADVANCED EXPORT/IMPORT
1018
+ // ========================================================================
1019
+ /**
1020
+ * Export wallet keys for all derived addresses
1021
+ */
1022
+ async exportWalletKeys() {
1023
+ this.ensureInitialized();
1024
+ const walletKeys = await this.loadWallets();
1025
+ const exportData = walletKeys.map((w) => ({
1026
+ address: w.address,
1027
+ privateKey: w.wallet.privateKey,
1028
+ path: w.path,
1029
+ publicKey: w.publicKey,
1030
+ }));
1031
+ return JSON.stringify(exportData);
1032
+ }
1033
+ /**
1034
+ * Export Gun SEA keypair
1035
+ */
1036
+ async exportGunPair() {
1037
+ const keyPair = this.identity.getKeyPair();
1038
+ if (!keyPair) {
1039
+ throw new Error("No keypair available");
1040
+ }
1041
+ return JSON.stringify({
1042
+ pub: keyPair.pub,
1043
+ priv: keyPair.priv,
1044
+ epub: keyPair.epub,
1045
+ epriv: keyPair.epriv,
1046
+ });
1047
+ }
1048
+ /**
1049
+ * Export all user data (mnemonic, wallets, Gun pair)
1050
+ */
1051
+ async exportAllUserData() {
1052
+ this.ensureInitialized();
1053
+ const data = {
1054
+ mnemonic: await this.exportMnemonic(),
1055
+ walletKeys: await this.exportWalletKeys(),
1056
+ gunPair: await this.exportGunPair(),
1057
+ addressBook: await this.exportAddressBook(),
1058
+ masterPublicKey: this.hdWallet.publicKey,
1059
+ timestamp: Date.now(),
1060
+ };
1061
+ // Encrypt the entire backup
1062
+ return await this.encryptSensitiveData(JSON.stringify(data));
1063
+ }
1064
+ /**
1065
+ * Import wallet keys and restore wallets
1066
+ */
1067
+ async importWalletKeys(walletsData) {
1068
+ this.ensureInitialized();
1069
+ const wallets = JSON.parse(walletsData);
1070
+ let count = 0;
1071
+ for (const walletData of wallets) {
1072
+ // Store path
1073
+ this.walletPaths[walletData.address] = {
1074
+ path: walletData.path,
1075
+ created: Date.now(),
1076
+ };
1077
+ // Re-derive wallet
1078
+ await this.deriveEthereumAddress(walletData.path);
1079
+ count++;
1080
+ }
1081
+ // Save to Gun
1082
+ if (this.persistToGun) {
1083
+ await this.saveWalletPathsToGun();
1084
+ }
1085
+ console.log(`✅ Imported ${count} wallet keys`);
1086
+ return count;
1087
+ }
1088
+ /**
1089
+ * Import Gun SEA keypair
1090
+ * Note: This is a placeholder for compatibility
1091
+ * SHIP-02 doesn't manage Gun keys directly (use SHIP-00 for that)
1092
+ */
1093
+ async importGunPair(pairData) {
1094
+ try {
1095
+ const pair = JSON.parse(pairData);
1096
+ // Validate Gun pair structure
1097
+ if (!pair.pub || !pair.priv || !pair.epub || !pair.epriv) {
1098
+ throw new Error("Invalid Gun pair structure");
1099
+ }
1100
+ console.log("⚠️ Gun pair import detected");
1101
+ console.log("💡 Gun keypair management is handled by SHIP-00");
1102
+ console.log("💡 Use identity.importKeyPair() instead for Gun key restoration");
1103
+ return true;
1104
+ }
1105
+ catch (error) {
1106
+ console.error("Error importing Gun pair:", error);
1107
+ return false;
1108
+ }
1109
+ }
1110
+ /**
1111
+ * Import all user data from backup
1112
+ */
1113
+ async importAllUserData(backupData, options = { importMnemonic: true, importWallets: true, importGunPair: true }) {
1114
+ try {
1115
+ // Decrypt backup
1116
+ const decrypted = await this.decryptSensitiveData(backupData);
1117
+ if (!decrypted) {
1118
+ throw new Error("Failed to decrypt backup data");
1119
+ }
1120
+ const data = JSON.parse(decrypted);
1121
+ const result = {
1122
+ success: true,
1123
+ mnemonicImported: false,
1124
+ walletsImported: 0,
1125
+ gunPairImported: false,
1126
+ };
1127
+ // Import mnemonic
1128
+ if (options.importMnemonic && data.mnemonic) {
1129
+ try {
1130
+ const mnemonicDecrypted = await this.decryptSensitiveData(data.mnemonic);
1131
+ if (mnemonicDecrypted) {
1132
+ await this.importMnemonic(mnemonicDecrypted);
1133
+ result.mnemonicImported = true;
1134
+ }
1135
+ }
1136
+ catch (error) {
1137
+ console.error("Error importing mnemonic:", error);
1138
+ }
1139
+ }
1140
+ // Import wallet keys
1141
+ if (options.importWallets && data.walletKeys) {
1142
+ try {
1143
+ result.walletsImported = await this.importWalletKeys(data.walletKeys);
1144
+ }
1145
+ catch (error) {
1146
+ console.error("Error importing wallet keys:", error);
1147
+ }
1148
+ }
1149
+ // Import address book
1150
+ if (data.addressBook) {
1151
+ try {
1152
+ await this.importAddressBook(data.addressBook);
1153
+ }
1154
+ catch (error) {
1155
+ console.error("Error importing address book:", error);
1156
+ }
1157
+ }
1158
+ console.log(`✅ Import completed:`, result);
1159
+ return result;
1160
+ }
1161
+ catch (error) {
1162
+ console.error("Error importing all user data:", error);
1163
+ return {
1164
+ success: false,
1165
+ mnemonicImported: false,
1166
+ walletsImported: 0,
1167
+ gunPairImported: false,
1168
+ };
1169
+ }
1170
+ }
1171
+ // ========================================================================
1172
+ // WALLET PATH MANAGEMENT
1173
+ // ========================================================================
1174
+ /**
1175
+ * Initialize wallet paths from Gun storage
1176
+ */
1177
+ async initializeWalletPaths() {
1178
+ try {
1179
+ this.walletPaths = {};
1180
+ await this.loadWalletPathsFromGun();
1181
+ await this.loadWalletPathsFromLocalStorage();
1182
+ const count = Object.keys(this.walletPaths).length;
1183
+ if (count === 0) {
1184
+ console.log("No wallet paths found, new wallets will be created when needed");
1185
+ }
1186
+ else {
1187
+ console.log(`✅ Initialized ${count} wallet paths`);
1188
+ }
1189
+ }
1190
+ catch (error) {
1191
+ console.error("Error initializing wallet paths:", error);
1192
+ throw new Error(`Failed to initialize wallet paths: ${error.message}`);
1193
+ }
1194
+ }
1195
+ /**
1196
+ * Save wallet paths to localStorage
1197
+ */
1198
+ async saveWalletPathsToLocalStorage() {
1199
+ try {
1200
+ const storageKey = `shogun_wallet_paths_${this.getStorageUserIdentifier()}`;
1201
+ const pathsToSave = JSON.stringify(this.walletPaths);
1202
+ if (typeof localStorage !== "undefined") {
1203
+ localStorage.setItem(storageKey, pathsToSave);
1204
+ console.log(`✅ Saved ${Object.keys(this.walletPaths).length} wallet paths to localStorage`);
1205
+ }
1206
+ }
1207
+ catch (error) {
1208
+ console.error("Error saving wallet paths to localStorage:", error);
1209
+ }
1210
+ }
1211
+ /**
1212
+ * Load wallet paths from localStorage
1213
+ */
1214
+ async loadWalletPathsFromLocalStorage() {
1215
+ try {
1216
+ if (typeof localStorage === "undefined") {
1217
+ return;
1218
+ }
1219
+ const storageKey = `shogun_wallet_paths_${this.getStorageUserIdentifier()}`;
1220
+ const storedPaths = localStorage.getItem(storageKey);
1221
+ if (storedPaths) {
1222
+ const parsedPaths = JSON.parse(storedPaths);
1223
+ Object.entries(parsedPaths).forEach(([address, pathData]) => {
1224
+ if (!this.walletPaths[address]) {
1225
+ this.walletPaths[address] = pathData;
1226
+ }
1227
+ });
1228
+ console.log(`✅ Loaded wallet paths from localStorage`);
1229
+ }
1230
+ }
1231
+ catch (error) {
1232
+ console.error("Error loading wallet paths from localStorage:", error);
1233
+ }
1234
+ }
1235
+ /**
1236
+ * Save wallet paths to Gun
1237
+ */
1238
+ async saveWalletPathsToGun() {
1239
+ try {
1240
+ const shogun = this.identity.getShogun();
1241
+ const gun = shogun?.db?.gun;
1242
+ if (!gun)
1243
+ return;
1244
+ const user = gun.user();
1245
+ if (!user || !user.is)
1246
+ return;
1247
+ await user.get(SHIP_02.NODES.WALLET_PATHS).put(JSON.stringify(this.walletPaths));
1248
+ console.log("✅ Wallet paths saved to Gun");
1249
+ }
1250
+ catch (error) {
1251
+ console.error("Error saving wallet paths to Gun:", error);
1252
+ }
1253
+ }
1254
+ /**
1255
+ * Load wallet paths from Gun
1256
+ */
1257
+ async loadWalletPathsFromGun() {
1258
+ try {
1259
+ const shogun = this.identity.getShogun();
1260
+ const gun = shogun?.db?.gun;
1261
+ if (!gun)
1262
+ return;
1263
+ const user = gun.user();
1264
+ if (!user || !user.is)
1265
+ return;
1266
+ const data = await new Promise((resolve) => {
1267
+ let resolved = false;
1268
+ const timeout = setTimeout(() => {
1269
+ if (!resolved) {
1270
+ resolved = true;
1271
+ resolve(null);
1272
+ }
1273
+ }, 5000);
1274
+ user.get(SHIP_02.NODES.WALLET_PATHS).once((data) => {
1275
+ if (!resolved) {
1276
+ resolved = true;
1277
+ clearTimeout(timeout);
1278
+ resolve(data || null);
1279
+ }
1280
+ });
1281
+ });
1282
+ if (data) {
1283
+ const paths = JSON.parse(data);
1284
+ Object.entries(paths).forEach(([address, pathData]) => {
1285
+ this.walletPaths[address] = pathData;
1286
+ });
1287
+ console.log("✅ Wallet paths loaded from Gun");
1288
+ }
1289
+ }
1290
+ catch (error) {
1291
+ console.error("Error loading wallet paths from Gun:", error);
1292
+ }
1293
+ }
1294
+ // ========================================================================
1295
+ // PRIVATE KEY GENERATION (from shogun-BIP44)
1296
+ // ========================================================================
1297
+ /**
1298
+ * Generate deterministic private key from string seed
1299
+ * Uses same algorithm as shogun-BIP44 for compatibility
1300
+ */
1301
+ generatePrivateKeyFromString(input) {
1302
+ try {
1303
+ const encoder = new TextEncoder();
1304
+ const data = encoder.encode(input);
1305
+ // MurmurHash3-style digest
1306
+ const digestSync = (data) => {
1307
+ let h1 = 0xdeadbeef;
1308
+ let h2 = 0x41c6ce57;
1309
+ for (let i = 0; i < data.length; i++) {
1310
+ h1 = Math.imul(h1 ^ data[i], 2654435761);
1311
+ h2 = Math.imul(h2 ^ data[i], 1597334677);
1312
+ }
1313
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
1314
+ h1 = Math.imul(h1 ^ (h1 >>> 13), 3266489909);
1315
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
1316
+ h2 = Math.imul(h2 ^ (h2 >>> 13), 3266489909);
1317
+ const out = new Uint8Array(32);
1318
+ for (let i = 0; i < 4; i++) {
1319
+ out[i] = (h1 >> (8 * i)) & 0xff;
1320
+ }
1321
+ for (let i = 0; i < 4; i++) {
1322
+ out[i + 4] = (h2 >> (8 * i)) & 0xff;
1323
+ }
1324
+ for (let i = 8; i < 32; i++) {
1325
+ out[i] = (out[i % 8] ^ out[(i - 1) % 8]) & 0xff;
1326
+ }
1327
+ return out;
1328
+ };
1329
+ const hashArray = digestSync(data);
1330
+ const privateKey = "0x" +
1331
+ Array.from(hashArray)
1332
+ .map((b) => b.toString(16).padStart(2, "0"))
1333
+ .join("");
1334
+ return privateKey;
1335
+ }
1336
+ catch (error) {
1337
+ console.error("Error generating private key:", error);
1338
+ throw new Error("Failed to generate private key from seed");
1339
+ }
1340
+ }
1341
+ // ========================================================================
1342
+ // UTILITIES
1343
+ // ========================================================================
1344
+ /**
1345
+ * Initialize wallet paths and test encryption system
1346
+ */
1347
+ async initializeWalletPathsAndTestEncryption() {
1348
+ await this.initializeWalletPaths();
1349
+ // Note: testEncryptionSystem is UI-specific, skipped
1350
+ console.log("✅ Wallet paths initialized");
1351
+ }
1352
+ /**
1353
+ * Cleanup resources
1354
+ */
1355
+ async cleanup() {
1356
+ await this.clearCache();
1357
+ console.log("✅ SHIP-02 cleanup completed");
1358
+ }
1359
+ }
1360
+ exports.SHIP_02 = SHIP_02;
1361
+ // GunDB Node Names for SHIP-02 storage
1362
+ SHIP_02.NODES = {
1363
+ ADDRESS_BOOK: "addressbook",
1364
+ MNEMONIC: "mnemonic",
1365
+ WALLET_PATHS: "wallet_paths",
1366
+ };