hodl-wallet 1.0.4 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +17 -18
  2. package/index.mjs +145 -92
  3. package/package.json +42 -37
package/README.md CHANGED
@@ -1,10 +1,17 @@
1
1
  # 🧊 HODL Wallet: Blazing-fast, transparent, and ad-free crypto wallet!
2
2
 
3
- ![HODL Wallet](example.jpg)
3
+ ![HODL Wallet](https://raw.githubusercontent.com/clasen/HODL/refs/heads/master/example.jpg)
4
+
5
+ ## 🚀 Install and try!
6
+
7
+ ```bash
8
+ npm install -g hodl-wallet
9
+ hodl
10
+ ```
4
11
 
5
12
  ## 🚀 Why HODL Wallet?
6
13
 
7
- Let's face it, Trust Wallet's sluggishness and annoying ads are so last season. HODL Wallet is here to revolutionize your crypto experience:
14
+ Let's face it, Trust Wallet's sluggishness and annoying ads are so last season. HODL Wallet is here to agilize your crypto experience.
8
15
 
9
16
  - 🏎️ Lightning-fast operations
10
17
  - 🧊 Cool, minimalist CLI interface
@@ -12,18 +19,6 @@ Let's face it, Trust Wallet's sluggishness and annoying ads are so last season.
12
19
  - 🔒 Create wallets offline (because paranoia is just good sense in crypto)
13
20
  - 🔍 Fully transparent, open-source code
14
21
 
15
- ## 🛠️ Installation
16
-
17
- ```bash
18
- npm install -g hodl-wallet
19
- ```
20
-
21
- ## 🚀 Quick Start
22
-
23
- ```bash
24
- hodl
25
- ```
26
-
27
22
  That's it! Follow the prompts and you're in crypto heaven.
28
23
 
29
24
  ## 🎮 Features
@@ -40,17 +35,21 @@ Smoother than sliding into your crush's DMs.
40
35
 
41
36
  Because constantly checking your balance is totally healthy.
42
37
 
38
+ ### 📘 Address Book
39
+
40
+ Keep your favorite addresses handy. No more copy-pasting!
41
+
43
42
  ## 🔒 Security
44
43
 
45
- ### Private Key Storage
44
+ ### 🔑 Private Key Storage
46
45
 
47
- 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
+ Your private key is securely stored in a JSON file, encrypted with a password of your choice. The encryption adds an extra layer of security, making it significantly harder for unauthorized parties to access your private key even if they gain access to the JSON file.
48
47
 
49
- ## 🔬 Transparency
48
+ ### 🔬 Transparency
50
49
 
51
50
  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.
52
51
 
53
- ## 🔍 Security Audit
52
+ ### 🔍 Security Audit
54
53
 
55
54
  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.
56
55
 
package/index.mjs CHANGED
@@ -85,56 +85,77 @@ class Wallet {
85
85
  async loadAccount(forceReload = false) {
86
86
  let account = this.db.secureGet('account');
87
87
 
88
+ const choices = ['Create New Account', 'Import Mnemonic (12 words)', 'Import private-key'];
89
+
90
+ if (forceReload) {
91
+ choices.push('Export Account');
92
+ choices.push('Go back');
93
+ }
94
+
88
95
  if (!account || forceReload) {
89
96
  const { accountAction } = await inquirer.prompt({
90
97
  type: 'list',
91
98
  name: 'accountAction',
92
99
  message: 'Select an account option:',
93
- choices: ['Import private key', 'Import from 12 words', 'Create new account'],
100
+ choices,
94
101
  });
95
102
 
96
- if (accountAction === 'Import private key') {
103
+ if (accountAction === 'Go back') {
104
+ return;
105
+ }
106
+
107
+ if (account && accountAction !== 'Export Account') {
108
+ const { confirmOverwrite } = await inquirer.prompt({
109
+ type: 'confirm',
110
+ name: 'confirmOverwrite',
111
+ message: 'This action will overwrite the existing account. Are you sure you want to continue?',
112
+ default: false,
113
+ });
114
+
115
+ if (!confirmOverwrite) {
116
+ return;
117
+ }
118
+ }
119
+
120
+ if (accountAction === 'Create New Account') {
121
+ const mnemonic = bip39.generateMnemonic();
122
+ const seed = await bip39.mnemonicToSeed(mnemonic);
123
+ const root = hdkey.fromMasterSeed(seed);
124
+ const addrNode = root.derive("m/44'/60'/0'/0/0");
125
+ const privateKey = addrNode.privateKey.toString('hex');
126
+ this.account = this.web3.eth.accounts.privateKeyToAccount('0x' + privateKey);
127
+ this.account.mnemonic = mnemonic;
128
+ await this.db.secureSet('account', this.account);
129
+ this.displayAccountDetails();
130
+ }
131
+
132
+ if (accountAction === 'Import private-key') {
97
133
  const { privateKey } = await inquirer.prompt({
98
134
  type: 'password',
99
135
  name: 'privateKey',
100
136
  message: 'Private key:',
101
137
  mask: '*',
102
138
  });
103
- account = this.web3.eth.accounts.privateKeyToAccount(privateKey);
104
- await this.db.secureSet('account', { privateKey: account.privateKey });
105
139
 
106
- this.account = account;
140
+ if (!privateKey) return;
141
+
142
+ this.account = this.web3.eth.accounts.privateKeyToAccount(privateKey);
143
+ await this.db.secureSet('account', this.account);
107
144
  this.displayAccountAddress();
108
- } else if (accountAction === 'Import from 12 words') {
145
+ }
146
+
147
+ if (accountAction === 'Import Mnemonic (12 words)') {
109
148
  account = await this.importFrom12Words();
149
+ if (!account) return;
110
150
  this.account = account;
111
151
  this.displayAccountAddress();
112
- } else {
113
- const mnemonic = bip39.generateMnemonic();
114
- const seed = await bip39.mnemonicToSeed(mnemonic);
115
- const root = hdkey.fromMasterSeed(seed);
116
- const addrNode = root.derive("m/44'/60'/0'/0/0");
117
- const privateKey = addrNode.privateKey.toString('hex');
118
- account = this.web3.eth.accounts.privateKeyToAccount('0x' + privateKey);
119
- this.db.secureSet('account', { privateKey: account.privateKey });
120
-
121
- const table = new Table({
122
- head: [{ colSpan: 2, content: 'New Account Created' }],
123
- style: { head: ['green'] },
124
- wordWrap: true
125
- });
126
-
127
- table.push(
128
- ['Address', account.address],
129
- ['Private Key', account.privateKey],
130
- ['Mnemonic Phrase', mnemonic],
131
- ['WARNING', "Please write down your mnemonic phrase and keep it in a safe place. \nIt\'s crucial for recovering your account."]
132
- );
152
+ }
133
153
 
134
- console.log('\n' + table.toString());
154
+ if (accountAction === 'Export Account') {
155
+ await this.displayAccountDetails();
156
+ return;
135
157
  }
136
- } else {
137
- account = this.web3.eth.accounts.privateKeyToAccount(account.privateKey);
158
+ return;
138
159
  }
139
160
 
140
161
  this.account = account;
@@ -149,7 +170,6 @@ class Wallet {
149
170
  });
150
171
 
151
172
  if (!bip39.validateMnemonic(mnemonic)) {
152
- console.error('Invalid mnemonic phrase');
153
173
  return null;
154
174
  }
155
175
 
@@ -159,8 +179,8 @@ class Wallet {
159
179
  const privateKey = addrNode.privateKey.toString('hex');
160
180
  const account = this.web3.eth.accounts.privateKeyToAccount('0x' + privateKey);
161
181
 
162
- await this.db.secureSet('account', { privateKey: account.privateKey });
163
-
182
+ account.mnemonic = mnemonic;
183
+ await this.db.secureSet('account', account);
164
184
  return account;
165
185
  }
166
186
 
@@ -179,9 +199,8 @@ class Wallet {
179
199
  colWidths: [21, 22]
180
200
  });
181
201
 
182
- // Add address as the top row
183
202
  // table.push([{ colSpan: 2, content: this.account.address }]);
184
- table.push([this.selectedNetwork.nativeToken, parseFloat(balance).toFixed(8)]);
203
+ table.push([this.selectedNetwork.nativeToken, parseFloat(balance).toFixed(3)]);
185
204
 
186
205
  // Check if USDT is available on the selected network
187
206
  if (this.selectedNetwork.tokens['USDT']) {
@@ -212,7 +231,7 @@ class Wallet {
212
231
  (BigInt(usdtBalance) * 100n) / (10n ** BigInt(decimals))
213
232
  ) / 100;
214
233
 
215
- table.push(['USDT', formattedUsdtBalance.toFixed(8)]);
234
+ table.push(['USDT', formattedUsdtBalance.toFixed(3)]);
216
235
  }
217
236
 
218
237
  console.log(table.toString());
@@ -258,7 +277,6 @@ class Wallet {
258
277
  });
259
278
 
260
279
  if (amount === '') {
261
- console.log('Transaction cancelled.');
262
280
  return;
263
281
  }
264
282
 
@@ -271,7 +289,7 @@ class Wallet {
271
289
  }
272
290
 
273
291
  const receipt = await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction);
274
- await this.displayTransactionResult(receipt);
292
+ this.displayTransactionResult(address, token, amount, receipt);
275
293
 
276
294
  // Add transaction to history
277
295
  this.addToTransactions(address, token, amount, receipt);
@@ -281,8 +299,33 @@ class Wallet {
281
299
  }
282
300
 
283
301
  } catch (error) {
284
- await this.displayTransactionResult(null, error);
302
+ this.displayError(error);
303
+ }
304
+ }
305
+
306
+ displayError(error) {
307
+ const table = new Table({
308
+ head: [error.message],
309
+ style: { head: ['red'] },
310
+ wordWrap: true,
311
+ });
312
+
313
+ if (error.reason) {
314
+ table.push([error.reason.replace(/(\w+):/g, "\n$1:").trim()]);
285
315
  }
316
+
317
+ console.log(table.toString());
318
+ }
319
+
320
+ formatDate(date) {
321
+ return new Date(date).toLocaleString('en-GB', {
322
+ year: 'numeric',
323
+ month: '2-digit',
324
+ day: '2-digit',
325
+ hour: '2-digit',
326
+ minute: '2-digit',
327
+ hour12: false
328
+ }).replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$2-$1').replace(",", "");
286
329
  }
287
330
 
288
331
  addToTransactions(recipient, token, amount, receipt) {
@@ -309,15 +352,8 @@ class Wallet {
309
352
  }
310
353
 
311
354
  history.forEach(tx => {
312
- const date = new Date(tx.timestamp).toLocaleString('en-GB', {
313
- year: 'numeric',
314
- month: '2-digit',
315
- day: '2-digit',
316
- hour: '2-digit',
317
- minute: '2-digit',
318
- hour12: false
319
- }).replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$2-$1').replace(",", "");
320
- table.push([date, tx.recipient, tx.token, tx.amount]);
355
+ const date = this.formatDate(tx.timestamp);
356
+ table.push([date, tx.recipient, tx.token, parseFloat(tx.amount).toFixed(3)]);
321
357
  table.push([{ colSpan: 4, content: this.selectedNetwork.explorer + tx.hash }]);
322
358
  });
323
359
 
@@ -389,14 +425,7 @@ class Wallet {
389
425
 
390
426
  return this.web3.eth.accounts.signTransaction(tx, this.account.privateKey);
391
427
  } catch (error) {
392
- if (error instanceof Web3ValidatorError) {
393
- const table = new Table({
394
- head: [{ colSpan: 2, content: 'Error: Invalid Address' }],
395
- style: { head: ['red'] }
396
- });
397
- table.push(['Address', recipient]);
398
- console.log(table.toString());
399
- }
428
+ this.displayError(error);
400
429
  }
401
430
  }
402
431
 
@@ -436,43 +465,56 @@ class Wallet {
436
465
  return this.web3.eth.accounts.signTransaction(tx, this.account.privateKey);
437
466
  }
438
467
 
439
- async displayTransactionResult(receipt, error = null) {
440
- if (receipt) {
441
- const gasUsed = receipt.gasUsed;
442
- const gasPrice = await this.web3.eth.getGasPrice();
443
- const transactionFee = this.web3.utils.fromWei((BigInt(gasUsed) * BigInt(gasPrice)).toString(), 'ether');
468
+ displayTransactionResult(address, token, amount, hash) {
444
469
 
445
- const table = new Table({
446
- head: [{ colSpan: 2, content: 'Transaction successful, share this link with the recipient' }],
447
- style: { head: ['green'] }
448
- });
449
- table.push(
450
- ['Explorer', this.selectedNetwork.explorer + receipt.transactionHash],
451
- ['Fee', `${transactionFee} ${this.selectedNetwork.nativeToken}`],
452
- );
470
+ const table = new Table({
471
+ head: ['Date', 'Recipient', 'Token', 'Amount'],
472
+ style: { head: ['green'] },
473
+ });
453
474
 
454
- console.log('\n' + table.toString());
455
- } else if (error) {
456
- const table = new Table({
457
- head: [error.message],
458
- style: { head: ['red'] },
459
- wordWrap: true,
460
- });
475
+ const date = this.formatDate(new Date());
461
476
 
462
- if (error.reason) {
463
- table.push([error.reason.replace(/(\w+):/g, "\n$1:").trim()]);
464
- }
477
+ table.push([
478
+ date,
479
+ address,
480
+ token,
481
+ amount.toFixed(3)
482
+ ]);
483
+ table.push([{ colSpan: 4, content: this.selectedNetwork.explorer + hash }]);
465
484
 
466
- console.log('\n' + table.toString());
467
- }
485
+ console.log('\n' + table.toString());
468
486
  }
469
487
 
470
488
  clearAccountData() {
471
489
  if (this.account) {
472
490
  this.account.privateKey = '0'.repeat(64);
491
+ this.account.mnemonic = 'x'.repeat(256);
473
492
  this.account = null;
474
493
  }
475
494
  }
495
+
496
+ displayAccountDetails() {
497
+
498
+ const table = new Table({
499
+ head: [{ colSpan: 2, content: 'Account Details' }],
500
+ style: { head: ['green'] },
501
+ wordWrap: true
502
+ });
503
+
504
+ table.push(
505
+ ['Address', this.account.address],
506
+ ['Private Key', this.account.privateKey]
507
+ );
508
+
509
+ if (this.account.mnemonic) {
510
+ table.push(['Mnemonic Phrase', this.account.mnemonic]);
511
+ table.push(['WARNING', "Please keep your private key and mnemonic phrase secure. Never share it."]);
512
+ } else {
513
+ table.push(['WARNING', "Please keep your private key secure. Never share it."]);
514
+ }
515
+
516
+ console.log('\n' + table.toString());
517
+ }
476
518
  }
477
519
 
478
520
  class UIManager {
@@ -585,19 +627,30 @@ async function main() {
585
627
  type: 'list',
586
628
  name: 'action',
587
629
  message: 'What would you like to do?',
588
- choices: ['Transfer funds', 'Show balance', 'Show transactions', 'Change account', 'Exit'],
630
+ choices: [
631
+ { name: 'Transfer Funds', value: 'transfer' },
632
+ { name: 'Show Balance', value: 'balance' },
633
+ { name: 'Show Sents', value: 'history' },
634
+ { name: 'Account', value: 'account' },
635
+ { name: 'Exit', value: 'exit' }
636
+ ],
589
637
  });
590
638
 
591
- if (action === 'Transfer funds') {
592
- await wallet.transferFunds();
593
- } else if (action === 'Show balance') {
594
- await wallet.showBalance();
595
- } else if (action === 'Show transactions') {
596
- await wallet.showTransactions();
597
- } else if (action === 'Change account') {
598
- await wallet.loadAccount(true);
599
- } else {
600
- process.exit();
639
+ switch (action) {
640
+ case 'balance':
641
+ await wallet.showBalance();
642
+ break;
643
+ case 'transfer':
644
+ await wallet.transferFunds();
645
+ break;
646
+ case 'history':
647
+ await wallet.showTransactions();
648
+ break;
649
+ case 'account':
650
+ await wallet.loadAccount(true);
651
+ break;
652
+ case 'exit':
653
+ process.exit();
601
654
  }
602
655
  }
603
656
  }
package/package.json CHANGED
@@ -1,39 +1,44 @@
1
1
  {
2
- "name": "hodl-wallet",
3
- "version": "1.0.4",
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
- }
2
+ "name": "hodl-wallet",
3
+ "version": "1.1.6",
4
+ "description": "🧊 HODL Wallet - Blazing-fast, transparent, and ad-free crypto wallet!",
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
+ "bnb",
21
+ "eth",
22
+ "binance",
23
+ "trust",
24
+ "blockchain",
25
+ "bitcoin",
26
+ "usdt",
27
+ "stable",
28
+ "coin",
29
+ "evm",
30
+ "clasen"
31
+ ],
32
+ "author": "Hodl Wallet",
33
+ "license": "ISC",
34
+ "dependencies": {
35
+ "bip39": "^3.1.0",
36
+ "cli-table3": "^0.6.5",
37
+ "crypto-js": "^4.2.0",
38
+ "deepbase": "^1.1.8",
39
+ "hdkey": "^2.1.0",
40
+ "inquirer": "^9.3.7",
41
+ "inquirer-autocomplete-prompt": "^3.0.1",
42
+ "web3": "^4.13.0"
43
+ }
39
44
  }