@sidhujag/sysweb3-keyring 1.0.491

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 (104) hide show
  1. package/README.md +201 -0
  2. package/cjs/errorUtils.js +75 -0
  3. package/cjs/errorUtils.js.map +1 -0
  4. package/cjs/hardware-wallet-manager.js +462 -0
  5. package/cjs/hardware-wallet-manager.js.map +1 -0
  6. package/cjs/index.js +31 -0
  7. package/cjs/index.js.map +1 -0
  8. package/cjs/initial-state.js +105 -0
  9. package/cjs/initial-state.js.map +1 -0
  10. package/cjs/keyring-manager.js +1687 -0
  11. package/cjs/keyring-manager.js.map +1 -0
  12. package/cjs/ledger/bitcoin_client/index.js +47 -0
  13. package/cjs/ledger/bitcoin_client/index.js.map +1 -0
  14. package/cjs/ledger/bitcoin_client/lib/appClient.js +408 -0
  15. package/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -0
  16. package/cjs/ledger/bitcoin_client/lib/bip32.js +61 -0
  17. package/cjs/ledger/bitcoin_client/lib/bip32.js.map +1 -0
  18. package/cjs/ledger/bitcoin_client/lib/buffertools.js +126 -0
  19. package/cjs/ledger/bitcoin_client/lib/buffertools.js.map +1 -0
  20. package/cjs/ledger/bitcoin_client/lib/clientCommands.js +270 -0
  21. package/cjs/ledger/bitcoin_client/lib/clientCommands.js.map +1 -0
  22. package/cjs/ledger/bitcoin_client/lib/constants.js +16 -0
  23. package/cjs/ledger/bitcoin_client/lib/constants.js.map +1 -0
  24. package/cjs/ledger/bitcoin_client/lib/merkelizedPsbt.js +54 -0
  25. package/cjs/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +1 -0
  26. package/cjs/ledger/bitcoin_client/lib/merkle.js +109 -0
  27. package/cjs/ledger/bitcoin_client/lib/merkle.js.map +1 -0
  28. package/cjs/ledger/bitcoin_client/lib/merkleMap.js +46 -0
  29. package/cjs/ledger/bitcoin_client/lib/merkleMap.js.map +1 -0
  30. package/cjs/ledger/bitcoin_client/lib/policy.js +66 -0
  31. package/cjs/ledger/bitcoin_client/lib/policy.js.map +1 -0
  32. package/cjs/ledger/bitcoin_client/lib/psbtv2.js +640 -0
  33. package/cjs/ledger/bitcoin_client/lib/psbtv2.js.map +1 -0
  34. package/cjs/ledger/bitcoin_client/lib/varint.js +113 -0
  35. package/cjs/ledger/bitcoin_client/lib/varint.js.map +1 -0
  36. package/cjs/ledger/consts.js +7 -0
  37. package/cjs/ledger/consts.js.map +1 -0
  38. package/cjs/ledger/index.js +319 -0
  39. package/cjs/ledger/index.js.map +1 -0
  40. package/cjs/ledger/types.js +3 -0
  41. package/cjs/ledger/types.js.map +1 -0
  42. package/cjs/network-utils.js +76 -0
  43. package/cjs/network-utils.js.map +1 -0
  44. package/cjs/providers.js +270 -0
  45. package/cjs/providers.js.map +1 -0
  46. package/cjs/signers.js +64 -0
  47. package/cjs/signers.js.map +1 -0
  48. package/cjs/storage.js +30 -0
  49. package/cjs/storage.js.map +1 -0
  50. package/cjs/transactions/__tests__/integration.test.js +237 -0
  51. package/cjs/transactions/__tests__/integration.test.js.map +1 -0
  52. package/cjs/transactions/__tests__/syscoin.test.js +361 -0
  53. package/cjs/transactions/__tests__/syscoin.test.js.map +1 -0
  54. package/cjs/transactions/ethereum.js +1577 -0
  55. package/cjs/transactions/ethereum.js.map +1 -0
  56. package/cjs/transactions/index.js +19 -0
  57. package/cjs/transactions/index.js.map +1 -0
  58. package/cjs/transactions/syscoin.js +328 -0
  59. package/cjs/transactions/syscoin.js.map +1 -0
  60. package/cjs/trezor/index.js +718 -0
  61. package/cjs/trezor/index.js.map +1 -0
  62. package/cjs/types.js +12 -0
  63. package/cjs/types.js.map +1 -0
  64. package/cjs/utils/derivation-paths.js +99 -0
  65. package/cjs/utils/derivation-paths.js.map +1 -0
  66. package/cjs/utils/psbt.js +60 -0
  67. package/cjs/utils/psbt.js.map +1 -0
  68. package/cjs/utils.js +130 -0
  69. package/cjs/utils.js.map +1 -0
  70. package/package.json +46 -0
  71. package/types/errorUtils.d.ts +1 -0
  72. package/types/hardware-wallet-manager.d.ts +110 -0
  73. package/types/index.d.ts +12 -0
  74. package/types/initial-state.d.ts +79 -0
  75. package/types/keyring-manager.d.ts +184 -0
  76. package/types/ledger/bitcoin_client/index.d.ts +5 -0
  77. package/types/ledger/bitcoin_client/lib/appClient.d.ts +106 -0
  78. package/types/ledger/bitcoin_client/lib/bip32.d.ts +11 -0
  79. package/types/ledger/bitcoin_client/lib/buffertools.d.ts +28 -0
  80. package/types/ledger/bitcoin_client/lib/clientCommands.d.ts +77 -0
  81. package/types/ledger/bitcoin_client/lib/constants.d.ts +12 -0
  82. package/types/ledger/bitcoin_client/lib/merkelizedPsbt.d.ts +24 -0
  83. package/types/ledger/bitcoin_client/lib/merkle.d.ts +32 -0
  84. package/types/ledger/bitcoin_client/lib/merkleMap.d.ts +23 -0
  85. package/types/ledger/bitcoin_client/lib/policy.d.ts +36 -0
  86. package/types/ledger/bitcoin_client/lib/psbtv2.d.ts +167 -0
  87. package/types/ledger/bitcoin_client/lib/varint.d.ts +23 -0
  88. package/types/ledger/consts.d.ts +3 -0
  89. package/types/ledger/index.d.ts +51 -0
  90. package/types/ledger/types.d.ts +48 -0
  91. package/types/network-utils.d.ts +14 -0
  92. package/types/providers.d.ts +47 -0
  93. package/types/signers.d.ts +95 -0
  94. package/types/storage.d.ts +2 -0
  95. package/types/transactions/__tests__/integration.test.d.ts +1 -0
  96. package/types/transactions/__tests__/syscoin.test.d.ts +1 -0
  97. package/types/transactions/ethereum.d.ts +80 -0
  98. package/types/transactions/index.d.ts +2 -0
  99. package/types/transactions/syscoin.d.ts +61 -0
  100. package/types/trezor/index.d.ts +170 -0
  101. package/types/types.d.ts +294 -0
  102. package/types/utils/derivation-paths.d.ts +35 -0
  103. package/types/utils/psbt.d.ts +17 -0
  104. package/types/utils.d.ts +4 -0
@@ -0,0 +1,1687 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.KeyringManager = void 0;
40
+ const secp256k1_1 = __importDefault(require("@bitcoinerlab/secp256k1"));
41
+ const sysweb3 = __importStar(require("@sidhujag/sysweb3-core"));
42
+ const sysweb3_network_1 = require("@sidhujag/sysweb3-network");
43
+ const bip32_1 = require("bip32");
44
+ const bip39_1 = require("bip39");
45
+ const bip84_1 = __importDefault(require("bip84"));
46
+ const bjs = __importStar(require("bitcoinjs-lib"));
47
+ const bs58check_1 = __importDefault(require("bs58check"));
48
+ const crypto_1 = __importDefault(require("crypto"));
49
+ const crypto_js_1 = __importDefault(require("crypto-js"));
50
+ const ethers_1 = require("ethers");
51
+ const mapValues_1 = __importDefault(require("lodash/mapValues"));
52
+ const omit_1 = __importDefault(require("lodash/omit"));
53
+ const syscoinjs = __importStar(require("syscoinjs-lib"));
54
+ const initial_state_1 = require("./initial-state");
55
+ const ledger_1 = require("./ledger");
56
+ const signers_1 = require("./signers");
57
+ const storage_1 = require("./storage");
58
+ const transactions_1 = require("./transactions");
59
+ const trezor_1 = require("./trezor");
60
+ const types_1 = require("./types");
61
+ const derivation_paths_1 = require("./utils/derivation-paths");
62
+ // Dynamic ETH HD path generation - will be computed as needed
63
+ /**
64
+ * Secure Buffer implementation for sensitive data
65
+ * Provides explicit memory clearing capability
66
+ */
67
+ class SecureBuffer {
68
+ constructor(data) {
69
+ this._isCleared = false;
70
+ if (typeof data === 'string') {
71
+ this.buffer = Buffer.from(data, 'utf8');
72
+ }
73
+ else {
74
+ this.buffer = Buffer.from(data);
75
+ }
76
+ }
77
+ get() {
78
+ if (this._isCleared || !this.buffer) {
79
+ throw new Error('SecureBuffer has been cleared');
80
+ }
81
+ return Buffer.from(this.buffer); // Return copy
82
+ }
83
+ toString() {
84
+ if (this._isCleared || !this.buffer) {
85
+ throw new Error('SecureBuffer has been cleared');
86
+ }
87
+ return this.buffer.toString('utf8');
88
+ }
89
+ clear() {
90
+ if (!this._isCleared && this.buffer) {
91
+ // Overwrite with random data first
92
+ crypto_1.default.randomFillSync(this.buffer);
93
+ // Then fill with zeros
94
+ this.buffer.fill(0);
95
+ this.buffer = null;
96
+ this._isCleared = true;
97
+ }
98
+ }
99
+ isCleared() {
100
+ return this._isCleared;
101
+ }
102
+ }
103
+ class KeyringManager {
104
+ constructor() {
105
+ // Store getter function for accessing Redux state
106
+ this.getVaultState = null;
107
+ // Method to inject store getter from Pali side
108
+ this.setVaultStateGetter = (getter) => {
109
+ this.getVaultState = getter;
110
+ };
111
+ // Helper method to get current vault state
112
+ this.getVault = () => {
113
+ if (!this.getVaultState) {
114
+ throw new Error('Vault state getter not configured. Call setVaultStateGetter() first.');
115
+ }
116
+ const vault = this.getVaultState();
117
+ // DEFENSIVE CHECK: Ensure vault state is properly structured
118
+ if (!vault) {
119
+ throw new Error('Vault state is undefined. Ensure Redux store is properly initialized with vault state.');
120
+ }
121
+ if (!vault.activeNetwork) {
122
+ throw new Error('Vault state is missing activeNetwork. Ensure vault state is properly initialized before keyring operations.');
123
+ }
124
+ if (!vault.activeAccount) {
125
+ throw new Error('Vault state is missing activeAccount. Ensure vault state is properly initialized before keyring operations.');
126
+ }
127
+ return vault;
128
+ };
129
+ // Helper to get active chain from vault state (replaces this.activeChain)
130
+ this.getActiveChain = () => {
131
+ return this.getVault().activeNetwork.kind;
132
+ };
133
+ // Secure session data - using Buffers that can be explicitly cleared
134
+ this.sessionPassword = null;
135
+ this.sessionMnemonic = null; // can be a mnemonic or a zprv, can be changed to a zprv when using an imported wallet
136
+ // ===================================== PUBLIC METHODS - KEYRING MANAGER FOR HD - SYS ALL ===================================== //
137
+ this.setStorage = (client) => this.storage.setClient(client);
138
+ this.validateAccountType = (account) => {
139
+ return account.isImported === true
140
+ ? types_1.KeyringAccountType.Imported
141
+ : types_1.KeyringAccountType.HDAccount;
142
+ };
143
+ this.isUnlocked = () => !!this.sessionPassword && !this.sessionPassword.isCleared();
144
+ this.lockWallet = () => {
145
+ // Clear secure session data
146
+ if (this.sessionPassword) {
147
+ this.sessionPassword.clear();
148
+ this.sessionPassword = null;
149
+ }
150
+ if (this.sessionMnemonic) {
151
+ this.sessionMnemonic.clear();
152
+ this.sessionMnemonic = null;
153
+ }
154
+ // Clean up hardware wallet connections
155
+ if (this.ledgerSigner) {
156
+ this.ledgerSigner.destroy().catch((error) => {
157
+ console.error('Error destroying Ledger connection:', error);
158
+ });
159
+ }
160
+ if (this.trezorSigner) {
161
+ this.trezorSigner.destroy().catch((error) => {
162
+ console.error('Error destroying Trezor connection:', error);
163
+ });
164
+ }
165
+ };
166
+ // Direct secure transfer of session data to another keyring
167
+ this.transferSessionTo = (targetKeyring) => {
168
+ if (!this.isUnlocked()) {
169
+ throw new Error('Source keyring must be unlocked to transfer session');
170
+ }
171
+ // Cast to access the receiveSessionOwnership method
172
+ const targetKeyringImpl = targetKeyring;
173
+ // Transfer ownership of our buffers to the target
174
+ if (!this.sessionPassword || !this.sessionMnemonic) {
175
+ throw new Error('Session data is missing during transfer');
176
+ }
177
+ targetKeyringImpl.receiveSessionOwnership(this.sessionPassword, this.sessionMnemonic);
178
+ // Null out our references (do NOT clear buffers - target owns them now)
179
+ this.sessionPassword = null;
180
+ this.sessionMnemonic = null;
181
+ };
182
+ // Private method for zero-copy transfer - takes ownership of buffers
183
+ this.receiveSessionOwnership = (sessionPassword, sessionMnemonic) => {
184
+ // Clear any existing data first
185
+ if (this.sessionPassword) {
186
+ this.sessionPassword.clear();
187
+ }
188
+ if (this.sessionMnemonic) {
189
+ this.sessionMnemonic.clear();
190
+ }
191
+ // Take ownership of the actual SecureBuffer objects
192
+ // No copying - these are the original objects
193
+ this.sessionPassword = sessionPassword;
194
+ this.sessionMnemonic = sessionMnemonic;
195
+ };
196
+ this.addNewAccount = async (label) => {
197
+ // Check if wallet is unlocked
198
+ if (!this.isUnlocked()) {
199
+ throw new Error('Wallet must be unlocked to add new accounts');
200
+ }
201
+ // addNewAccount should only create accounts from the main seed
202
+ // For importing accounts (including zprvs), use importAccount
203
+ if (this.getActiveChain() === sysweb3_network_1.INetworkType.Syscoin) {
204
+ return await this.addNewAccountToSyscoinChain(label);
205
+ }
206
+ else {
207
+ // EVM chainType
208
+ return await this.addNewAccountToEth(label);
209
+ }
210
+ };
211
+ this.getNewChangeAddress = async () => {
212
+ const vault = this.getVault();
213
+ const { accounts, activeAccount } = vault;
214
+ const account = accounts[activeAccount.type]?.[activeAccount.id];
215
+ if (!account) {
216
+ throw new Error('Active account not found');
217
+ }
218
+ const { xpub } = account;
219
+ return await this.getAddress(xpub, true); // Don't skip increment - get next unused
220
+ };
221
+ this.getChangeAddress = async (id) => {
222
+ const vault = this.getVault();
223
+ const { accounts, activeAccount } = vault;
224
+ const account = accounts[activeAccount.type]?.[id];
225
+ if (!account) {
226
+ throw new Error(`Account with id ${id} not found`);
227
+ }
228
+ const { xpub } = account;
229
+ return await this.getAddress(xpub, true);
230
+ };
231
+ this.getPubkey = async (id, isChangeAddress) => {
232
+ const vault = this.getVault();
233
+ const { accounts, activeAccount } = vault;
234
+ const account = accounts[activeAccount.type]?.[id];
235
+ if (!account) {
236
+ throw new Error(`Account with id ${id} not found`);
237
+ }
238
+ const { xpub } = account;
239
+ return await this.getCurrentAddressPubkey(xpub, isChangeAddress);
240
+ };
241
+ this.getBip32Path = async (id, isChangeAddress) => {
242
+ const vault = this.getVault();
243
+ const { accounts, activeAccount } = vault;
244
+ const account = accounts[activeAccount.type]?.[id];
245
+ if (!account) {
246
+ throw new Error(`Account with id ${id} not found`);
247
+ }
248
+ const { xpub } = account;
249
+ return await this.getCurrentAddressBip32Path(xpub, isChangeAddress);
250
+ };
251
+ this.updateReceivingAddress = async () => {
252
+ const vault = this.getVault();
253
+ const { accounts, activeAccount } = vault;
254
+ const account = accounts[activeAccount.type]?.[activeAccount.id];
255
+ if (!account) {
256
+ throw new Error('Active account not found');
257
+ }
258
+ const { xpub } = account;
259
+ const address = await this.getAddress(xpub, false);
260
+ // NOTE: Address updates should be dispatched to Redux store, not updated here
261
+ // The calling code should handle the Redux dispatch
262
+ return address;
263
+ };
264
+ this.getAccountById = (id, accountType) => {
265
+ const vault = this.getVault();
266
+ const accounts = vault.accounts[accountType];
267
+ const account = accounts[id];
268
+ if (!account) {
269
+ throw new Error('Account not found');
270
+ }
271
+ return (0, omit_1.default)(account, 'xprv');
272
+ };
273
+ this.getPrivateKeyByAccountId = async (id, accountType, pwd) => {
274
+ try {
275
+ // Validate password using vault salt (same pattern as getSeed)
276
+ if (!this.sessionPassword) {
277
+ throw new Error('Unlock wallet first');
278
+ }
279
+ // Get vault salt for password validation
280
+ const vaultKeys = await this.storage.get('vault-keys');
281
+ if (!vaultKeys || !vaultKeys.salt) {
282
+ throw new Error('Vault keys not found');
283
+ }
284
+ const genPwd = this.encryptSHA512(pwd, vaultKeys.salt);
285
+ if (this.getSessionPasswordString() !== genPwd) {
286
+ throw new Error('Invalid password');
287
+ }
288
+ const vault = this.getVault();
289
+ const account = vault.accounts[accountType][id];
290
+ if (!account) {
291
+ throw new Error('Account not found');
292
+ }
293
+ // Decrypt the stored private key (works for both HD and imported accounts)
294
+ const decryptedPrivateKey = crypto_js_1.default.AES.decrypt(account.xprv, this.getSessionPasswordString()).toString(crypto_js_1.default.enc.Utf8);
295
+ if (!decryptedPrivateKey) {
296
+ throw new Error('Failed to decrypt private key. Invalid password or corrupted data.');
297
+ }
298
+ return decryptedPrivateKey;
299
+ }
300
+ catch (error) {
301
+ console.log('ERROR getPrivateKeyByAccountId', {
302
+ error,
303
+ });
304
+ this.validateAndHandleErrorByMessage(error.message);
305
+ throw error;
306
+ }
307
+ };
308
+ this.getActiveAccount = () => {
309
+ const vault = this.getVault();
310
+ const { accounts, activeAccount } = vault;
311
+ const activeAccountId = activeAccount.id;
312
+ const activeAccountType = activeAccount.type;
313
+ return {
314
+ activeAccount: (0, omit_1.default)(accounts[activeAccountType][activeAccountId], 'xprv'),
315
+ activeAccountType,
316
+ };
317
+ };
318
+ this.getEncryptedXprv = (hd) => {
319
+ return crypto_js_1.default.AES.encrypt(this.getSysActivePrivateKey(hd), this.getSessionPasswordString()).toString();
320
+ };
321
+ this.getSeed = async (pwd) => {
322
+ if (!this.sessionPassword) {
323
+ throw new Error('Unlock wallet first');
324
+ }
325
+ // Get vault salt for password validation (consistent with getPrivateKeyByAccountId)
326
+ const vaultKeys = await this.storage.get('vault-keys');
327
+ if (!vaultKeys || !vaultKeys.salt) {
328
+ throw new Error('Vault keys not found');
329
+ }
330
+ const genPwd = this.encryptSHA512(pwd, vaultKeys.salt);
331
+ if (this.getSessionPasswordString() !== genPwd) {
332
+ throw new Error('Invalid password');
333
+ }
334
+ let { mnemonic } = await (0, storage_1.getDecryptedVault)(pwd);
335
+ if (!mnemonic) {
336
+ throw new Error('Mnemonic not found in vault or is empty');
337
+ }
338
+ // Try to detect if mnemonic is encrypted or plain text
339
+ const isLikelyPlainMnemonic = mnemonic.includes(' ') &&
340
+ (mnemonic.split(' ').length === 12 || mnemonic.split(' ').length === 24);
341
+ if (!isLikelyPlainMnemonic) {
342
+ try {
343
+ mnemonic = crypto_js_1.default.AES.decrypt(mnemonic, pwd).toString(crypto_js_1.default.enc.Utf8);
344
+ }
345
+ catch (decryptError) {
346
+ // If decryption fails, assume mnemonic is already decrypted
347
+ console.warn('Mnemonic decryption failed in getSeed, using as-is:', decryptError.message);
348
+ }
349
+ }
350
+ if (!mnemonic) {
351
+ throw new Error('Failed to decrypt mnemonic or mnemonic is empty after decryption');
352
+ }
353
+ return mnemonic;
354
+ };
355
+ this.setSignerNetwork = async (network) => {
356
+ // With multi-keyring architecture, each keyring is dedicated to specific slip44
357
+ if (sysweb3_network_1.INetworkType.Ethereum !== network.kind &&
358
+ sysweb3_network_1.INetworkType.Syscoin !== network.kind) {
359
+ throw new Error('Unsupported chain');
360
+ }
361
+ // Validate network/chain type compatibility
362
+ if (network.kind === sysweb3_network_1.INetworkType.Ethereum &&
363
+ this.getActiveChain() === sysweb3_network_1.INetworkType.Syscoin) {
364
+ throw new Error('Cannot use Ethereum chain type with Syscoin network');
365
+ }
366
+ if (network.kind === sysweb3_network_1.INetworkType.Syscoin &&
367
+ this.getActiveChain() === sysweb3_network_1.INetworkType.Ethereum) {
368
+ throw new Error('Cannot use Syscoin chain type with Ethereum network');
369
+ }
370
+ // CRITICAL: Prevent UTXO-to-UTXO network switching within same keyring
371
+ // Each UTXO network should have its own KeyringManager instance based on slip44
372
+ const vault = this.getVault();
373
+ if (this.getActiveChain() === sysweb3_network_1.INetworkType.Syscoin && vault.activeNetwork) {
374
+ const currentSlip44 = vault.activeNetwork.slip44;
375
+ const newSlip44 = network.slip44;
376
+ if (currentSlip44 !== newSlip44) {
377
+ throw new Error(`Cannot switch between different UTXO networks within the same keyring. ` +
378
+ `Current network uses slip44=${currentSlip44}, target network uses slip44=${newSlip44}. ` +
379
+ `Each UTXO network requires a separate KeyringManager instance.`);
380
+ }
381
+ }
382
+ try {
383
+ // With multi-keyring architecture:
384
+ // - UTXO: Each keyring is dedicated to one network (slip44), so this is only called during initialization
385
+ // - EVM: All EVM networks share slip44=60, so network can change within the same keyring
386
+ if (network.kind === sysweb3_network_1.INetworkType.Syscoin) {
387
+ // For UTXO networks: validate that active account exists (accounts should be created via addNewAccount/initialize)
388
+ const accountId = vault.activeAccount.id || 0;
389
+ const accountType = vault.activeAccount.type || types_1.KeyringAccountType.HDAccount;
390
+ const accounts = vault.accounts[accountType];
391
+ if (!accounts[accountId] || !accounts[accountId].xpub) {
392
+ throw new Error(`Active account ${accountType}:${accountId} does not exist. Create accounts using addNewAccount() or initializeWalletSecurely() first.`);
393
+ }
394
+ // No additional setup needed - on-demand signers will be created when needed
395
+ }
396
+ else if (network.kind === sysweb3_network_1.INetworkType.Ethereum) {
397
+ // For EVM networks: validate that active account exists
398
+ const accountId = vault.activeAccount.id || 0;
399
+ const accountType = vault.activeAccount.type || types_1.KeyringAccountType.HDAccount;
400
+ const accounts = vault.accounts[accountType];
401
+ if (!accounts[accountId] || !accounts[accountId].xpub) {
402
+ throw new Error(`Active account ${accountType}:${accountId} does not exist. Create accounts using addNewAccount() or initializeWalletSecurely() first.`);
403
+ }
404
+ // Set up EVM provider for network switching
405
+ await this.setSignerEVM(network);
406
+ }
407
+ return {
408
+ success: true,
409
+ };
410
+ }
411
+ catch (err) {
412
+ console.log('ERROR setSignerNetwork', {
413
+ err,
414
+ });
415
+ this.validateAndHandleErrorByMessage(err.message);
416
+ //Rollback to previous values
417
+ console.error('Set Signer Network failed with', err);
418
+ return { success: false };
419
+ }
420
+ };
421
+ this.forgetMainWallet = async (pwd) => {
422
+ const vaultKeys = await this.storage.get('vault-keys');
423
+ if (!vaultKeys || !vaultKeys.salt) {
424
+ throw new Error('Vault keys not found');
425
+ }
426
+ const genPwd = this.encryptSHA512(pwd, vaultKeys.salt);
427
+ if (!this.sessionPassword) {
428
+ throw new Error('Unlock wallet first');
429
+ }
430
+ else if (this.getSessionPasswordString() !== genPwd) {
431
+ throw new Error('Invalid password');
432
+ }
433
+ await this.clearTemporaryLocalKeys(pwd);
434
+ };
435
+ this.importWeb3Account = (mnemonicOrPrivKey) => {
436
+ // Check if it's a hex string (Ethereum private key)
437
+ if (ethers_1.ethers.utils.isHexString(mnemonicOrPrivKey)) {
438
+ return new ethers_1.ethers.Wallet(mnemonicOrPrivKey);
439
+ }
440
+ // Check if it's a zprv/tprv (Syscoin private key)
441
+ const zprvPrefixes = ['zprv', 'tprv', 'vprv', 'xprv'];
442
+ if (zprvPrefixes.some((prefix) => mnemonicOrPrivKey.startsWith(prefix))) {
443
+ throw new Error('Syscoin extended private keys (zprv/tprv) should be imported using importAccount, not importWeb3Account');
444
+ }
445
+ // Otherwise, assume it's a mnemonic
446
+ const account = ethers_1.ethers.Wallet.fromMnemonic(mnemonicOrPrivKey);
447
+ return account;
448
+ };
449
+ this.getAccountXpub = () => {
450
+ const vault = this.getVault();
451
+ const { activeAccount } = vault;
452
+ const account = vault.accounts[activeAccount.type]?.[activeAccount.id];
453
+ if (!account) {
454
+ throw new Error('Active account not found');
455
+ }
456
+ return account.xpub;
457
+ };
458
+ this.isSeedValid = (seedPhrase) => (0, bip39_1.validateMnemonic)(seedPhrase);
459
+ this.createNewSeed = () => (0, bip39_1.generateMnemonic)();
460
+ this.getUTXOState = () => {
461
+ const vault = this.getVault();
462
+ if (vault.activeNetwork.kind !== sysweb3_network_1.INetworkType.Syscoin) {
463
+ throw new Error('Cannot get state in a ethereum network');
464
+ }
465
+ const utxOAccounts = (0, mapValues_1.default)(vault.accounts.HDAccount, (value) => (0, omit_1.default)(value, 'xprv'));
466
+ return {
467
+ ...vault,
468
+ accounts: {
469
+ [types_1.KeyringAccountType.HDAccount]: utxOAccounts,
470
+ [types_1.KeyringAccountType.Imported]: {},
471
+ [types_1.KeyringAccountType.Trezor]: {},
472
+ [types_1.KeyringAccountType.Ledger]: {},
473
+ },
474
+ };
475
+ };
476
+ this.getActiveUTXOAccountState = () => {
477
+ const vault = this.getVault();
478
+ const { activeAccount } = vault;
479
+ return {
480
+ ...vault.accounts.HDAccount[activeAccount.id],
481
+ xprv: undefined,
482
+ };
483
+ };
484
+ this.getNetwork = () => this.getVault().activeNetwork;
485
+ this.createEthAccount = (privateKey) => new ethers_1.ethers.Wallet(privateKey);
486
+ // Helper to get current account data from backend
487
+ this.fetchCurrentAccountData = async (xpub, isChangeAddress) => {
488
+ const vault = this.getVault();
489
+ const { activeNetwork } = vault;
490
+ // Use read-only signer for backend calls - works for all account types including hardware wallets
491
+ const { main } = this.getReadOnlySigner();
492
+ const options = 'tokens=used&details=tokens';
493
+ const { tokens } = await syscoinjs.utils.fetchBackendAccount(main.blockbookURL, xpub, options, true, undefined);
494
+ const { receivingIndex, changeIndex } = this.setLatestIndexesFromXPubTokens(tokens);
495
+ // Get network configuration for BIP84
496
+ const networkConfig = (0, sysweb3_network_1.getNetworkConfig)(activeNetwork.slip44, activeNetwork.currency);
497
+ // BIP84 needs pubTypes and networks config
498
+ // For read-only operations, we construct these from networkConfig
499
+ const pubTypes = networkConfig?.types?.zPubType || {
500
+ mainnet: { zprv: 0x04b2430c, zpub: 0x04b24746 },
501
+ testnet: { vprv: 0x045f18bc, vpub: 0x045f1cf6 },
502
+ };
503
+ const networks = networkConfig?.networks || {
504
+ mainnet: {},
505
+ testnet: {},
506
+ };
507
+ const currentAccount = new bip84_1.default.fromZPub(xpub, pubTypes, networks);
508
+ const addressIndex = isChangeAddress ? changeIndex : receivingIndex;
509
+ return {
510
+ currentAccount,
511
+ addressIndex,
512
+ main,
513
+ };
514
+ };
515
+ this.getAddress = async (xpub, isChangeAddress) => {
516
+ const { currentAccount, addressIndex } = await this.fetchCurrentAccountData(xpub, isChangeAddress);
517
+ const address = currentAccount.getAddress(addressIndex, isChangeAddress, 84);
518
+ return address;
519
+ };
520
+ this.getCurrentAddressPubkey = async (xpub, isChangeAddress) => {
521
+ const { currentAccount, addressIndex } = await this.fetchCurrentAccountData(xpub, isChangeAddress);
522
+ // BIP84 returns the public key as a hex string directly
523
+ return currentAccount.getPublicKey(addressIndex, isChangeAddress);
524
+ };
525
+ this.getCurrentAddressBip32Path = async (xpub, isChangeAddress) => {
526
+ const vault = this.getVault();
527
+ const { activeAccount, activeNetwork } = vault;
528
+ const { addressIndex } = await this.fetchCurrentAccountData(xpub, isChangeAddress);
529
+ // Use the utility function to generate the proper derivation path
530
+ const coinShortcut = activeNetwork.currency.toLowerCase(); // e.g., 'sys', 'btc'
531
+ const path = (0, derivation_paths_1.getAddressDerivationPath)(coinShortcut, activeNetwork.slip44, activeAccount.id, isChangeAddress, addressIndex);
532
+ return path;
533
+ };
534
+ this.logout = () => {
535
+ this.lockWallet();
536
+ };
537
+ /**
538
+ * PRIVATE METHODS
539
+ */
540
+ // ===================================== AUXILIARY METHOD - FOR TRANSACTIONS CLASSES ===================================== //
541
+ this.getDecryptedPrivateKey = () => {
542
+ try {
543
+ const vault = this.getVault();
544
+ const { accounts, activeAccount } = vault;
545
+ const activeAccountId = activeAccount.id;
546
+ const activeAccountType = activeAccount.type;
547
+ if (!this.sessionPassword)
548
+ throw new Error('Wallet is locked cant proceed with transaction');
549
+ const activeAccountData = accounts[activeAccountType][activeAccountId];
550
+ if (!activeAccountData) {
551
+ throw new Error(`Active account (${activeAccountType}:${activeAccountId}) not found. Account switching may be in progress.`);
552
+ }
553
+ const { xprv, address } = activeAccountData;
554
+ if (!xprv) {
555
+ throw new Error(`Private key not found for account ${activeAccountType}:${activeAccountId}. Account may not be fully initialized.`);
556
+ }
557
+ let decryptedPrivateKey;
558
+ try {
559
+ decryptedPrivateKey = crypto_js_1.default.AES.decrypt(xprv, this.getSessionPasswordString()).toString(crypto_js_1.default.enc.Utf8);
560
+ }
561
+ catch (decryptError) {
562
+ throw new Error(`Failed to decrypt private key for account ${activeAccountType}:${activeAccountId}. The wallet may be locked or corrupted.`);
563
+ }
564
+ if (!decryptedPrivateKey) {
565
+ throw new Error(`Decrypted private key is empty for account ${activeAccountType}:${activeAccountId}. Invalid password or corrupted data.`);
566
+ }
567
+ // For EVM accounts, validate that the derived address matches the stored address
568
+ // This helps catch account switching race conditions early
569
+ if (this.getActiveChain() === sysweb3_network_1.INetworkType.Ethereum) {
570
+ try {
571
+ const derivedWallet = new ethers_1.ethers.Wallet(decryptedPrivateKey);
572
+ if (derivedWallet.address.toLowerCase() !== address.toLowerCase()) {
573
+ throw new Error(`Address mismatch for account ${activeAccountType}:${activeAccountId}. Expected ${address} but derived ${derivedWallet.address}. Account switching may be in progress.`);
574
+ }
575
+ }
576
+ catch (ethersError) {
577
+ throw new Error(`Failed to validate EVM address for account ${activeAccountType}:${activeAccountId}: ${ethersError.message}`);
578
+ }
579
+ }
580
+ return {
581
+ address,
582
+ decryptedPrivateKey,
583
+ };
584
+ }
585
+ catch (error) {
586
+ const vaultForLogging = this.getVault();
587
+ console.error('ERROR getDecryptedPrivateKey', {
588
+ error: error.message,
589
+ activeChain: this.getActiveChain(),
590
+ vault: {
591
+ activeAccountId: vaultForLogging?.activeAccount?.id,
592
+ activeAccountType: vaultForLogging?.activeAccount?.type,
593
+ },
594
+ });
595
+ this.validateAndHandleErrorByMessage(error.message);
596
+ throw error;
597
+ }
598
+ };
599
+ this.getSigner = () => {
600
+ if (!this.sessionPassword) {
601
+ throw new Error('Wallet is locked cant proceed with transaction');
602
+ }
603
+ if (this.getActiveChain() !== sysweb3_network_1.INetworkType.Syscoin) {
604
+ throw new Error('Switch to UTXO chain');
605
+ }
606
+ // Create fresh on-demand signer for active account (now synchronous!)
607
+ const freshHDSigner = this.createOnDemandSignerForActiveAccount();
608
+ // Create fresh syscoinjs instance with current network
609
+ const vault = this.getVault();
610
+ const network = vault.activeNetwork;
611
+ const networkConfig = (0, sysweb3_network_1.getNetworkConfig)(network.slip44, network.currency);
612
+ const syscoinMainSigner = new syscoinjs.SyscoinJSLib(freshHDSigner, network.url, networkConfig?.networks?.mainnet || undefined);
613
+ return {
614
+ hd: freshHDSigner,
615
+ main: syscoinMainSigner,
616
+ };
617
+ };
618
+ // Read-only version that works when wallet is locked
619
+ this.getReadOnlySigner = () => {
620
+ if (this.getActiveChain() !== sysweb3_network_1.INetworkType.Syscoin) {
621
+ throw new Error('Switch to UTXO chain');
622
+ }
623
+ // Create syscoinjs instance without HD signer for read-only operations
624
+ const vault = this.getVault();
625
+ const network = vault.activeNetwork;
626
+ const networkConfig = (0, sysweb3_network_1.getNetworkConfig)(network.slip44, network.currency);
627
+ const syscoinMainSigner = new syscoinjs.SyscoinJSLib(null, // No HD signer needed for read-only operations
628
+ network.url, networkConfig?.networks?.mainnet || undefined);
629
+ return {
630
+ main: syscoinMainSigner,
631
+ };
632
+ };
633
+ this.getAccountsState = () => {
634
+ const vault = this.getVault();
635
+ const { accounts, activeAccount, activeNetwork } = vault;
636
+ return {
637
+ activeAccountId: activeAccount.id,
638
+ accounts,
639
+ activeAccountType: activeAccount.type,
640
+ activeNetwork,
641
+ };
642
+ };
643
+ /**
644
+ *
645
+ * @param password
646
+ * @param salt
647
+ * @returns hash: string
648
+ */
649
+ this.encryptSHA512 = (password, salt) => crypto_1.default.createHmac('sha512', salt).update(password).digest('hex');
650
+ this.getSysActivePrivateKey = (hd) => {
651
+ if (hd === null)
652
+ throw new Error('No HD Signer');
653
+ const accountIndex = hd.Signer.accountIndex;
654
+ // Verify the account exists now
655
+ if (!hd.Signer.accounts.has(accountIndex)) {
656
+ throw new Error(`Account at index ${accountIndex} could not be created`);
657
+ }
658
+ return hd.Signer.accounts.get(accountIndex).getAccountPrivateKey();
659
+ };
660
+ this.getInitialAccountData = ({ label, signer, sysAccount, xprv, }) => {
661
+ const { address, xpub } = sysAccount;
662
+ return {
663
+ id: signer.Signer.accountIndex,
664
+ label: label || `Account ${signer.Signer.accountIndex + 1}`,
665
+ xpub,
666
+ xprv,
667
+ address,
668
+ isTrezorWallet: false,
669
+ isLedgerWallet: false,
670
+ isImported: false,
671
+ };
672
+ };
673
+ this.getFormattedBackendAccount = async ({ signer, }) => {
674
+ // MUCH SIMPLER: Just use the signer directly - no BIP84 needed!
675
+ // Get address directly from the signer (always correct for current network)
676
+ const address = signer.createAddress(0, false, 84);
677
+ const xpub = signer.getAccountXpub();
678
+ return {
679
+ address,
680
+ xpub,
681
+ };
682
+ };
683
+ this.setLatestIndexesFromXPubTokens = function (tokens) {
684
+ let changeIndexInternal = -1, receivingIndexInternal = -1;
685
+ if (tokens) {
686
+ tokens.forEach((token) => {
687
+ if (!token.transfers || !token.path) {
688
+ return {
689
+ changeIndex: changeIndexInternal + 1,
690
+ receivingIndex: receivingIndexInternal + 1,
691
+ };
692
+ }
693
+ const transfers = parseInt(token.transfers, 10);
694
+ if (token.path && transfers > 0) {
695
+ const splitPath = token.path.split('/');
696
+ if (splitPath.length >= 6) {
697
+ const change = parseInt(splitPath[4], 10);
698
+ const index = parseInt(splitPath[5], 10);
699
+ if (change === 1) {
700
+ if (index > changeIndexInternal) {
701
+ changeIndexInternal = index;
702
+ }
703
+ }
704
+ else if (index > receivingIndexInternal) {
705
+ receivingIndexInternal = index;
706
+ }
707
+ }
708
+ }
709
+ });
710
+ }
711
+ return {
712
+ changeIndex: changeIndexInternal + 1,
713
+ receivingIndex: receivingIndexInternal + 1,
714
+ };
715
+ };
716
+ this.getBasicWeb3AccountInfo = (id, label) => {
717
+ return {
718
+ id,
719
+ isTrezorWallet: false,
720
+ isLedgerWallet: false,
721
+ label: label ? label : `Account ${id + 1}`,
722
+ };
723
+ };
724
+ this.setDerivedWeb3Accounts = async (id, label) => {
725
+ try {
726
+ // For account creation, derive from mnemonic (since account doesn't exist yet)
727
+ const mnemonic = this.getDecryptedMnemonic();
728
+ const hdNode = ethers_1.ethers.utils.HDNode.fromMnemonic(mnemonic);
729
+ const derivationPath = (0, derivation_paths_1.getAddressDerivationPath)('eth', 60, 0, false, id);
730
+ const derivedAccount = hdNode.derivePath(derivationPath);
731
+ const basicAccountInfo = this.getBasicWeb3AccountInfo(id, label);
732
+ const createdAccount = {
733
+ address: derivedAccount.address,
734
+ xpub: derivedAccount.publicKey,
735
+ xprv: crypto_js_1.default.AES.encrypt(derivedAccount.privateKey, this.getSessionPasswordString()).toString(),
736
+ isImported: false,
737
+ ...basicAccountInfo,
738
+ balances: { syscoin: 0, ethereum: 0 },
739
+ };
740
+ // NOTE: Account creation should be dispatched to Redux store, not stored here
741
+ // Return the account data for Pali to add to store
742
+ return createdAccount;
743
+ }
744
+ catch (error) {
745
+ console.log('ERROR setDerivedWeb3Accounts', {
746
+ error,
747
+ });
748
+ this.validateAndHandleErrorByMessage(error.message);
749
+ throw error;
750
+ }
751
+ };
752
+ this.setSignerEVM = async (network) => {
753
+ const abortController = new AbortController();
754
+ try {
755
+ // With multi-keyring architecture, this is only called on EVM keyrings
756
+ this.ethereumTransaction.setWeb3Provider(network);
757
+ abortController.abort();
758
+ }
759
+ catch (error) {
760
+ abortController.abort();
761
+ throw new Error(`SetSignerEVM: Failed with ${error}`);
762
+ }
763
+ };
764
+ this.clearTemporaryLocalKeys = async (pwd) => {
765
+ // Clear the vault completely (set empty mnemonic)
766
+ await (0, storage_1.setEncryptedVault)({
767
+ mnemonic: '',
768
+ }, pwd);
769
+ // Remove vault-keys from storage so no vault exists at all
770
+ await this.storage.deleteItem('vault-keys');
771
+ console.log('[KeyringManager] Temporary local keys cleared');
772
+ this.logout();
773
+ };
774
+ // NEW: Separate session initialization from account creation
775
+ this.initializeSession = async (seedPhrase, password) => {
776
+ // Validate inputs first
777
+ if (!(0, bip39_1.validateMnemonic)(seedPhrase)) {
778
+ throw new Error('Invalid Seed');
779
+ }
780
+ let foundVaultKeys = true;
781
+ let salt = '';
782
+ const vaultKeys = await this.storage.get('vault-keys');
783
+ if (!vaultKeys || !vaultKeys.salt) {
784
+ foundVaultKeys = false;
785
+ salt = crypto_1.default.randomBytes(16).toString('hex');
786
+ }
787
+ else {
788
+ salt = vaultKeys.salt;
789
+ }
790
+ const sessionPasswordSaltedHash = this.encryptSHA512(password, salt);
791
+ if (!foundVaultKeys) {
792
+ // Store vault-keys using the storage abstraction
793
+ await this.storage.set('vault-keys', {
794
+ hash: sessionPasswordSaltedHash,
795
+ salt,
796
+ });
797
+ }
798
+ // Check if already initialized with the same password (idempotent behavior)
799
+ if (this.sessionPassword) {
800
+ if (sessionPasswordSaltedHash === this.getSessionPasswordString()) {
801
+ // Same password - check if it's the same mnemonic to ensure full idempotency
802
+ try {
803
+ const currentMnemonic = crypto_js_1.default.AES.decrypt(this.getSessionMnemonicString(), this.getSessionPasswordString()).toString(crypto_js_1.default.enc.Utf8);
804
+ if (currentMnemonic === seedPhrase) {
805
+ // Same mnemonic and password - already initialized
806
+ return;
807
+ }
808
+ }
809
+ catch (error) {
810
+ // If we can't decrypt, fall through to error
811
+ }
812
+ }
813
+ // Different password or mnemonic - this is not a simple re-initialization
814
+ throw new Error('Wallet already initialized with different parameters. Create a new keyring instance for different parameters.');
815
+ }
816
+ // Encrypt and store vault (mnemonic storage) - now uses single vault for all networks
817
+ await (0, storage_1.setEncryptedVault)({
818
+ mnemonic: seedPhrase, // Store plain mnemonic - setEncryptedVault will encrypt the entire vault
819
+ }, password);
820
+ await this.recreateSessionFromVault(password, sessionPasswordSaltedHash);
821
+ };
822
+ // NEW: Create first account without signer setup
823
+ this.createFirstAccount = async (label) => {
824
+ if (!this.sessionPassword || !this.sessionMnemonic) {
825
+ throw new Error('Session must be initialized first. Call initializeSession.');
826
+ }
827
+ const vault = this.getVault();
828
+ const network = vault.activeNetwork;
829
+ if (network.kind === sysweb3_network_1.INetworkType.Syscoin) {
830
+ // UTXO accounts get network-aware labels since each network has separate keyrings
831
+ const defaultLabel = label || this.generateNetworkAwareLabel(0, network);
832
+ // Create UTXO account using on-demand signer
833
+ const freshHDSigner = this.createOnDemandUTXOSigner(0);
834
+ const sysAccount = await this.getFormattedBackendAccount({
835
+ signer: freshHDSigner,
836
+ });
837
+ const encryptedXprv = this.getEncryptedXprv(freshHDSigner);
838
+ return {
839
+ ...this.getInitialAccountData({
840
+ label: defaultLabel,
841
+ signer: freshHDSigner,
842
+ sysAccount,
843
+ xprv: encryptedXprv,
844
+ }),
845
+ balances: { syscoin: 0, ethereum: 0 },
846
+ };
847
+ }
848
+ else {
849
+ // EVM accounts get generic labels since they work across all EVM networks
850
+ const defaultLabel = label || 'Account 1';
851
+ return await this.setDerivedWeb3Accounts(0, defaultLabel);
852
+ }
853
+ };
854
+ this.initializeWalletSecurely = async (seedPhrase, password) => {
855
+ // Use new separated approach
856
+ await this.initializeSession(seedPhrase, password);
857
+ return await this.createFirstAccount();
858
+ };
859
+ this.storage = sysweb3.sysweb3Di.getStateStorageDb();
860
+ // Don't initialize secure buffers in constructor - they're created on unlock
861
+ this.storage.set('utf8Error', {
862
+ hasUtf8Error: false,
863
+ });
864
+ // NOTE: activeChain is now derived from vault state, not stored locally
865
+ // NOTE: No more persistent signers - use getSigner() for fresh on-demand signers
866
+ this.utf8Error = false;
867
+ // sessionMnemonic is initialized as null - created on unlock
868
+ this.initialTrezorAccountState = initial_state_1.initialActiveTrezorAccountState;
869
+ this.initialLedgerAccountState = initial_state_1.initialActiveLedgerAccountState;
870
+ this.trezorSigner = new trezor_1.TrezorKeyring(this.getSigner);
871
+ this.ledgerSigner = new ledger_1.LedgerKeyring();
872
+ // this.syscoinTransaction = SyscoinTransactions();
873
+ this.syscoinTransaction = new transactions_1.SyscoinTransactions(this.getSigner, this.getReadOnlySigner, this.getAccountsState, this.getAddress, this.ledgerSigner, this.trezorSigner);
874
+ this.ethereumTransaction = new transactions_1.EthereumTransactions(this.getNetwork, this.getDecryptedPrivateKey, this.getAccountsState, this.ledgerSigner, this.trezorSigner);
875
+ }
876
+ // Static factory method for creating a fully initialized KeyringManager with slip44 support
877
+ static async createInitialized(seed, password, vaultStateGetter) {
878
+ const keyringManager = new KeyringManager();
879
+ // Set the vault state getter
880
+ keyringManager.setVaultStateGetter(vaultStateGetter);
881
+ // Use the new secure initialization method (eliminates temporary plaintext storage)
882
+ await keyringManager.initializeWalletSecurely(seed, password);
883
+ // NOTE: Active account management is now handled by vault state/Redux
884
+ // No need to explicitly set active account - it's managed externally
885
+ return keyringManager;
886
+ }
887
+ // Convenience method for complete setup after construction
888
+ async initialize(seed, password, network) {
889
+ // Set the network if provided (this is crucial for proper address derivation)
890
+ if (network) {
891
+ await this.setSignerNetwork(network);
892
+ }
893
+ // Use the new secure initialization method (eliminates temporary plaintext storage)
894
+ const account = await this.initializeWalletSecurely(seed, password);
895
+ // NOTE: Active account management is now handled by vault state/Redux
896
+ // No need to explicitly set active account - it's managed externally
897
+ return account;
898
+ }
899
+ async unlock(password) {
900
+ try {
901
+ const vaultKeys = await this.storage.get('vault-keys');
902
+ if (!vaultKeys) {
903
+ return {
904
+ canLogin: false,
905
+ };
906
+ }
907
+ // FIRST: Validate password against stored hash
908
+ const { hash, salt } = vaultKeys;
909
+ const saltedHashPassword = this.encryptSHA512(password, salt);
910
+ if (saltedHashPassword !== hash) {
911
+ // Password is wrong - return immediately
912
+ return {
913
+ canLogin: false,
914
+ };
915
+ }
916
+ // Handle migration from old vault format with currentSessionSalt
917
+ if (vaultKeys.currentSessionSalt) {
918
+ console.log('[KeyringManager] Detected old vault format, handling session migration...');
919
+ // The old format used currentSessionSalt for session data encryption
920
+ // We need to use it temporarily to decrypt the mnemonic correctly
921
+ const oldSessionPassword = this.encryptSHA512(password, vaultKeys.currentSessionSalt);
922
+ // Get the vault and check if mnemonic needs migration
923
+ const { mnemonic } = await (0, storage_1.getDecryptedVault)(password);
924
+ if (mnemonic) {
925
+ // Check if mnemonic is double-encrypted (old format behavior)
926
+ const isLikelyPlainMnemonic = mnemonic.includes(' ') &&
927
+ (mnemonic.split(' ').length === 12 ||
928
+ mnemonic.split(' ').length === 24);
929
+ let decryptedMnemonic = mnemonic;
930
+ if (!isLikelyPlainMnemonic) {
931
+ try {
932
+ // Try to decrypt with raw password first (as vault stores it)
933
+ decryptedMnemonic = crypto_js_1.default.AES.decrypt(mnemonic, password).toString(crypto_js_1.default.enc.Utf8);
934
+ }
935
+ catch (e) {
936
+ console.warn('[KeyringManager] Failed to decrypt mnemonic with password, trying old session password');
937
+ // If that fails, try with old session password
938
+ try {
939
+ decryptedMnemonic = crypto_js_1.default.AES.decrypt(mnemonic, oldSessionPassword).toString(crypto_js_1.default.enc.Utf8);
940
+ }
941
+ catch (e2) {
942
+ // If both fail, assume it's already decrypted
943
+ decryptedMnemonic = mnemonic;
944
+ }
945
+ }
946
+ }
947
+ // Re-save the vault with properly formatted mnemonic (single encryption)
948
+ await (0, storage_1.setEncryptedVault)({ mnemonic: decryptedMnemonic }, password);
949
+ console.log('[KeyringManager] Vault mnemonic format normalized');
950
+ }
951
+ // Remove currentSessionSalt from vault-keys
952
+ const migratedVaultKeys = {
953
+ hash: vaultKeys.hash,
954
+ salt: vaultKeys.salt,
955
+ };
956
+ await this.storage.set('vault-keys', migratedVaultKeys);
957
+ console.log('[KeyringManager] Old vault format migration completed');
958
+ }
959
+ // If session data missing or corrupted, recreate from vault
960
+ if (!this.sessionMnemonic) {
961
+ await this.recreateSessionFromVault(password, saltedHashPassword);
962
+ }
963
+ // NOTE: Active account management is now handled by vault state/Redux
964
+ // No need to explicitly set active account after unlock - it's managed externally
965
+ const vault = this.getVault();
966
+ if (vault.activeAccount?.id !== undefined && vault.activeAccount?.type) {
967
+ // Check if the active account actually exists in the accounts map
968
+ const accountType = vault.activeAccount.type;
969
+ const accountId = vault.activeAccount.id;
970
+ const accountExists = vault.accounts?.[accountType]?.[accountId];
971
+ if (!accountExists) {
972
+ console.log(`[KeyringManager] Active account ${accountType}:${accountId} not found in accounts map. This may indicate a migration from old vault format.`);
973
+ // Signal that accounts need to be created after migration
974
+ return {
975
+ canLogin: true,
976
+ needsAccountCreation: true,
977
+ };
978
+ }
979
+ console.log(`[KeyringManager] Active account ${vault.activeAccount.id} available after unlock`);
980
+ }
981
+ return {
982
+ canLogin: true,
983
+ };
984
+ }
985
+ catch (error) {
986
+ console.log('ERROR unlock', {
987
+ error,
988
+ });
989
+ return {
990
+ canLogin: false,
991
+ };
992
+ }
993
+ }
994
+ async importTrezorAccount(label) {
995
+ const vault = this.getVault();
996
+ const currency = vault.activeNetwork.currency;
997
+ if (!currency) {
998
+ throw new Error('Active network currency is not defined');
999
+ }
1000
+ // Use getNextAccountId to filter out placeholder accounts
1001
+ const nextIndex = this.getNextAccountId(vault.accounts[types_1.KeyringAccountType.Trezor]);
1002
+ const importedAccount = await this._createTrezorAccount(currency, vault.activeNetwork.slip44, nextIndex);
1003
+ importedAccount.label = label ? label : `Trezor ${importedAccount.id + 1}`;
1004
+ // NOTE: Account creation should be dispatched to Redux store, not updated here
1005
+ // The calling code should handle the Redux dispatch
1006
+ // Return the created account for Pali to add to store
1007
+ return importedAccount;
1008
+ }
1009
+ async importLedgerAccount(label) {
1010
+ try {
1011
+ const vault = this.getVault();
1012
+ const currency = vault.activeNetwork.currency;
1013
+ if (!currency) {
1014
+ throw new Error('Active network currency is not defined');
1015
+ }
1016
+ // Use getNextAccountId to filter out placeholder accounts
1017
+ const nextIndex = this.getNextAccountId(vault.accounts[types_1.KeyringAccountType.Ledger]);
1018
+ const importedAccount = await this._createLedgerAccount(currency, vault.activeNetwork.slip44, nextIndex);
1019
+ importedAccount.label = label
1020
+ ? label
1021
+ : `Ledger ${importedAccount.id + 1}`;
1022
+ // NOTE: Account creation should be dispatched to Redux store, not updated here
1023
+ // The calling code should handle the Redux dispatch
1024
+ // Return the created account for Pali to add to store
1025
+ return importedAccount;
1026
+ }
1027
+ catch (error) {
1028
+ console.log({ error });
1029
+ throw error;
1030
+ }
1031
+ }
1032
+ async importAccount(privKey, label) {
1033
+ // Check if wallet is unlocked
1034
+ if (!this.isUnlocked()) {
1035
+ throw new Error('Wallet must be unlocked to import accounts');
1036
+ }
1037
+ const importedAccount = await this._getPrivateKeyAccountInfos(privKey, label);
1038
+ // NOTE: Account creation should be dispatched to Redux store, not updated here
1039
+ // The calling code should handle the Redux dispatch
1040
+ // Return the created account for Pali to add to store
1041
+ return importedAccount;
1042
+ }
1043
+ validateZprv(zprv, targetNetwork) {
1044
+ // Use the active network if targetNetwork is not provided
1045
+ const networkToValidateAgainst = targetNetwork || this.getVault().activeNetwork;
1046
+ if (!networkToValidateAgainst) {
1047
+ throw new Error('No network available for validation');
1048
+ }
1049
+ try {
1050
+ // Check if it looks like an extended key based on known prefixes
1051
+ const knownExtendedKeyPrefixes = [
1052
+ 'xprv',
1053
+ 'xpub',
1054
+ 'yprv',
1055
+ 'ypub',
1056
+ 'zprv',
1057
+ 'zpub',
1058
+ 'tprv',
1059
+ 'tpub',
1060
+ 'uprv',
1061
+ 'upub',
1062
+ 'vprv',
1063
+ 'vpub',
1064
+ ];
1065
+ const prefix = zprv.substring(0, 4);
1066
+ const looksLikeExtendedKey = knownExtendedKeyPrefixes.includes(prefix);
1067
+ // Only check prefix validity if it looks like an extended key
1068
+ if (looksLikeExtendedKey) {
1069
+ const validBip84Prefixes = ['zprv', 'vprv']; // zprv for mainnet, vprv for testnet
1070
+ if (!validBip84Prefixes.includes(prefix)) {
1071
+ throw new Error(`Invalid key prefix '${prefix}'. Only BIP84 keys (zprv/vprv) are supported for UTXO imports. BIP44 keys (xprv/tprv) are not supported.`);
1072
+ }
1073
+ }
1074
+ else {
1075
+ // Not an extended key format
1076
+ throw new Error('Not an extended private key');
1077
+ }
1078
+ const bip32 = (0, bip32_1.BIP32Factory)(secp256k1_1.default);
1079
+ const decoded = bs58check_1.default.decode(zprv);
1080
+ if (decoded.length !== 78) {
1081
+ throw new Error('Invalid length for a BIP-32 key');
1082
+ }
1083
+ // Get network configuration for the target network
1084
+ const { networks, types } = (0, sysweb3_network_1.getNetworkConfig)(networkToValidateAgainst.slip44, networkToValidateAgainst.currency || 'Bitcoin');
1085
+ // For BIP84 (zprv/zpub), we need to use the correct magic bytes from zPubType
1086
+ // Determine key type: zprv = mainnet key, vprv = testnet key
1087
+ const keyIsTestnet = prefix === 'vprv';
1088
+ // Determine target network type: testnet networks typically have chainId 5700+ or slip44 1
1089
+ const targetIsTestnet = networkToValidateAgainst.chainId >= 5700 ||
1090
+ networkToValidateAgainst.slip44 === 1;
1091
+ // Cross-network validation: reject if key type doesn't match target network
1092
+ if (keyIsTestnet && !targetIsTestnet) {
1093
+ throw new Error(`Extended private key is not compatible with ${networkToValidateAgainst.label}. ` +
1094
+ `This appears to be a testnet key (${prefix}) but the target network is mainnet.`);
1095
+ }
1096
+ if (!keyIsTestnet && targetIsTestnet) {
1097
+ throw new Error(`Extended private key is not compatible with ${networkToValidateAgainst.label}. ` +
1098
+ `This appears to be a mainnet key (${prefix}) but the target network is testnet.`);
1099
+ }
1100
+ const pubTypes = keyIsTestnet
1101
+ ? types.zPubType.testnet
1102
+ : types.zPubType.mainnet;
1103
+ const baseNetwork = keyIsTestnet ? networks.testnet : networks.mainnet;
1104
+ const network = {
1105
+ ...baseNetwork,
1106
+ bip32: {
1107
+ public: parseInt(pubTypes.vpub || pubTypes.zpub, 16),
1108
+ private: parseInt(pubTypes.vprv || pubTypes.zprv, 16),
1109
+ },
1110
+ };
1111
+ // Validate that the key prefix matches the expected network format
1112
+ // This ensures the key was generated for a compatible network
1113
+ const expectedPrefixes = ['zprv', 'vprv', 'xprv', 'yprv']; // Accept various BIP32/84 formats
1114
+ if (!expectedPrefixes.includes(prefix)) {
1115
+ throw new Error(`Invalid extended private key prefix: ${prefix}. Expected one of: ${expectedPrefixes.join(', ')}`);
1116
+ }
1117
+ // Strict network matching - only allow keys that match the target network
1118
+ let node;
1119
+ try {
1120
+ node = bip32.fromBase58(zprv, network);
1121
+ }
1122
+ catch (e) {
1123
+ throw new Error(`Extended private key is not compatible with ${networkToValidateAgainst.label}. Please use a key generated for this specific network.`);
1124
+ }
1125
+ if (!node.privateKey) {
1126
+ throw new Error('Private key not found in extended private key');
1127
+ }
1128
+ if (!secp256k1_1.default.isPrivate(node.privateKey)) {
1129
+ throw new Error('Invalid private key for secp256k1 curve');
1130
+ }
1131
+ return {
1132
+ isValid: true,
1133
+ node,
1134
+ network,
1135
+ message: 'The extended private key is valid for this network.',
1136
+ };
1137
+ }
1138
+ catch (error) {
1139
+ return { isValid: false, message: error.message };
1140
+ }
1141
+ }
1142
+ validateAndHandleErrorByMessage(message) {
1143
+ const utf8ErrorMessage = 'Malformed UTF-8 data';
1144
+ if (message.includes(utf8ErrorMessage) ||
1145
+ message.toLowerCase().includes(utf8ErrorMessage.toLowerCase())) {
1146
+ this.storage.set('utf8Error', { hasUtf8Error: true });
1147
+ }
1148
+ }
1149
+ async _createTrezorAccount(coin, slip44, index, label) {
1150
+ const vault = this.getVault();
1151
+ const { accounts } = vault;
1152
+ let xpub, balance;
1153
+ // For EVM networks, Trezor expects 'eth' regardless of the network's currency
1154
+ const trezorCoin = slip44 === 60 ? 'eth' : coin;
1155
+ try {
1156
+ const { descriptor, balance: _balance } = await this.trezorSigner.getAccountInfo({
1157
+ coin: trezorCoin,
1158
+ slip44,
1159
+ index,
1160
+ });
1161
+ xpub = descriptor;
1162
+ balance = _balance;
1163
+ }
1164
+ catch (e) {
1165
+ throw new Error(e);
1166
+ }
1167
+ let ethPubKey = '';
1168
+ const isEVM = (0, derivation_paths_1.isEvmCoin)(coin, slip44);
1169
+ // For EVM networks, we need to get the actual address from Trezor
1170
+ let address;
1171
+ if (isEVM) {
1172
+ // For EVM, the descriptor from getAccountInfo is the address
1173
+ address = xpub;
1174
+ // Get the public key for EVM
1175
+ const response = await this.trezorSigner.getPublicKey({
1176
+ coin: trezorCoin,
1177
+ slip44,
1178
+ index: +index,
1179
+ });
1180
+ ethPubKey = response.publicKey;
1181
+ }
1182
+ else {
1183
+ // For UTXO, use the xpub to derive the address
1184
+ address = await this.getAddress(xpub, false);
1185
+ }
1186
+ const accountAlreadyExists = Object.values(accounts[types_1.KeyringAccountType.Ledger]).some((account) => account.address === address) ||
1187
+ Object.values(accounts[types_1.KeyringAccountType.Trezor]).some((account) => account.address === address) ||
1188
+ Object.values(accounts[types_1.KeyringAccountType.HDAccount]).some((account) => account.address === address) ||
1189
+ Object.values(accounts[types_1.KeyringAccountType.Imported]).some((account) => account.address === address);
1190
+ if (accountAlreadyExists)
1191
+ throw new Error('Account already exists on your Wallet.');
1192
+ if (!xpub || !address)
1193
+ throw new Error('Something wrong happened. Please, try again or report it');
1194
+ // Use getNextAccountId to properly handle placeholder accounts
1195
+ const id = this.getNextAccountId(accounts[types_1.KeyringAccountType.Trezor]);
1196
+ const trezorAccount = {
1197
+ ...this.initialTrezorAccountState,
1198
+ balances: {
1199
+ syscoin: isEVM ? 0 : +balance / 1e8,
1200
+ ethereum: 0,
1201
+ },
1202
+ address,
1203
+ label: label ? label : `Trezor ${id + 1}`,
1204
+ id,
1205
+ xprv: '',
1206
+ xpub: isEVM ? ethPubKey : xpub,
1207
+ assets: {
1208
+ syscoin: [],
1209
+ ethereum: [],
1210
+ },
1211
+ };
1212
+ return trezorAccount;
1213
+ }
1214
+ async _createLedgerAccount(coin, slip44, index, label) {
1215
+ const vault = this.getVault();
1216
+ const { accounts } = vault;
1217
+ let xpub;
1218
+ let address = '';
1219
+ if ((0, derivation_paths_1.isEvmCoin)(coin, slip44)) {
1220
+ const { address: ethAddress, publicKey } = await this.ledgerSigner.evm.getEvmAddressAndPubKey({
1221
+ accountIndex: index,
1222
+ });
1223
+ address = ethAddress;
1224
+ xpub = publicKey;
1225
+ }
1226
+ else {
1227
+ try {
1228
+ const ledgerXpub = await this.ledgerSigner.utxo.getXpub({
1229
+ index: index,
1230
+ coin,
1231
+ slip44,
1232
+ });
1233
+ xpub = ledgerXpub;
1234
+ // Use the generic getAddress method like Trezor does - no need to query device again
1235
+ address = await this.getAddress(xpub, false);
1236
+ }
1237
+ catch (e) {
1238
+ throw new Error(e);
1239
+ }
1240
+ }
1241
+ const accountAlreadyExists = Object.values(accounts[types_1.KeyringAccountType.Ledger]).some((account) => account.address === address) ||
1242
+ Object.values(accounts[types_1.KeyringAccountType.Trezor]).some((account) => account.address === address) ||
1243
+ Object.values(accounts[types_1.KeyringAccountType.HDAccount]).some((account) => account.address === address) ||
1244
+ Object.values(accounts[types_1.KeyringAccountType.Imported]).some((account) => account.address === address);
1245
+ if (accountAlreadyExists)
1246
+ throw new Error('Account already exists on your Wallet.');
1247
+ if (!xpub || !address)
1248
+ throw new Error('Something wrong happened. Please, try again or report it');
1249
+ // Use getNextAccountId to properly handle placeholder accounts
1250
+ const id = this.getNextAccountId(accounts[types_1.KeyringAccountType.Ledger]);
1251
+ const currentBalances = { syscoin: 0, ethereum: 0 };
1252
+ const ledgerAccount = {
1253
+ ...this.initialLedgerAccountState,
1254
+ balances: currentBalances,
1255
+ address,
1256
+ label: label ? label : `Ledger ${id + 1}`,
1257
+ id,
1258
+ xprv: '',
1259
+ xpub,
1260
+ assets: {
1261
+ syscoin: [],
1262
+ ethereum: [],
1263
+ },
1264
+ };
1265
+ return ledgerAccount;
1266
+ }
1267
+ // Common helper method for UTXO account creation
1268
+ async createUTXOAccountAtIndex(accountId, label) {
1269
+ try {
1270
+ // Create fresh signer just for this account creation operation
1271
+ const freshHDSigner = this.createOnDemandUTXOSigner(accountId);
1272
+ const sysAccount = await this.getFormattedBackendAccount({
1273
+ signer: freshHDSigner,
1274
+ });
1275
+ const encryptedXprv = this.getEncryptedXprv(freshHDSigner);
1276
+ // Generate network-aware label if none provided
1277
+ const vault = this.getVault();
1278
+ const network = vault.activeNetwork;
1279
+ const defaultLabel = this.generateNetworkAwareLabel(accountId, network);
1280
+ return {
1281
+ ...this.getInitialAccountData({
1282
+ label: label || defaultLabel,
1283
+ signer: freshHDSigner,
1284
+ sysAccount,
1285
+ xprv: encryptedXprv,
1286
+ }),
1287
+ balances: { syscoin: 0, ethereum: 0 },
1288
+ };
1289
+ }
1290
+ catch (error) {
1291
+ console.log('ERROR createUTXOAccountAtIndex', {
1292
+ error,
1293
+ });
1294
+ this.validateAndHandleErrorByMessage(error.message);
1295
+ throw error;
1296
+ }
1297
+ }
1298
+ async addNewAccountToSyscoinChain(label) {
1299
+ try {
1300
+ // Get next available account ID
1301
+ const vault = this.getVault();
1302
+ const accounts = vault.accounts[types_1.KeyringAccountType.HDAccount];
1303
+ const nextId = this.getNextAccountId(accounts);
1304
+ return await this.createUTXOAccountAtIndex(nextId, label);
1305
+ }
1306
+ catch (error) {
1307
+ console.log('ERROR addNewAccountToSyscoinChain', {
1308
+ error,
1309
+ });
1310
+ this.validateAndHandleErrorByMessage(error.message);
1311
+ throw error;
1312
+ }
1313
+ }
1314
+ async addNewAccountToEth(label) {
1315
+ try {
1316
+ // Get next available account ID
1317
+ const vault = this.getVault();
1318
+ const accounts = vault.accounts[types_1.KeyringAccountType.HDAccount];
1319
+ const nextId = this.getNextAccountId(accounts);
1320
+ // EVM accounts should use generic labels since they work across all EVM networks
1321
+ const defaultLabel = `Account ${nextId + 1}`;
1322
+ const newAccount = await this.setDerivedWeb3Accounts(nextId, label || defaultLabel);
1323
+ return newAccount;
1324
+ }
1325
+ catch (error) {
1326
+ console.log('ERROR addNewAccountToEth', {
1327
+ error,
1328
+ });
1329
+ this.validateAndHandleErrorByMessage(error.message);
1330
+ throw error;
1331
+ }
1332
+ }
1333
+ // Helper method to get next available account ID - fills gaps when accounts are deleted
1334
+ getNextAccountId(accounts) {
1335
+ const existingIds = Object.values(accounts)
1336
+ .filter((account) => {
1337
+ // Only count accounts that have been properly initialized
1338
+ // Placeholder accounts have empty addresses/xprv/xpub
1339
+ return account && account.address && account.xpub;
1340
+ })
1341
+ .map((account) => account.id)
1342
+ .filter((id) => !isNaN(id))
1343
+ .sort((a, b) => a - b); // Sort to find gaps efficiently
1344
+ if (existingIds.length === 0) {
1345
+ return 0;
1346
+ }
1347
+ // Find the first gap in the sequence
1348
+ for (let i = 0; i < existingIds.length; i++) {
1349
+ if (existingIds[i] !== i) {
1350
+ // Found a gap at position i
1351
+ return i;
1352
+ }
1353
+ }
1354
+ // No gaps found, return next sequential ID
1355
+ return existingIds.length;
1356
+ }
1357
+ async recreateSessionFromVault(password, saltedHashPassword) {
1358
+ try {
1359
+ const { mnemonic } = await (0, storage_1.getDecryptedVault)(password);
1360
+ if (!mnemonic) {
1361
+ throw new Error('Mnemonic not found in vault');
1362
+ }
1363
+ // Encrypt session data with sessionPassword hash for consistency
1364
+ // This allows keyring manager to decrypt with this.sessionPassword
1365
+ this.sessionPassword = new SecureBuffer(saltedHashPassword);
1366
+ // Encrypt the mnemonic with session password for consistency with the rest of the code
1367
+ const encryptedMnemonic = crypto_js_1.default.AES.encrypt(mnemonic, saltedHashPassword).toString();
1368
+ this.sessionMnemonic = new SecureBuffer(encryptedMnemonic);
1369
+ console.log('[KeyringManager] Session data recreated from vault');
1370
+ }
1371
+ catch (error) {
1372
+ console.error('ERROR recreateSessionFromVault', { error });
1373
+ throw error;
1374
+ }
1375
+ }
1376
+ async _getPrivateKeyAccountInfos(privKey, label) {
1377
+ const vault = this.getVault();
1378
+ const { accounts } = vault;
1379
+ let importedAccountValue;
1380
+ const balances = {
1381
+ syscoin: 0,
1382
+ ethereum: 0,
1383
+ };
1384
+ // Try to validate as extended private key first, regardless of prefix
1385
+ const networkToUse = vault.activeNetwork;
1386
+ const zprvValidation = this.validateZprv(privKey, networkToUse);
1387
+ if (zprvValidation.isValid) {
1388
+ const { node, network } = zprvValidation;
1389
+ if (!node || !network) {
1390
+ throw new Error('Failed to validate extended private key');
1391
+ }
1392
+ // Always use index 0 for consistency
1393
+ const nodeChild = node.derivePath(`0/0`);
1394
+ if (!nodeChild) {
1395
+ throw new Error('Failed to derive child node');
1396
+ }
1397
+ const { address } = bjs.payments.p2wpkh({
1398
+ pubkey: nodeChild.publicKey,
1399
+ network,
1400
+ });
1401
+ if (!address) {
1402
+ throw new Error('Failed to generate address');
1403
+ }
1404
+ importedAccountValue = {
1405
+ address,
1406
+ publicKey: node.neutered().toBase58(),
1407
+ privateKey: privKey,
1408
+ };
1409
+ balances.syscoin = 0;
1410
+ }
1411
+ else {
1412
+ // Check if the validation failed due to network mismatch
1413
+ if (zprvValidation.message &&
1414
+ zprvValidation.message.includes('Network mismatch')) {
1415
+ throw new Error(zprvValidation.message);
1416
+ }
1417
+ // Check if the validation failed due to invalid key prefix (only for known extended key formats)
1418
+ if (zprvValidation.message &&
1419
+ zprvValidation.message.includes('Invalid key prefix')) {
1420
+ throw new Error(zprvValidation.message);
1421
+ }
1422
+ // Check if it failed parsing as an extended key
1423
+ if (zprvValidation.message &&
1424
+ zprvValidation.message.includes('Failed to parse extended private key')) {
1425
+ throw new Error(zprvValidation.message);
1426
+ }
1427
+ // If it's not an extended key, treat it as an Ethereum private key
1428
+ const hexPrivateKey = privKey.slice(0, 2) === '0x' ? privKey : `0x${privKey}`;
1429
+ // Validate it's a valid hex string (32 bytes = 64 hex chars)
1430
+ if (!/^0x[0-9a-fA-F]{64}$/.test(hexPrivateKey) &&
1431
+ !/^[0-9a-fA-F]{64}$/.test(privKey)) {
1432
+ throw new Error('Invalid private key format. Expected 32-byte hex string or extended private key.');
1433
+ }
1434
+ importedAccountValue =
1435
+ this.ethereumTransaction.importAccount(hexPrivateKey);
1436
+ balances.ethereum = 0;
1437
+ }
1438
+ const { address, publicKey, privateKey } = importedAccountValue;
1439
+ //Validate if account already exists
1440
+ const accountAlreadyExists = (accounts[types_1.KeyringAccountType.Imported] &&
1441
+ Object.values(accounts[types_1.KeyringAccountType.Imported]).some((account) => account.address === address)) ||
1442
+ Object.values(accounts[types_1.KeyringAccountType.HDAccount]).some((account) => account.address === address); //Find a way to verify if private Key is not par of seed wallet derivation path
1443
+ if (accountAlreadyExists)
1444
+ throw new Error('Account already exists, try again with another Private Key.');
1445
+ const id = this.getNextAccountId(accounts[types_1.KeyringAccountType.Imported]);
1446
+ // Generate appropriate label based on account type
1447
+ const network = vault.activeNetwork;
1448
+ let defaultLabel;
1449
+ if (zprvValidation.isValid) {
1450
+ // UTXO imported account - use network-aware label
1451
+ const networkPrefix = network.label;
1452
+ defaultLabel = label || `${networkPrefix} Imported ${id + 1}`;
1453
+ }
1454
+ else {
1455
+ // EVM imported account - use generic label
1456
+ defaultLabel = label || `Imported ${id + 1}`;
1457
+ }
1458
+ return {
1459
+ ...initial_state_1.initialActiveImportedAccountState,
1460
+ address,
1461
+ label: defaultLabel,
1462
+ id,
1463
+ balances,
1464
+ isImported: true,
1465
+ xprv: crypto_js_1.default.AES.encrypt(privateKey, this.getSessionPasswordString()).toString(),
1466
+ xpub: publicKey,
1467
+ assets: {
1468
+ syscoin: [],
1469
+ ethereum: [],
1470
+ },
1471
+ };
1472
+ }
1473
+ // NEW: On-demand signer creation methods
1474
+ /**
1475
+ * Common method to decrypt mnemonic from session
1476
+ * Eliminates code duplication across multiple methods
1477
+ */
1478
+ getDecryptedMnemonic() {
1479
+ if (!this.sessionMnemonic || !this.sessionPassword) {
1480
+ throw new Error('Session information not available');
1481
+ }
1482
+ const mnemonic = crypto_js_1.default.AES.decrypt(this.getSessionMnemonicString(), this.getSessionPasswordString()).toString(crypto_js_1.default.enc.Utf8);
1483
+ if (!mnemonic) {
1484
+ throw new Error('Failed to decrypt mnemonic');
1485
+ }
1486
+ return mnemonic;
1487
+ }
1488
+ /**
1489
+ * Creates network RPC config from current active network without making RPC calls
1490
+ * Common utility for all on-demand signer creation
1491
+ */
1492
+ createNetworkRpcConfig() {
1493
+ const network = this.getVault().activeNetwork;
1494
+ return {
1495
+ formattedNetwork: network,
1496
+ networkConfig: (0, sysweb3_network_1.getNetworkConfig)(network.slip44, network.currency),
1497
+ };
1498
+ }
1499
+ /**
1500
+ * Common signer creation logic - takes decrypted mnemonic/zprv and creates fresh signer
1501
+ * OPTIMIZED: No RPC call needed - uses network config directly
1502
+ */
1503
+ createFreshUTXOSigner(mnemonicOrZprv, accountId) {
1504
+ // Create signer using network config directly (no RPC call)
1505
+ const rpcConfig = this.createNetworkRpcConfig();
1506
+ // Type assertion to match getSyscoinSigners expected interface
1507
+ const { hd } = (0, signers_1.getSyscoinSigners)({
1508
+ mnemonic: mnemonicOrZprv,
1509
+ rpc: rpcConfig,
1510
+ });
1511
+ // Create account at the specified index and set it as active
1512
+ // Note: createAccountAtIndex is synchronous despite TypeScript types
1513
+ hd.createAccountAtIndex(accountId, 84);
1514
+ // Verify the account was created correctly
1515
+ if (!hd.Signer.accounts.has(accountId)) {
1516
+ throw new Error(`Failed to create account at index ${accountId}`);
1517
+ }
1518
+ // Verify the correct account is active
1519
+ if (hd.Signer.accountIndex !== accountId) {
1520
+ throw new Error(`Account index mismatch: expected ${accountId}, got ${hd.Signer.accountIndex}`);
1521
+ }
1522
+ return hd;
1523
+ }
1524
+ /**
1525
+ * Creates a fresh UTXO signer for HD accounts derived from the main seed
1526
+ * OPTIMIZED: No RPC call needed - uses network config directly
1527
+ */
1528
+ createOnDemandUTXOSigner(accountId) {
1529
+ // Use common method to avoid code duplication
1530
+ const mnemonic = this.getDecryptedMnemonic();
1531
+ return this.createFreshUTXOSigner(mnemonic, accountId);
1532
+ }
1533
+ /**
1534
+ * Creates a fresh UTXO signer for imported accounts from stored zprv
1535
+ * OPTIMIZED: No RPC call needed - uses network config directly
1536
+ */
1537
+ createOnDemandUTXOSignerFromImported(accountId) {
1538
+ if (!this.sessionPassword) {
1539
+ throw new Error('Session password not available');
1540
+ }
1541
+ const vault = this.getVault();
1542
+ const account = vault.accounts[types_1.KeyringAccountType.Imported][accountId];
1543
+ if (!account) {
1544
+ throw new Error(`Imported account ${accountId} not found`);
1545
+ }
1546
+ // Decrypt the stored zprv
1547
+ const zprv = crypto_js_1.default.AES.decrypt(account.xprv, this.getSessionPasswordString()).toString(crypto_js_1.default.enc.Utf8);
1548
+ if (!zprv) {
1549
+ throw new Error('Failed to decrypt imported account private key');
1550
+ }
1551
+ if (!this.isZprv(zprv)) {
1552
+ throw new Error('Imported account does not contain a valid zprv');
1553
+ }
1554
+ return this.createFreshUTXOSigner(zprv, accountId);
1555
+ }
1556
+ /**
1557
+ * Common method to create on-demand signer for active account
1558
+ * Handles account type determination and delegates to appropriate method
1559
+ */
1560
+ createOnDemandSignerForActiveAccount() {
1561
+ const vault = this.getVault();
1562
+ const { activeAccount } = vault;
1563
+ const accountId = activeAccount.id;
1564
+ const accountType = activeAccount.type;
1565
+ if (accountType === types_1.KeyringAccountType.HDAccount) {
1566
+ return this.createOnDemandUTXOSigner(accountId);
1567
+ }
1568
+ else if (accountType === types_1.KeyringAccountType.Imported) {
1569
+ return this.createOnDemandUTXOSignerFromImported(accountId);
1570
+ }
1571
+ else {
1572
+ throw new Error(`Unsupported account type for UTXO signing: ${accountType}`);
1573
+ }
1574
+ }
1575
+ // NEW: Helper methods for HD signer management
1576
+ isZprv(key) {
1577
+ const zprvPrefixes = ['zprv', 'tprv', 'vprv', 'xprv'];
1578
+ return zprvPrefixes.some((prefix) => key.startsWith(prefix));
1579
+ }
1580
+ // Helper methods for secure buffer operations
1581
+ getSessionPasswordString() {
1582
+ if (!this.sessionPassword || this.sessionPassword.isCleared()) {
1583
+ throw new Error('Session password not available');
1584
+ }
1585
+ return this.sessionPassword.toString();
1586
+ }
1587
+ getSessionMnemonicString() {
1588
+ if (!this.sessionMnemonic || this.sessionMnemonic.isCleared()) {
1589
+ throw new Error('Session mnemonic not available');
1590
+ }
1591
+ return this.sessionMnemonic.toString();
1592
+ }
1593
+ generateNetworkAwareLabel(accountId, network) {
1594
+ // Generate concise network-specific labels using actual network config
1595
+ const { label, chainId, kind, currency } = network;
1596
+ // Create a shortened network identifier based on actual network configurations
1597
+ let networkPrefix = '';
1598
+ if (kind === sysweb3_network_1.INetworkType.Syscoin) {
1599
+ // UTXO networks (slip44 = 57 for mainnet, 1 for testnet)
1600
+ if (chainId === 57) {
1601
+ networkPrefix = 'SYS'; // Syscoin UTXO Mainnet
1602
+ }
1603
+ else if (chainId === 5700) {
1604
+ networkPrefix = 'SYS-T'; // Syscoin UTXO Testnet (slip44=1)
1605
+ }
1606
+ else {
1607
+ // Other UTXO networks - use currency shortcut (e.g., "btc" -> "BTC")
1608
+ if (currency) {
1609
+ networkPrefix = currency.toUpperCase();
1610
+ }
1611
+ else {
1612
+ // Fallback to first word from label if no currency
1613
+ const firstWord = label.split(' ')[0];
1614
+ networkPrefix =
1615
+ firstWord.length > 6 ? firstWord.substring(0, 6) : firstWord;
1616
+ }
1617
+ }
1618
+ }
1619
+ else {
1620
+ // EVM networks (all use slip44=60)
1621
+ if (chainId === 1) {
1622
+ networkPrefix = 'ETH'; // Ethereum Mainnet
1623
+ }
1624
+ else if (chainId === 11155111) {
1625
+ networkPrefix = 'ETH-T'; // Ethereum Sepolia Testnet
1626
+ }
1627
+ else if (chainId === 137) {
1628
+ networkPrefix = 'POLY'; // Polygon Mainnet
1629
+ }
1630
+ else if (chainId === 80001) {
1631
+ networkPrefix = 'POLY-T'; // Polygon Mumbai Testnet
1632
+ }
1633
+ else if (chainId === 57) {
1634
+ networkPrefix = 'NEVM'; // Syscoin NEVM Mainnet
1635
+ }
1636
+ else if (chainId === 5700) {
1637
+ networkPrefix = 'NEVM-T'; // Syscoin NEVM Testnet
1638
+ }
1639
+ else if (chainId === 570) {
1640
+ networkPrefix = 'ROLLUX'; // Rollux Mainnet
1641
+ }
1642
+ else if (chainId === 57000) {
1643
+ networkPrefix = 'ROLLUX-T'; // Rollux Testnet
1644
+ }
1645
+ else {
1646
+ // Other EVM networks - use currency shortcut if available
1647
+ if (currency) {
1648
+ // Check if it's a testnet network
1649
+ if (label.toLowerCase().includes('testnet') ||
1650
+ label.toLowerCase().includes('test')) {
1651
+ networkPrefix = `${currency.toUpperCase()}-T`;
1652
+ }
1653
+ else {
1654
+ networkPrefix = currency.toUpperCase();
1655
+ }
1656
+ }
1657
+ else {
1658
+ // Fallback to extracting meaningful prefix from label
1659
+ const firstWord = label.split(' ')[0];
1660
+ if (firstWord.toLowerCase().includes('testnet') ||
1661
+ firstWord.toLowerCase().includes('test')) {
1662
+ const baseWord = label.split(' ')[0];
1663
+ networkPrefix = `${baseWord.substring(0, 4).toUpperCase()}-T`;
1664
+ }
1665
+ else {
1666
+ networkPrefix =
1667
+ firstWord.length > 6
1668
+ ? firstWord.substring(0, 6).toUpperCase()
1669
+ : firstWord.toUpperCase();
1670
+ }
1671
+ }
1672
+ }
1673
+ }
1674
+ return `${networkPrefix} ${accountId + 1}`;
1675
+ }
1676
+ /**
1677
+ * Clean up all resources
1678
+ */
1679
+ async destroy() {
1680
+ this.lockWallet();
1681
+ // Clear any remaining references
1682
+ this.ethereumTransaction = {};
1683
+ this.syscoinTransaction = {};
1684
+ }
1685
+ }
1686
+ exports.KeyringManager = KeyringManager;
1687
+ //# sourceMappingURL=keyring-manager.js.map