hodl-wallet 1.4.4 โ†’ 1.4.6

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 CHANGED
@@ -41,7 +41,18 @@ Keep your favorite addresses handy. No more copy-pasting!
41
41
 
42
42
  ### ๐ŸŒ Multi-Network Support
43
43
 
44
- Seamlessly manage your assets on both BEP-20 (Binance Smart Chain) and ERC-20 (Ethereum) networks. Switch between networks with ease!
44
+ Seamlessly manage your assets on both BEP-20 (Binance Smart Chain) and ERC-20 (Ethereum and Polygon) networks. Switch between networks with ease!
45
+
46
+ ### ๐Ÿ’พ Export and Import HODL Files
47
+
48
+ HODL Wallet now supports exporting and importing encrypted .HODL files, which securely store your wallet information.
49
+
50
+ - **Export HODL File**: Save your wallet data (including private keys and addresses) to an encrypted .HODL file.
51
+ - **Import HODL File**: Restore your wallet from a previously exported .HODL file.
52
+
53
+ These files are encrypted using your wallet password, providing an additional layer of security for storing and transferring your wallet information.
54
+
55
+ The main advantage of exporting a HODL file is that to access the private key, you need BOTH the file AND the password. This two-factor approach significantly enhances security. However, keep in mind that this solution is only compatible with HODL Wallet.
45
56
 
46
57
  ## ๐Ÿ”’ Security
47
58
 
@@ -65,6 +76,7 @@ We've carefully selected trusted and well-maintained dependencies for this proje
65
76
  - **deepbase**: For persistent storage.
66
77
  - **crypto-js**: For encryption.
67
78
  - **inquirer** and **inquirer-autocomplete-prompt**: For interactive command-line interfaces.
79
+ - **inquirer-fuzzy-path**: For fuzzy searching and selecting file paths during HODL file import.
68
80
  - **cli-table3**: For creating formatted CLI tables.
69
81
  - **bip39**: For generating and handling mnemonic phrases.
70
82
  - **hdkey**: For handling hierarchical deterministic (HD) keys.
package/index.mjs CHANGED
@@ -11,12 +11,14 @@ import Table from 'cli-table3';
11
11
  import bip39 from 'bip39';
12
12
  import hdkey from 'hdkey';
13
13
  import os from 'os';
14
+ import inquirerFuzzyPath from 'inquirer-fuzzy-path';
14
15
 
15
16
  const __filename = fileURLToPath(import.meta.url);
16
17
  const __dirname = dirname(__filename);
17
18
 
18
19
  import inquirerAutocomplete from 'inquirer-autocomplete-prompt';
19
20
  inquirer.registerPrompt('autocomplete', inquirerAutocomplete);
21
+ inquirer.registerPrompt('fuzzypath', inquirerFuzzyPath);
20
22
 
21
23
  process.on('SIGINT', () => {
22
24
  process.exit();
@@ -97,113 +99,101 @@ class Wallet {
97
99
  async loadAccount(forceReload = false) {
98
100
  let account = this.db.secureGet('account');
99
101
 
100
- const choices = ['Create New Account', 'Import Mnemonic (12 words)', 'Import Private-key'];
102
+ const mainChoices = ['Create New Account'];
101
103
 
102
104
  if (forceReload) {
103
- choices.push('Export Account');
104
- choices.push('Switch Network');
105
- choices.push('Go Back');
105
+ mainChoices.push('Import Options');
106
+ mainChoices.push('Export Options');
107
+ mainChoices.push('Switch Network');
108
+ mainChoices.push('Go Back');
109
+ } else {
110
+ mainChoices.push('Import Mnemonic (12 words)');
111
+ mainChoices.push('Import Private-key');
112
+ mainChoices.push('Import HODL File');
106
113
  }
107
114
 
108
115
  if (!account || forceReload) {
109
- const { accountAction } = await inquirer.prompt({
116
+ let { accountAction } = await inquirer.prompt({
110
117
  type: 'list',
111
118
  name: 'accountAction',
112
119
  message: 'Select an account option:',
113
- choices,
120
+ choices: mainChoices,
114
121
  });
115
122
 
116
123
  if (accountAction === 'Go Back') {
117
124
  return;
118
125
  }
119
126
 
120
- if (account && accountAction !== 'Export Account' && accountAction !== 'Switch Network') {
121
- const { confirmOverwrite } = await inquirer.prompt({
122
- type: 'confirm',
123
- name: 'confirmOverwrite',
124
- message: 'This action will overwrite the existing account. Are you sure you want to continue?',
125
- default: false,
127
+ if (accountAction === 'Import Options') {
128
+ const importChoices = ['Import Mnemonic (12 words)', 'Import Private-key', 'Import HODL File', 'Go Back'];
129
+ const { importAction } = await inquirer.prompt({
130
+ type: 'list',
131
+ name: 'importAction',
132
+ message: 'Select an import option:',
133
+ choices: importChoices,
126
134
  });
127
135
 
128
- if (!confirmOverwrite) {
129
- return;
136
+ if (importAction === 'Go Back') {
137
+ return this.loadAccount(forceReload);
130
138
  }
131
- }
132
-
133
- if (accountAction === 'Create New Account') {
134
- const { createWithMnemonic } = await inquirer.prompt({
135
- type: 'confirm',
136
- name: 'createWithMnemonic',
137
- message: 'Create account with mnemonic?',
138
- default: true
139
- });
140
139
 
141
- let message = 'Do you want to display sensitive information (private key';
142
- if (createWithMnemonic) {
143
- const mnemonic = bip39.generateMnemonic();
144
- const seed = await bip39.mnemonicToSeed(mnemonic);
145
- const root = hdkey.fromMasterSeed(seed);
146
- const addrNode = root.derive("m/44'/60'/0'/0/0");
147
- const privateKey = addrNode.privateKey.toString('hex');
148
- this.account = this.web3.eth.accounts.privateKeyToAccount('0x' + privateKey);
149
- this.account.mnemonic = mnemonic;
150
- message += ' and mnemonic';
151
- } else {
152
- this.account = this.web3.eth.accounts.create();
140
+ if (!await this.confirmOverwrite()) {
141
+ return;
153
142
  }
154
143
 
155
- message += ')?';
156
-
157
- await this.db.secureSet('account', this.account);
158
-
159
- const { showSensitive } = await inquirer.prompt({
160
- type: 'confirm',
161
- name: 'showSensitive',
162
- message,
163
- default: false,
164
- });
144
+ accountAction = importAction;
145
+ }
165
146
 
166
- if (showSensitive) {
167
- this.displayAccountDetails();
168
- } else {
147
+ switch (accountAction) {
148
+ case 'Import Mnemonic (12 words)':
149
+ account = await this.importFrom12Words();
150
+ if (!account) {
151
+ Wallet.displayError('Invalid mnemonic.');
152
+ return;
153
+ }
154
+ this.account = account;
169
155
  this.displayAccountAddress();
170
- }
156
+ break;
157
+ case 'Import Private-key':
158
+ await this.importPrivateKey();
159
+ break;
160
+ case 'Import HODL File':
161
+ this.account = await this.importHODLFile();
162
+ this.displayAccountAddress();
163
+ break;
171
164
  }
172
165
 
173
- if (accountAction === 'Import Private-key') {
174
- const { privateKey } = await inquirer.prompt({
175
- type: 'password',
176
- name: 'privateKey',
177
- message: 'Private-key:',
178
- mask: '*',
166
+ if (accountAction === 'Export Options') {
167
+ const exportChoices = ['Export Private-key', 'Export HODL File', 'Go Back'];
168
+ const { exportAction } = await inquirer.prompt({
169
+ type: 'list',
170
+ name: 'exportAction',
171
+ message: 'Select an export option:',
172
+ choices: exportChoices,
179
173
  });
180
174
 
181
- if (!privateKey.trim()) {
182
- Wallet.displayError('Private-key is empty.');
183
- return;
175
+ if (exportAction === 'Go Back') {
176
+ return this.loadAccount(forceReload);
184
177
  }
185
178
 
186
- try {
187
- this.account = this.web3.eth.accounts.privateKeyToAccount(privateKey);
188
- await this.db.secureSet('account', this.account);
189
- this.displayAccountAddress();
190
- } catch (error) {
191
- Wallet.displayError('Invalid private-key.');
179
+ switch (exportAction) {
180
+ case 'Export Private-key':
181
+ await this.displayAccountDetails();
182
+ break;
183
+ case 'Export HODL File':
184
+ await this.exportHODLFile();
185
+ break;
192
186
  }
187
+ return;
193
188
  }
194
189
 
195
- if (accountAction === 'Import Mnemonic (12 words)') {
196
- account = await this.importFrom12Words();
197
- if (!account) {
198
- Wallet.displayError('Invalid mnemonic.');
190
+
191
+ if (accountAction === 'Create New Account') {
192
+ if (!await this.confirmOverwrite()) {
199
193
  return;
200
194
  }
201
- this.account = account;
202
- this.displayAccountAddress();
203
- }
204
195
 
205
- if (accountAction === 'Export Account') {
206
- await this.displayAccountDetails();
196
+ await this.createNewAccount();
207
197
  }
208
198
 
209
199
  if (accountAction === 'Switch Network') {
@@ -216,6 +206,20 @@ class Wallet {
216
206
  this.account = account;
217
207
  }
218
208
 
209
+ async confirmOverwrite() {
210
+ if (this.account) {
211
+ const { confirmOverwrite } = await inquirer.prompt({
212
+ type: 'confirm',
213
+ name: 'confirmOverwrite',
214
+ message: 'This action will overwrite the existing account. Are you sure you want to continue?',
215
+ default: false,
216
+ });
217
+
218
+ return confirmOverwrite;
219
+ }
220
+ return false;
221
+ }
222
+
219
223
  async importFrom12Words() {
220
224
  const { mnemonic } = await inquirer.prompt({
221
225
  type: 'password',
@@ -228,13 +232,7 @@ class Wallet {
228
232
  return null;
229
233
  }
230
234
 
231
- const seed = await bip39.mnemonicToSeed(mnemonic);
232
- const root = hdkey.fromMasterSeed(seed);
233
- const addrNode = root.derive("m/44'/60'/0'/0/0");
234
- const privateKey = addrNode.privateKey.toString('hex');
235
- const account = this.web3.eth.accounts.privateKeyToAccount('0x' + privateKey);
236
-
237
- account.mnemonic = mnemonic;
235
+ const account = await this.accountFromMnemonic(mnemonic);
238
236
  await this.db.secureSet('account', account);
239
237
  return account;
240
238
  }
@@ -585,6 +583,134 @@ class Wallet {
585
583
  await this.selectNetwork(networkPlugins);
586
584
  this.web3 = new Web3(this.selectedNetwork.rpcUrl);
587
585
  }
586
+
587
+ async exportHODLFile() {
588
+ const defaultFileName = `${this.account.address.slice(-6).toUpperCase()}`;
589
+ let { fileName } = await inquirer.prompt({
590
+ type: 'input',
591
+ name: 'fileName',
592
+ message: 'Enter the name for the HODL file:',
593
+ default: defaultFileName
594
+ });
595
+
596
+ fileName += '.HODL';
597
+
598
+ const data = this.db.get();
599
+ const encryptedData = this.db.encrypt(data);
600
+
601
+ fs.writeFileSync(fileName, encryptedData);
602
+
603
+ const table = new Table({
604
+ head: ['HODL File Exported'],
605
+ style: { head: ['green'] }
606
+ });
607
+ table.push([`File saved as: ${fileName}`]);
608
+ console.log(table.toString());
609
+ }
610
+
611
+ async importHODLFile() {
612
+ const { filePath } = await inquirer.prompt({
613
+ type: 'fuzzypath',
614
+ name: 'filePath',
615
+ message: 'Select the .HODL file:',
616
+ rootPath: '.',
617
+ itemType: 'file',
618
+ suggestOnly: false,
619
+ depthLimit: 5,
620
+ excludePath: nodePath => nodePath.startsWith('node_modules'),
621
+ excludeFilter: nodePath => !nodePath.endsWith('.HODL'),
622
+ });
623
+
624
+ if (!fs.existsSync(filePath)) {
625
+ Wallet.displayError('File not found.');
626
+ return;
627
+ }
628
+
629
+ const encryptedData = fs.readFileSync(filePath, 'utf8');
630
+
631
+ try {
632
+ const decryptedData = this.db.decrypt(encryptedData);
633
+ this.db.set(decryptedData);
634
+ return this.db.secureGet('account');
635
+ } catch (error) {
636
+ Wallet.displayError('Failed to import HODL file.', 'The file may be corrupted or the encryption key is incorrect.');
637
+ }
638
+ }
639
+
640
+ async importPrivateKey() {
641
+ const { privateKey } = await inquirer.prompt({
642
+ type: 'password',
643
+ name: 'privateKey',
644
+ message: 'Private-key:',
645
+ mask: '*',
646
+ });
647
+
648
+ if (!privateKey.trim()) {
649
+ Wallet.displayError('Private-key is empty.');
650
+ return;
651
+ }
652
+
653
+ try {
654
+ this.account = this.web3.eth.accounts.privateKeyToAccount(privateKey);
655
+ await this.db.secureSet('account', this.account);
656
+ this.displayAccountAddress();
657
+ } catch (error) {
658
+ Wallet.displayError('Invalid private-key.');
659
+ }
660
+ }
661
+
662
+ async createNewAccount() {
663
+ const { createWithMnemonic } = await inquirer.prompt({
664
+ type: 'confirm',
665
+ name: 'createWithMnemonic',
666
+ message: 'Create account with mnemonic?',
667
+ default: true
668
+ });
669
+
670
+ let message = 'Do you want to display sensitive information (private key';
671
+ if (createWithMnemonic) {
672
+ this.account = await this.createAccountFromMnemonic();
673
+ message += ' and mnemonic';
674
+ } else {
675
+ this.account = this.web3.eth.accounts.create();
676
+ }
677
+
678
+ message += ')?';
679
+
680
+ await this.db.secureSet('account', this.account);
681
+
682
+ const { showSensitive } = await inquirer.prompt({
683
+ type: 'confirm',
684
+ name: 'showSensitive',
685
+ message,
686
+ default: false,
687
+ });
688
+
689
+ if (showSensitive) {
690
+ this.displayAccountDetails();
691
+ } else {
692
+ this.displayAccountAddress();
693
+ }
694
+ }
695
+
696
+ async accountFromMnemonic(mnemonic) {
697
+ const seed = await bip39.mnemonicToSeed(mnemonic);
698
+ const root = hdkey.fromMasterSeed(seed);
699
+ const addrNode = root.derive("m/44'/60'/0'/0/0");
700
+ const privateKey = addrNode.privateKey.toString('hex');
701
+ const account = this.web3.eth.accounts.privateKeyToAccount('0x' + privateKey);
702
+ account.mnemonic = mnemonic;
703
+ return account;
704
+ }
705
+
706
+ async createAccountFromMnemonic() {
707
+ try {
708
+ const mnemonic = bip39.generateMnemonic();
709
+ return this.accountFromMnemonic(mnemonic);
710
+ } catch (error) {
711
+ Wallet.displayError('Failed to create account from mnemonic.', error);
712
+ }
713
+ }
588
714
  }
589
715
 
590
716
  class UIManager {
package/network/pol.js ADDED
@@ -0,0 +1,10 @@
1
+ // network/matic.js
2
+ module.exports = {
3
+ name: '[ERC-20] Polygon',
4
+ explorer: 'https://polygonscan.com/tx/',
5
+ rpcUrl: 'https://polygon-rpc.com/',
6
+ nativeToken: 'POL',
7
+ tokens: {
8
+ 'USDT': '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', // Direcciรณn del contrato USDT en Polygon
9
+ },
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hodl-wallet",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
4
4
  "description": "๐ŸงŠ HODL Wallet - Fast CLI crypto wallet!",
5
5
  "author": "Martin Clasen",
6
6
  "repository": {
@@ -37,16 +37,20 @@
37
37
  "stable",
38
38
  "coin",
39
39
  "evm",
40
+ "polygon",
41
+ "matic",
42
+ "pol",
40
43
  "clasen"
41
44
  ],
42
45
  "dependencies": {
43
46
  "bip39": "^3.1.0",
44
47
  "cli-table3": "^0.6.5",
45
48
  "crypto-js": "^4.2.0",
46
- "deepbase": "^1.2.2",
49
+ "deepbase": "^1.2.4",
47
50
  "hdkey": "^2.1.0",
48
51
  "inquirer": "^9.3.7",
49
52
  "inquirer-autocomplete-prompt": "^3.0.1",
53
+ "inquirer-fuzzy-path": "^2.3.0",
50
54
  "web3": "^4.13.0"
51
55
  }
52
56
  }
File without changes