hodl-wallet 1.4.4 → 1.5.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/README.md +35 -1
- package/index.mjs +231 -83
- package/network/arb.js +10 -0
- package/network/avax.js +10 -0
- package/network/ftm.js +10 -0
- package/network/op.js +10 -0
- package/network/pol.js +11 -0
- package/package.json +11 -3
- /package/network/{erc.js → eth.js} +0 -0
package/README.md
CHANGED
|
@@ -41,7 +41,28 @@ Keep your favorite addresses handy. No more copy-pasting!
|
|
|
41
41
|
|
|
42
42
|
### 🌐 Multi-Network Support
|
|
43
43
|
|
|
44
|
-
Seamlessly manage your assets on
|
|
44
|
+
Seamlessly manage your assets on multiple networks. HODL Wallet supports the following networks:
|
|
45
|
+
|
|
46
|
+
- Binance Smart Chain
|
|
47
|
+
- Ethereum
|
|
48
|
+
- Polygon
|
|
49
|
+
- Optimism
|
|
50
|
+
- Arbitrum One
|
|
51
|
+
- Fantom
|
|
52
|
+
- Avalanche C-Chain
|
|
53
|
+
|
|
54
|
+
Each network supports its native token and popular tokens like USDT. You can easily add more tokens as needed.
|
|
55
|
+
|
|
56
|
+
## 💾 Export and Import HODL Files
|
|
57
|
+
|
|
58
|
+
HODL Wallet now supports exporting and importing encrypted .HODL files, which securely store your wallet information.
|
|
59
|
+
|
|
60
|
+
- **Export HODL File**: Save your wallet data (including private keys and addresses) to an encrypted .HODL file.
|
|
61
|
+
- **Import HODL File**: Restore your wallet from a previously exported .HODL file.
|
|
62
|
+
|
|
63
|
+
These files are encrypted using your wallet password, providing an additional layer of security for storing and transferring your wallet information.
|
|
64
|
+
|
|
65
|
+
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
66
|
|
|
46
67
|
## 🔒 Security
|
|
47
68
|
|
|
@@ -65,12 +86,25 @@ We've carefully selected trusted and well-maintained dependencies for this proje
|
|
|
65
86
|
- **deepbase**: For persistent storage.
|
|
66
87
|
- **crypto-js**: For encryption.
|
|
67
88
|
- **inquirer** and **inquirer-autocomplete-prompt**: For interactive command-line interfaces.
|
|
89
|
+
- **inquirer-fuzzy-path**: For fuzzy searching and selecting file paths during HODL file import.
|
|
68
90
|
- **cli-table3**: For creating formatted CLI tables.
|
|
69
91
|
- **bip39**: For generating and handling mnemonic phrases.
|
|
70
92
|
- **hdkey**: For handling hierarchical deterministic (HD) keys.
|
|
71
93
|
|
|
72
94
|
⚠️ **Important Notice**: HODL Wallet is a personal project created with the best intentions. While we strive for security, it may contain security flaws or vulnerabilities. Use at your own risk and always exercise caution with your crypto assets.
|
|
73
95
|
|
|
96
|
+
## 📘 What HODL means
|
|
97
|
+
|
|
98
|
+
The term "HODL" is a cornerstone of crypto culture, and it's worth understanding its origins:
|
|
99
|
+
|
|
100
|
+
- 🎂 Born on December 18, 2013, in a Bitcoin Talk forum post
|
|
101
|
+
- 🍺 Originally a typo for "HOLD" in a drunk, impassioned rant about not selling Bitcoin
|
|
102
|
+
- 🔤 Later backronymed to mean "Hold On for Dear Life"
|
|
103
|
+
- 💎 Symbolizes a long-term investment strategy and resistance to panic selling
|
|
104
|
+
- 🌍 Now used across various cryptocurrency communities as a rallying cry
|
|
105
|
+
|
|
106
|
+
HODL embodies the belief in the long-term potential of cryptocurrencies, often in the face of short-term market volatility. It's more than just a misspelling; it's a philosophy that has shaped the crypto landscape.
|
|
107
|
+
|
|
74
108
|
## 🤝 Contributing
|
|
75
109
|
|
|
76
110
|
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.
|
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();
|
|
@@ -34,6 +36,7 @@ class Wallet {
|
|
|
34
36
|
this.web3 = null;
|
|
35
37
|
this.selectedNetwork = null;
|
|
36
38
|
this.account = null;
|
|
39
|
+
this.networkUsage = this.db.get('networkUsage') || {};
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
async initialize() {
|
|
@@ -82,14 +85,23 @@ class Wallet {
|
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
async selectNetwork(networkPlugins) {
|
|
88
|
+
// Sort networks by usage count (descending)
|
|
89
|
+
const sortedNetworks = networkPlugins.sort((a, b) =>
|
|
90
|
+
(this.networkUsage[b.name] || 0) - (this.networkUsage[a.name] || 0)
|
|
91
|
+
);
|
|
92
|
+
|
|
85
93
|
const { network } = await inquirer.prompt({
|
|
86
94
|
type: 'list',
|
|
87
95
|
name: 'network',
|
|
88
96
|
message: 'Select the network:',
|
|
89
|
-
choices:
|
|
97
|
+
choices: sortedNetworks.map(plugin => plugin.name),
|
|
90
98
|
});
|
|
91
99
|
|
|
92
|
-
const selectedNetwork =
|
|
100
|
+
const selectedNetwork = sortedNetworks.find(plugin => plugin.name === network);
|
|
101
|
+
|
|
102
|
+
// Increment usage count for the selected network
|
|
103
|
+
this.networkUsage[network] = (this.networkUsage[network] || 0) + 1;
|
|
104
|
+
this.db.set('networkUsage', this.networkUsage);
|
|
93
105
|
|
|
94
106
|
this.selectedNetwork = selectedNetwork;
|
|
95
107
|
}
|
|
@@ -97,113 +109,101 @@ class Wallet {
|
|
|
97
109
|
async loadAccount(forceReload = false) {
|
|
98
110
|
let account = this.db.secureGet('account');
|
|
99
111
|
|
|
100
|
-
const
|
|
112
|
+
const mainChoices = ['Create New Account'];
|
|
101
113
|
|
|
102
114
|
if (forceReload) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
mainChoices.push('Import Options');
|
|
116
|
+
mainChoices.push('Export Options');
|
|
117
|
+
mainChoices.push('Switch Network');
|
|
118
|
+
mainChoices.push('Go Back');
|
|
119
|
+
} else {
|
|
120
|
+
mainChoices.push('Import Mnemonic (12 words)');
|
|
121
|
+
mainChoices.push('Import Private-key');
|
|
122
|
+
mainChoices.push('Import HODL File');
|
|
106
123
|
}
|
|
107
124
|
|
|
108
125
|
if (!account || forceReload) {
|
|
109
|
-
|
|
126
|
+
let { accountAction } = await inquirer.prompt({
|
|
110
127
|
type: 'list',
|
|
111
128
|
name: 'accountAction',
|
|
112
129
|
message: 'Select an account option:',
|
|
113
|
-
choices,
|
|
130
|
+
choices: mainChoices,
|
|
114
131
|
});
|
|
115
132
|
|
|
116
133
|
if (accountAction === 'Go Back') {
|
|
117
134
|
return;
|
|
118
135
|
}
|
|
119
136
|
|
|
120
|
-
if (
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
if (accountAction === 'Import Options') {
|
|
138
|
+
const importChoices = ['Import Mnemonic (12 words)', 'Import Private-key', 'Import HODL File', 'Go Back'];
|
|
139
|
+
const { importAction } = await inquirer.prompt({
|
|
140
|
+
type: 'list',
|
|
141
|
+
name: 'importAction',
|
|
142
|
+
message: 'Select an import option:',
|
|
143
|
+
choices: importChoices,
|
|
126
144
|
});
|
|
127
145
|
|
|
128
|
-
if (
|
|
129
|
-
return;
|
|
146
|
+
if (importAction === 'Go Back') {
|
|
147
|
+
return this.loadAccount(forceReload);
|
|
130
148
|
}
|
|
131
|
-
}
|
|
132
149
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
type: 'confirm',
|
|
136
|
-
name: 'createWithMnemonic',
|
|
137
|
-
message: 'Create account with mnemonic?',
|
|
138
|
-
default: true
|
|
139
|
-
});
|
|
140
|
-
|
|
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();
|
|
150
|
+
if (!await this.confirmOverwrite()) {
|
|
151
|
+
return;
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
|
|
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
|
-
});
|
|
154
|
+
accountAction = importAction;
|
|
155
|
+
}
|
|
165
156
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
switch (accountAction) {
|
|
158
|
+
case 'Import Mnemonic (12 words)':
|
|
159
|
+
account = await this.importFrom12Words();
|
|
160
|
+
if (!account) {
|
|
161
|
+
Wallet.displayError('Invalid mnemonic.');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.account = account;
|
|
169
165
|
this.displayAccountAddress();
|
|
170
|
-
|
|
166
|
+
break;
|
|
167
|
+
case 'Import Private-key':
|
|
168
|
+
await this.importPrivateKey();
|
|
169
|
+
break;
|
|
170
|
+
case 'Import HODL File':
|
|
171
|
+
this.account = await this.importHODLFile();
|
|
172
|
+
this.displayAccountAddress();
|
|
173
|
+
break;
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
if (accountAction === '
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
if (accountAction === 'Export Options') {
|
|
177
|
+
const exportChoices = ['Export Private-key', 'Export HODL File', 'Go Back'];
|
|
178
|
+
const { exportAction } = await inquirer.prompt({
|
|
179
|
+
type: 'list',
|
|
180
|
+
name: 'exportAction',
|
|
181
|
+
message: 'Select an export option:',
|
|
182
|
+
choices: exportChoices,
|
|
179
183
|
});
|
|
180
184
|
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
return;
|
|
185
|
+
if (exportAction === 'Go Back') {
|
|
186
|
+
return this.loadAccount(forceReload);
|
|
184
187
|
}
|
|
185
188
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
switch (exportAction) {
|
|
190
|
+
case 'Export Private-key':
|
|
191
|
+
await this.displayAccountDetails();
|
|
192
|
+
break;
|
|
193
|
+
case 'Export HODL File':
|
|
194
|
+
await this.exportHODLFile();
|
|
195
|
+
break;
|
|
192
196
|
}
|
|
197
|
+
return;
|
|
193
198
|
}
|
|
194
199
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (!
|
|
198
|
-
Wallet.displayError('Invalid mnemonic.');
|
|
200
|
+
|
|
201
|
+
if (accountAction === 'Create New Account') {
|
|
202
|
+
if (!await this.confirmOverwrite()) {
|
|
199
203
|
return;
|
|
200
204
|
}
|
|
201
|
-
this.account = account;
|
|
202
|
-
this.displayAccountAddress();
|
|
203
|
-
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
await this.displayAccountDetails();
|
|
206
|
+
await this.createNewAccount();
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
if (accountAction === 'Switch Network') {
|
|
@@ -216,6 +216,20 @@ class Wallet {
|
|
|
216
216
|
this.account = account;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
async confirmOverwrite() {
|
|
220
|
+
if (this.account) {
|
|
221
|
+
const { confirmOverwrite } = await inquirer.prompt({
|
|
222
|
+
type: 'confirm',
|
|
223
|
+
name: 'confirmOverwrite',
|
|
224
|
+
message: 'This action will overwrite the existing account. Are you sure you want to continue?',
|
|
225
|
+
default: false,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return confirmOverwrite;
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
219
233
|
async importFrom12Words() {
|
|
220
234
|
const { mnemonic } = await inquirer.prompt({
|
|
221
235
|
type: 'password',
|
|
@@ -228,13 +242,7 @@ class Wallet {
|
|
|
228
242
|
return null;
|
|
229
243
|
}
|
|
230
244
|
|
|
231
|
-
const
|
|
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;
|
|
245
|
+
const account = await this.accountFromMnemonic(mnemonic);
|
|
238
246
|
await this.db.secureSet('account', account);
|
|
239
247
|
return account;
|
|
240
248
|
}
|
|
@@ -329,7 +337,7 @@ class Wallet {
|
|
|
329
337
|
const { amount } = await inquirer.prompt({
|
|
330
338
|
type: 'input',
|
|
331
339
|
name: 'amount',
|
|
332
|
-
message: `Amount to transfer
|
|
340
|
+
message: `Amount to transfer:`,
|
|
333
341
|
validate: value => {
|
|
334
342
|
if (value === '') return true;
|
|
335
343
|
return !isNaN(value) && Number(value) > 0 ? true : 'Please enter a valid number or leave empty to cancel.';
|
|
@@ -340,6 +348,18 @@ class Wallet {
|
|
|
340
348
|
return;
|
|
341
349
|
}
|
|
342
350
|
|
|
351
|
+
// Add confirmation step
|
|
352
|
+
const { confirmTransaction } = await inquirer.prompt({
|
|
353
|
+
type: 'confirm',
|
|
354
|
+
name: 'confirmTransaction',
|
|
355
|
+
message: `Confirm transfer?`,
|
|
356
|
+
default: true
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (!confirmTransaction) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
343
363
|
try {
|
|
344
364
|
let signedTx;
|
|
345
365
|
if (tokens[token] && token !== this.selectedNetwork.nativeToken) {
|
|
@@ -585,6 +605,134 @@ class Wallet {
|
|
|
585
605
|
await this.selectNetwork(networkPlugins);
|
|
586
606
|
this.web3 = new Web3(this.selectedNetwork.rpcUrl);
|
|
587
607
|
}
|
|
608
|
+
|
|
609
|
+
async exportHODLFile() {
|
|
610
|
+
const defaultFileName = `${this.account.address.slice(-6).toUpperCase()}`;
|
|
611
|
+
let { fileName } = await inquirer.prompt({
|
|
612
|
+
type: 'input',
|
|
613
|
+
name: 'fileName',
|
|
614
|
+
message: 'Enter the name for the HODL file:',
|
|
615
|
+
default: defaultFileName
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
fileName += '.HODL';
|
|
619
|
+
|
|
620
|
+
const data = this.db.get();
|
|
621
|
+
const encryptedData = this.db.encrypt(data);
|
|
622
|
+
|
|
623
|
+
fs.writeFileSync(fileName, encryptedData);
|
|
624
|
+
|
|
625
|
+
const table = new Table({
|
|
626
|
+
head: ['HODL File Exported'],
|
|
627
|
+
style: { head: ['green'] }
|
|
628
|
+
});
|
|
629
|
+
table.push([`File saved as: ${fileName}`]);
|
|
630
|
+
console.log(table.toString());
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async importHODLFile() {
|
|
634
|
+
const { filePath } = await inquirer.prompt({
|
|
635
|
+
type: 'fuzzypath',
|
|
636
|
+
name: 'filePath',
|
|
637
|
+
message: 'Select the .HODL file:',
|
|
638
|
+
rootPath: '.',
|
|
639
|
+
itemType: 'file',
|
|
640
|
+
suggestOnly: false,
|
|
641
|
+
depthLimit: 5,
|
|
642
|
+
excludePath: nodePath => nodePath.startsWith('node_modules'),
|
|
643
|
+
excludeFilter: nodePath => !nodePath.endsWith('.HODL'),
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
if (!fs.existsSync(filePath)) {
|
|
647
|
+
Wallet.displayError('File not found.');
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const encryptedData = fs.readFileSync(filePath, 'utf8');
|
|
652
|
+
|
|
653
|
+
try {
|
|
654
|
+
const decryptedData = this.db.decrypt(encryptedData);
|
|
655
|
+
this.db.set(decryptedData);
|
|
656
|
+
return this.db.secureGet('account');
|
|
657
|
+
} catch (error) {
|
|
658
|
+
Wallet.displayError('Failed to import HODL file.', 'The file may be corrupted or the encryption key is incorrect.');
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
async importPrivateKey() {
|
|
663
|
+
const { privateKey } = await inquirer.prompt({
|
|
664
|
+
type: 'password',
|
|
665
|
+
name: 'privateKey',
|
|
666
|
+
message: 'Private-key:',
|
|
667
|
+
mask: '*',
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
if (!privateKey.trim()) {
|
|
671
|
+
Wallet.displayError('Private-key is empty.');
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
try {
|
|
676
|
+
this.account = this.web3.eth.accounts.privateKeyToAccount(privateKey);
|
|
677
|
+
await this.db.secureSet('account', this.account);
|
|
678
|
+
this.displayAccountAddress();
|
|
679
|
+
} catch (error) {
|
|
680
|
+
Wallet.displayError('Invalid private-key.');
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
async createNewAccount() {
|
|
685
|
+
const { createWithMnemonic } = await inquirer.prompt({
|
|
686
|
+
type: 'confirm',
|
|
687
|
+
name: 'createWithMnemonic',
|
|
688
|
+
message: 'Create account with mnemonic?',
|
|
689
|
+
default: true
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
let message = 'Do you want to display sensitive information (private key';
|
|
693
|
+
if (createWithMnemonic) {
|
|
694
|
+
this.account = await this.createAccountFromMnemonic();
|
|
695
|
+
message += ' and mnemonic';
|
|
696
|
+
} else {
|
|
697
|
+
this.account = this.web3.eth.accounts.create();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
message += ')?';
|
|
701
|
+
|
|
702
|
+
await this.db.secureSet('account', this.account);
|
|
703
|
+
|
|
704
|
+
const { showSensitive } = await inquirer.prompt({
|
|
705
|
+
type: 'confirm',
|
|
706
|
+
name: 'showSensitive',
|
|
707
|
+
message,
|
|
708
|
+
default: false,
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
if (showSensitive) {
|
|
712
|
+
this.displayAccountDetails();
|
|
713
|
+
} else {
|
|
714
|
+
this.displayAccountAddress();
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
async accountFromMnemonic(mnemonic) {
|
|
719
|
+
const seed = await bip39.mnemonicToSeed(mnemonic);
|
|
720
|
+
const root = hdkey.fromMasterSeed(seed);
|
|
721
|
+
const addrNode = root.derive("m/44'/60'/0'/0/0");
|
|
722
|
+
const privateKey = addrNode.privateKey.toString('hex');
|
|
723
|
+
const account = this.web3.eth.accounts.privateKeyToAccount('0x' + privateKey);
|
|
724
|
+
account.mnemonic = mnemonic;
|
|
725
|
+
return account;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
async createAccountFromMnemonic() {
|
|
729
|
+
try {
|
|
730
|
+
const mnemonic = bip39.generateMnemonic();
|
|
731
|
+
return this.accountFromMnemonic(mnemonic);
|
|
732
|
+
} catch (error) {
|
|
733
|
+
Wallet.displayError('Failed to create account from mnemonic.', error);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
588
736
|
}
|
|
589
737
|
|
|
590
738
|
class UIManager {
|
package/network/arb.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: '[ERC-20] Arbitrum One',
|
|
3
|
+
explorer: 'https://arbiscan.io/tx/',
|
|
4
|
+
rpcUrl: 'https://arb1.arbitrum.io/rpc',
|
|
5
|
+
nativeToken: 'ETH',
|
|
6
|
+
tokens: {
|
|
7
|
+
'USDT': '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', // USDT on Arbitrum
|
|
8
|
+
'ARB': '0x0000000000000000000000000000000000000000', // Native ETH on Arbitrum
|
|
9
|
+
},
|
|
10
|
+
};
|
package/network/avax.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: '[ERC-20] Avalanche C-Chain',
|
|
3
|
+
explorer: 'https://snowtrace.io/tx/',
|
|
4
|
+
rpcUrl: 'https://api.avax.network/ext/bc/C/rpc',
|
|
5
|
+
nativeToken: 'AVAX',
|
|
6
|
+
tokens: {
|
|
7
|
+
'USDT': '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', // USDT.e on Avalanche C-Chain
|
|
8
|
+
'AVAX': '0x0000000000000000000000000000000000000000', // Native AVAX
|
|
9
|
+
},
|
|
10
|
+
};
|
package/network/ftm.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: '[ERC-20] Fantom',
|
|
3
|
+
explorer: 'https://ftmscan.com/tx/',
|
|
4
|
+
rpcUrl: 'https://rpc.ftm.tools/',
|
|
5
|
+
nativeToken: 'FTM',
|
|
6
|
+
tokens: {
|
|
7
|
+
'USDT': '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // USDT on Fantom
|
|
8
|
+
'FTM': '0x0000000000000000000000000000000000000000', // Native FTM
|
|
9
|
+
},
|
|
10
|
+
};
|
package/network/op.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: '[ERC-20] Optimism',
|
|
3
|
+
explorer: 'https://optimistic.etherscan.io/tx/',
|
|
4
|
+
rpcUrl: 'https://mainnet.optimism.io',
|
|
5
|
+
nativeToken: 'ETH',
|
|
6
|
+
tokens: {
|
|
7
|
+
'USDT': '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', // USDT on Optimism
|
|
8
|
+
'OP': '0x0000000000000000000000000000000000000000',
|
|
9
|
+
},
|
|
10
|
+
};
|
package/network/pol.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
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',
|
|
9
|
+
'POL': '0x0000000000000000000000000000000000000000',
|
|
10
|
+
},
|
|
11
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hodl-wallet",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "🧊 HODL Wallet - Fast CLI crypto wallet!",
|
|
5
5
|
"author": "Martin Clasen",
|
|
6
6
|
"repository": {
|
|
@@ -37,16 +37,24 @@
|
|
|
37
37
|
"stable",
|
|
38
38
|
"coin",
|
|
39
39
|
"evm",
|
|
40
|
+
"polygon",
|
|
41
|
+
"matic",
|
|
42
|
+
"arbitrum",
|
|
43
|
+
"fantom",
|
|
44
|
+
"optimism",
|
|
45
|
+
"avalanche",
|
|
46
|
+
"avax",
|
|
40
47
|
"clasen"
|
|
41
48
|
],
|
|
42
49
|
"dependencies": {
|
|
43
50
|
"bip39": "^3.1.0",
|
|
44
51
|
"cli-table3": "^0.6.5",
|
|
45
52
|
"crypto-js": "^4.2.0",
|
|
46
|
-
"deepbase": "^1.2.
|
|
53
|
+
"deepbase": "^1.2.4",
|
|
47
54
|
"hdkey": "^2.1.0",
|
|
48
55
|
"inquirer": "^9.3.7",
|
|
49
56
|
"inquirer-autocomplete-prompt": "^3.0.1",
|
|
57
|
+
"inquirer-fuzzy-path": "^2.3.0",
|
|
50
58
|
"web3": "^4.13.0"
|
|
51
59
|
}
|
|
52
|
-
}
|
|
60
|
+
}
|
|
File without changes
|