hodl-wallet 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # 🧊 HODL Wallet: Blazing-fast, transparent, and ad-free crypto wallet!
2
+
3
+ ## 🚀 Why HODL Wallet?
4
+
5
+ Let's face it, Trust Wallet's sluggishness and annoying ads are so last season. HODL Wallet is here to revolutionize your crypto experience:
6
+
7
+ - 🏎️ Lightning-fast operations
8
+ - 🧊 Cool, minimalist CLI interface
9
+ - 🚫 Zero ads, zero BS
10
+ - 🔒 Create wallets offline (because paranoia is just good sense in crypto)
11
+ - 🔍 Fully transparent, open-source code
12
+
13
+ ## 🛠️ Installation
14
+
15
+ ```bash
16
+ npm install -g hodl-wallet
17
+ ```
18
+
19
+ ## 🚀 Quick Start
20
+
21
+ ```bash
22
+ hodl
23
+ ```
24
+
25
+ That's it! Follow the prompts and you're in crypto heaven.
26
+
27
+ ## 🎮 Features
28
+
29
+ ### 💰 Create a Wallet
30
+
31
+ Pro tip: Do this offline if you're feeling extra cautious. We won't judge.
32
+
33
+ ### 💸 Send Funds
34
+
35
+ Smoother than sliding into your crush's DMs.
36
+
37
+ ### 👀 Check Balance
38
+
39
+ Because constantly checking your balance is totally healthy.
40
+
41
+ ## 🔒 Security
42
+
43
+ ### Private Key Storage
44
+
45
+ Your private key is securely stored in a JSON file, encrypted with your first password. This ensures that your sensitive information remains protected while still being accessible when you need it.
46
+
47
+ ## 🔬 Transparency
48
+
49
+ We're as transparent as your ex's excuses. Our code is open-source, and we encourage you to dive in, explore, and contribute. Trust isn't given; it's earned and verified.
50
+
51
+ ## 🔍 Security Audit
52
+
53
+ We encourage users to perform their own security audits. One easy way to do this is to copy the entire codebase into ChatGPT or another AI assistant and ask if the code appears secure or if there are any malicious intentions. This is a good practice for any open-source project you're considering using.
54
+
55
+ ## 🤝 Contributing
56
+
57
+ Found a bug? Want to add a feature? We're all ears! Open an issue or submit a PR. Let's make crypto easier together.
58
+
59
+ ## 📜 License
60
+
61
+ MIT License. Go wild, but don't blame us if you YOLO your life savings into DogeMoonRocket tokens.
62
+
63
+ ---
64
+
65
+ Remember: With great power comes great responsibility. And with crypto, comes great volatility. HODL responsibly! 🚀🌕
package/index.mjs ADDED
@@ -0,0 +1,612 @@
1
+ // wallet.js
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import Web3 from 'web3';
5
+ import inquirer from 'inquirer';
6
+ import Persist from './persist.js';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname } from 'path';
9
+ import Table from 'cli-table3';
10
+ import bip39 from 'bip39';
11
+ import hdkey from 'hdkey';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ import inquirerAutocomplete from 'inquirer-autocomplete-prompt';
17
+ inquirer.registerPrompt('autocomplete', inquirerAutocomplete);
18
+
19
+ class Wallet {
20
+ constructor(encryptionKey) {
21
+ this.db = new Persist(encryptionKey);
22
+ this.web3 = null;
23
+ this.selectedNetwork = null;
24
+ this.account = null;
25
+ }
26
+
27
+ async initialize() {
28
+ try {
29
+ const networkPlugins = await this.loadNetworkPlugins();
30
+ if (networkPlugins.length === 0) {
31
+ console.log('No valid network plugins found.');
32
+ process.exit(1);
33
+ }
34
+ await this.selectNetwork(networkPlugins);
35
+ this.web3 = new Web3(this.selectedNetwork.rpcUrl);
36
+ await this.loadAccount();
37
+
38
+ if (!this.account) {
39
+ throw new Error('Failed to initialize account');
40
+ }
41
+
42
+ } catch (error) {
43
+ console.error('Initialization failed:', error);
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ displayAccountAddress() {
49
+ const table = new Table({
50
+ head: ['Account Address'],
51
+ style: {
52
+ head: ['green']
53
+ }
54
+ });
55
+ table.push([this.account.address]);
56
+ console.log(table.toString());
57
+ }
58
+
59
+ async loadNetworkPlugins() {
60
+ const pluginsDir = path.join(__dirname, 'network');
61
+ const pluginFiles = fs.readdirSync(pluginsDir).filter(file => file.endsWith('.js'));
62
+
63
+ const networks = await Promise.all(pluginFiles.map(async file => {
64
+ const plugin = await import(`./network/${file}`);
65
+ return plugin.default;
66
+ }));
67
+
68
+ return networks.filter(network => network && network.name);
69
+ }
70
+
71
+ async selectNetwork(networkPlugins) {
72
+ const { network } = await inquirer.prompt({
73
+ type: 'list',
74
+ name: 'network',
75
+ message: 'Select the network:',
76
+ choices: networkPlugins.map(plugin => plugin.name),
77
+ });
78
+
79
+ const selectedNetwork = networkPlugins.find(plugin => plugin.name === network);
80
+
81
+ this.selectedNetwork = selectedNetwork;
82
+ }
83
+
84
+ async loadAccount(forceReload = false) {
85
+ let account = this.db.secureGet('account');
86
+
87
+ if (!account || forceReload) {
88
+ const { accountAction } = await inquirer.prompt({
89
+ type: 'list',
90
+ name: 'accountAction',
91
+ message: 'Select an account option:',
92
+ choices: ['Import private key', 'Import from 12 words', 'Create new account'],
93
+ });
94
+
95
+ if (accountAction === 'Import private key') {
96
+ const { privateKey } = await inquirer.prompt({
97
+ type: 'password',
98
+ name: 'privateKey',
99
+ message: 'Private key:',
100
+ mask: '*',
101
+ });
102
+ account = this.web3.eth.accounts.privateKeyToAccount(privateKey);
103
+ await this.db.secureSet('account', { privateKey: account.privateKey });
104
+
105
+ this.account = account;
106
+ this.displayAccountAddress();
107
+ } else if (accountAction === 'Import from 12 words') {
108
+ account = await this.importFrom12Words();
109
+ this.account = account;
110
+ this.displayAccountAddress();
111
+ } else {
112
+ const mnemonic = bip39.generateMnemonic();
113
+ const seed = await bip39.mnemonicToSeed(mnemonic);
114
+ const root = hdkey.fromMasterSeed(seed);
115
+ const addrNode = root.derive("m/44'/60'/0'/0/0");
116
+ const privateKey = addrNode.privateKey.toString('hex');
117
+ account = this.web3.eth.accounts.privateKeyToAccount('0x' + privateKey);
118
+ this.db.secureSet('account', { privateKey: account.privateKey });
119
+
120
+ const table = new Table({
121
+ head: [{ colSpan: 2, content: 'New Account Created' }],
122
+ style: { head: ['green'] },
123
+ wordWrap: true
124
+ });
125
+
126
+ table.push(
127
+ ['Address', account.address],
128
+ ['Private Key', account.privateKey],
129
+ ['Mnemonic Phrase', mnemonic],
130
+ ['WARNING', "Please write down your mnemonic phrase and keep it in a safe place. \nIt\'s crucial for recovering your account."]
131
+ );
132
+
133
+ console.log('\n' + table.toString());
134
+ }
135
+ } else {
136
+ account = this.web3.eth.accounts.privateKeyToAccount(account.privateKey);
137
+ }
138
+
139
+ this.account = account;
140
+ }
141
+
142
+ async importFrom12Words() {
143
+ const { mnemonic } = await inquirer.prompt({
144
+ type: 'password',
145
+ name: 'mnemonic',
146
+ message: 'Enter your 12-word mnemonic phrase:',
147
+ mask: '*',
148
+ });
149
+
150
+ if (!bip39.validateMnemonic(mnemonic)) {
151
+ console.error('Invalid mnemonic phrase');
152
+ return null;
153
+ }
154
+
155
+ const seed = await bip39.mnemonicToSeed(mnemonic);
156
+ const root = hdkey.fromMasterSeed(seed);
157
+ const addrNode = root.derive("m/44'/60'/0'/0/0");
158
+ const privateKey = addrNode.privateKey.toString('hex');
159
+ const account = this.web3.eth.accounts.privateKeyToAccount('0x' + privateKey);
160
+
161
+ await this.db.secureSet('account', { privateKey: account.privateKey });
162
+
163
+ return account;
164
+ }
165
+
166
+ async showBalance() {
167
+ if (!this.account) {
168
+ console.error('Account not initialized. Please try restarting the application.');
169
+ return;
170
+ }
171
+
172
+ const balanceWei = await this.web3.eth.getBalance(this.account.address);
173
+ const balance = Web3.utils.fromWei(balanceWei, 'ether');
174
+
175
+ const table = new Table({
176
+ head: ['Token', 'Balance'],
177
+ style: { head: ['blue'] },
178
+ colWidths: [21, 22]
179
+ });
180
+
181
+ // Add address as the top row
182
+ // table.push([{ colSpan: 2, content: this.account.address }]);
183
+ table.push([this.selectedNetwork.nativeToken, parseFloat(balance).toFixed(8)]);
184
+
185
+ // Check if USDT is available on the selected network
186
+ if (this.selectedNetwork.tokens['USDT']) {
187
+ const usdtAddress = this.selectedNetwork.tokens['USDT'];
188
+ const ERC20_ABI = [
189
+ {
190
+ constant: true,
191
+ inputs: [{ name: "_owner", type: "address" }],
192
+ name: "balanceOf",
193
+ outputs: [{ name: "balance", type: "uint256" }],
194
+ type: "function"
195
+ },
196
+ {
197
+ constant: true,
198
+ inputs: [],
199
+ name: "decimals",
200
+ outputs: [{ name: "", type: "uint8" }],
201
+ type: "function"
202
+ }
203
+ ];
204
+
205
+ const usdtContract = new this.web3.eth.Contract(ERC20_ABI, usdtAddress);
206
+ const usdtBalance = await usdtContract.methods.balanceOf(this.account.address).call();
207
+ const decimals = await usdtContract.methods.decimals().call();
208
+
209
+ // Fixed calculation using BigInt consistently
210
+ const formattedUsdtBalance = Number(
211
+ (BigInt(usdtBalance) * 100n) / (10n ** BigInt(decimals))
212
+ ) / 100;
213
+
214
+ table.push(['USDT', formattedUsdtBalance.toFixed(8)]);
215
+ }
216
+
217
+ console.log(table.toString());
218
+ console.log('');
219
+ }
220
+
221
+ async transferFunds() {
222
+ let addressBook = this.db.get('addressBook') || [];
223
+
224
+ const { token } = await inquirer.prompt({
225
+ type: 'list',
226
+ name: 'token',
227
+ message: 'Token to transfer:',
228
+ choices: Object.keys(this.selectedNetwork.tokens),
229
+ });
230
+
231
+ const tokens = this.selectedNetwork.tokens;
232
+
233
+ // Implement autocomplete for address book
234
+ const { recipient } = await inquirer.prompt({
235
+ type: 'autocomplete',
236
+ name: 'recipient',
237
+ message: 'Recipient address:',
238
+ source: (answersSoFar, input) => {
239
+ input = input || '';
240
+ return addressBook
241
+ .filter(entry => entry.name.toLowerCase().includes(input.toLowerCase()) || entry.address.toLowerCase().includes(input.toLowerCase()))
242
+ .map(entry => ({ name: `${entry.name} (${entry.address})`, value: entry.address }))
243
+ .concat([{ name: input, value: input }]); // Add the input as a possible choice
244
+ },
245
+ });
246
+
247
+ let address = recipient;
248
+
249
+ const { amount } = await inquirer.prompt({
250
+ type: 'input',
251
+ name: 'amount',
252
+ message: `Amount to transfer [${token}] (leave empty to cancel):`,
253
+ validate: value => {
254
+ if (value === '') return true;
255
+ return !isNaN(value) && Number(value) > 0 ? true : 'Please enter a valid number or leave empty to cancel.';
256
+ },
257
+ });
258
+
259
+ if (amount === '') {
260
+ console.log('Transaction cancelled.');
261
+ return;
262
+ }
263
+
264
+ try {
265
+ let signedTx;
266
+ if (tokens[token] && token !== this.selectedNetwork.nativeToken) {
267
+ signedTx = await this.handleERC20Transfer(token, address, amount);
268
+ } else {
269
+ signedTx = await this.handleNativeTransfer(address, amount);
270
+ }
271
+
272
+ const receipt = await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction);
273
+ await this.displayTransactionResult(receipt);
274
+
275
+ // Add transaction to history
276
+ this.addToTransactions(address, token, amount, receipt);
277
+
278
+ if (!addressBook.some(entry => entry.address === address)) {
279
+ await this.addToAddressBook(address);
280
+ }
281
+
282
+ } catch (error) {
283
+ await this.displayTransactionResult(null, error);
284
+ }
285
+ }
286
+
287
+ addToTransactions(recipient, token, amount, receipt) {
288
+ const transaction = {
289
+ timestamp: new Date().toISOString(),
290
+ recipient,
291
+ token,
292
+ amount,
293
+ hash: receipt.transactionHash
294
+ };
295
+ this.db.add('transactions', this.selectedNetwork.nativeToken, transaction);
296
+ }
297
+
298
+ async showTransactions() {
299
+ const history = this.db.values('transactions', this.selectedNetwork.nativeToken) || [];
300
+
301
+ const table = new Table({
302
+ head: ['Date', 'Recipient', 'Token', 'Amount'],
303
+ style: { head: ['blue'] },
304
+ });
305
+
306
+ if (history.length === 0) {
307
+ table.push([{ colSpan: 4, content: 'No transaction history available.' }]);
308
+ }
309
+
310
+ history.forEach(tx => {
311
+ const date = new Date(tx.timestamp).toLocaleString('en-GB', {
312
+ year: 'numeric',
313
+ month: '2-digit',
314
+ day: '2-digit',
315
+ hour: '2-digit',
316
+ minute: '2-digit',
317
+ hour12: false
318
+ }).replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$2-$1').replace(",", "");
319
+ table.push([date, tx.recipient, tx.token, tx.amount]);
320
+ table.push([{ colSpan: 4, content: this.selectedNetwork.explorer + tx.hash }]);
321
+ });
322
+
323
+ console.log(table.toString());
324
+ }
325
+
326
+ async addToAddressBook(address) {
327
+ const { name } = await inquirer.prompt({
328
+ type: 'input',
329
+ name: 'name',
330
+ message: 'Name for the address book (leave empty to skip):',
331
+ });
332
+
333
+ if (name.trim() !== '') {
334
+ let addressBook = this.db.get('addressBook') || [];
335
+ addressBook.push({ name, address });
336
+ this.db.set('addressBook', addressBook);
337
+
338
+ const table = new Table({
339
+ head: [{ colSpan: 2, content: "Recipient saved to the address book." }],
340
+ style: { head: ['green'] },
341
+ });
342
+
343
+ table.push([name, address]);
344
+ console.log('\n' + table.toString());
345
+ }
346
+ }
347
+
348
+ async handleERC20Transfer(token, recipient, amount) {
349
+ const ERC20_ABI = [
350
+ // Minimal ABI to interact with ERC20 tokens
351
+ {
352
+ constant: true,
353
+ name: 'decimals',
354
+ inputs: [],
355
+ outputs: [{ name: '', type: 'uint8' }],
356
+ type: 'function',
357
+ },
358
+ {
359
+ constant: false,
360
+ name: 'transfer',
361
+ inputs: [
362
+ { name: '_to', type: 'address' },
363
+ { name: '_value', type: 'uint256' },
364
+ ],
365
+ outputs: [{ name: '', type: 'bool' }],
366
+ type: 'function',
367
+ },
368
+ ];
369
+
370
+ const contract = new this.web3.eth.Contract(ERC20_ABI, this.selectedNetwork.tokens[token]);
371
+ const decimals = parseInt(await contract.methods.decimals().call(), 10);
372
+
373
+ const weiAmount = BigInt(this.web3.utils.toWei(amount, 'ether'));
374
+ const adjustedAmount = weiAmount / (10n ** BigInt(18 - decimals));
375
+
376
+ try {
377
+ const data = contract.methods.transfer(recipient, adjustedAmount.toString()).encodeABI();
378
+ const gasLimit = 100000;
379
+ const gasPrice = await this.web3.eth.getGasPrice();
380
+
381
+ const tx = {
382
+ from: this.account.address,
383
+ to: this.selectedNetwork.tokens[token],
384
+ data: data,
385
+ gas: gasLimit,
386
+ gasPrice: gasPrice,
387
+ };
388
+
389
+ return this.web3.eth.accounts.signTransaction(tx, this.account.privateKey);
390
+ } catch (error) {
391
+ if (error instanceof Web3ValidatorError) {
392
+ const table = new Table({
393
+ head: [{ colSpan: 2, content: 'Error: Invalid Address' }],
394
+ style: { head: ['red'] }
395
+ });
396
+ table.push(['Address', recipient]);
397
+ console.log(table.toString());
398
+ }
399
+ }
400
+ }
401
+
402
+ async handleNativeTransfer(recipient, amount) {
403
+ const gasPrice = await this.web3.eth.getGasPrice();
404
+ const gasLimit = 21000; // Gas estándar para una transacción simple
405
+ const gasCost = BigInt(gasPrice) * BigInt(gasLimit);
406
+ let amountWei = BigInt(this.web3.utils.toWei(amount, 'ether'));
407
+
408
+ // Obtener el balance actual
409
+ const balance = BigInt(await this.web3.eth.getBalance(this.account.address));
410
+
411
+ if (balance < amountWei + gasCost) {
412
+ amountWei -= gasCost;
413
+ }
414
+
415
+ if (balance < amountWei + gasCost) {
416
+ const maxAmount = this.web3.utils.fromWei((balance - gasCost).toString(), 'ether');
417
+ const table = new Table({
418
+ head: [{ colSpan: 2, content: 'Insufficient balance for this transaction' }],
419
+ style: { head: ['red'] },
420
+ wordWrap: true
421
+ });
422
+ table.push(['Maximum Amount', `${maxAmount} ${this.selectedNetwork.nativeToken}`]);
423
+ console.log(table.toString());
424
+ return;
425
+ }
426
+
427
+ const tx = {
428
+ from: this.account.address,
429
+ to: recipient,
430
+ value: amountWei.toString(),
431
+ gas: gasLimit,
432
+ gasPrice: gasPrice
433
+ };
434
+
435
+ return this.web3.eth.accounts.signTransaction(tx, this.account.privateKey);
436
+ }
437
+
438
+ async displayTransactionResult(receipt, error = null) {
439
+ if (receipt) {
440
+ const gasUsed = receipt.gasUsed;
441
+ const gasPrice = await this.web3.eth.getGasPrice();
442
+ const transactionFee = this.web3.utils.fromWei((BigInt(gasUsed) * BigInt(gasPrice)).toString(), 'ether');
443
+
444
+ const table = new Table({
445
+ head: [{ colSpan: 2, content: 'Transaction successful, share this link with the recipient' }],
446
+ style: { head: ['green'] }
447
+ });
448
+ table.push(
449
+ ['Explorer', this.selectedNetwork.explorer + receipt.transactionHash],
450
+ ['Fee', `${transactionFee} ${this.selectedNetwork.nativeToken}`],
451
+ );
452
+
453
+ console.log('\n' + table.toString());
454
+ } else if (error) {
455
+ const table = new Table({
456
+ head: [error.message],
457
+ style: { head: ['red'] },
458
+ wordWrap: true,
459
+ });
460
+
461
+ if (error.reason) {
462
+ table.push([error.reason.replace(/(\w+):/g, "\n$1:").trim()]);
463
+ }
464
+
465
+ console.log('\n' + table.toString());
466
+ }
467
+ }
468
+
469
+ clearAccountData() {
470
+ if (this.account) {
471
+ this.account.privateKey = '0'.repeat(64);
472
+ this.account = null;
473
+ }
474
+ }
475
+ }
476
+
477
+ class UIManager {
478
+ static displayWelcome() {
479
+ console.log('\x1b[32m'); // Set text color to green
480
+ console.log(` ░░░░░░░░░░░░░░ █ █ █▀█ █▀▄ █ ░░░░░░░░░░░░░░
481
+ ░░░░░░░░░░░░░░ █▀█ █▄█ █▄▀ █▄▄ ░░░░░░░░░░░░░░
482
+ ░░░░░░░░░░░░░░ ──────── WALLET ░░░░░░░░░░░░░░`);
483
+ console.log('\x1b[0m'); // Reset text color
484
+ }
485
+
486
+ static async getEncryptionKey() {
487
+ const { key } = await inquirer.prompt({
488
+ type: 'password',
489
+ name: 'key',
490
+ message: 'Password:',
491
+ mask: '*',
492
+ });
493
+ return key;
494
+ }
495
+
496
+ static async confirmEncryptionKey(originalKey) {
497
+ const { confirmKey } = await inquirer.prompt({
498
+ type: 'password',
499
+ name: 'confirmKey',
500
+ message: 'Repeat Password:',
501
+ mask: '*',
502
+ });
503
+ return confirmKey;
504
+ }
505
+
506
+ static displayExitPhrase() {
507
+ const phrases = [
508
+ "Buy the rumor, sell the news",
509
+ "The trend is your friend",
510
+ "Don't fight the tape",
511
+ "Cut your losses and let your profits run",
512
+ "Be fearful when others are greedy, and greedy when others are fearful",
513
+ "The market can remain irrational longer than you can remain solvent",
514
+ "Bulls make money, bears make money, pigs get slaughtered",
515
+ "No one is bigger than the market",
516
+ "Don't catch a falling knife",
517
+ "Past performance is not indicative of future results",
518
+ "The stock market is a device for transferring money from the impatient to the patient",
519
+ "Time in the market beats timing the market",
520
+ "Buy low, sell high",
521
+ "Diversification is the only free lunch in investing",
522
+ "The four most dangerous words in investing are: 'This time it's different'",
523
+ "Markets can remain irrational a lot longer than you and I can remain solvent",
524
+ "Risk comes from not knowing what you're doing",
525
+ "In the short run, the market is a voting machine. In the long run, it's a weighing machine",
526
+ "Invest in yourself. Your career is the engine of your wealth",
527
+ "Who has the gold makes the rules",
528
+ "The best time to invest was yesterday. The second best time is now",
529
+ "Don't put all your eggs in one basket",
530
+ "Knowledge is power in the world of investing",
531
+ "Patience is a virtue in the stock market",
532
+ "The market is never wrong, but opinions often are"
533
+ ];
534
+ const randomPhrase = phrases[Math.floor(Math.random() * phrases.length)];
535
+
536
+ const table = new Table({
537
+ head: ['✨ Good bye!'],
538
+ style: { head: ['yellow'] },
539
+ wordWrap: true,
540
+ });
541
+
542
+ table.push([randomPhrase]);
543
+
544
+ console.log(table.toString());
545
+ }
546
+ }
547
+
548
+ async function main() {
549
+ UIManager.displayWelcome();
550
+ const encryptionKey = await UIManager.getEncryptionKey();
551
+ const wallet = new Wallet(encryptionKey);
552
+
553
+ // Check if an account exists
554
+ const accountExists = wallet.db.secureGet('account');
555
+ if (accountExists === null) {
556
+ console.error('Wrong password.');
557
+ process.exit(1);
558
+ }
559
+
560
+ if (!accountExists) {
561
+ const confirmedKey = await UIManager.confirmEncryptionKey(encryptionKey);
562
+ if (confirmedKey !== encryptionKey) {
563
+ console.error('Passwords do not match. Please try again.');
564
+ process.exit(1);
565
+ }
566
+ }
567
+
568
+ try {
569
+ await wallet.initialize();
570
+ if (accountExists) wallet.displayAccountAddress();
571
+ } catch (error) {
572
+ console.error('Failed to initialize wallet:', error);
573
+ process.exit(1);
574
+ }
575
+
576
+ // Register the displayExitPhrase method and account clearing to be called on process exit
577
+ process.on('exit', () => {
578
+ wallet.clearAccountData();
579
+ UIManager.displayExitPhrase();
580
+ });
581
+
582
+ while (true) {
583
+ const { action } = await inquirer.prompt({
584
+ type: 'list',
585
+ name: 'action',
586
+ message: 'What would you like to do?',
587
+ choices: ['Transfer funds', 'Show balance', 'Show transactions', 'Change account', 'Exit'],
588
+ });
589
+
590
+ if (action === 'Transfer funds') {
591
+ await wallet.transferFunds();
592
+ } else if (action === 'Show balance') {
593
+ await wallet.showBalance();
594
+ } else if (action === 'Show transactions') {
595
+ await wallet.showTransactions();
596
+ } else if (action === 'Change account') {
597
+ await wallet.loadAccount(true);
598
+ } else {
599
+ process.exit();
600
+ }
601
+ }
602
+ }
603
+
604
+ main().catch(error => {
605
+ console.error('An unexpected error occurred:', error);
606
+ process.exit(1);
607
+ });
608
+
609
+ process.on('SIGINT', () => {
610
+ console.log('\n');
611
+ process.exit();
612
+ });
package/network/bsc.js ADDED
@@ -0,0 +1,12 @@
1
+ // plugins/bsc.js
2
+ module.exports = {
3
+ name: '[BEP-20] Binance Smart Chain',
4
+ explorer: 'https://bscscan.com/tx/',
5
+ rpcUrl: 'https://bsc-dataseed.binance.org/',
6
+ nativeToken: 'BNB',
7
+ tokens: {
8
+ 'USDT': '0x55d398326f99059fF775485246999027B3197955', // Dirección del contrato USDT en BSC
9
+ 'BNB': '0x0000000000000000000000000000000000000000', // Dirección nativa (BNB)
10
+ // Puedes agregar otros tokens BEP20 aquí
11
+ },
12
+ };
package/network/erc.js ADDED
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ name: '[ERC-20] Ethereum',
3
+ explorer: 'https://etherscan.io/tx/',
4
+ rpcUrl: 'https://eth.public-rpc.com',
5
+ nativeToken: 'ETH',
6
+ tokens: {
7
+ 'USDT': '0xdAC17F958D2ee523a2206206994597C13D831ec7', // Dirección del contrato USDT en Ethereum
8
+ 'ETH': '0x0000000000000000000000000000000000000000', // Dirección nativa (ETH)
9
+ // Puedes agregar otros tokens ERC20 aquí
10
+ },
11
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "hodl-wallet",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.mjs",
6
+ "bin": {
7
+ "hodl": "./index.mjs"
8
+ },
9
+ "scripts": {
10
+ "start": "node ./index.mjs",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [
14
+ "hodl",
15
+ "wallet",
16
+ "crypto",
17
+ "ethereum",
18
+ "bsc20",
19
+ "bep20",
20
+ "evm",
21
+ "bnb",
22
+ "eth",
23
+ "binance",
24
+ "trust",
25
+ "blockchain"
26
+ ],
27
+ "author": "Hodl Wallet",
28
+ "license": "ISC",
29
+ "dependencies": {
30
+ "bip39": "^3.1.0",
31
+ "cli-table3": "^0.6.5",
32
+ "crypto-js": "^4.2.0",
33
+ "deepbase": "^1.1.8",
34
+ "hdkey": "^2.1.0",
35
+ "inquirer": "^9.3.7",
36
+ "inquirer-autocomplete-prompt": "^3.0.1",
37
+ "web3": "^4.13.0"
38
+ }
39
+ }
package/persist.js ADDED
@@ -0,0 +1,43 @@
1
+ // db.js
2
+ const Deepbase = require('deepbase');
3
+ const CryptoJS = require('crypto-js');
4
+
5
+ class Persist extends Deepbase {
6
+ constructor(encryptionKey) {
7
+ super();
8
+ this.encryptionKey = encryptionKey;
9
+ }
10
+
11
+ encrypt(data) {
12
+ const iv = CryptoJS.lib.WordArray.random(128/8);
13
+ const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), this.encryptionKey, { iv });
14
+ return iv.toString(CryptoJS.enc.Hex) + ':' + encrypted.toString();
15
+ }
16
+
17
+ decrypt(encryptedData) {
18
+ const [ivHex, encrypted] = encryptedData.split(':');
19
+ const iv = CryptoJS.enc.Hex.parse(ivHex);
20
+ const bytes = CryptoJS.AES.decrypt(encrypted, this.encryptionKey, { iv });
21
+ return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
22
+ }
23
+
24
+ // Method to set encrypted account
25
+ secureSet(key, value) {
26
+ const encryptedData = this.encrypt(value);
27
+ return this.set(key, encryptedData);
28
+ }
29
+
30
+ // Method to retrieve encrypted account
31
+ secureGet(key) {
32
+ const data = this.get(key);
33
+ if (!data) return "";
34
+ try {
35
+ // Attempt to decrypt
36
+ return this.decrypt(data);
37
+ } catch (error) {
38
+ return null;
39
+ }
40
+ }
41
+ }
42
+
43
+ module.exports = Persist;