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.
- package/README.md +17 -18
- package/index.mjs +145 -92
- 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
|
-

|
|
3
|
+

|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
100
|
+
choices,
|
|
94
101
|
});
|
|
95
102
|
|
|
96
|
-
if (accountAction === '
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
|
|
154
|
+
if (accountAction === 'Export Account') {
|
|
155
|
+
await this.displayAccountDetails();
|
|
156
|
+
return;
|
|
135
157
|
}
|
|
136
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
313
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
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
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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: [
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
}
|