hodl-wallet 1.8.4 โ 1.8.8
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/index.js +92 -38
- package/network/lib/BitcoinNetwork.js +4 -2
- package/network/lib/TONNetwork.js +66 -22
- package/network/lib/Web3Network.js +6 -3
- package/package.json +11 -2
- package/test/README.md +65 -0
- package/test/TESTING.md +199 -0
- package/test/TEST_RESULTS.md +132 -0
- package/test/test-all.js +224 -0
- package/test/test-integration.js +234 -0
- package/test/test.js +454 -0
- package/.claude/settings.local.json +0 -10
- package/CLAUDE.md +0 -90
package/index.js
CHANGED
|
@@ -8,7 +8,6 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
import { dirname } from 'path';
|
|
9
9
|
import Table from 'cli-table3';
|
|
10
10
|
import os from 'os';
|
|
11
|
-
import inquirerFuzzyPath from 'inquirer-fuzzy-path';
|
|
12
11
|
import ora from 'ora';
|
|
13
12
|
|
|
14
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -16,7 +15,6 @@ const __dirname = dirname(__filename);
|
|
|
16
15
|
|
|
17
16
|
import inquirerAutocomplete from 'inquirer-autocomplete-prompt';
|
|
18
17
|
inquirer.registerPrompt('autocomplete', inquirerAutocomplete);
|
|
19
|
-
inquirer.registerPrompt('fuzzypath', inquirerFuzzyPath);
|
|
20
18
|
|
|
21
19
|
process.on('SIGINT', () => {
|
|
22
20
|
process.exit();
|
|
@@ -39,20 +37,20 @@ class Wallet {
|
|
|
39
37
|
|
|
40
38
|
formatAmount(num) {
|
|
41
39
|
num = parseFloat(num);
|
|
42
|
-
|
|
40
|
+
|
|
43
41
|
// Handle integers - add .00
|
|
44
42
|
if (num === Math.floor(num)) {
|
|
45
43
|
return num.toString() + '.00';
|
|
46
44
|
}
|
|
47
|
-
|
|
45
|
+
|
|
48
46
|
// For decimals, format to max 3 decimal places, then remove trailing zeros
|
|
49
47
|
let formatted = num.toFixed(3);
|
|
50
|
-
|
|
48
|
+
|
|
51
49
|
// Remove trailing zeros, but keep at least 2 decimal places
|
|
52
50
|
while (formatted.endsWith('0') && formatted.split('.')[1].length > 2) {
|
|
53
51
|
formatted = formatted.slice(0, -1);
|
|
54
52
|
}
|
|
55
|
-
|
|
53
|
+
|
|
56
54
|
return formatted;
|
|
57
55
|
}
|
|
58
56
|
|
|
@@ -84,6 +82,9 @@ class Wallet {
|
|
|
84
82
|
}
|
|
85
83
|
|
|
86
84
|
setAccount(account) {
|
|
85
|
+
if (!account) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
87
88
|
this.db.set('account', this.network.constructor.name, account);
|
|
88
89
|
if (account.mnemonic) {
|
|
89
90
|
this.db.set('mnemonic', account.mnemonic);
|
|
@@ -92,8 +93,8 @@ class Wallet {
|
|
|
92
93
|
|
|
93
94
|
async getAccount() {
|
|
94
95
|
const account = this.db.get('account', this.network.constructor.name);
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
|
|
97
|
+
|
|
97
98
|
return account;
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -113,7 +114,7 @@ class Wallet {
|
|
|
113
114
|
head: ['green']
|
|
114
115
|
}
|
|
115
116
|
});
|
|
116
|
-
|
|
117
|
+
|
|
117
118
|
table.push([await this.getAddress()]);
|
|
118
119
|
console.log(table.toString());
|
|
119
120
|
}
|
|
@@ -135,18 +136,18 @@ class Wallet {
|
|
|
135
136
|
const sortedNetworks = networkPlugins.sort((a, b) => {
|
|
136
137
|
const aUsage = this.networkUsage[a.name];
|
|
137
138
|
const bUsage = this.networkUsage[b.name];
|
|
138
|
-
|
|
139
|
+
|
|
139
140
|
// Handle old format (number) vs new format (object)
|
|
140
141
|
const aLastUsed = typeof aUsage === 'object' ? aUsage.lastUsed || 0 : 0;
|
|
141
142
|
const bLastUsed = typeof bUsage === 'object' ? bUsage.lastUsed || 0 : 0;
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
// If neither has lastUsed timestamp, sort by old count format
|
|
144
145
|
if (aLastUsed === 0 && bLastUsed === 0) {
|
|
145
146
|
const aCount = typeof aUsage === 'number' ? aUsage : (aUsage?.count || 0);
|
|
146
147
|
const bCount = typeof bUsage === 'number' ? bUsage : (bUsage?.count || 0);
|
|
147
148
|
return bCount - aCount;
|
|
148
149
|
}
|
|
149
|
-
|
|
150
|
+
|
|
150
151
|
return bLastUsed - aLastUsed;
|
|
151
152
|
});
|
|
152
153
|
|
|
@@ -169,19 +170,19 @@ class Wallet {
|
|
|
169
170
|
// Update usage info for the selected network
|
|
170
171
|
// Handle migration from old format (number) to new format (object)
|
|
171
172
|
const currentUsage = this.networkUsage[selectedNetwork.name];
|
|
172
|
-
|
|
173
|
+
|
|
173
174
|
if (!currentUsage || typeof currentUsage === 'number') {
|
|
174
175
|
// Old format (number) or doesn't exist - create new object
|
|
175
|
-
this.networkUsage[selectedNetwork.name] = {
|
|
176
|
-
count: typeof currentUsage === 'number' ? currentUsage + 1 : 1,
|
|
177
|
-
lastUsed: Date.now()
|
|
176
|
+
this.networkUsage[selectedNetwork.name] = {
|
|
177
|
+
count: typeof currentUsage === 'number' ? currentUsage + 1 : 1,
|
|
178
|
+
lastUsed: Date.now()
|
|
178
179
|
};
|
|
179
180
|
} else {
|
|
180
181
|
// New format (object) - update values
|
|
181
182
|
this.networkUsage[selectedNetwork.name].count = (currentUsage.count || 0) + 1;
|
|
182
183
|
this.networkUsage[selectedNetwork.name].lastUsed = Date.now();
|
|
183
184
|
}
|
|
184
|
-
|
|
185
|
+
|
|
185
186
|
await this.db.set('networkUsage', this.networkUsage);
|
|
186
187
|
|
|
187
188
|
this.selectedNetwork = selectedNetwork;
|
|
@@ -199,9 +200,9 @@ class Wallet {
|
|
|
199
200
|
mainChoices.push('Manage Address Book');
|
|
200
201
|
mainChoices.push('Go Back');
|
|
201
202
|
} else {
|
|
202
|
-
mainChoices.push('Import Mnemonic (12 words)');
|
|
203
|
-
mainChoices.push('Import Private-key');
|
|
204
203
|
mainChoices.push('Import HODL File');
|
|
204
|
+
mainChoices.push('Import Mnemonic (12 or 24 words)');
|
|
205
|
+
mainChoices.push('Import Private-key');
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
if (!account || loggedIn) {
|
|
@@ -238,7 +239,7 @@ class Wallet {
|
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
if (accountAction === 'Import Options') {
|
|
241
|
-
const importChoices = ['Import Mnemonic (12 words)', 'Import Private-key', '
|
|
242
|
+
const importChoices = ['Import HODL File', 'Import Mnemonic (12 or 24 words)', 'Import Private-key', 'Go Back'];
|
|
242
243
|
const { importAction } = await inquirer.prompt({
|
|
243
244
|
type: 'list',
|
|
244
245
|
name: 'importAction',
|
|
@@ -257,7 +258,7 @@ class Wallet {
|
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
switch (accountAction) {
|
|
260
|
-
case 'Import Mnemonic (12 words)':
|
|
261
|
+
case 'Import Mnemonic (12 or 24 words)':
|
|
261
262
|
account = await this.importFromMnemonic();
|
|
262
263
|
if (!account) {
|
|
263
264
|
// Wallet.displayError('Invalid mnemonic.');
|
|
@@ -270,13 +271,16 @@ class Wallet {
|
|
|
270
271
|
await this.importPrivateKey();
|
|
271
272
|
break;
|
|
272
273
|
case 'Import HODL File':
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
const importedAccount = await this.importHODLFile();
|
|
275
|
+
if (importedAccount) {
|
|
276
|
+
this.setAccount(importedAccount);
|
|
277
|
+
await this.displayAccountAddress();
|
|
278
|
+
}
|
|
275
279
|
break;
|
|
276
280
|
}
|
|
277
281
|
|
|
278
282
|
if (accountAction === 'Export Options') {
|
|
279
|
-
const exportChoices = ['Export
|
|
283
|
+
const exportChoices = ['Export HODL File', 'Export Private-key', 'Go Back'];
|
|
280
284
|
const { exportAction } = await inquirer.prompt({
|
|
281
285
|
type: 'list',
|
|
282
286
|
name: 'exportAction',
|
|
@@ -335,7 +339,7 @@ class Wallet {
|
|
|
335
339
|
const { mnemonic } = await inquirer.prompt({
|
|
336
340
|
type: 'password',
|
|
337
341
|
name: 'mnemonic',
|
|
338
|
-
message: 'Enter your 12
|
|
342
|
+
message: 'Enter your mnemonic phrase (12 or 24 words):',
|
|
339
343
|
mask: '*',
|
|
340
344
|
validate: (input) => {
|
|
341
345
|
if (input.trim() === '') return true;
|
|
@@ -468,17 +472,17 @@ class Wallet {
|
|
|
468
472
|
let currentBalance = 0;
|
|
469
473
|
try {
|
|
470
474
|
const walletAddress = await this.getAddress();
|
|
471
|
-
|
|
475
|
+
|
|
472
476
|
// Get the balance AFTER transaction (not before)
|
|
473
477
|
if (token === this.selectedNetwork.nativeToken) {
|
|
474
478
|
currentBalance = await this.network.getBalance(walletAddress);
|
|
475
479
|
} else {
|
|
476
480
|
currentBalance = await this.network.getTokenBalance(walletAddress, token);
|
|
477
481
|
}
|
|
478
|
-
|
|
482
|
+
|
|
479
483
|
// Round to avoid floating point precision issues
|
|
480
484
|
currentBalance = Math.round(currentBalance * 100000000) / 100000000;
|
|
481
|
-
|
|
485
|
+
|
|
482
486
|
} catch (error) {
|
|
483
487
|
console.error('Error getting post-transaction balance:', error.message);
|
|
484
488
|
}
|
|
@@ -726,21 +730,58 @@ class Wallet {
|
|
|
726
730
|
}
|
|
727
731
|
|
|
728
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
|
|
729
751
|
const { filePath } = await inquirer.prompt({
|
|
730
|
-
type: '
|
|
752
|
+
type: 'autocomplete',
|
|
731
753
|
name: 'filePath',
|
|
732
|
-
message: '
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
+
},
|
|
739
769
|
});
|
|
740
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
|
+
|
|
741
782
|
if (!fs.existsSync(filePath)) {
|
|
742
783
|
Wallet.displayError('File not found.');
|
|
743
|
-
return;
|
|
784
|
+
return null;
|
|
744
785
|
}
|
|
745
786
|
|
|
746
787
|
const encryptedData = fs.readFileSync(filePath, 'utf8');
|
|
@@ -751,6 +792,7 @@ class Wallet {
|
|
|
751
792
|
return this.getAccount();
|
|
752
793
|
} catch (error) {
|
|
753
794
|
Wallet.displayError('Failed to import HODL file.', 'The password is incorrect.');
|
|
795
|
+
return null;
|
|
754
796
|
}
|
|
755
797
|
}
|
|
756
798
|
|
|
@@ -805,7 +847,19 @@ class Wallet {
|
|
|
805
847
|
});
|
|
806
848
|
|
|
807
849
|
if (createWithMnemonic) {
|
|
808
|
-
|
|
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));
|
|
809
863
|
} else {
|
|
810
864
|
this.setAccount(await this.network.createAccount());
|
|
811
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
|
-
|
|
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);
|
|
@@ -86,40 +86,84 @@ export default class TONNetwork extends BaseNetwork {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
async createAccount() {
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
try {
|
|
90
|
+
const mnemonic = await this.generateMnemonic();
|
|
91
|
+
const account = await this.accountFromMnemonic(mnemonic);
|
|
92
|
+
|
|
93
|
+
// Ensure all required fields are present
|
|
94
|
+
if (!account.address || !account.privateKey) {
|
|
95
|
+
throw new Error('Failed to create complete account');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return account;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Error creating new account:', error.message);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
async accountFromMnemonic(mnemonic) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
try {
|
|
107
|
+
const mnemonicArray = Array.isArray(mnemonic) ? mnemonic : mnemonic.split(' ');
|
|
108
|
+
|
|
109
|
+
// Validate mnemonic first
|
|
110
|
+
if (!mnemonicValidate(mnemonicArray)) {
|
|
111
|
+
throw new Error('Invalid mnemonic phrase');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const keyPair = await mnemonicToPrivateKey(mnemonicArray);
|
|
115
|
+
const wallet = WalletContractV4.create({
|
|
116
|
+
publicKey: keyPair.publicKey,
|
|
117
|
+
workchain: 0
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const account = {
|
|
121
|
+
address: wallet.address.toString(),
|
|
122
|
+
publicKey: keyPair.publicKey,
|
|
123
|
+
// Store secretKey as hex string to avoid Buffer serialization issues
|
|
124
|
+
secretKey: keyPair.secretKey.toString('hex'),
|
|
125
|
+
privateKey: keyPair.secretKey.toString('hex'), // Add privateKey for compatibility
|
|
126
|
+
mnemonic: Array.isArray(mnemonic) ? mnemonic.join(' ') : mnemonic
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return account;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('Error creating account from mnemonic:', error.message);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
108
134
|
}
|
|
109
135
|
|
|
110
|
-
async createAccountFromMnemonic() {
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
async createAccountFromMnemonic(wordCount = 12) {
|
|
137
|
+
try {
|
|
138
|
+
const mnemonic = await this.generateMnemonic(wordCount);
|
|
139
|
+
const account = await this.accountFromMnemonic(mnemonic);
|
|
140
|
+
|
|
141
|
+
// Ensure all required fields are present including mnemonic
|
|
142
|
+
if (!account.address || !account.privateKey || !account.mnemonic) {
|
|
143
|
+
throw new Error('Failed to create complete account with mnemonic');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return account;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('Error creating account with mnemonic:', error.message);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
113
151
|
}
|
|
114
152
|
|
|
115
|
-
async generateMnemonic() {
|
|
116
|
-
const mnemonic = await mnemonicNew();
|
|
153
|
+
async generateMnemonic(wordCount = 12) {
|
|
154
|
+
const mnemonic = await mnemonicNew(wordCount);
|
|
117
155
|
return mnemonic.join(' ');
|
|
118
156
|
}
|
|
119
157
|
|
|
120
158
|
validateMnemonic(mnemonic) {
|
|
121
159
|
try {
|
|
122
|
-
|
|
160
|
+
const mnemonicArray = mnemonic.split(' ');
|
|
161
|
+
// Check if it has exactly 12 or 24 words
|
|
162
|
+
if (mnemonicArray.length !== 12 && mnemonicArray.length !== 24) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
// Use TON's built-in validation
|
|
166
|
+
return mnemonicValidate(mnemonicArray);
|
|
123
167
|
} catch {
|
|
124
168
|
return false;
|
|
125
169
|
}
|
|
@@ -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:
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "1.8.8",
|
|
4
4
|
"description": "๐ง HODL Wallet - Fast CLI crypto wallet!",
|
|
5
5
|
"author": "Martin Clasen",
|
|
6
6
|
"repository": {
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"start": "node ./index.js",
|
|
21
|
-
"test": "
|
|
21
|
+
"test": "node ./test/test-all.js",
|
|
22
|
+
"test:network": "node ./test/test.js",
|
|
23
|
+
"test:integration": "node ./test/test-integration.js",
|
|
24
|
+
"test:quick": "node ./test/test.js --quick"
|
|
22
25
|
},
|
|
23
26
|
"keywords": [
|
|
24
27
|
"hodl",
|
|
@@ -60,12 +63,18 @@
|
|
|
60
63
|
"crypto-js": "^4.2.0",
|
|
61
64
|
"deepbase": "^1.5.2",
|
|
62
65
|
"ecpair": "^2.1.0",
|
|
66
|
+
"external-editor": "^3.1.0",
|
|
63
67
|
"hdkey": "^2.1.0",
|
|
64
68
|
"inquirer": "^9.3.7",
|
|
65
69
|
"inquirer-autocomplete-prompt": "^3.0.1",
|
|
66
70
|
"inquirer-fuzzy-path": "^2.3.0",
|
|
71
|
+
"ora": "^8.1.1",
|
|
67
72
|
"tiny-secp256k1": "^2.2.3",
|
|
73
|
+
"tmp": "^0.2.4",
|
|
68
74
|
"web3": "^4.14.0"
|
|
69
75
|
},
|
|
76
|
+
"overrides": {
|
|
77
|
+
"tmp": "^0.2.4"
|
|
78
|
+
},
|
|
70
79
|
"type": "module"
|
|
71
80
|
}
|
package/test/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# ๐งช Test Directory - HODL Wallet
|
|
2
|
+
|
|
3
|
+
This directory contains the complete test suite to validate the functionality of the HODL Wallet library.
|
|
4
|
+
|
|
5
|
+
## ๐ File Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
test/
|
|
9
|
+
โโโ test.js # Main network tests
|
|
10
|
+
โโโ test-integration.js # Cross-network integration tests
|
|
11
|
+
โโโ test-all.js # Complete runner with interactive menu
|
|
12
|
+
โโโ TESTING.md # Complete testing documentation
|
|
13
|
+
โโโ TEST_RESULTS.md # Latest execution results
|
|
14
|
+
โโโ README.md # This file
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## ๐ Quick Start
|
|
18
|
+
|
|
19
|
+
### Run all tests:
|
|
20
|
+
```bash
|
|
21
|
+
npm test
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Run specific tests:
|
|
25
|
+
```bash
|
|
26
|
+
npm run test:network # Network tests only
|
|
27
|
+
npm run test:integration # Integration tests only
|
|
28
|
+
npm run test:quick # Quick validation
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## ๐ Current Status
|
|
32
|
+
|
|
33
|
+
- **โ
9/9 networks fully functional** (100% success)
|
|
34
|
+
- **โ
79 individual tests**
|
|
35
|
+
- **โ
Complete functionality coverage**
|
|
36
|
+
|
|
37
|
+
### Validated Networks:
|
|
38
|
+
- โ
Ethereum (ETH)
|
|
39
|
+
- โ
Binance Smart Chain (BNB)
|
|
40
|
+
- โ
Arbitrum One (ARB)
|
|
41
|
+
- โ
Avalanche C-Chain (AVAX)
|
|
42
|
+
- โ
Fantom (FTM)
|
|
43
|
+
- โ
Optimism (OP)
|
|
44
|
+
- โ
Polygon (MATIC)
|
|
45
|
+
- โ
Bitcoin (BTC)
|
|
46
|
+
- โ
TON (fully fixed and functional)
|
|
47
|
+
|
|
48
|
+
## ๐ Documentation
|
|
49
|
+
|
|
50
|
+
For detailed information on how to use the tests, see:
|
|
51
|
+
- **[TESTING.md](./TESTING.md)** - Complete testing guide
|
|
52
|
+
- **[TEST_RESULTS.md](./TEST_RESULTS.md)** - Detailed results
|
|
53
|
+
|
|
54
|
+
## ๐ฏ Purpose
|
|
55
|
+
|
|
56
|
+
This test suite validates:
|
|
57
|
+
1. **Correct configuration** of all networks
|
|
58
|
+
2. **Complete implementation** of required methods
|
|
59
|
+
3. **Consistency between networks** EVM
|
|
60
|
+
4. **Account functionality** (creation, import)
|
|
61
|
+
5. **Network connectivity** and validations
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
*Keep this directory updated when adding new networks or functionality.*
|