hodl-wallet 1.6.8 → 1.7.2

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/example.jpg CHANGED
Binary file
package/index.js CHANGED
@@ -34,23 +34,26 @@ class Wallet {
34
34
 
35
35
  this.network = null;
36
36
  this.selectedNetwork = null;
37
- this.networkUsage = this.db.get('networkUsage') || {};
37
+ this.networkUsage = {};
38
38
  }
39
39
 
40
40
  async initialize() {
41
41
  try {
42
+ this.networkUsage = await this.db.get('networkUsage') || {};
43
+
42
44
  const networkPlugins = await this.loadNetworkPlugins();
43
45
  if (networkPlugins.length === 0) {
44
46
  Wallet.displayError('No valid network plugins found.');
45
47
  process.exit(1);
46
48
  }
47
- await this.selectNetwork(networkPlugins);
49
+ await this.selectNetwork(networkPlugins, { autoSelect: true });
48
50
  this.network = new this.selectedNetwork.NetworkClass(this.selectedNetwork);
49
51
  this.network.name = this.selectedNetwork.name;
50
52
 
51
53
  await this.loadAccount();
54
+ const account = await this.getAccount();
52
55
 
53
- if (!this.getAccount()) {
56
+ if (!account) {
54
57
  Wallet.displayError('Failed to initialize account.');
55
58
  process.exit(1);
56
59
  }
@@ -68,26 +71,28 @@ class Wallet {
68
71
  }
69
72
  }
70
73
 
71
- getAccount() {
74
+ async getAccount() {
72
75
  return this.db.get('account', this.network.constructor.name);
73
76
  }
74
77
 
75
- getAddress() {
78
+ async getAddress() {
76
79
  return this.db.get('account', this.network.constructor.name, 'address');
77
80
  }
78
81
 
79
- getMnemonic() {
82
+ async getMnemonic() {
80
83
  return this.db.get('mnemonic');
81
84
  }
82
85
 
83
- displayAccountAddress() {
86
+ async displayAccountAddress() {
87
+
84
88
  const table = new Table({
85
- head: ['Account Address'],
89
+ head: [`${this.selectedNetwork.name} Address`],
86
90
  style: {
87
91
  head: ['green']
88
92
  }
89
93
  });
90
- table.push([this.getAddress()]);
94
+
95
+ table.push([await this.getAddress()]);
91
96
  console.log(table.toString());
92
97
  }
93
98
 
@@ -103,30 +108,37 @@ class Wallet {
103
108
  return networks.filter(network => network && network.name);
104
109
  }
105
110
 
106
- async selectNetwork(networkPlugins) {
111
+ async selectNetwork(networkPlugins, { autoSelect = false } = {}) {
107
112
  // Sort networks by usage count (descending)
108
113
  const sortedNetworks = networkPlugins.sort((a, b) =>
109
114
  (this.networkUsage[b.name] || 0) - (this.networkUsage[a.name] || 0)
110
115
  );
111
116
 
112
- const { network } = await inquirer.prompt({
113
- type: 'list',
114
- name: 'network',
115
- message: 'Select the network:',
116
- choices: sortedNetworks.map(plugin => plugin.name),
117
- });
117
+ let selectedNetwork;
118
118
 
119
- const selectedNetwork = sortedNetworks.find(plugin => plugin.name === network);
119
+ if (autoSelect) {
120
+ // Automatically select the first network (most used)
121
+ selectedNetwork = sortedNetworks[0];
122
+ } else {
123
+ // Let user choose the network
124
+ const { network } = await inquirer.prompt({
125
+ type: 'list',
126
+ name: 'network',
127
+ message: 'Select the network:',
128
+ choices: sortedNetworks.map(plugin => plugin.name),
129
+ });
130
+ selectedNetwork = sortedNetworks.find(plugin => plugin.name === network);
131
+ }
120
132
 
121
133
  // Increment usage count for the selected network
122
- this.networkUsage[network] = (this.networkUsage[network] || 0) + 1;
123
- this.db.set('networkUsage', this.networkUsage);
134
+ this.networkUsage[selectedNetwork.name] = (this.networkUsage[selectedNetwork.name] || 0) + 1;
135
+ await this.db.set('networkUsage', this.networkUsage);
124
136
 
125
137
  this.selectedNetwork = selectedNetwork;
126
138
  }
127
139
 
128
140
  async loadAccount(loggedIn = false) {
129
- let account = this.getAccount();
141
+ let account = await this.getAccount();
130
142
 
131
143
  const mainChoices = ['Create New Account'];
132
144
 
@@ -134,6 +146,7 @@ class Wallet {
134
146
  mainChoices.push('Import Options');
135
147
  mainChoices.push('Export Options');
136
148
  mainChoices.push('Switch Network');
149
+ mainChoices.push('Manage Address Book');
137
150
  mainChoices.push('Go Back');
138
151
  } else {
139
152
  mainChoices.push('Import Mnemonic (12 words)');
@@ -153,6 +166,27 @@ class Wallet {
153
166
  return;
154
167
  }
155
168
 
169
+ if (accountAction === 'Manage Address Book') {
170
+ const addressBookChoices = ['Delete Address', 'Go Back'];
171
+ const { addressBookAction } = await inquirer.prompt({
172
+ type: 'list',
173
+ name: 'addressBookAction',
174
+ message: 'Select an address book option:',
175
+ choices: addressBookChoices,
176
+ });
177
+
178
+ if (addressBookAction === 'Go Back') {
179
+ return this.loadAccount(loggedIn);
180
+ }
181
+
182
+ switch (addressBookAction) {
183
+ case 'Delete Address':
184
+ await this.deleteFromAddressBook();
185
+ break;
186
+ }
187
+ return;
188
+ }
189
+
156
190
  if (accountAction === 'Import Options') {
157
191
  const importChoices = ['Import Mnemonic (12 words)', 'Import Private-key', 'Import HODL File', 'Go Back'];
158
192
  const { importAction } = await inquirer.prompt({
@@ -180,14 +214,14 @@ class Wallet {
180
214
  return;
181
215
  }
182
216
  this.setAccount(account);
183
- this.displayAccountAddress();
217
+ await this.displayAccountAddress();
184
218
  break;
185
219
  case 'Import Private-key':
186
220
  await this.importPrivateKey();
187
221
  break;
188
222
  case 'Import HODL File':
189
223
  this.setAccount(await this.importHODLFile());
190
- this.displayAccountAddress();
224
+ await this.displayAccountAddress();
191
225
  break;
192
226
  }
193
227
 
@@ -215,7 +249,7 @@ class Wallet {
215
249
  return;
216
250
  }
217
251
 
218
-
252
+
219
253
  if (accountAction === 'Create New Account') {
220
254
  const confirmOverwrite = await this.confirmOverwrite();
221
255
  if (!confirmOverwrite) return;
@@ -233,7 +267,7 @@ class Wallet {
233
267
  }
234
268
 
235
269
  async confirmOverwrite() {
236
- if (this.getAccount()) {
270
+ if (await this.getAccount()) {
237
271
  const { confirmOverwrite } = await inquirer.prompt({
238
272
  type: 'confirm',
239
273
  name: 'confirmOverwrite',
@@ -265,12 +299,14 @@ class Wallet {
265
299
  }
266
300
 
267
301
  async showBalance() {
268
- if (!this.getAccount()) {
302
+ const account = await this.getAccount();
303
+
304
+ if (!account) {
269
305
  Wallet.displayError('Account not initialized.');
270
306
  return;
271
307
  }
272
308
 
273
- const balances = await this.network.getTokenBalances(this.getAddress());
309
+ const balances = await this.network.getTokenBalances(await this.getAddress());
274
310
 
275
311
  const table = new Table({
276
312
  head: ['Token', 'Balance'],
@@ -286,8 +322,8 @@ class Wallet {
286
322
  }
287
323
 
288
324
  async transferFunds() {
289
- const contacts = this.db.get('contact', this.network.name) || {};
290
- const addressBook = Object.entries(contacts).map(([address, data]) => ({
325
+ const contacts = await this.db.entries('contact', this.network.name) || [];
326
+ const addressBook = contacts.map(([address, data]) => ({
291
327
  address,
292
328
  name: data.name
293
329
  }));
@@ -362,20 +398,21 @@ class Wallet {
362
398
 
363
399
  try {
364
400
  let signedTx;
401
+ const account = await this.getAccount();
365
402
  if (token === this.selectedNetwork.nativeToken) {
366
- signedTx = await this.network.handleNativeTransfer(this.getAccount(), address, amount);
403
+ signedTx = await this.network.handleNativeTransfer(account, address, amount);
367
404
  } else {
368
- signedTx = await this.network.handleERC20Transfer(this.getAccount(), token, address, amount);
405
+ signedTx = await this.network.handleERC20Transfer(account, token, address, amount);
369
406
  }
370
407
 
371
408
  const receipt = await this.network.sendSignedTransaction(signedTx);
372
409
 
373
410
  spinner.succeed('Transaction confirmed!');
374
411
 
375
- this.displayTransactionResult(address, token, amount, receipt.transactionHash);
412
+ await this.displayTransactionResult(address, token, amount, receipt.transactionHash);
376
413
 
377
414
  // Add transaction to history
378
- this.addToTransactions(address, token, amount, receipt.transactionHash);
415
+ await this.addToTransactions(address, token, amount, receipt.transactionHash);
379
416
 
380
417
  if (!contacts[address]) {
381
418
  await this.addToAddressBook(address);
@@ -417,7 +454,7 @@ class Wallet {
417
454
  }).replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$2-$1').replace(",", "");
418
455
  }
419
456
 
420
- addToTransactions(recipient, token, amount, hash) {
457
+ async addToTransactions(recipient, token, amount, hash) {
421
458
  const transaction = {
422
459
  timestamp: new Date().toISOString(),
423
460
  recipient,
@@ -425,11 +462,13 @@ class Wallet {
425
462
  amount,
426
463
  hash
427
464
  };
428
- this.db.add('transactions', this.getAddress(), this.selectedNetwork.nativeToken, transaction);
465
+ const address = await this.getAddress();
466
+ this.db.add('transactions', address, this.selectedNetwork.nativeToken, transaction);
429
467
  }
430
468
 
431
469
  async showTransactions() {
432
- const history = this.db.values('transactions', this.getAddress(), this.selectedNetwork.nativeToken) || [];
470
+ const address = await this.getAddress();
471
+ const history = await this.db.values('transactions', address, this.selectedNetwork.nativeToken) || [];
433
472
 
434
473
  const table = new Table({
435
474
  head: ['Date', 'Recipient', 'Token', 'Amount'],
@@ -440,14 +479,14 @@ class Wallet {
440
479
  table.push([{ colSpan: 4, content: 'No transaction history available.' }]);
441
480
  }
442
481
 
443
- history.forEach(tx => {
482
+ for (const tx of history) {
444
483
  const date = this.formatDate(tx.timestamp);
445
- const contact = this.db.get('contact', this.network.name, tx.recipient);
484
+ const contact = await this.db.get('contact', this.network.name, tx.recipient);
446
485
  const recipient = contact ? `${tx.recipient} (${contact.name})` : tx.recipient;
447
486
  const amount = parseFloat(tx.amount).toFixed(3);
448
487
  table.push([date, recipient, tx.token, amount]);
449
488
  table.push([{ colSpan: 4, content: this.selectedNetwork.explorer + tx.hash }]);
450
- });
489
+ }
451
490
 
452
491
  console.log(table.toString());
453
492
  }
@@ -460,8 +499,7 @@ class Wallet {
460
499
  });
461
500
 
462
501
  if (name.trim() !== '') {
463
-
464
- this.db.set('contact', this.network.name, address, 'name', name);
502
+ await this.db.set('contact', this.network.name, address, 'name', name);
465
503
 
466
504
  const table = new Table({
467
505
  head: [{ colSpan: 2, content: "Recipient saved to the address book." }],
@@ -473,7 +511,56 @@ class Wallet {
473
511
  }
474
512
  }
475
513
 
476
- displayTransactionResult(address, token, amount, hash) {
514
+ async deleteFromAddressBook() {
515
+ const contacts = await this.db.get('contact', this.network.name) || {};
516
+ const addressBook = Object.entries(contacts).map(([address, data]) => ({
517
+ address,
518
+ name: data.name
519
+ }));
520
+
521
+ if (addressBook.length === 0) {
522
+ const table = new Table({
523
+ head: ['Address Book'],
524
+ style: { head: ['yellow'] },
525
+ });
526
+ table.push(['No addresses in the address book.']);
527
+ console.log(table.toString());
528
+ return;
529
+ }
530
+
531
+ addressBook.push({ name: 'Go Back', address: '' });
532
+
533
+ const { addressToDelete } = await inquirer.prompt({
534
+ type: 'list',
535
+ name: 'addressToDelete',
536
+ message: 'Select an address to delete:',
537
+ choices: addressBook.map(entry => ({
538
+ name: entry.address ? `${entry.address} (${entry.name})` : entry.name,
539
+ value: entry.address
540
+ }))
541
+ });
542
+
543
+ if (!addressToDelete) return;
544
+
545
+ const { confirmDelete } = await inquirer.prompt({
546
+ type: 'confirm',
547
+ name: 'confirmDelete',
548
+ message: 'Are you sure you want to delete this address?',
549
+ default: false
550
+ });
551
+
552
+ if (confirmDelete) {
553
+ await this.db.del('contact', this.network.name, addressToDelete);
554
+ const table = new Table({
555
+ head: ['Address Book'],
556
+ style: { head: ['green'] },
557
+ });
558
+ table.push(['Address deleted successfully.']);
559
+ console.log(table.toString());
560
+ }
561
+ }
562
+
563
+ async displayTransactionResult(address, token, amount, hash) {
477
564
 
478
565
  const table = new Table({
479
566
  head: ['Date', 'Recipient', 'Token', 'Amount'],
@@ -482,7 +569,7 @@ class Wallet {
482
569
 
483
570
  const date = this.formatDate(new Date());
484
571
 
485
- const contact = this.db.get('contact', this.network.name, address);
572
+ const contact = await this.db.get('contact', this.network.name, address);
486
573
  const recipient = contact ? `${address} (${contact.name})` : address;
487
574
 
488
575
  table.push([date, recipient, token, parseFloat(amount).toFixed(3)]);
@@ -495,7 +582,7 @@ class Wallet {
495
582
 
496
583
  }
497
584
 
498
- displayAccountDetails() {
585
+ async displayAccountDetails() {
499
586
 
500
587
  const table = new Table({
501
588
  head: [{ colSpan: 2, content: 'Account Details' }],
@@ -503,12 +590,13 @@ class Wallet {
503
590
  wordWrap: true
504
591
  });
505
592
 
593
+ const account = await this.getAccount();
506
594
  table.push(
507
- ['Address', this.getAddress()],
508
- ['Private-key', this.getAccount().privateKey]
595
+ ['Address', await this.getAddress()],
596
+ ['Private-key', account.privateKey]
509
597
  );
510
598
 
511
- const mnemonic = this.getMnemonic();
599
+ const mnemonic = await this.getMnemonic();
512
600
  if (mnemonic) {
513
601
  table.push(['Mnemonic Phrase', mnemonic]);
514
602
  table.push(['WARNING', "Please keep your private-key and mnemonic phrase secure. Never share it."]);
@@ -523,11 +611,12 @@ class Wallet {
523
611
  const networkPlugins = await this.loadNetworkPlugins();
524
612
  await this.selectNetwork(networkPlugins);
525
613
  this.network = new this.selectedNetwork.NetworkClass(this.selectedNetwork);
526
- this.displayAccountAddress();
614
+ await this.displayAccountAddress();
527
615
  }
528
616
 
529
617
  async exportHODLFile() {
530
- const defaultFileName = `${this.getAddress().slice(-6).toUpperCase()}`;
618
+ const address = await this.getAddress();
619
+ const defaultFileName = `${address.slice(-6).toUpperCase()}`;
531
620
  let { fileName } = await inquirer.prompt({
532
621
  type: 'input',
533
622
  name: 'fileName',
@@ -537,7 +626,7 @@ class Wallet {
537
626
 
538
627
  fileName += '.HODL';
539
628
 
540
- const data = this.db.get();
629
+ const data = await this.db.get();
541
630
  const encryptionKey = await UIManager.getEncryptionKey();
542
631
  const encryptedData = Persist.encrypt(data, encryptionKey);
543
632
 
@@ -598,14 +687,14 @@ class Wallet {
598
687
 
599
688
  try {
600
689
  this.setAccount(await this.network.privateKeyToAccount(privateKey));
601
- this.displayAccountAddress();
690
+ await this.displayAccountAddress();
602
691
  } catch (error) {
603
692
  Wallet.displayError('Invalid private-key.');
604
693
  }
605
694
  }
606
695
 
607
696
  async createNewAccount() {
608
- let existingMnemonic = this.getMnemonic();
697
+ let existingMnemonic = await this.getMnemonic();
609
698
  let useMnemonic = false;
610
699
 
611
700
  if (existingMnemonic) {
@@ -615,7 +704,7 @@ class Wallet {
615
704
  message: 'Use existing mnemonic to create account?',
616
705
  default: true
617
706
  });
618
-
707
+
619
708
  if (useExistingMnemonic) {
620
709
  useMnemonic = true;
621
710
  this.setAccount(await this.network.accountFromMnemonic(existingMnemonic));
@@ -637,8 +726,32 @@ class Wallet {
637
726
  }
638
727
  }
639
728
 
729
+ // Check if there are addresses in the address book
730
+ const addressBook = await this.db.entries('contact', this.network.name) || [];
731
+
732
+ if (addressBook.length > 0) {
733
+ const { deleteAddresses } = await inquirer.prompt({
734
+ type: 'confirm',
735
+ name: 'deleteAddresses',
736
+ message: `Do you want to delete all ${addressBook.length} addresses from the previous account?`,
737
+ default: false
738
+ });
739
+
740
+ if (deleteAddresses) {
741
+
742
+ await this.db.del('contact');
743
+
744
+ const table = new Table({
745
+ head: ['Address Book'],
746
+ style: { head: ['green'] },
747
+ });
748
+ table.push(['All addresses deleted successfully.']);
749
+ console.log(table.toString());
750
+ }
751
+ }
752
+
640
753
  let message = 'Do you want to display sensitive information (private key';
641
- if (this.getMnemonic()) {
754
+ if (await this.getMnemonic()) {
642
755
  message += ' and mnemonic';
643
756
  }
644
757
  message += ')?';
@@ -651,9 +764,9 @@ class Wallet {
651
764
  });
652
765
 
653
766
  if (showSensitive) {
654
- this.displayAccountDetails();
767
+ await this.displayAccountDetails();
655
768
  } else {
656
- this.displayAccountAddress();
769
+ await this.displayAccountAddress();
657
770
  }
658
771
  }
659
772
  }
@@ -677,7 +790,7 @@ class UIManager {
677
790
  return key;
678
791
  }
679
792
 
680
- static async confirmEncryptionKey(originalKey) {
793
+ static async confirmEncryptionKey() {
681
794
  const { confirmKey } = await inquirer.prompt({
682
795
  type: 'password',
683
796
  name: 'confirmKey',
@@ -741,10 +854,10 @@ try {
741
854
  process.exit(1);
742
855
  }
743
856
 
744
- const accountExists = wallet.getMnemonic();
857
+ const accountExists = await wallet.getMnemonic();
745
858
 
746
859
  if (!accountExists) {
747
- const confirmedKey = await UIManager.confirmEncryptionKey(encryptionKey);
860
+ const confirmedKey = await UIManager.confirmEncryptionKey();
748
861
  if (confirmedKey !== encryptionKey) {
749
862
  Wallet.displayError('Passwords do not match. Please try again.');
750
863
  process.exit(1);
@@ -753,7 +866,7 @@ if (!accountExists) {
753
866
 
754
867
  try {
755
868
  await wallet.initialize();
756
- if (accountExists) wallet.displayAccountAddress();
869
+ if (accountExists) await wallet.displayAccountAddress();
757
870
  } catch (error) {
758
871
  Wallet.displayError('Failed to initialize wallet.', error);
759
872
  process.exit(1);
@@ -764,7 +877,7 @@ process.on('exit', () => {
764
877
  UIManager.displayExitPhrase();
765
878
  });
766
879
 
767
- while (true) {
880
+ async function mainMenu() {
768
881
  const { action } = await inquirer.prompt({
769
882
  type: 'list',
770
883
  name: 'action',
@@ -781,17 +894,20 @@ while (true) {
781
894
  switch (action) {
782
895
  case 'transferFunds':
783
896
  await wallet.transferFunds();
784
- break;
897
+ return mainMenu();
785
898
  case 'balance':
786
899
  await wallet.showBalance();
787
- break;
900
+ return mainMenu();
788
901
  case 'showTransactions':
789
902
  await wallet.showTransactions();
790
- break;
903
+ return mainMenu();
791
904
  case 'account':
792
905
  await wallet.loadAccount(true);
793
- break;
906
+ return mainMenu();
794
907
  case 'exit':
795
908
  process.exit();
796
909
  }
797
910
  }
911
+
912
+ // Start the application
913
+ mainMenu();
@@ -0,0 +1,14 @@
1
+ import Web3Network from './lib/Web3Network.js';
2
+
3
+ export default {
4
+ NetworkClass: Web3Network,
5
+ name: '[ERC-20] Rootstock',
6
+ url: 'https://public-node.rsk.co',
7
+ nativeToken: 'RBTC',
8
+ explorer: 'https://explorer.rootstock.io/tx/',
9
+ tokens: {
10
+ // 'USDT': {
11
+ // address: '0x833589fCD6eDb6E08B1Daf2d5F90D7ae4Dfd9F59',
12
+ // }
13
+ }
14
+ };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "hodl-wallet",
3
- "version": "1.6.8",
3
+ "version": "1.7.2",
4
4
  "description": "🧊 HODL Wallet - Fast CLI crypto wallet!",
5
- "author": "Martin Cl6sen",
5
+ "author": "Martin Clasen",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/clasen/HODL.git"
@@ -47,13 +47,13 @@
47
47
  "clasen"
48
48
  ],
49
49
  "dependencies": {
50
- "axios": "^1.7.7",
50
+ "axios": "^1.8.4",
51
51
  "bip32": "^5.0.0-rc.0",
52
52
  "bip39": "^3.1.0",
53
53
  "bitcoinjs-lib": "^7.0.0-rc.0",
54
54
  "cli-table3": "^0.6.5",
55
55
  "crypto-js": "^4.2.0",
56
- "deepbase": "^1.2.4",
56
+ "deepbase": "^1.5.2",
57
57
  "ecpair": "^2.1.0",
58
58
  "hdkey": "^2.1.0",
59
59
  "inquirer": "^9.3.7",
File without changes