pwc-sdk-wallet 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +2062 -0
  2. package/dist/Vault.d.ts +493 -0
  3. package/dist/Vault.js +1090 -0
  4. package/dist/chain/ChainService.d.ts +84 -0
  5. package/dist/chain/ChainService.js +136 -0
  6. package/dist/chain/SolanaChainService.d.ts +94 -0
  7. package/dist/chain/SolanaChainService.js +167 -0
  8. package/dist/config/chains.d.ts +233 -0
  9. package/dist/config/chains.js +285 -0
  10. package/dist/config/constants.d.ts +102 -0
  11. package/dist/config/constants.js +109 -0
  12. package/dist/config/environment.d.ts +46 -0
  13. package/dist/config/environment.js +114 -0
  14. package/dist/config/gas.d.ts +107 -0
  15. package/dist/config/gas.js +123 -0
  16. package/dist/crypto/EncryptionService.d.ts +74 -0
  17. package/dist/crypto/EncryptionService.js +178 -0
  18. package/dist/index.d.ts +22 -0
  19. package/dist/index.js +96 -0
  20. package/dist/keyrings/HDKeyring.d.ts +72 -0
  21. package/dist/keyrings/HDKeyring.js +156 -0
  22. package/dist/keyrings/SimpleKeyring.d.ts +31 -0
  23. package/dist/keyrings/SimpleKeyring.js +49 -0
  24. package/dist/keyrings/SolanaKeyring.d.ts +71 -0
  25. package/dist/keyrings/SolanaKeyring.js +159 -0
  26. package/dist/services/BatchProcessor.d.ts +42 -0
  27. package/dist/services/BatchProcessor.js +188 -0
  28. package/dist/services/MultiTransferService.d.ts +78 -0
  29. package/dist/services/MultiTransferService.js +252 -0
  30. package/dist/services/QRCodeService.d.ts +193 -0
  31. package/dist/services/QRCodeService.js +299 -0
  32. package/dist/services/TokenUtils.d.ts +307 -0
  33. package/dist/services/TokenUtils.js +429 -0
  34. package/dist/services/nft/MetadataResolver.d.ts +57 -0
  35. package/dist/services/nft/MetadataResolver.js +162 -0
  36. package/dist/services/nft/NFTAPIService.d.ts +53 -0
  37. package/dist/services/nft/NFTAPIService.js +122 -0
  38. package/dist/services/nft/NFTService.d.ts +241 -0
  39. package/dist/services/nft/NFTService.js +910 -0
  40. package/dist/types/multiTransfer.d.ts +68 -0
  41. package/dist/types/multiTransfer.js +2 -0
  42. package/dist/types/nft/index.d.ts +68 -0
  43. package/dist/types/nft/index.js +2 -0
  44. package/dist/types/nft.d.ts +265 -0
  45. package/dist/types/nft.js +6 -0
  46. package/package.json +70 -0
package/dist/Vault.js ADDED
@@ -0,0 +1,1090 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Vault = void 0;
4
+ const HDKeyring_1 = require("./keyrings/HDKeyring");
5
+ const SimpleKeyring_1 = require("./keyrings/SimpleKeyring");
6
+ const SolanaKeyring_1 = require("./keyrings/SolanaKeyring");
7
+ const ChainService_1 = require("./chain/ChainService");
8
+ const SolanaChainService_1 = require("./chain/SolanaChainService");
9
+ const EncryptionService_1 = require("./crypto/EncryptionService");
10
+ const chains_1 = require("./config/chains");
11
+ const constants_1 = require("./config/constants");
12
+ const ethers_1 = require("ethers");
13
+ const MultiTransferService_1 = require("./services/MultiTransferService");
14
+ const NFTService_1 = require("./services/nft/NFTService");
15
+ const QRCodeService_1 = require("./services/QRCodeService");
16
+ class Vault {
17
+ /**
18
+ * Creates a new Vault instance with the specified keyrings.
19
+ * @param keyrings - Array of keyrings to initialize the vault with. Defaults to empty array.
20
+ */
21
+ constructor(keyrings = [], config) {
22
+ this.keyrings = [];
23
+ this.chainId = '1'; // Default to Ethereum mainnet
24
+ this.keyrings = keyrings;
25
+ this.config = config;
26
+ if (config?.rpcUrls) {
27
+ require('./config/chains').setGlobalRPCConfig(config.rpcUrls);
28
+ }
29
+ if (config?.explorerUrls) {
30
+ require('./config/chains').setGlobalExplorerConfig(config.explorerUrls);
31
+ }
32
+ if (config?.gasConfig) {
33
+ require('./config/gas').setGlobalGasConfig(config.gasConfig);
34
+ }
35
+ if (config?.networkGasConfig) {
36
+ require('./config/gas').setGlobalNetworkGasConfig(config.networkGasConfig);
37
+ }
38
+ if (config?.defaultChainId) {
39
+ this.chainId = config.defaultChainId;
40
+ }
41
+ }
42
+ // --- Vault Creation / Loading ---
43
+ /**
44
+ * Creates a new Vault with a fresh mnemonic.
45
+ * @param password - The password used to encrypt the vault data
46
+ * @param chainType - The type of blockchain to support ('evm' for Ethereum-compatible chains or 'solana' for Solana). Defaults to 'evm'
47
+ * @returns Promise resolving to an object containing the created vault instance and its encrypted data
48
+ * @throws Error if mnemonic generation or keyring initialization fails
49
+ */
50
+ static async createNew(password, chainType = 'evm', config) {
51
+ const mnemonic = EncryptionService_1.EncryptionService.generateMnemonic();
52
+ if (chainType === 'solana') {
53
+ const solanaKeyring = new SolanaKeyring_1.SolanaKeyring(mnemonic);
54
+ const vault = new Vault([solanaKeyring], config);
55
+ vault.chainId = 'solana'; // Set Solana chain ID
56
+ const encryptedVault = await vault.encrypt(password);
57
+ return { vault, encryptedVault };
58
+ }
59
+ else {
60
+ const hdKeyring = new HDKeyring_1.HDKeyring(mnemonic);
61
+ await hdKeyring.initialize();
62
+ const vault = new Vault([hdKeyring], config);
63
+ vault.chainId = '1'; // Set Ethereum mainnet as default
64
+ const encryptedVault = await vault.encrypt(password);
65
+ return { vault, encryptedVault };
66
+ }
67
+ }
68
+ /**
69
+ * Creates a new Vault from an existing mnemonic phrase.
70
+ * @param mnemonic - The existing mnemonic phrase (12, 15, 18, 21, or 24 words)
71
+ * @param password - The password used to encrypt the vault data
72
+ * @param chainType - The type of blockchain to support ('evm' for Ethereum-compatible chains or 'solana' for Solana). Defaults to 'evm'
73
+ * @returns Promise resolving to an object containing the created vault instance and its encrypted data
74
+ * @throws Error if mnemonic is invalid or keyring initialization fails
75
+ */
76
+ static async createFromMnemonic(mnemonic, password, chainType = 'evm', config) {
77
+ if (chainType === 'solana') {
78
+ const solanaKeyring = new SolanaKeyring_1.SolanaKeyring(mnemonic);
79
+ const vault = new Vault([solanaKeyring], config);
80
+ vault.chainId = 'solana'; // Set Solana chain ID
81
+ const encryptedVault = await vault.encrypt(password);
82
+ return { vault, encryptedVault };
83
+ }
84
+ else {
85
+ const hdKeyring = new HDKeyring_1.HDKeyring(mnemonic);
86
+ await hdKeyring.initialize();
87
+ const vault = new Vault([hdKeyring], config);
88
+ vault.chainId = '1'; // Set Ethereum mainnet as default
89
+ const encryptedVault = await vault.encrypt(password);
90
+ return { vault, encryptedVault };
91
+ }
92
+ }
93
+ /**
94
+ * Loads a Vault from its encrypted state using the provided password.
95
+ * @param password - The password used to decrypt the vault data
96
+ * @param encryptedVault - The encrypted vault data to decrypt and load
97
+ * @returns Promise resolving to the loaded Vault instance
98
+ * @throws Error if password is incorrect or decryption fails
99
+ */
100
+ static async load(password, encryptedVault, config) {
101
+ const decryptedString = await EncryptionService_1.EncryptionService.decrypt(encryptedVault, password);
102
+ const serializedKeyrings = JSON.parse(decryptedString);
103
+ const keyrings = [];
104
+ for (const sKeyring of serializedKeyrings) {
105
+ if (sKeyring.type === 'HD') {
106
+ keyrings.push(await HDKeyring_1.HDKeyring.deserialize(sKeyring));
107
+ }
108
+ else if (sKeyring.type === 'Simple') {
109
+ keyrings.push(SimpleKeyring_1.SimpleKeyring.deserialize(sKeyring));
110
+ }
111
+ else if (sKeyring.type === 'Solana') {
112
+ keyrings.push(await SolanaKeyring_1.SolanaKeyring.deserialize(sKeyring));
113
+ }
114
+ }
115
+ const vault = new Vault(keyrings, config);
116
+ // Set chain ID based on keyring type
117
+ if (keyrings.some(k => k instanceof SolanaKeyring_1.SolanaKeyring)) {
118
+ vault.chainId = 'solana';
119
+ }
120
+ else {
121
+ vault.chainId = '1'; // Default to Ethereum mainnet
122
+ }
123
+ return vault;
124
+ }
125
+ // --- Account Management ---
126
+ /**
127
+ * Adds a new account derived from the HD keyring using BIP-44 derivation.
128
+ * @returns Promise resolving to the newly created account information
129
+ * @throws Error if no HD keyring is available in the vault
130
+ */
131
+ async addNewHDAccount() {
132
+ const hdKeyring = this.keyrings.find(k => k instanceof HDKeyring_1.HDKeyring);
133
+ if (!hdKeyring) {
134
+ throw new Error('No HD keyring available to create new accounts.');
135
+ }
136
+ const newAddress = await hdKeyring.addNewAccount();
137
+ return {
138
+ address: newAddress,
139
+ type: 'HD',
140
+ name: `Account ${hdKeyring.accounts.length}`
141
+ };
142
+ }
143
+ /**
144
+ * Adds a new Solana account derived from the Solana keyring.
145
+ * @returns Promise resolving to the newly created Solana account information
146
+ * @throws Error if no Solana keyring is available in the vault
147
+ */
148
+ async addNewSolanaAccount() {
149
+ const solanaKeyring = this.keyrings.find(k => k instanceof SolanaKeyring_1.SolanaKeyring);
150
+ if (!solanaKeyring) {
151
+ throw new Error('No Solana keyring available to create new accounts.');
152
+ }
153
+ const newAddress = await solanaKeyring.addNewAccount();
154
+ return {
155
+ address: newAddress,
156
+ type: 'Solana',
157
+ name: `Solana ${solanaKeyring.accounts.length}`
158
+ };
159
+ }
160
+ /**
161
+ * Imports an account from a private key and adds it to the vault.
162
+ * @param privateKey - The private key to import (hex string without '0x' prefix for EVM, base58 for Solana)
163
+ * @returns Promise resolving to the imported account information
164
+ * @throws Error if the private key is already managed by an HD keyring or if import fails
165
+ */
166
+ async importAccount(privateKey) {
167
+ // Prevent importing a private key that's already managed by the HD keyring
168
+ for (const keyring of this.keyrings) {
169
+ if (keyring instanceof HDKeyring_1.HDKeyring) {
170
+ const pk = await keyring.getPrivateKeyForAddress(ChainService_1.ChainService.getAddress(privateKey)).catch(() => null);
171
+ if (pk) {
172
+ throw new Error("This private key is already part of your mnemonic-derived accounts.");
173
+ }
174
+ }
175
+ }
176
+ const newKeyring = new SimpleKeyring_1.SimpleKeyring(privateKey);
177
+ this.keyrings.push(newKeyring);
178
+ return {
179
+ address: newKeyring.address,
180
+ type: newKeyring.type,
181
+ name: `Imported ${this.keyrings.filter(k => k.type === 'Simple').length}`
182
+ };
183
+ }
184
+ /**
185
+ * Returns a list of all accounts managed by the vault across all keyrings.
186
+ * @returns Array of Account objects representing all accounts in the vault
187
+ */
188
+ getAccounts() {
189
+ const accounts = [];
190
+ let hdAccountCount = 1;
191
+ let importedAccountCount = 1;
192
+ let solanaAccountCount = 1;
193
+ for (const keyring of this.keyrings) {
194
+ if (keyring instanceof HDKeyring_1.HDKeyring) {
195
+ for (const address of keyring.accounts) {
196
+ accounts.push({
197
+ address,
198
+ type: 'HD',
199
+ name: `Account ${hdAccountCount++}`,
200
+ });
201
+ }
202
+ }
203
+ else if (keyring instanceof SimpleKeyring_1.SimpleKeyring) {
204
+ accounts.push({
205
+ address: keyring.address,
206
+ type: 'Simple',
207
+ name: `Imported ${importedAccountCount++}`,
208
+ });
209
+ }
210
+ else if (keyring instanceof SolanaKeyring_1.SolanaKeyring) {
211
+ for (const address of keyring.accounts) {
212
+ accounts.push({
213
+ address,
214
+ type: 'Solana',
215
+ name: `Solana ${solanaAccountCount++}`,
216
+ });
217
+ }
218
+ }
219
+ }
220
+ return accounts;
221
+ }
222
+ /**
223
+ * Exports the mnemonic phrase from the vault for backup purposes.
224
+ * Requires password verification and includes rate limiting for security.
225
+ * @param password - The vault password to verify before exporting
226
+ * @returns Promise resolving to the mnemonic phrase as a string
227
+ * @throws Error if password is incorrect, no mnemonic exists, or rate limit exceeded
228
+ */
229
+ async exportMnemonic(password) {
230
+ // Rate limiting check
231
+ const vaultId = this.getVaultId();
232
+ const now = Date.now();
233
+ const attempts = Vault.exportAttempts.get(vaultId);
234
+ if (attempts) {
235
+ if (attempts.count >= Vault.MAX_EXPORT_ATTEMPTS) {
236
+ const timeRemaining = Vault.EXPORT_COOLDOWN_MS - (now - attempts.lastAttempt);
237
+ if (timeRemaining > 0) {
238
+ throw new Error(`Too many export attempts. Please wait ${Math.ceil(timeRemaining / 1000)} seconds before trying again.`);
239
+ }
240
+ else {
241
+ // Reset attempts after cooldown
242
+ Vault.exportAttempts.delete(vaultId);
243
+ }
244
+ }
245
+ }
246
+ const hdKeyring = this.keyrings.find(k => k instanceof HDKeyring_1.HDKeyring);
247
+ if (!hdKeyring) {
248
+ throw new Error('This vault does not have a mnemonic.');
249
+ }
250
+ // Update attempt counter
251
+ const currentAttempts = Vault.exportAttempts.get(vaultId) || { count: 0, lastAttempt: 0 };
252
+ currentAttempts.count++;
253
+ currentAttempts.lastAttempt = now;
254
+ Vault.exportAttempts.set(vaultId, currentAttempts);
255
+ // Audit logging (without sensitive data)
256
+ // console.log(`Mnemonic export attempted for vault ${vaultId}, attempt ${currentAttempts.count}`);
257
+ try {
258
+ // Verify password by attempting to decrypt the vault
259
+ const encryptedVault = await this.encrypt(password);
260
+ // If encryption succeeds, password is correct
261
+ const mnemonic = hdKeyring.getMnemonic();
262
+ // Reset attempts on successful export
263
+ Vault.exportAttempts.delete(vaultId);
264
+ // Audit logging for successful export
265
+ // console.log(`Mnemonic export successful for vault ${vaultId}`);
266
+ // Use temporary data protection
267
+ return EncryptionService_1.EncryptionService.withTemporaryData(mnemonic, (tempMnemonic) => {
268
+ // Return a copy to avoid direct reference to the original
269
+ return String(tempMnemonic);
270
+ });
271
+ }
272
+ catch (error) {
273
+ // Use constant-time error message to prevent timing attacks
274
+ throw new Error('Incorrect password. Cannot export mnemonic.');
275
+ }
276
+ }
277
+ /**
278
+ * Gets a unique identifier for this vault used for rate limiting purposes.
279
+ * @returns String identifier based on the first account address
280
+ */
281
+ getVaultId() {
282
+ // Use the first account address as vault identifier
283
+ const accounts = this.getAccounts();
284
+ return accounts.length > 0 ? accounts[0].address : 'unknown';
285
+ }
286
+ // --- Internal & Utility ---
287
+ /**
288
+ * Encrypts the entire vault's state using the provided password.
289
+ * @param password - The password to use for encryption
290
+ * @returns Promise resolving to the encrypted vault data
291
+ * @throws Error if encryption fails
292
+ */
293
+ async encrypt(password) {
294
+ const serializedKeyrings = this.keyrings.map(k => k.serialize());
295
+ const jsonString = JSON.stringify(serializedKeyrings);
296
+ return EncryptionService_1.EncryptionService.encrypt(jsonString, password);
297
+ }
298
+ /**
299
+ * Retrieves the private key for a specific address from the appropriate keyring.
300
+ * @param address - The account address to get the private key for
301
+ * @returns Promise resolving to the private key as a string
302
+ * @throws Error if the address is not found in any keyring
303
+ */
304
+ async getPrivateKeyFor(address) {
305
+ for (const keyring of this.keyrings) {
306
+ if (keyring instanceof HDKeyring_1.HDKeyring && keyring.accounts.includes(address)) {
307
+ return keyring.getPrivateKeyForAddress(address);
308
+ }
309
+ if (keyring instanceof SimpleKeyring_1.SimpleKeyring && keyring.address === address) {
310
+ return keyring.getPrivateKey();
311
+ }
312
+ if (keyring instanceof SolanaKeyring_1.SolanaKeyring && keyring.accounts.includes(address)) {
313
+ return keyring.getPrivateKeyForAddress(address);
314
+ }
315
+ }
316
+ throw new Error('Private key not found for the given address.');
317
+ }
318
+ // --- Blockchain Interaction Methods ---
319
+ /**
320
+ * Gets the token balance for a specific account and token contract.
321
+ * @param accountAddress - The account address to check balance for
322
+ * @param tokenAddress - The contract address of the token
323
+ * @param chainId - The ID of the blockchain where the token is deployed
324
+ * @returns Promise resolving to the token balance as a bigint
325
+ * @throws Error if account or token not found, or chain service fails
326
+ */
327
+ async getTokenBalance(accountAddress, tokenAddress, chainId) {
328
+ const chainInfo = (0, chains_1.getChainConfig)(chainId);
329
+ const privateKey = await this.getPrivateKeyFor(accountAddress);
330
+ if (chainInfo.type === 'solana') {
331
+ const chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainInfo);
332
+ return chainService.getTokenBalance(tokenAddress);
333
+ }
334
+ else {
335
+ const chainService = new ChainService_1.ChainService(privateKey, chainInfo);
336
+ return chainService.getTokenBalance(tokenAddress);
337
+ }
338
+ }
339
+ /**
340
+ * Sends tokens from a specific account to another address.
341
+ * @param fromAddress - The sender's account address
342
+ * @param to - The recipient's address
343
+ * @param amount - The amount of tokens to send as a string
344
+ * @param tokenAddress - The contract address of the token to send
345
+ * @param chainId - The ID of the blockchain for the transaction
346
+ * @returns Promise resolving to the transaction response with hash and details
347
+ * @throws Error if insufficient balance, invalid addresses, or transaction fails
348
+ */
349
+ async sendToken(fromAddress, to, amount, tokenAddress, chainId) {
350
+ const chainInfo = (0, chains_1.getChainConfig)(chainId);
351
+ const privateKey = await this.getPrivateKeyFor(fromAddress);
352
+ if (chainInfo.type === 'solana') {
353
+ const chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainInfo);
354
+ const result = await chainService.sendToken(tokenAddress, to, amount);
355
+ return {
356
+ hash: result.hash,
357
+ from: result.from,
358
+ to: result.to,
359
+ blockNumber: result.blockNumber
360
+ };
361
+ }
362
+ else {
363
+ const chainService = new ChainService_1.ChainService(privateKey, chainInfo);
364
+ const result = await chainService.sendToken(tokenAddress, to, amount);
365
+ return {
366
+ hash: result.hash,
367
+ from: result.from,
368
+ to: result.to || '',
369
+ blockNumber: result.blockNumber || undefined
370
+ };
371
+ }
372
+ }
373
+ /**
374
+ * Generates a vanity HD wallet with a specific address prefix.
375
+ * Uses default configuration from VANITY_WALLET_CONFIG for optimal settings.
376
+ * The prefix defaults to 'aaa' as configured in VANITY_WALLET_CONFIG.DEFAULT_PREFIX.
377
+ *
378
+ * @param password - The password to encrypt the generated vault
379
+ * @param onProgress - Optional callback function for progress updates (attempts count and current address)
380
+ * @returns Promise resolving to an object containing the generated vault, encrypted vault, number of attempts, and the found address
381
+ * @throws Error if generation fails or max attempts exceeded
382
+ *
383
+ * @example
384
+ * ```typescript
385
+ * // Simple usage - will use default prefix 'aaa'
386
+ * const result = await Vault.generateVanityHDWallet("mypassword");
387
+ *
388
+ * // With progress callback
389
+ * const result = await Vault.generateVanityHDWallet(
390
+ * "mypassword",
391
+ * (attempts, address) => console.log(`Attempt ${attempts}: ${address}`)
392
+ * );
393
+ * ```
394
+ */
395
+ static async generateVanityHDWallet(password, onProgress) {
396
+ // Use default values from config
397
+ const targetPrefix = constants_1.VANITY_WALLET_CONFIG.DEFAULT_PREFIX;
398
+ const maxAttempts = constants_1.VANITY_WALLET_CONFIG.DEFAULT_MAX_ATTEMPTS;
399
+ const caseSensitive = constants_1.VANITY_WALLET_CONFIG.DEFAULT_CASE_SENSITIVE;
400
+ const chainType = 'evm'; // Default to EVM for simplicity
401
+ // Validate prefix
402
+ if (targetPrefix.length > constants_1.VANITY_WALLET_CONFIG.MAX_PREFIX_LENGTH) {
403
+ throw new Error(`Prefix too long. Maximum length is ${constants_1.VANITY_WALLET_CONFIG.MAX_PREFIX_LENGTH} characters.`);
404
+ }
405
+ const normalizedPrefix = caseSensitive ? targetPrefix : targetPrefix.toLowerCase();
406
+ let attempts = 0;
407
+ let foundAddress = '';
408
+ let foundMnemonic = '';
409
+ while (attempts < maxAttempts) {
410
+ attempts++;
411
+ // Generate new mnemonic
412
+ const mnemonic = EncryptionService_1.EncryptionService.generateMnemonic();
413
+ // Create HD keyring and get first address
414
+ const hdKeyring = new HDKeyring_1.HDKeyring(mnemonic);
415
+ await hdKeyring.initialize();
416
+ const firstAddress = hdKeyring.accounts[0];
417
+ // Check if address matches prefix (remove '0x' for EVM)
418
+ const addressWithoutPrefix = firstAddress.slice(2);
419
+ const addressToCheck = caseSensitive ? addressWithoutPrefix : addressWithoutPrefix.toLowerCase();
420
+ if (addressToCheck.startsWith(normalizedPrefix)) {
421
+ foundAddress = firstAddress;
422
+ foundMnemonic = mnemonic;
423
+ break;
424
+ }
425
+ // Progress callback
426
+ if (onProgress && attempts % constants_1.VANITY_WALLET_CONFIG.PROGRESS_UPDATE_INTERVAL === 0) {
427
+ onProgress(attempts, firstAddress);
428
+ }
429
+ // Non-blocking interval to allow event loop to breathe
430
+ if (attempts % constants_1.VANITY_WALLET_CONFIG.NON_BLOCKING_INTERVAL === 0) {
431
+ await new Promise(resolve => setTimeout(resolve, 0));
432
+ }
433
+ }
434
+ if (!foundAddress) {
435
+ throw new Error(`Could not generate vanity address with prefix "${targetPrefix}" after ${attempts} attempts. Try using a shorter prefix.`);
436
+ }
437
+ // Create vault with the found mnemonic
438
+ const { vault, encryptedVault } = await Vault.createFromMnemonic(foundMnemonic, password, chainType);
439
+ return { vault, encryptedVault, attempts, foundAddress };
440
+ }
441
+ /**
442
+ * Generates a vanity HD wallet optimized for mobile devices.
443
+ * Uses mobile-optimized settings for faster completion and better UX.
444
+ *
445
+ * @param password - The password to encrypt the generated vault
446
+ * @param onProgress - Optional callback function for progress updates (attempts count and current address)
447
+ * @param options - Optional configuration options for mobile optimization
448
+ * @returns Promise resolving to an object containing the generated vault, encrypted vault, number of attempts, and the found address
449
+ * @throws Error if generation fails, max attempts exceeded, or timeout reached
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * // Simple mobile-optimized usage
454
+ * const result = await Vault.generateVanityHDWalletMobile("mypassword");
455
+ *
456
+ * // With custom options
457
+ * const result = await Vault.generateVanityHDWalletMobile(
458
+ * "mypassword",
459
+ * (attempts, address) => console.log(`Attempt ${attempts}: ${address}`),
460
+ * { prefix: 'ab', maxAttempts: 10000 }
461
+ * );
462
+ * ```
463
+ */
464
+ static async generateVanityHDWalletMobile(password, onProgress, options = {}) {
465
+ // Use mobile-optimized defaults
466
+ const targetPrefix = options.prefix || constants_1.VANITY_WALLET_CONFIG.MOBILE_DEFAULT_PREFIX;
467
+ const maxAttempts = options.maxAttempts || constants_1.VANITY_WALLET_CONFIG.MOBILE_DEFAULT_MAX_ATTEMPTS;
468
+ const caseSensitive = options.caseSensitive ?? constants_1.VANITY_WALLET_CONFIG.DEFAULT_CASE_SENSITIVE;
469
+ const timeout = options.timeout || constants_1.VANITY_WALLET_CONFIG.MOBILE_TIMEOUT_MS;
470
+ const chainType = 'evm'; // Default to EVM for simplicity
471
+ // Validate prefix
472
+ if (targetPrefix.length > constants_1.VANITY_WALLET_CONFIG.MAX_PREFIX_LENGTH) {
473
+ throw new Error(`Prefix too long. Maximum length is ${constants_1.VANITY_WALLET_CONFIG.MAX_PREFIX_LENGTH} characters.`);
474
+ }
475
+ const normalizedPrefix = caseSensitive ? targetPrefix : targetPrefix.toLowerCase();
476
+ let attempts = 0;
477
+ let foundAddress = '';
478
+ let foundMnemonic = '';
479
+ const startTime = Date.now();
480
+ // Batch processing for better performance
481
+ const processBatch = async (batchSize) => {
482
+ const batchPromises = [];
483
+ for (let i = 0; i < batchSize; i++) {
484
+ batchPromises.push((async () => {
485
+ const mnemonic = EncryptionService_1.EncryptionService.generateMnemonic();
486
+ const hdKeyring = new HDKeyring_1.HDKeyring(mnemonic);
487
+ await hdKeyring.initialize();
488
+ const address = hdKeyring.accounts[0];
489
+ return { address, mnemonic };
490
+ })());
491
+ }
492
+ const results = await Promise.all(batchPromises);
493
+ for (const { address, mnemonic } of results) {
494
+ attempts++;
495
+ // Check if address matches prefix (remove '0x' for EVM)
496
+ const addressWithoutPrefix = address.slice(2);
497
+ const addressToCheck = caseSensitive ? addressWithoutPrefix : addressWithoutPrefix.toLowerCase();
498
+ if (addressToCheck.startsWith(normalizedPrefix)) {
499
+ return { found: true, address, mnemonic };
500
+ }
501
+ // Progress callback
502
+ if (onProgress && attempts % constants_1.VANITY_WALLET_CONFIG.MOBILE_PROGRESS_UPDATE_INTERVAL === 0) {
503
+ onProgress(attempts, address);
504
+ }
505
+ // Check timeout
506
+ if (Date.now() - startTime > timeout) {
507
+ throw new Error(`Vanity wallet generation timed out after ${timeout}ms. Try using a shorter prefix.`);
508
+ }
509
+ }
510
+ return { found: false };
511
+ };
512
+ // Process in batches until found or max attempts reached
513
+ while (attempts < maxAttempts) {
514
+ const batchSize = Math.min(constants_1.VANITY_WALLET_CONFIG.MOBILE_BATCH_SIZE, maxAttempts - attempts);
515
+ const result = await processBatch(batchSize);
516
+ if (result.found) {
517
+ foundAddress = result.address;
518
+ foundMnemonic = result.mnemonic;
519
+ break;
520
+ }
521
+ // Non-blocking interval to allow event loop to breathe (more frequent for mobile)
522
+ if (attempts % constants_1.VANITY_WALLET_CONFIG.MOBILE_NON_BLOCKING_INTERVAL === 0) {
523
+ await new Promise(resolve => setTimeout(resolve, 0));
524
+ }
525
+ }
526
+ if (!foundAddress) {
527
+ throw new Error(`Could not generate vanity address with prefix "${targetPrefix}" after ${attempts} attempts. Try using a shorter prefix.`);
528
+ }
529
+ // Create vault with the found mnemonic
530
+ const { vault, encryptedVault } = await Vault.createFromMnemonic(foundMnemonic, password, chainType);
531
+ return { vault, encryptedVault, attempts, foundAddress };
532
+ }
533
+ /**
534
+ * Ultra-optimized vanity wallet generation for maximum performance.
535
+ * Bypasses heavy HDKeyring initialization and uses direct cryptographic operations.
536
+ * Similar to web-based vanity generators for maximum speed.
537
+ *
538
+ * @param password - The password to encrypt the generated vault
539
+ * @param onProgress - Optional callback function for progress updates
540
+ * @param options - Configuration options for ultra-optimized generation
541
+ * @returns Promise resolving to generated vault data
542
+ */
543
+ static async generateVanityHDWalletUltra(password, onProgress, options = {}) {
544
+ const targetPrefix = options.prefix || 'a';
545
+ const maxAttempts = options.maxAttempts || 100000;
546
+ const caseSensitive = options.caseSensitive ?? false;
547
+ const timeout = options.timeout || 15000; // 15 seconds default
548
+ const batchSize = options.batchSize || 1000; // Larger batches for better performance
549
+ const chainType = 'evm';
550
+ // Validate prefix
551
+ if (targetPrefix.length > constants_1.VANITY_WALLET_CONFIG.MAX_PREFIX_LENGTH) {
552
+ throw new Error(`Prefix too long. Maximum length is ${constants_1.VANITY_WALLET_CONFIG.MAX_PREFIX_LENGTH} characters.`);
553
+ }
554
+ const normalizedPrefix = caseSensitive ? targetPrefix : targetPrefix.toLowerCase();
555
+ let attempts = 0;
556
+ let foundAddress = '';
557
+ let foundMnemonic = '';
558
+ const startTime = Date.now();
559
+ // Ultra-optimized batch processing with direct crypto operations
560
+ const processUltraBatch = async (batchSize) => {
561
+ const batchPromises = [];
562
+ for (let i = 0; i < batchSize; i++) {
563
+ batchPromises.push((async () => {
564
+ // Generate mnemonic directly using existing service
565
+ const mnemonic = EncryptionService_1.EncryptionService.generateMnemonic();
566
+ // Use existing HDKeyring but with minimal overhead
567
+ const hdKeyring = new HDKeyring_1.HDKeyring(mnemonic);
568
+ await hdKeyring.initialize();
569
+ const address = hdKeyring.accounts[0];
570
+ return { address, mnemonic };
571
+ })());
572
+ }
573
+ const results = await Promise.all(batchPromises);
574
+ for (const { address, mnemonic } of results) {
575
+ attempts++;
576
+ // Check if address matches prefix (remove '0x' for EVM)
577
+ const addressWithoutPrefix = address.slice(2);
578
+ const addressToCheck = caseSensitive ? addressWithoutPrefix : addressWithoutPrefix.toLowerCase();
579
+ if (addressToCheck.startsWith(normalizedPrefix)) {
580
+ return { found: true, address, mnemonic };
581
+ }
582
+ // Progress callback (less frequent for performance)
583
+ if (onProgress && attempts % 5000 === 0) {
584
+ onProgress(attempts, address);
585
+ }
586
+ // Check timeout
587
+ if (Date.now() - startTime > timeout) {
588
+ throw new Error(`Vanity wallet generation timed out after ${timeout}ms. Try using a shorter prefix.`);
589
+ }
590
+ }
591
+ return { found: false };
592
+ };
593
+ // Process in ultra-batches until found or max attempts reached
594
+ while (attempts < maxAttempts) {
595
+ const currentBatchSize = Math.min(batchSize, maxAttempts - attempts);
596
+ const result = await processUltraBatch(currentBatchSize);
597
+ if (result.found) {
598
+ foundAddress = result.address;
599
+ foundMnemonic = result.mnemonic;
600
+ break;
601
+ }
602
+ // Minimal non-blocking interval for ultra-performance
603
+ if (attempts % 10000 === 0) {
604
+ await new Promise(resolve => setTimeout(resolve, 0));
605
+ }
606
+ }
607
+ if (!foundAddress) {
608
+ throw new Error(`Could not generate vanity address with prefix "${targetPrefix}" after ${attempts} attempts. Try using a shorter prefix.`);
609
+ }
610
+ // Create vault with the found mnemonic
611
+ const { vault, encryptedVault } = await Vault.createFromMnemonic(foundMnemonic, password, chainType);
612
+ return { vault, encryptedVault, attempts, foundAddress };
613
+ }
614
+ /**
615
+ * Transfers native tokens to multiple recipients in a single operation.
616
+ * Uses batch processing for optimal performance and provides progress tracking.
617
+ *
618
+ * @param fromAddress - The sender's account address
619
+ * @param recipients - Array of recipients with addresses and amounts
620
+ * @param chainId - The ID of the blockchain for the transaction
621
+ * @param onProgress - Optional callback for progress updates (completed, total, txHash)
622
+ * @returns Promise resolving to the multi-transfer result with success/failure details
623
+ * @throws Error if validation fails, insufficient balance, or transfer fails
624
+ *
625
+ * @example
626
+ * ```typescript
627
+ * const recipients = [
628
+ * { address: '0x123...', amount: '0.1' },
629
+ * { address: '0x456...', amount: '0.2' },
630
+ * { address: '0x789...', amount: '0.05' }
631
+ * ];
632
+ *
633
+ * const result = await vault.multiTransferNativeTokens(
634
+ * '0xmyAddress',
635
+ * recipients,
636
+ * '1', // Ethereum
637
+ * (completed, total, txHash) => console.log(`Completed ${completed}/${total}: ${txHash}`)
638
+ * );
639
+ *
640
+ * console.log(`Success: ${result.successfulCount}, Failed: ${result.failedCount}`);
641
+ * ```
642
+ */
643
+ async multiTransferNativeTokens(fromAddress, recipients, chainId, onProgress) {
644
+ const chainInfo = (0, chains_1.getChainConfig)(chainId);
645
+ const privateKey = await this.getPrivateKeyFor(fromAddress);
646
+ let chainService;
647
+ if (chainInfo.type === 'solana') {
648
+ chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainInfo);
649
+ }
650
+ else {
651
+ chainService = new ChainService_1.ChainService(privateKey, chainInfo);
652
+ }
653
+ const multiTransferService = new MultiTransferService_1.MultiTransferService(this, chainService, chainId);
654
+ return multiTransferService.transferNativeTokens(fromAddress, recipients, chainId, { onProgress });
655
+ }
656
+ /**
657
+ * Transfers ERC-20/SPL tokens to multiple recipients in a single operation.
658
+ * Uses batch processing for optimal performance and provides progress tracking.
659
+ *
660
+ * @param fromAddress - The sender's account address
661
+ * @param tokenAddress - The contract address of the token to send
662
+ * @param recipients - Array of recipients with addresses and amounts
663
+ * @param chainId - The ID of the blockchain for the transaction
664
+ * @param onProgress - Optional callback for progress updates (completed, total, txHash)
665
+ * @returns Promise resolving to the multi-transfer result with success/failure details
666
+ * @throws Error if validation fails, insufficient balance, or transfer fails
667
+ *
668
+ * @example
669
+ * ```typescript
670
+ * const recipients = [
671
+ * { address: '0x1111...', amount: '100' },
672
+ * { address: '0x456...', amount: '200' },
673
+ * { address: '0x789...', amount: '50' }
674
+ * ];
675
+ *
676
+ * const result = await vault.multiTransferTokens(
677
+ * '0xmyAddress',
678
+ * '0xtokenContract',
679
+ * recipients,
680
+ * '1', // Ethereum
681
+ * (completed, total, txHash) => console.log(`Completed ${completed}/${total}: ${txHash}`)
682
+ * );
683
+ *
684
+ * console.log(`Success: ${result.successfulCount}, Failed: ${result.failedCount}`);
685
+ * ```
686
+ */
687
+ async multiTransferTokens(fromAddress, tokenAddress, recipients, chainId, onProgress) {
688
+ const chainInfo = (0, chains_1.getChainConfig)(chainId);
689
+ const privateKey = await this.getPrivateKeyFor(fromAddress);
690
+ let chainService;
691
+ if (chainInfo.type === 'solana') {
692
+ chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainInfo);
693
+ }
694
+ else {
695
+ chainService = new ChainService_1.ChainService(privateKey, chainInfo);
696
+ }
697
+ const multiTransferService = new MultiTransferService_1.MultiTransferService(this, chainService, chainId);
698
+ return multiTransferService.transferTokens(fromAddress, tokenAddress, recipients, chainId, { onProgress });
699
+ }
700
+ /**
701
+ * Estimates gas cost for a native token transfer.
702
+ * Useful for displaying gas costs to users before sending transactions.
703
+ *
704
+ * @param fromAddress - The sender's account address
705
+ * @param to - The recipient's address
706
+ * @param amount - The amount to send as a string (e.g., "0.1")
707
+ * @param chainId - The ID of the blockchain for the transaction
708
+ * @returns Promise resolving to the estimated gas cost as a bigint
709
+ * @throws Error if estimation fails or addresses are invalid
710
+ *
711
+ * @example
712
+ * ```typescript
713
+ * const gasEstimate = await vault.estimateNativeTransferGas(
714
+ * '0x1234...',
715
+ * '0x5678...',
716
+ * '0.01', // 0.01 ETH
717
+ * '1' // Ethereum
718
+ * );
719
+ *
720
+ * console.log('Estimated gas:', gasEstimate.toString());
721
+ * console.log('Estimated cost (in ETH):', ethers.formatEther(gasEstimate * gasPrice));
722
+ * ```
723
+ */
724
+ async estimateNativeTransferGas(fromAddress, to, amount, chainId) {
725
+ const chainInfo = (0, chains_1.getChainConfig)(chainId);
726
+ const privateKey = await this.getPrivateKeyFor(fromAddress);
727
+ if (chainInfo.type === 'solana') {
728
+ const chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainInfo);
729
+ return chainService.estimateTransactionFee();
730
+ }
731
+ else {
732
+ const chainService = new ChainService_1.ChainService(privateKey, chainInfo);
733
+ const tx = {
734
+ to: to,
735
+ value: ethers_1.ethers.parseEther(amount)
736
+ };
737
+ return chainService.estimateGas(tx);
738
+ }
739
+ }
740
+ /**
741
+ * Estimates gas cost for an ERC-20/SPL token transfer.
742
+ * Useful for displaying gas costs to users before sending token transactions.
743
+ *
744
+ * @param fromAddress - The sender's account address
745
+ * @param tokenAddress - The contract address of the token
746
+ * @param to - The recipient's address
747
+ * @param amount - The amount of tokens to send as a string
748
+ * @param chainId - The ID of the blockchain for the transaction
749
+ * @returns Promise resolving to the estimated gas cost as a bigint
750
+ * @throws Error if estimation fails, token contract is invalid, or addresses are invalid
751
+ *
752
+ * @example
753
+ * ```typescript
754
+ * const gasEstimate = await vault.estimateTokenTransferGas(
755
+ * '0x1234...',
756
+ * '0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8C', // USDC
757
+ * '0x5678...',
758
+ * '100', // 100 USDC
759
+ * '1' // Ethereum
760
+ * );
761
+ *
762
+ * console.log('Estimated gas for token transfer:', gasEstimate.toString());
763
+ * ```
764
+ */
765
+ async estimateTokenTransferGas(fromAddress, tokenAddress, to, amount, chainId) {
766
+ const chainInfo = (0, chains_1.getChainConfig)(chainId);
767
+ const privateKey = await this.getPrivateKeyFor(fromAddress);
768
+ if (chainInfo.type === 'solana') {
769
+ const chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainInfo);
770
+ return chainService.estimateTransactionFee();
771
+ }
772
+ else {
773
+ const chainService = new ChainService_1.ChainService(privateKey, chainInfo);
774
+ // Get token info to parse amount correctly
775
+ const tokenInfo = await chainService.getTokenInfo(tokenAddress);
776
+ const parsedAmount = ethers_1.ethers.parseUnits(amount, tokenInfo.decimals);
777
+ // Create contract instance for gas estimation
778
+ const contract = new ethers_1.ethers.Contract(tokenAddress, [
779
+ "function transfer(address to, uint amount) returns (bool)"
780
+ ], chainService['wallet'].provider);
781
+ // Estimate gas for token transfer
782
+ return contract.transfer.estimateGas(to, parsedAmount);
783
+ }
784
+ }
785
+ /**
786
+ * Estimates gas cost for multi-transfer operations.
787
+ * Useful for displaying total gas costs before executing batch transfers.
788
+ *
789
+ * @param fromAddress - The sender's account address
790
+ * @param recipients - Array of recipients with addresses and amounts
791
+ * @param chainId - The ID of the blockchain for the transaction
792
+ * @param isNative - Whether estimating for native tokens (true) or ERC-20/SPL tokens (false)
793
+ * @param tokenAddress - Token contract address (required if isNative is false)
794
+ * @returns Promise resolving to the estimated total gas cost as a bigint
795
+ * @throws Error if estimation fails or parameters are invalid
796
+ *
797
+ * @example
798
+ * ```typescript
799
+ * const recipients = [
800
+ * { address: '0x1111...', amount: '0.01' },
801
+ * { address: '0x2222...', amount: '0.02' },
802
+ * { address: '0x3333...', amount: '0.005' }
803
+ * ];
804
+ *
805
+ * // Estimate for native token multi-transfer
806
+ * const nativeGasEstimate = await vault.estimateMultiTransferGas(
807
+ * '0x1234...',
808
+ * recipients,
809
+ * '1', // Ethereum
810
+ * true // Native tokens
811
+ * );
812
+ *
813
+ * // Estimate for token multi-transfer
814
+ * const tokenGasEstimate = await vault.estimateMultiTransferGas(
815
+ * '0x1234...',
816
+ * recipients,
817
+ * '1', // Ethereum
818
+ * false, // ERC-20 tokens
819
+ * '0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8C' // USDC
820
+ * );
821
+ *
822
+ * console.log('Native multi-transfer gas:', nativeGasEstimate.toString());
823
+ * console.log('Token multi-transfer gas:', tokenGasEstimate.toString());
824
+ * ```
825
+ */
826
+ async estimateMultiTransferGas(fromAddress, recipients, chainId, isNative = true, tokenAddress) {
827
+ const chainInfo = (0, chains_1.getChainConfig)(chainId);
828
+ const privateKey = await this.getPrivateKeyFor(fromAddress);
829
+ let chainService;
830
+ if (chainInfo.type === 'solana') {
831
+ chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainInfo);
832
+ }
833
+ else {
834
+ chainService = new ChainService_1.ChainService(privateKey, chainInfo);
835
+ }
836
+ const multiTransferService = new MultiTransferService_1.MultiTransferService(this, chainService, chainId);
837
+ return multiTransferService.estimateGasCost(recipients, chainId, isNative, tokenAddress);
838
+ }
839
+ // --- NFT Methods ---
840
+ /**
841
+ * Gets all NFTs owned by an address across multiple collections.
842
+ * Pure blockchain implementation - reads from smart contracts.
843
+ *
844
+ * @param address - Wallet address to check
845
+ * @param contractAddresses - Array of NFT contract addresses to check
846
+ * @param options - NFT query options
847
+ * @returns Promise resolving to array of NFT details
848
+ * @throws Error if any contract query fails
849
+ *
850
+ * @example
851
+ * ```typescript
852
+ * const collections = [
853
+ * '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D', // BAYC
854
+ * '0x60E4d786628Fea6478F785A6d7e704777c86a7c6' // MAYC
855
+ * ];
856
+ *
857
+ * const nfts = await vault.getOwnedNFTs(
858
+ * '0x1234...', // Wallet address
859
+ * collections,
860
+ * { includeMetadata: true }
861
+ * );
862
+ *
863
+ * console.log(`Found ${nfts.length} owned NFTs`);
864
+ * nfts.forEach(nft => {
865
+ * console.log(`${nft.name} from ${nft.contractAddress}`);
866
+ * });
867
+ * ```
868
+ */
869
+ async getOwnedNFTs(address, contractAddresses, options = {}) {
870
+ const nftService = new NFTService_1.NFTService(this.chainId, this, address);
871
+ const nfts = [];
872
+ for (const contractAddress of contractAddresses) {
873
+ try {
874
+ const balance = await nftService.getNFTBalance(address, contractAddress);
875
+ for (const tokenId of balance.tokenIds) {
876
+ try {
877
+ const nftDetail = await nftService.getNFTDetail(contractAddress, tokenId, options);
878
+ nfts.push(nftDetail);
879
+ }
880
+ catch (error) {
881
+ console.warn(`Failed to get NFT detail for ${contractAddress}:${tokenId}:`, error);
882
+ }
883
+ }
884
+ }
885
+ catch (error) {
886
+ console.warn(`Failed to get balance for contract ${contractAddress}:`, error);
887
+ }
888
+ }
889
+ return nfts;
890
+ }
891
+ /**
892
+ * Transfers an NFT from one address to another.
893
+ * Pure blockchain implementation using smart contract calls.
894
+ *
895
+ * @param fromAddress - Sender's address
896
+ * @param toAddress - Recipient's address
897
+ * @param contractAddress - NFT contract address
898
+ * @param tokenId - Token ID to transfer
899
+ * @returns Promise resolving to transaction response
900
+ * @throws Error if transfer fails, insufficient permissions, or NFT not owned
901
+ *
902
+ * @example
903
+ * ```typescript
904
+ * const result = await vault.transferNFT(
905
+ * '0x1234...', // From address
906
+ * '0x5678...', // To address
907
+ * '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D', // BAYC contract
908
+ * '1234' // Token ID
909
+ * );
910
+ *
911
+ * console.log('Transfer successful:', result.hash);
912
+ * ```
913
+ */
914
+ async transferNFT(fromAddress, toAddress, contractAddress, tokenId) {
915
+ const chainInfo = (0, chains_1.getChainConfig)(this.chainId);
916
+ const privateKey = await this.getPrivateKeyFor(fromAddress);
917
+ if (chainInfo.type === 'solana') {
918
+ const chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainInfo);
919
+ // Solana NFT transfer implementation would go here
920
+ throw new Error('Solana NFT transfer not yet implemented');
921
+ }
922
+ else {
923
+ const chainService = new ChainService_1.ChainService(privateKey, chainInfo);
924
+ // Create ERC-721 contract instance
925
+ const contract = new ethers_1.ethers.Contract(contractAddress, [
926
+ "function transferFrom(address from, address to, uint256 tokenId)",
927
+ "function ownerOf(uint256 tokenId) view returns (address)"
928
+ ], chainService['wallet']);
929
+ // Verify ownership
930
+ const owner = await contract.ownerOf(tokenId);
931
+ if (owner.toLowerCase() !== fromAddress.toLowerCase()) {
932
+ throw new Error('NFT is not owned by the specified address');
933
+ }
934
+ // Transfer NFT
935
+ const tx = await contract.transferFrom(fromAddress, toAddress, tokenId);
936
+ const receipt = await tx.wait();
937
+ return {
938
+ hash: receipt.hash,
939
+ from: fromAddress,
940
+ to: toAddress,
941
+ blockNumber: receipt.blockNumber
942
+ };
943
+ }
944
+ }
945
+ // --- QR Code Methods ---
946
+ /**
947
+ * Imports wallet from QR code data.
948
+ * @param qrString - QR code string to parse
949
+ * @param password - Password to decrypt the mnemonic
950
+ * @returns Promise resolving to imported account information
951
+ * @throws Error if QR data is invalid or import fails
952
+ *
953
+ * @example
954
+ * ```typescript
955
+ * // Import wallet from scanned QR code
956
+ * const accounts = await vault.importFromQR(qrString, 'my-password');
957
+ * console.log('Imported accounts:', accounts);
958
+ * ```
959
+ */
960
+ async importFromQR(qrString, password) {
961
+ try {
962
+ const importData = QRCodeService_1.QRCodeService.extractWalletImportData(qrString);
963
+ // Decrypt the mnemonic
964
+ const mnemonic = await EncryptionService_1.EncryptionService.decrypt(importData.encryptedMnemonic, password);
965
+ // Create new keyring from mnemonic
966
+ let newKeyring;
967
+ if (importData.chainType === 'solana') {
968
+ newKeyring = new SolanaKeyring_1.SolanaKeyring(mnemonic);
969
+ }
970
+ else {
971
+ newKeyring = new HDKeyring_1.HDKeyring(mnemonic);
972
+ await newKeyring.initialize();
973
+ }
974
+ // Add the keyring to vault
975
+ this.keyrings.push(newKeyring);
976
+ // Update chain ID if needed
977
+ if (importData.chainType === 'solana' && this.chainId !== 'solana') {
978
+ this.chainId = 'solana';
979
+ }
980
+ // Return imported accounts
981
+ const accounts = [];
982
+ for (let i = 0; i < Math.min(importData.accountCount, newKeyring.accounts.length); i++) {
983
+ accounts.push({
984
+ address: newKeyring.accounts[i],
985
+ type: newKeyring.type,
986
+ name: `${newKeyring.type} ${i + 1}`
987
+ });
988
+ }
989
+ return accounts;
990
+ }
991
+ catch (error) {
992
+ throw new Error(`Failed to import wallet from QR: ${error instanceof Error ? error.message : 'Unknown error'}`);
993
+ }
994
+ }
995
+ /**
996
+ * Processes transaction from QR code data.
997
+ * @param qrString - QR code string to parse
998
+ * @param fromAddress - Sender's address
999
+ * @returns Promise resolving to transaction response
1000
+ * @throws Error if QR data is invalid or transaction fails
1001
+ *
1002
+ * @example
1003
+ * ```typescript
1004
+ * // Process transaction from scanned QR code
1005
+ * const result = await vault.processTransactionFromQR(qrString, '0x1234...');
1006
+ * console.log('Transaction hash:', result.hash);
1007
+ * ```
1008
+ */
1009
+ async processTransactionFromQR(qrString, fromAddress) {
1010
+ try {
1011
+ const txData = QRCodeService_1.QRCodeService.extractTransactionData(qrString);
1012
+ // Validate sender address
1013
+ const accounts = this.getAccounts();
1014
+ const senderAccount = accounts.find(acc => acc.address.toLowerCase() === fromAddress.toLowerCase());
1015
+ if (!senderAccount) {
1016
+ throw new Error('Sender address not found in wallet accounts');
1017
+ }
1018
+ // Execute transaction based on type
1019
+ switch (txData.txType) {
1020
+ case 'native':
1021
+ return await this.sendNativeToken(fromAddress, txData.to, txData.amount, txData.chainId);
1022
+ case 'token':
1023
+ if (!txData.tokenAddress) {
1024
+ throw new Error('Token address required for token transfer');
1025
+ }
1026
+ return await this.sendToken(fromAddress, txData.to, txData.amount, txData.tokenAddress, txData.chainId);
1027
+ case 'nft':
1028
+ if (!txData.tokenAddress || !txData.tokenId) {
1029
+ throw new Error('Token address and token ID required for NFT transfer');
1030
+ }
1031
+ return await this.transferNFT(fromAddress, txData.to, txData.tokenAddress, txData.tokenId);
1032
+ default:
1033
+ throw new Error(`Unsupported transaction type: ${txData.txType}`);
1034
+ }
1035
+ }
1036
+ catch (error) {
1037
+ throw new Error(`Failed to process transaction from QR: ${error instanceof Error ? error.message : 'Unknown error'}`);
1038
+ }
1039
+ }
1040
+ /**
1041
+ * Sends native tokens (ETH, BNB, MATIC, etc.) from a specific account to another address.
1042
+ * @param fromAddress - The sender's account address
1043
+ * @param to - The recipient's address
1044
+ * @param amount - The amount to send as a string (e.g., "0.1")
1045
+ * @param chainId - The ID of the blockchain for the transaction
1046
+ * @returns Promise resolving to the transaction response
1047
+ * @throws Error if insufficient balance, invalid addresses, or transaction fails
1048
+ *
1049
+ * @example
1050
+ * ```typescript
1051
+ * // Send 0.1 ETH on Ethereum mainnet
1052
+ * const tx = await vault.sendNativeToken(from, to, '0.1', '1');
1053
+ * console.log('Transaction hash:', tx.hash);
1054
+ * ```
1055
+ */
1056
+ async sendNativeToken(fromAddress, to, amount, chainId) {
1057
+ const chainInfo = (0, chains_1.getChainConfig)(chainId);
1058
+ const privateKey = await this.getPrivateKeyFor(fromAddress);
1059
+ if (chainInfo.type === 'solana') {
1060
+ const chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainInfo);
1061
+ const result = await chainService.sendTransaction(to, amount);
1062
+ return {
1063
+ hash: result.hash,
1064
+ from: result.from,
1065
+ to: result.to,
1066
+ blockNumber: result.blockNumber || undefined
1067
+ };
1068
+ }
1069
+ else {
1070
+ const chainService = new ChainService_1.ChainService(privateKey, chainInfo);
1071
+ // Create transaction request for native token transfer
1072
+ const txRequest = {
1073
+ to: to,
1074
+ value: ethers_1.ethers.parseEther(amount)
1075
+ };
1076
+ const result = await chainService.sendTransaction(txRequest);
1077
+ return {
1078
+ hash: result.hash,
1079
+ from: fromAddress,
1080
+ to: to,
1081
+ blockNumber: result.blockNumber || undefined
1082
+ };
1083
+ }
1084
+ }
1085
+ }
1086
+ exports.Vault = Vault;
1087
+ // Rate limiting for export attempts
1088
+ Vault.exportAttempts = new Map();
1089
+ Vault.MAX_EXPORT_ATTEMPTS = 5;
1090
+ Vault.EXPORT_COOLDOWN_MS = 30000; // 30 seconds