hodl-wallet 1.8.6 → 1.9.0

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
@@ -17,7 +17,7 @@ Let's face it, Trust Wallet's sluggishness and annoying ads are so last season.
17
17
  - 🚫 Zero ads, zero BS
18
18
  - 🔒 Create wallets offline (because paranoia is just good sense in crypto)
19
19
  - 🔍 Fully transparent, open-source code
20
- - 🌐 Support for Bitcoin and Ethereum. Binance Smart Chain, TON, Polygon, Avalanche, Optimism, Arbitrum, and Fantom.
20
+ - 🌐 Support for Bitcoin and Ethereum. Binance Smart Chain, Polygon, Avalanche, Optimism, Arbitrum, and Fantom.
21
21
 
22
22
  That's it! Follow the prompts and you're in crypto heaven.
23
23
 
@@ -44,9 +44,6 @@ Keep your favorite addresses handy. No more copy-pasting!
44
44
  Seamlessly manage your assets on multiple networks. HODL Wallet supports the following networks:
45
45
 
46
46
  - Bitcoin
47
- - TON (The Open Network)
48
- - Native TON transfers
49
- - Jetton support (USDT)
50
47
  - EVM
51
48
  - Ethereum
52
49
  - Binance Smart Chain
@@ -75,6 +72,8 @@ The main advantage of exporting a HODL file is that to access the private key, y
75
72
 
76
73
  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.
77
74
 
75
+ **For a deeper understanding**: [HODL DeepWiki](https://deepwiki.com/clasen/HODL)
76
+
78
77
  ### 🔑 Private Key Storage
79
78
 
80
79
  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.
@@ -98,11 +97,6 @@ We've carefully selected trusted and well-maintained dependencies for this proje
98
97
  - Web3
99
98
  - **web3**: The Ethereum JavaScript API for blockchain interactions.
100
99
  - **hdkey**: For handling hierarchical deterministic (HD) keys.
101
- - TON
102
- - **@ton/ton**: The TON JavaScript SDK for blockchain interactions.
103
- - **@ton/core**: Core TON blockchain operations and data structures.
104
- - **@ton/crypto**: Cryptographic functions for TON blockchain.
105
- - **@orbs-network/ton-access**: For reliable TON network access.
106
100
  - Bitcoin
107
101
  - **bitcoinjs-lib**: For Bitcoin-specific operations.
108
102
  - **bip32**: For handling hierarchical deterministic (HD) keys.
package/index.js CHANGED
@@ -37,20 +37,20 @@ class Wallet {
37
37
 
38
38
  formatAmount(num) {
39
39
  num = parseFloat(num);
40
-
40
+
41
41
  // Handle integers - add .00
42
42
  if (num === Math.floor(num)) {
43
43
  return num.toString() + '.00';
44
44
  }
45
-
45
+
46
46
  // For decimals, format to max 3 decimal places, then remove trailing zeros
47
47
  let formatted = num.toFixed(3);
48
-
48
+
49
49
  // Remove trailing zeros, but keep at least 2 decimal places
50
50
  while (formatted.endsWith('0') && formatted.split('.')[1].length > 2) {
51
51
  formatted = formatted.slice(0, -1);
52
52
  }
53
-
53
+
54
54
  return formatted;
55
55
  }
56
56
 
@@ -82,6 +82,9 @@ class Wallet {
82
82
  }
83
83
 
84
84
  setAccount(account) {
85
+ if (!account) {
86
+ return;
87
+ }
85
88
  this.db.set('account', this.network.constructor.name, account);
86
89
  if (account.mnemonic) {
87
90
  this.db.set('mnemonic', account.mnemonic);
@@ -90,8 +93,8 @@ class Wallet {
90
93
 
91
94
  async getAccount() {
92
95
  const account = this.db.get('account', this.network.constructor.name);
93
-
94
-
96
+
97
+
95
98
  return account;
96
99
  }
97
100
 
@@ -111,7 +114,7 @@ class Wallet {
111
114
  head: ['green']
112
115
  }
113
116
  });
114
-
117
+
115
118
  table.push([await this.getAddress()]);
116
119
  console.log(table.toString());
117
120
  }
@@ -133,18 +136,18 @@ class Wallet {
133
136
  const sortedNetworks = networkPlugins.sort((a, b) => {
134
137
  const aUsage = this.networkUsage[a.name];
135
138
  const bUsage = this.networkUsage[b.name];
136
-
139
+
137
140
  // Handle old format (number) vs new format (object)
138
141
  const aLastUsed = typeof aUsage === 'object' ? aUsage.lastUsed || 0 : 0;
139
142
  const bLastUsed = typeof bUsage === 'object' ? bUsage.lastUsed || 0 : 0;
140
-
143
+
141
144
  // If neither has lastUsed timestamp, sort by old count format
142
145
  if (aLastUsed === 0 && bLastUsed === 0) {
143
146
  const aCount = typeof aUsage === 'number' ? aUsage : (aUsage?.count || 0);
144
147
  const bCount = typeof bUsage === 'number' ? bUsage : (bUsage?.count || 0);
145
148
  return bCount - aCount;
146
149
  }
147
-
150
+
148
151
  return bLastUsed - aLastUsed;
149
152
  });
150
153
 
@@ -167,19 +170,19 @@ class Wallet {
167
170
  // Update usage info for the selected network
168
171
  // Handle migration from old format (number) to new format (object)
169
172
  const currentUsage = this.networkUsage[selectedNetwork.name];
170
-
173
+
171
174
  if (!currentUsage || typeof currentUsage === 'number') {
172
175
  // Old format (number) or doesn't exist - create new object
173
- this.networkUsage[selectedNetwork.name] = {
174
- count: typeof currentUsage === 'number' ? currentUsage + 1 : 1,
175
- lastUsed: Date.now()
176
+ this.networkUsage[selectedNetwork.name] = {
177
+ count: typeof currentUsage === 'number' ? currentUsage + 1 : 1,
178
+ lastUsed: Date.now()
176
179
  };
177
180
  } else {
178
181
  // New format (object) - update values
179
182
  this.networkUsage[selectedNetwork.name].count = (currentUsage.count || 0) + 1;
180
183
  this.networkUsage[selectedNetwork.name].lastUsed = Date.now();
181
184
  }
182
-
185
+
183
186
  await this.db.set('networkUsage', this.networkUsage);
184
187
 
185
188
  this.selectedNetwork = selectedNetwork;
@@ -197,9 +200,9 @@ class Wallet {
197
200
  mainChoices.push('Manage Address Book');
198
201
  mainChoices.push('Go Back');
199
202
  } else {
200
- mainChoices.push('Import Mnemonic (12 words)');
201
- mainChoices.push('Import Private-key');
202
203
  mainChoices.push('Import HODL File');
204
+ mainChoices.push('Import Mnemonic (12 or 24 words)');
205
+ mainChoices.push('Import Private-key');
203
206
  }
204
207
 
205
208
  if (!account || loggedIn) {
@@ -236,7 +239,7 @@ class Wallet {
236
239
  }
237
240
 
238
241
  if (accountAction === 'Import Options') {
239
- const importChoices = ['Import Mnemonic (12 words)', 'Import Private-key', 'Import HODL File', 'Go Back'];
242
+ const importChoices = ['Import HODL File', 'Import Mnemonic (12 or 24 words)', 'Import Private-key', 'Go Back'];
240
243
  const { importAction } = await inquirer.prompt({
241
244
  type: 'list',
242
245
  name: 'importAction',
@@ -255,7 +258,7 @@ class Wallet {
255
258
  }
256
259
 
257
260
  switch (accountAction) {
258
- case 'Import Mnemonic (12 words)':
261
+ case 'Import Mnemonic (12 or 24 words)':
259
262
  account = await this.importFromMnemonic();
260
263
  if (!account) {
261
264
  // Wallet.displayError('Invalid mnemonic.');
@@ -268,13 +271,16 @@ class Wallet {
268
271
  await this.importPrivateKey();
269
272
  break;
270
273
  case 'Import HODL File':
271
- this.setAccount(await this.importHODLFile());
272
- await this.displayAccountAddress();
274
+ const importedAccount = await this.importHODLFile();
275
+ if (importedAccount) {
276
+ this.setAccount(importedAccount);
277
+ await this.displayAccountAddress();
278
+ }
273
279
  break;
274
280
  }
275
281
 
276
282
  if (accountAction === 'Export Options') {
277
- const exportChoices = ['Export Private-key', 'Export HODL File', 'Go Back'];
283
+ const exportChoices = ['Export HODL File', 'Export Private-key', 'Go Back'];
278
284
  const { exportAction } = await inquirer.prompt({
279
285
  type: 'list',
280
286
  name: 'exportAction',
@@ -333,7 +339,7 @@ class Wallet {
333
339
  const { mnemonic } = await inquirer.prompt({
334
340
  type: 'password',
335
341
  name: 'mnemonic',
336
- message: 'Enter your 12-word mnemonic phrase:',
342
+ message: 'Enter your mnemonic phrase (12 or 24 words):',
337
343
  mask: '*',
338
344
  validate: (input) => {
339
345
  if (input.trim() === '') return true;
@@ -466,17 +472,17 @@ class Wallet {
466
472
  let currentBalance = 0;
467
473
  try {
468
474
  const walletAddress = await this.getAddress();
469
-
475
+
470
476
  // Get the balance AFTER transaction (not before)
471
477
  if (token === this.selectedNetwork.nativeToken) {
472
478
  currentBalance = await this.network.getBalance(walletAddress);
473
479
  } else {
474
480
  currentBalance = await this.network.getTokenBalance(walletAddress, token);
475
481
  }
476
-
482
+
477
483
  // Round to avoid floating point precision issues
478
484
  currentBalance = Math.round(currentBalance * 100000000) / 100000000;
479
-
485
+
480
486
  } catch (error) {
481
487
  console.error('Error getting post-transaction balance:', error.message);
482
488
  }
@@ -724,21 +730,58 @@ class Wallet {
724
730
  }
725
731
 
726
732
  async importHODLFile() {
733
+ // Scan current directory for .HODL files
734
+ const currentDir = process.cwd();
735
+ let hodlFiles = [];
736
+
737
+ try {
738
+ const files = fs.readdirSync(currentDir);
739
+ hodlFiles = files.filter(file => file.endsWith('.HODL'));
740
+ } catch (error) {
741
+ console.error('Error reading directory:', error.message);
742
+ }
743
+
744
+ // Create file options array for autocomplete
745
+ const fileOptions = hodlFiles.map(file => ({
746
+ name: file,
747
+ value: path.join(currentDir, file)
748
+ }));
749
+
750
+ // Use autocomplete pattern similar to transferFunds
727
751
  const { filePath } = await inquirer.prompt({
728
- type: 'input',
752
+ type: 'autocomplete',
729
753
  name: 'filePath',
730
- message: 'Enter the path to the .HODL file:',
731
- validate: (input) => {
732
- if (!input) return 'Please enter a file path';
733
- if (!input.endsWith('.HODL')) return 'File must have .HODL extension';
734
- if (!fs.existsSync(input)) return 'File does not exist';
735
- return true;
736
- }
754
+ message: 'HODL file path:',
755
+ source: (answersSoFar, input) => {
756
+ input = input || '';
757
+
758
+ const filenameOptions = fileOptions
759
+ .filter(entry => entry.name.toLowerCase().includes(input.toLowerCase()) || entry.value.toLowerCase().includes(input.toLowerCase()))
760
+ .map(entry => ({
761
+ name: entry.name,
762
+ value: entry.value
763
+ }));
764
+
765
+ // Put path options first, then filename options, then manual input
766
+ return [].concat([{ name: input, value: input }])
767
+ .concat(filenameOptions)
768
+ },
737
769
  });
738
770
 
771
+ // If user leaves empty, skip the operation
772
+ if (!filePath || filePath.trim() === '') {
773
+ return null;
774
+ }
775
+
776
+ // Validate the file path
777
+ if (!filePath.endsWith('.HODL')) {
778
+ Wallet.displayError('File must have .HODL extension');
779
+ return null;
780
+ }
781
+
739
782
  if (!fs.existsSync(filePath)) {
740
783
  Wallet.displayError('File not found.');
741
- return;
784
+ return null;
742
785
  }
743
786
 
744
787
  const encryptedData = fs.readFileSync(filePath, 'utf8');
@@ -749,6 +792,7 @@ class Wallet {
749
792
  return this.getAccount();
750
793
  } catch (error) {
751
794
  Wallet.displayError('Failed to import HODL file.', 'The password is incorrect.');
795
+ return null;
752
796
  }
753
797
  }
754
798
 
@@ -803,7 +847,19 @@ class Wallet {
803
847
  });
804
848
 
805
849
  if (createWithMnemonic) {
806
- this.setAccount(await this.network.createAccountFromMnemonic());
850
+ // Ask for mnemonic word count
851
+ const { wordCount } = await inquirer.prompt({
852
+ type: 'list',
853
+ name: 'wordCount',
854
+ message: 'Choose mnemonic phrase length:',
855
+ choices: [
856
+ { name: '12 words (standard)', value: 12 },
857
+ { name: '24 words (more secure)', value: 24 }
858
+ ],
859
+ default: 12
860
+ });
861
+
862
+ this.setAccount(await this.network.createAccountFromMnemonic(wordCount));
807
863
  } else {
808
864
  this.setAccount(await this.network.createAccount());
809
865
  }
@@ -185,9 +185,11 @@ export default class BitcoinNetwork extends BaseNetwork {
185
185
  };
186
186
  }
187
187
 
188
- async createAccountFromMnemonic() {
188
+ async createAccountFromMnemonic(wordCount = 12) {
189
189
  try {
190
- const mnemonic = bip39.generateMnemonic();
190
+ // Support both 12 and 24 word mnemonics
191
+ const strength = wordCount === 24 ? 256 : 128; // 256 bits = 24 words, 128 bits = 12 words
192
+ const mnemonic = bip39.generateMnemonic(strength);
191
193
  return this.accountFromMnemonic(mnemonic);
192
194
  } catch (error) {
193
195
  throw new Error('Failed to create account from mnemonic: ' + error.message);
@@ -133,9 +133,9 @@ export default class TONNetwork extends BaseNetwork {
133
133
  }
134
134
  }
135
135
 
136
- async createAccountFromMnemonic() {
136
+ async createAccountFromMnemonic(wordCount = 12) {
137
137
  try {
138
- const mnemonic = await this.generateMnemonic();
138
+ const mnemonic = await this.generateMnemonic(wordCount);
139
139
  const account = await this.accountFromMnemonic(mnemonic);
140
140
 
141
141
  // Ensure all required fields are present including mnemonic
@@ -150,8 +150,8 @@ export default class TONNetwork extends BaseNetwork {
150
150
  }
151
151
  }
152
152
 
153
- async generateMnemonic() {
154
- const mnemonic = await mnemonicNew();
153
+ async generateMnemonic(wordCount = 12) {
154
+ const mnemonic = await mnemonicNew(wordCount);
155
155
  return mnemonic.join(' ');
156
156
  }
157
157
 
@@ -89,10 +89,11 @@ export default class Web3Network extends BaseNetwork {
89
89
 
90
90
  async handleNativeTransfer(account, recipient, amount) {
91
91
  const gasPrice = await this.getGasPrice();
92
+ const valueInWei = this.web3.utils.toWei(amount.toString(), 'ether');
92
93
  const gasLimit = await this.estimateGas({
93
94
  from: account.address,
94
95
  to: recipient,
95
- value: amount
96
+ value: valueInWei
96
97
  });
97
98
 
98
99
  return this.transfer(account, recipient, amount, { gasLimit, gasPrice });
@@ -144,9 +145,11 @@ export default class Web3Network extends BaseNetwork {
144
145
  return account;
145
146
  }
146
147
 
147
- async createAccountFromMnemonic() {
148
+ async createAccountFromMnemonic(wordCount = 12) {
148
149
  try {
149
- const mnemonic = bip39.generateMnemonic();
150
+ // Support both 12 and 24 word mnemonics
151
+ const strength = wordCount === 24 ? 256 : 128; // 256 bits = 24 words, 128 bits = 12 words
152
+ const mnemonic = bip39.generateMnemonic(strength);
150
153
  return this.accountFromMnemonic(mnemonic);
151
154
  } catch (error) {
152
155
  throw new Error('Failed to create account from mnemonic: ' + error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hodl-wallet",
3
- "version": "1.8.6",
3
+ "version": "1.9.0",
4
4
  "description": "🧊 HODL Wallet - Fast CLI crypto wallet!",
5
5
  "author": "Martin Clasen",
6
6
  "repository": {
@@ -32,7 +32,6 @@
32
32
  "bep20",
33
33
  "bnb",
34
34
  "eth",
35
- "ton",
36
35
  "binance",
37
36
  "trust",
38
37
  "blockchain",
@@ -51,30 +50,26 @@
51
50
  "clasen"
52
51
  ],
53
52
  "dependencies": {
54
- "@orbs-network/ton-access": "^2.3.3",
55
- "@ton/core": "^0.60.0",
56
- "@ton/crypto": "^3.2.0",
57
- "@ton/ton": "^15.0.0",
58
- "axios": "^1.8.4",
59
- "bip32": "^5.0.0-rc.0",
60
- "bip39": "^3.1.0",
61
- "bitcoinjs-lib": "^6.1.7",
62
- "cli-table3": "^0.6.5",
63
- "crypto-js": "^4.2.0",
64
- "deepbase": "^1.5.2",
65
- "ecpair": "^2.1.0",
66
- "external-editor": "^3.1.0",
67
- "hdkey": "^2.1.0",
68
- "inquirer": "^9.3.7",
69
- "inquirer-autocomplete-prompt": "^3.0.1",
70
- "inquirer-fuzzy-path": "^2.3.0",
71
- "ora": "^8.1.1",
72
- "tiny-secp256k1": "^2.2.3",
73
- "tmp": "^0.2.4",
74
- "web3": "^4.14.0"
53
+ "axios": "1.8.4",
54
+ "bip32": "5.0.0-rc.0",
55
+ "bip39": "3.1.0",
56
+ "bitcoinjs-lib": "6.1.7",
57
+ "cli-table3": "0.6.5",
58
+ "crypto-js": "4.2.0",
59
+ "deepbase": "1.5.2",
60
+ "ecpair": "2.1.0",
61
+ "external-editor": "3.1.0",
62
+ "hdkey": "2.1.0",
63
+ "inquirer": "9.3.7",
64
+ "inquirer-autocomplete-prompt": "3.0.1",
65
+ "inquirer-fuzzy-path": "2.3.0",
66
+ "ora": "8.1.1",
67
+ "tiny-secp256k1": "2.2.3",
68
+ "tmp": "0.2.4",
69
+ "web3": "4.14.0"
75
70
  },
76
71
  "overrides": {
77
- "tmp": "^0.2.4"
72
+ "tmp": "0.2.4"
78
73
  },
79
74
  "type": "module"
80
75
  }
package/test/test-all.js CHANGED
@@ -14,9 +14,9 @@ class ComprehensiveTester {
14
14
  displayWelcome() {
15
15
  console.log('\x1b[32m'); // Set text color to green
16
16
  console.log(`
17
- ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
18
- ░░░░░░░░░░░░░░ 🧊 HODL WALLET - COMPREHENSIVE TEST SUITE ░░░░░░░░░░░░░░
19
- ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
17
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
18
+ ░░░░░░░░░░░░░░ 🧊 HODL WALLET - COMPREHENSIVE TEST SUITE ░░░░░░░░░░░░░░░
19
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
20
20
  `);
21
21
  console.log('\x1b[0m'); // Reset text color
22
22
  console.log('This test suite will validate all network implementations and integrations.\n');
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from 'fs';
4
- import path from 'path';
5
3
  import { fileURLToPath } from 'url';
6
4
  import { dirname } from 'path';
7
5
  import Table from 'cli-table3';
@@ -99,7 +97,9 @@ class IntegrationTester extends NetworkTester {
99
97
  this.crossNetworkResults.push({
100
98
  test: 'Network Type Distribution',
101
99
  status: totalNetworks === allNetworks.length ? 'PASS' : 'FAIL',
102
- message: `Found ${totalNetworks} categorized networks out of ${allNetworks.length} total`,
100
+ message: tonNetworks.length === 0 ?
101
+ `Found ${totalNetworks} categorized networks out of ${allNetworks.length} total (TON network disabled)` :
102
+ `Found ${totalNetworks} categorized networks out of ${allNetworks.length} total`,
103
103
  details: results
104
104
  });
105
105
 
package/test/test.js CHANGED
@@ -197,22 +197,51 @@ class NetworkTester {
197
197
  }
198
198
  });
199
199
 
200
- // Test 8: Create account from new mnemonic
200
+ // Test 8: Create account from new mnemonic (12 words)
201
201
  tests.push({
202
- name: 'Create Account From New Mnemonic',
202
+ name: 'Create Account From New Mnemonic (12 words)',
203
203
  test: async () => {
204
- const account = await instance.createAccountFromMnemonic();
204
+ const account = await instance.createAccountFromMnemonic(12);
205
205
 
206
206
  if (!account || !account.address || !account.privateKey || !account.mnemonic) {
207
- throw new Error('Account creation with mnemonic failed');
207
+ throw new Error('Account creation with 12-word mnemonic failed');
208
+ }
209
+
210
+ const wordCount = account.mnemonic.split(' ').length;
211
+ if (wordCount !== 12) {
212
+ throw new Error(`Expected 12 words, got: ${wordCount}`);
213
+ }
214
+
215
+ // Verify the mnemonic is valid
216
+ if (!instance.validateMnemonic(account.mnemonic)) {
217
+ throw new Error('Generated 12-word mnemonic is invalid');
218
+ }
219
+
220
+ return `12-word account: ${account.address.substring(0, 10)}...`;
221
+ }
222
+ });
223
+
224
+ // Test 8b: Create account from new mnemonic (24 words)
225
+ tests.push({
226
+ name: 'Create Account From New Mnemonic (24 words)',
227
+ test: async () => {
228
+ const account = await instance.createAccountFromMnemonic(24);
229
+
230
+ if (!account || !account.address || !account.privateKey || !account.mnemonic) {
231
+ throw new Error('Account creation with 24-word mnemonic failed');
232
+ }
233
+
234
+ const wordCount = account.mnemonic.split(' ').length;
235
+ if (wordCount !== 24) {
236
+ throw new Error(`Expected 24 words, got: ${wordCount}`);
208
237
  }
209
238
 
210
239
  // Verify the mnemonic is valid
211
240
  if (!instance.validateMnemonic(account.mnemonic)) {
212
- throw new Error('Generated mnemonic is invalid');
241
+ throw new Error('Generated 24-word mnemonic is invalid');
213
242
  }
214
243
 
215
- return `Account with mnemonic: ${account.address.substring(0, 10)}...`;
244
+ return `24-word account: ${account.address.substring(0, 10)}...`;
216
245
  }
217
246
  });
218
247
 
@@ -1,11 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(mv:*)",
5
- "Bash(npm install)",
6
- "Bash(node:*)",
7
- "Bash(npm audit:*)"
8
- ],
9
- "deny": []
10
- }
11
- }
package/CLAUDE.md DELETED
@@ -1,90 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Project Overview
6
-
7
- HODL Wallet is a CLI-based multi-network cryptocurrency wallet written in Node.js. It supports Bitcoin, TON (The Open Network), and multiple EVM-compatible networks (Ethereum, BSC, Polygon, Arbitrum, Optimism, Fantom, Avalanche).
8
-
9
- ## Key Commands
10
-
11
- - **Start the application**: `npm start` or `node index.js`
12
- - **Install globally**: `npm install -g hodl-wallet` then run `hodl`
13
- - **No test suite configured**: The project uses `echo "Error: no test specified" && exit 1` for tests
14
-
15
- ## Architecture Overview
16
-
17
- ### Core Components
18
-
19
- - **index.js**: Main application entry point containing the `Wallet` class and `UIManager` class
20
- - **persist.js**: Encrypted data persistence layer using Deepbase with AES encryption
21
- - **network/**: Network implementations following a plugin architecture
22
-
23
- ### Network Plugin System
24
-
25
- The application uses a modular network plugin system:
26
-
27
- - **BaseNetwork.js**: Abstract base class defining the interface all networks must implement
28
- - **Web3Network.js**: EVM-compatible network implementation extending BaseNetwork
29
- - **BitcoinNetwork.js**: Bitcoin-specific network implementation
30
- - **TONNetwork.js**: TON network implementation
31
-
32
- Each network plugin exports:
33
- - `NetworkClass`: The implementation class
34
- - `name`: Display name for the network
35
- - `url`: RPC endpoint URL
36
- - `nativeToken`: Native token symbol (e.g., 'ETH', 'BTC')
37
- - `explorer`: Block explorer URL template
38
- - `tokens`: Object mapping token symbols to contract addresses
39
-
40
- ### Data Storage
41
-
42
- - User data stored in `~/.HODL/` directory
43
- - All data encrypted using user-provided password
44
- - Supports mnemonic phrases, private keys, address book, and transaction history
45
- - Network usage tracking for auto-selection of last-used network
46
-
47
- ### Key Features
48
-
49
- - Multi-network wallet with unified interface
50
- - Encrypted local storage with password protection
51
- - Address book with autocomplete
52
- - Transaction history tracking
53
- - HODL file export/import for wallet backup
54
- - Mnemonic and private key import/export
55
-
56
- ### Security Considerations
57
-
58
- - Private keys and mnemonics are encrypted at rest
59
- - Password required for all operations
60
- - Support for offline account creation
61
- - Transparent open-source codebase encourages security audits
62
-
63
- ## Common Development Patterns
64
-
65
- - Network implementations extend either `Web3Network`, `BitcoinNetwork`, or `BaseNetwork` (for TON)
66
- - All user interactions use the `inquirer` library for CLI prompts
67
- - Tables displayed using `cli-table3` for consistent formatting
68
- - Async/await pattern used throughout
69
- - ES6 modules with `.js` extensions
70
-
71
- ## TON Network Integration
72
-
73
- The TON network has been integrated with the following features:
74
- - Native TON balance checking
75
- - TON transfers using WalletContractV4
76
- - Mnemonic phrase support (TON uses 24-word mnemonics)
77
- - Integration with @ton/ton, @ton/crypto, and @ton/core libraries
78
- - Fallback RPC endpoint if @orbs-network/ton-access is unavailable
79
-
80
- **TON Features:**
81
- - Native TON balance checking and transfers ✅
82
- - Jetton (token) balance checking ✅
83
- - Jetton transfers ✅
84
- - Uses standard jetton master contract methods
85
- - Supports TEP-74 jetton standard
86
-
87
- **TON Limitations:**
88
- - Private key import not supported (use mnemonic instead)
89
- - Gas estimation uses fixed approximation
90
- - Jetton decimals assumed to be 9 (standard for most tokens)
@@ -1,14 +0,0 @@
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
- };
File without changes