hodl-wallet 1.9.2 → 1.9.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 CHANGED
@@ -92,7 +92,7 @@ We've carefully selected trusted and well-maintained dependencies for this proje
92
92
  - **cli-table3**: For creating formatted CLI tables.
93
93
  - **ora**: For displaying progress bars.
94
94
  - **deepbase**: For persistent storage.
95
- - **crypto-js**: For JSON encryption.
95
+ - **node:crypto**: Native encryption for JSON storage.
96
96
  - **bip39**: For generating and handling mnemonic phrases.
97
97
  - Web3
98
98
  - **web3**: The Ethereum JavaScript API for blockchain interactions.
@@ -102,7 +102,6 @@ We've carefully selected trusted and well-maintained dependencies for this proje
102
102
  - **bip32**: For handling hierarchical deterministic (HD) keys.
103
103
  - **ecpair**: For elliptic curve pairings.
104
104
  - **tiny-secp256k1**: For elliptic curve secp256k1 operations.
105
- - **axios**: For making HTTP requests.
106
105
 
107
106
  ⚠️ **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.
108
107
 
package/network/btc.js CHANGED
@@ -5,6 +5,6 @@ export default {
5
5
  name: '[BTC] Bitcoin',
6
6
  url: 'https://blockstream.info/api',
7
7
  nativeToken: 'BTC',
8
- explorer: 'https://blockstream.info/tx/',
8
+ explorer: 'https://btcscan.org/tx/',
9
9
  tokens: {}
10
10
  };
@@ -4,7 +4,6 @@ import bip39 from 'bip39';
4
4
  import * as ecc from 'tiny-secp256k1';
5
5
  import { BIP32Factory } from 'bip32';
6
6
  import { ECPairFactory } from 'ecpair';
7
- import axios from 'axios';
8
7
 
9
8
  const bip32 = BIP32Factory(ecc);
10
9
  const ECPair = ECPairFactory(ecc);
@@ -19,9 +18,9 @@ export default class BitcoinNetwork extends BaseNetwork {
19
18
 
20
19
  async getBalance(address) {
21
20
  try {
22
- const response = await axios.get(`${this.url}/address/${address}`);
23
- const chainStats = response.data.chain_stats;
24
- const mempoolStats = response.data.mempool_stats;
21
+ const data = await this.fetchFromApi(`/address/${address}`);
22
+ const chainStats = data.chain_stats;
23
+ const mempoolStats = data.mempool_stats;
25
24
  const balance = (chainStats.funded_txo_sum - chainStats.spent_txo_sum) +
26
25
  (mempoolStats.funded_txo_sum - mempoolStats.spent_txo_sum);
27
26
  return this.satoshisToBTC(balance);
@@ -118,8 +117,7 @@ export default class BitcoinNetwork extends BaseNetwork {
118
117
  // Add this helper method to get full transaction data
119
118
  async getTransaction(txid) {
120
119
  try {
121
- const response = await axios.get(`${this.url}/tx/${txid}/hex`);
122
- return response.data;
120
+ return await this.fetchFromApi(`/tx/${txid}/hex`, {}, 'text');
123
121
  } catch (error) {
124
122
  throw new Error(`Failed to get transaction: ${error.message}`);
125
123
  }
@@ -211,8 +209,7 @@ export default class BitcoinNetwork extends BaseNetwork {
211
209
 
212
210
  async getUTXOs(address) {
213
211
  try {
214
- const response = await axios.get(`${this.url}/address/${address}/utxo`);
215
- return response.data;
212
+ return await this.fetchFromApi(`/address/${address}/utxo`);
216
213
  } catch (error) {
217
214
  throw new Error(`Failed to get UTXOs: ${error.message}`);
218
215
  }
@@ -224,10 +221,32 @@ export default class BitcoinNetwork extends BaseNetwork {
224
221
 
225
222
  async sendSignedTransaction(signedTx) {
226
223
  try {
227
- const response = await axios.post(`${this.url}/tx`, signedTx);
228
- return { transactionHash: response.data };
224
+ const txHash = await this.fetchFromApi(
225
+ '/tx',
226
+ {
227
+ method: 'POST',
228
+ headers: {
229
+ 'Content-Type': 'text/plain'
230
+ },
231
+ body: signedTx
232
+ },
233
+ 'text'
234
+ );
235
+ return { transactionHash: txHash };
229
236
  } catch (error) {
230
237
  throw new Error(`Failed to broadcast transaction: ${error.message}`);
231
238
  }
232
239
  }
240
+
241
+ async fetchFromApi(path, options = {}, responseType = 'json') {
242
+ const response = await fetch(`${this.url}${path}`, options);
243
+
244
+ if (!response.ok) {
245
+ const errorBody = await response.text();
246
+ const details = errorBody ? ` - ${errorBody}` : '';
247
+ throw new Error(`${response.status} ${response.statusText}${details}`);
248
+ }
249
+
250
+ return responseType === 'text' ? response.text() : response.json();
251
+ }
233
252
  }
package/package.json CHANGED
@@ -1,75 +1,73 @@
1
1
  {
2
- "name": "hodl-wallet",
3
- "version": "1.9.2",
4
- "description": "🧊 HODL Wallet - Fast CLI crypto wallet!",
5
- "author": "Martin Clasen",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/clasen/HODL.git"
9
- },
10
- "license": "MIT",
11
- "bugs": {
12
- "url": "https://github.com/clasen/HODL/issues"
13
- },
14
- "homepage": "https://github.com/clasen/HODL#readme",
15
- "main": "index.js",
16
- "bin": {
17
- "hodl": "index.js"
18
- },
19
- "scripts": {
20
- "start": "node ./index.js",
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"
25
- },
26
- "keywords": [
27
- "hodl",
28
- "wallet",
29
- "crypto",
30
- "ethereum",
31
- "bsc20",
32
- "bep20",
33
- "bnb",
34
- "eth",
35
- "binance",
36
- "trust",
37
- "blockchain",
38
- "bitcoin",
39
- "usdt",
40
- "stable",
41
- "coin",
42
- "evm",
43
- "polygon",
44
- "matic",
45
- "arbitrum",
46
- "fantom",
47
- "optimism",
48
- "avalanche",
49
- "avax",
50
- "clasen"
51
- ],
52
- "dependencies": {
53
- "axios": "^1.13.2",
54
- "bip32": "^4.0.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": "^0.6.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"
70
- },
71
- "overrides": {
72
- "tmp": "0.2.4"
73
- },
74
- "type": "module"
75
- }
2
+ "name": "hodl-wallet",
3
+ "version": "1.9.6",
4
+ "description": "🧊 HODL Wallet - Fast CLI crypto wallet!",
5
+ "author": "Martin Clasen",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/clasen/HODL.git"
9
+ },
10
+ "license": "MIT",
11
+ "bugs": {
12
+ "url": "https://github.com/clasen/HODL/issues"
13
+ },
14
+ "homepage": "https://github.com/clasen/HODL#readme",
15
+ "main": "index.js",
16
+ "bin": {
17
+ "hodl": "index.js"
18
+ },
19
+ "keywords": [
20
+ "hodl",
21
+ "wallet",
22
+ "crypto",
23
+ "ethereum",
24
+ "bsc20",
25
+ "bep20",
26
+ "bnb",
27
+ "eth",
28
+ "binance",
29
+ "trust",
30
+ "blockchain",
31
+ "bitcoin",
32
+ "usdt",
33
+ "stable",
34
+ "coin",
35
+ "evm",
36
+ "polygon",
37
+ "matic",
38
+ "arbitrum",
39
+ "fantom",
40
+ "optimism",
41
+ "avalanche",
42
+ "avax",
43
+ "clasen"
44
+ ],
45
+ "dependencies": {
46
+ "bip32": "^4.0.0",
47
+ "bip39": "3.1.0",
48
+ "bitcoinjs-lib": "6.1.7",
49
+ "cli-table3": "0.6.5",
50
+ "deepbase": "1.5.2",
51
+ "ecpair": "2.1.0",
52
+ "external-editor": "3.1.0",
53
+ "hdkey": "^0.6.0",
54
+ "inquirer": "9.3.7",
55
+ "inquirer-autocomplete-prompt": "3.0.1",
56
+ "inquirer-fuzzy-path": "2.3.0",
57
+ "ora": "8.1.1",
58
+ "tiny-secp256k1": "2.2.3",
59
+ "tmp": "0.2.4",
60
+ "web3": "4.14.0"
61
+ },
62
+ "overrides": {
63
+ "tmp": "0.2.4"
64
+ },
65
+ "type": "module",
66
+ "scripts": {
67
+ "start": "node ./index.js",
68
+ "test": "node ./test/test-all.js",
69
+ "test:network": "node ./test/test.js",
70
+ "test:integration": "node ./test/test-integration.js",
71
+ "test:quick": "node ./test/test.js --quick"
72
+ }
73
+ }
package/persist.js CHANGED
@@ -1,5 +1,10 @@
1
1
  import Deepbase from 'deepbase';
2
- import CryptoJS from 'crypto-js';
2
+ import crypto from 'node:crypto';
3
+
4
+ const ALGORITHM = 'aes-256-gcm';
5
+ const KEY_LENGTH = 32;
6
+ const IV_LENGTH = 12;
7
+ const SALT_LENGTH = 16;
3
8
 
4
9
  class Persist extends Deepbase {
5
10
  constructor(opts) {
@@ -10,16 +15,84 @@ class Persist extends Deepbase {
10
15
  }
11
16
 
12
17
  static encrypt(obj, encryptionKey) {
13
- const iv = CryptoJS.lib.WordArray.random(128 / 8);
14
- const encrypted = CryptoJS.AES.encrypt(JSON.stringify(obj), encryptionKey, { iv });
15
- return iv.toString(CryptoJS.enc.Hex) + ':' + encrypted.toString();
18
+ const salt = crypto.randomBytes(SALT_LENGTH);
19
+ const iv = crypto.randomBytes(IV_LENGTH);
20
+ const key = crypto.scryptSync(encryptionKey, salt, KEY_LENGTH);
21
+
22
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
23
+ const encrypted = Buffer.concat([
24
+ cipher.update(JSON.stringify(obj), 'utf8'),
25
+ cipher.final()
26
+ ]);
27
+ const authTag = cipher.getAuthTag();
28
+
29
+ return `v2:${salt.toString('hex')}:${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted.toString('hex')}`;
16
30
  }
17
31
 
18
32
  static decrypt(encryptedData, encryptionKey) {
19
- const [ivHex, encrypted] = encryptedData.split(':');
20
- const iv = CryptoJS.enc.Hex.parse(ivHex);
21
- const bytes = CryptoJS.AES.decrypt(encrypted, encryptionKey, { iv });
22
- return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
33
+ const parts = encryptedData.split(':');
34
+
35
+ if (parts[0] === 'v2' && parts.length === 5) {
36
+ const [, saltHex, ivHex, authTagHex, encryptedHex] = parts;
37
+ const salt = Buffer.from(saltHex, 'hex');
38
+ const iv = Buffer.from(ivHex, 'hex');
39
+ const authTag = Buffer.from(authTagHex, 'hex');
40
+ const encrypted = Buffer.from(encryptedHex, 'hex');
41
+ const key = crypto.scryptSync(encryptionKey, salt, KEY_LENGTH);
42
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
43
+ decipher.setAuthTag(authTag);
44
+
45
+ const decrypted = Buffer.concat([
46
+ decipher.update(encrypted),
47
+ decipher.final()
48
+ ]);
49
+
50
+ return JSON.parse(decrypted.toString('utf8'));
51
+ }
52
+
53
+ return Persist.decryptLegacy(encryptedData, encryptionKey);
54
+ }
55
+
56
+ static decryptLegacy(encryptedData, encryptionKey) {
57
+ const [, encrypted] = encryptedData.split(':');
58
+ const payload = Buffer.from(encrypted, 'base64');
59
+
60
+ const saltedPrefix = payload.subarray(0, 8).toString('utf8');
61
+ if (saltedPrefix !== 'Salted__') {
62
+ throw new Error('Unsupported encrypted payload format');
63
+ }
64
+
65
+ const salt = payload.subarray(8, 16);
66
+ const ciphertext = payload.subarray(16);
67
+ const { key, iv } = Persist.evpBytesToKey(
68
+ Buffer.from(encryptionKey, 'utf8'),
69
+ salt,
70
+ KEY_LENGTH,
71
+ 16
72
+ );
73
+
74
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
75
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
76
+ return JSON.parse(decrypted.toString('utf8'));
77
+ }
78
+
79
+ static evpBytesToKey(password, salt, keyLen, ivLen) {
80
+ let derived = Buffer.alloc(0);
81
+ let block = Buffer.alloc(0);
82
+
83
+ while (derived.length < keyLen + ivLen) {
84
+ const hash = crypto.createHash('md5');
85
+ hash.update(block);
86
+ hash.update(password);
87
+ hash.update(salt);
88
+ block = hash.digest();
89
+ derived = Buffer.concat([derived, block]);
90
+ }
91
+
92
+ return {
93
+ key: derived.subarray(0, keyLen),
94
+ iv: derived.subarray(keyLen, keyLen + ivLen)
95
+ };
23
96
  }
24
97
  }
25
98
 
@@ -0,0 +1,7 @@
1
+ minimumReleaseAgeExclude:
2
+ - tmp@0.2.4
3
+ - lodash@4.17.24
4
+ - ws@8.20.1
5
+
6
+ overrides:
7
+ tmp: 0.2.4