chroid 1.0.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.
@@ -0,0 +1,746 @@
1
+ const bitcoin = require("bitcoinjs-lib");
2
+ const { ECPairFactory } = require("ecpair");
3
+ const ecc = require("tiny-secp256k1");
4
+ bitcoin.initEccLib(ecc)
5
+ const ECPair = ECPairFactory(ecc)
6
+ const crypto = require('crypto');
7
+ const {BIP32Factory} = require('bip32')
8
+ const bip39 = require('bip39')
9
+ const bip32 = BIP32Factory(ecc)
10
+
11
+ const network = bitcoin.networks.bitcoin
12
+ const MEMPOOL_URL = `https://mempool.space`
13
+
14
+ // configuration for testnet
15
+ // const network = bitcoin.networks.testnet
16
+ // const MEMPOOL_URL = `https://mempool.space/testnet`
17
+
18
+ const DUST_LIMIT = 546
19
+ const BASE_TX_SIZE = 10
20
+
21
+ const BitcoinAddressType = {
22
+ Legacy: 'legacy',
23
+ NestedSegwit: 'nested-segwit',
24
+ NativeSegwit: 'native-segwit',
25
+ Taproot: 'taproot',
26
+ Invalid: 'invalid'
27
+ }
28
+
29
+ const LEGACY_TX_INPUT_SIZE = 148
30
+ const LEGACY_TX_OUTPUT_SIZE = 34
31
+ const NESTED_SEGWIT_TX_INPUT_SIZE = 91
32
+ const NESTED_SEGWIT_TX_OUTPUT_SIZE = 31
33
+ const NATIVE_SEGWIT_TX_INPUT_SIZE = 68
34
+ const NATIVE_SEGWIT_TX_OUTPUT_SIZE = 31
35
+ const TAPROOT_TX_INPUT_SIZE = 58
36
+ const TAPROOT_TX_OUTPUT_SIZE = 43
37
+
38
+ // Function to fetch unspent transaction outputs (UTXOs) for the fromAddress
39
+ async function getUTXOs(address) {
40
+ const url = `${MEMPOOL_URL}/api/address/${address}/utxo`
41
+ const response = await fetch(url)
42
+ if (response.ok) {
43
+ const utxo_array = await response.json()
44
+ let confirmed = [], unconfirmed = []
45
+ for (const i in utxo_array)
46
+ utxo_array[i]['status']['confirmed'] ? confirmed.push(utxo_array[i]) : unconfirmed.push(utxo_array[i])
47
+ return {
48
+ success: true,
49
+ confirmed: utxo_array.filter((elem) => elem?.status?.confirmed) || [],
50
+ unconfirmed: utxo_array.filter((elem) => !elem?.status?.confirmed) || []
51
+ }
52
+ }
53
+ else {
54
+ return {
55
+ success: false,
56
+ confirmed: [],
57
+ unconfirmed: []
58
+ }
59
+ }
60
+ }
61
+
62
+ // Function to get confirmed total balance of a bitcoin address
63
+ async function getConfirmedBalanceFromAddress(address) {
64
+ const { confirmed } = await getUTXOs(address)
65
+ let totalBalance = 0
66
+ for (const i in confirmed)
67
+ totalBalance += parseInt(confirmed[i]['value'])
68
+ return totalBalance
69
+ }
70
+
71
+ // Function to get current mempool status
72
+ async function getSatsbyte() {
73
+ const url = `${MEMPOOL_URL}/api/v1/fees/recommended`
74
+ const response = await fetch(url)
75
+ if (response.ok) {
76
+ const recommendedFees = await response.json()
77
+ return {
78
+ success: true,
79
+ recommendedFees
80
+ }
81
+ }
82
+ else {
83
+ return {
84
+ success: false,
85
+ recommendedFees: {}
86
+ }
87
+ }
88
+ }
89
+
90
+ // Function to determine what type of address it is among 4 bitcoin address types
91
+ function getBitcoinAddressType(address) {
92
+ // Regular expressions for different Bitcoin address types
93
+ const legacyRegex = network === bitcoin.networks.bitcoin ? /^[1][a-km-zA-HJ-NP-Z1-9]{25,34}$/ : /^[m,n][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
94
+ const nestedSegwitRegex = network === bitcoin.networks.bitcoin ? /^[3][a-km-zA-HJ-NP-Z1-9]{25,34}$/ : /^[2][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
95
+ const nativeSegwitRegex = network === bitcoin.networks.bitcoin ? /^(bc1q)[0-9a-z]{35,59}$/ : /^(tb1q)[0-9a-z]{35,59}$/;
96
+ const taprootRegex = network === bitcoin.networks.bitcoin ? /^(bc1p)[0-9a-z]{39,59}$/ : /^(tb1p)[0-9a-z]{39,59}$/;
97
+
98
+ if (legacyRegex.test(address)) {
99
+ return BitcoinAddressType.Legacy;
100
+ } else if (nestedSegwitRegex.test(address)) {
101
+ return BitcoinAddressType.NestedSegwit;
102
+ } else if (nativeSegwitRegex.test(address)) {
103
+ return BitcoinAddressType.NativeSegwit;
104
+ } else if (taprootRegex.test(address)) {
105
+ return BitcoinAddressType.Taproot;
106
+ } else {
107
+ return BitcoinAddressType.Invalid;
108
+ }
109
+ }
110
+
111
+ function getAddressFromWIFandType(wif, type) {
112
+ const keyPair = ECPair.fromWIF(wif);
113
+
114
+ if (type === BitcoinAddressType.Legacy)
115
+ return bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network }).address
116
+ else if (type === BitcoinAddressType.NestedSegwit)
117
+ return bitcoin.payments.p2sh({
118
+ redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network }),
119
+ network
120
+ }).address;
121
+ else if (type === BitcoinAddressType.NativeSegwit)
122
+ return bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network }).address
123
+ else if (type === BitcoinAddressType.Taproot)
124
+ return bitcoin.payments.p2tr({
125
+ internalPubkey: toXOnly(keyPair.publicKey),
126
+ network
127
+ }).address;
128
+ else
129
+ return "invalid"
130
+ }
131
+
132
+ function toXOnly (publicKey) {
133
+ return publicKey.slice(1, 33);
134
+ }
135
+
136
+ function getKeypairInfo (childNode) {
137
+ const childNodeXOnlyPubkey = toXOnly(childNode.publicKey);
138
+
139
+ const { address, output } = bitcoin.payments.p2tr({
140
+ internalPubkey: childNodeXOnlyPubkey,
141
+ network
142
+ });
143
+
144
+ const tweakedChildNode = childNode.tweak(
145
+ bitcoin.crypto.taggedHash('TapTweak', childNodeXOnlyPubkey),
146
+ );
147
+
148
+ return {
149
+ address,
150
+ tweakedChildNode,
151
+ childNodeXOnlyPubkey,
152
+ output,
153
+ childNode
154
+ }
155
+ }
156
+
157
+ // Function to estimate transaction size from input utxos and output utxos and the address type
158
+ function estimateTransactionSize(numInputs, numOutputs, type) {
159
+ let inputSize, outputSize
160
+
161
+ switch (type) {
162
+ case BitcoinAddressType.Legacy:
163
+ inputSize = LEGACY_TX_INPUT_SIZE;
164
+ outputSize = LEGACY_TX_OUTPUT_SIZE;
165
+ break;
166
+ case BitcoinAddressType.NestedSegwit:
167
+ inputSize = NESTED_SEGWIT_TX_INPUT_SIZE;
168
+ outputSize = NESTED_SEGWIT_TX_OUTPUT_SIZE;
169
+ break;
170
+ case BitcoinAddressType.NativeSegwit:
171
+ inputSize = NATIVE_SEGWIT_TX_INPUT_SIZE;
172
+ outputSize = NATIVE_SEGWIT_TX_OUTPUT_SIZE;
173
+ break;
174
+ case BitcoinAddressType.Taproot:
175
+ inputSize = TAPROOT_TX_INPUT_SIZE;
176
+ outputSize = TAPROOT_TX_OUTPUT_SIZE;
177
+ break;
178
+ default:
179
+ throw new Error('Unknown transaction type');
180
+ }
181
+
182
+ return BASE_TX_SIZE + (numInputs * inputSize) + (numOutputs * outputSize);
183
+ }
184
+
185
+ function estimateTransactionFee(numInputs, numOutputs, type, feeRate) {
186
+ const txSize = estimateTransactionSize(numInputs, numOutputs, type);
187
+ return txSize * feeRate;
188
+ }
189
+
190
+ async function getTransactionDetailFromTxID(txid) {
191
+ const url = `${MEMPOOL_URL}/api/tx/${txid}/hex`
192
+ try {
193
+ const response = await fetch(url)
194
+ if (response.ok) {
195
+ const hex = await response.text()
196
+ const txDetail = bitcoin.Transaction.fromHex(hex)
197
+ return {
198
+ hex,
199
+ txDetail
200
+ }
201
+ }
202
+ } catch (error) {
203
+ console.log(error)
204
+ }
205
+ return {
206
+ hex: "",
207
+ txDetail: {}
208
+ }
209
+ }
210
+
211
+ // Function to send bitcoin from one address to another
212
+ // returns the txid when success, error msg when error
213
+ async function sendBtc(fromAddressPair, toAddress, amountInSats, satsbyte) {
214
+
215
+ // validate address types
216
+ const { address: fromAddress, wif: fromWIF } = fromAddressPair
217
+ const fromAddressType = getBitcoinAddressType(fromAddress)
218
+ if (fromAddressType === BitcoinAddressType.Invalid)
219
+ throw "Invalid Source Address"
220
+
221
+ const toAddressType = getBitcoinAddressType(toAddress)
222
+ if (toAddressType === BitcoinAddressType.Invalid)
223
+ throw "Invalid Destination Address"
224
+
225
+ // first check if that address holds such balance
226
+ const currentBalance = await getConfirmedBalanceFromAddress(fromAddress)
227
+ if (amountInSats >= currentBalance)
228
+ throw "Insufficient balance"
229
+
230
+ // check if fromWIF matches the fromAddress
231
+ const checkingFromAddress = getAddressFromWIFandType(fromWIF, fromAddressType);
232
+ if (fromAddress !== checkingFromAddress)
233
+ throw "Source Address does not match WIF"
234
+
235
+ // now building transactions based on address types
236
+ const keyPair = ECPair.fromWIF(fromAddressPair.wif);
237
+ const keyPairInfo = getKeypairInfo(keyPair)
238
+ const { confirmed } = await getUTXOs(fromAddress)
239
+ const sortedUTXOs = confirmed.sort((a, b) => parseInt(a.value) - parseInt(b.value))
240
+
241
+ const fastestFee = satsbyte
242
+
243
+ // build transaction
244
+ const psbt = new bitcoin.Psbt({ network });
245
+ let totalInputSats = 0, inputUtxoCount = 0
246
+ let estimatedTransactionFee = estimateTransactionFee(1, 1, toAddressType, fastestFee)
247
+ let inputsAreEnough = false
248
+ for (const i in sortedUTXOs) {
249
+ const { txid, vout, value } = sortedUTXOs[i]
250
+ const { hex, txDetail } = await getTransactionDetailFromTxID(txid)
251
+ if (!hex) {
252
+ return {
253
+ success: false,
254
+ result: `cannot find proper hex for transaction ${txid}`
255
+ }
256
+ }
257
+ const input = {
258
+ hash: txid,
259
+ index: vout
260
+ }
261
+
262
+ if (fromAddressType === BitcoinAddressType.Legacy)
263
+ input.nonWitnessUtxo = Buffer.from(hex, 'hex');
264
+ if (fromAddressType === BitcoinAddressType.NestedSegwit) {
265
+ input.witnessUtxo = {
266
+ script: txDetail.outs[vout].script,
267
+ value: txDetail.outs[vout].value,
268
+ }
269
+ const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network });
270
+ input.redeemScript = p2wpkh.output
271
+ }
272
+ if (fromAddressType === BitcoinAddressType.NativeSegwit)
273
+ input.witnessUtxo = {
274
+ script: txDetail.outs[vout].script,
275
+ value: txDetail.outs[vout].value,
276
+ };
277
+ if (fromAddressType === BitcoinAddressType.Taproot) {
278
+ input.witnessUtxo = {
279
+ script: txDetail.outs[vout].script,
280
+ value: txDetail.outs[vout].value,
281
+ };
282
+ input.tapInternalKey = keyPairInfo.childNodeXOnlyPubkey
283
+ }
284
+
285
+ psbt.addInput(input)
286
+ inputUtxoCount ++
287
+ totalInputSats += value
288
+ estimatedTransactionFee = estimateTransactionFee(inputUtxoCount, 2, toAddressType, fastestFee)
289
+
290
+ if (totalInputSats >= amountInSats + estimatedTransactionFee) {
291
+ inputsAreEnough = true
292
+ psbt.addOutput({
293
+ address: toAddress,
294
+ value: amountInSats
295
+ })
296
+ if (totalInputSats - amountInSats - estimatedTransactionFee > DUST_LIMIT) {
297
+ psbt.addOutput({
298
+ address: fromAddress,
299
+ value: totalInputSats - amountInSats - estimatedTransactionFee
300
+ })
301
+ }
302
+ break
303
+ }
304
+ }
305
+
306
+ if (!inputsAreEnough) {
307
+ return {
308
+ success: false,
309
+ result: "Input UTXOs are not enough to send..."
310
+ }
311
+ }
312
+
313
+ if (fromAddressType === BitcoinAddressType.Taproot) {
314
+ for (let i = 0; i < inputUtxoCount; i ++)
315
+ psbt.signInput(i, keyPairInfo.tweakedChildNode)
316
+ }
317
+ else {
318
+ for (let i = 0; i < inputUtxoCount; i ++)
319
+ psbt.signInput(i, keyPairInfo.childNode)
320
+ }
321
+
322
+ psbt.finalizeAllInputs()
323
+
324
+ const tx = psbt.extractTransaction()
325
+ const txHex = tx.toHex();
326
+
327
+ // broadcast the transaction
328
+ const broadcastAPI = `${MEMPOOL_URL}/api/tx`
329
+ const response = await fetch(broadcastAPI, {
330
+ method: "POST",
331
+ body: txHex,
332
+ })
333
+
334
+ if (response.ok) {
335
+ const transactionId = await response.text()
336
+ return {
337
+ success: true,
338
+ result: transactionId
339
+ }
340
+ }
341
+
342
+ throw 'Error while broadcast...'
343
+ }
344
+
345
+ async function sendBtcFromMultiSig(fromAddress, pwds, toAddress, amountInSats, satsbyte) {
346
+ const toAddressType = getBitcoinAddressType(toAddress)
347
+ if (toAddressType === BitcoinAddressType.Invalid)
348
+ throw "Invalid Destination Address"
349
+
350
+ // first check if that address holds such balance
351
+ const currentBalance = await getConfirmedBalanceFromAddress(fromAddress)
352
+ if (amountInSats >= currentBalance)
353
+ throw "Insufficient balance"
354
+
355
+ const mnemonic0 = generateMnemonicFromPassword(pwds[0])
356
+ const wif0 = await getWIFFromMnemonic(mnemonic0)
357
+ const keyPair0 = ECPair.fromWIF(wif0);
358
+ const mnemonic1 = generateMnemonicFromPassword(pwds[1])
359
+ const wif1 = await getWIFFromMnemonic(mnemonic1)
360
+ const keyPair1 = ECPair.fromWIF(wif1);
361
+ const mnemonic2 = generateMnemonicFromPassword(pwds[2])
362
+ const wif2 = await getWIFFromMnemonic(mnemonic2)
363
+ const keyPair2 = ECPair.fromWIF(wif2);
364
+
365
+ const keyPairs = [keyPair0, keyPair1, keyPair2]
366
+ const pubkeys = keyPairs.map(kp => kp.publicKey)
367
+ const p2ms = bitcoin.payments.p2ms({m: 3, pubkeys, network})
368
+ const p2sh = bitcoin.payments.p2sh({redeem: p2ms, network})
369
+
370
+ // now building transactions based on address types
371
+ const { confirmed } = await getUTXOs(fromAddress)
372
+ const sortedUTXOs = confirmed.sort((a, b) => parseInt(a.value) - parseInt(b.value))
373
+
374
+ const fastestFee = satsbyte * 4
375
+
376
+ // build transaction
377
+ const psbt = new bitcoin.Psbt({ network });
378
+ let totalInputSats = 0, inputUtxoCount = 0
379
+ let estimatedTransactionFee = estimateTransactionFee(1, 1, toAddressType, fastestFee)
380
+ let inputsAreEnough = false
381
+ for (const i in sortedUTXOs) {
382
+ if (inputsAreEnough)
383
+ break;
384
+ const { txid, vout, value } = sortedUTXOs[i]
385
+ const { hex } = await getTransactionDetailFromTxID(txid)
386
+ const input = {
387
+ hash: txid,
388
+ index: vout,
389
+ nonWitnessUtxo: Buffer.from(hex, 'hex'),
390
+ redeemScript: p2sh.redeem.output
391
+ }
392
+
393
+ psbt.addInput(input)
394
+ inputUtxoCount ++
395
+ totalInputSats += value
396
+ estimatedTransactionFee = estimateTransactionFee(inputUtxoCount, 2, toAddressType, fastestFee)
397
+
398
+ if (totalInputSats >= amountInSats + estimatedTransactionFee) {
399
+ inputsAreEnough = true
400
+ psbt.addOutput({
401
+ address: toAddress,
402
+ value: amountInSats
403
+ })
404
+ if (totalInputSats - amountInSats - estimatedTransactionFee > DUST_LIMIT)
405
+ psbt.addOutput({
406
+ address: fromAddress,
407
+ value: totalInputSats - amountInSats - estimatedTransactionFee
408
+ })
409
+ }
410
+ }
411
+
412
+ if (!inputsAreEnough) {
413
+ return {
414
+ success: false,
415
+ result: "Input UTXOs are not enough to send..."
416
+ }
417
+ }
418
+
419
+ for (let i = 0; i < inputUtxoCount; i ++) {
420
+ psbt.signInput(i, keyPair0)
421
+ psbt.signInput(i, keyPair1)
422
+ psbt.signInput(i, keyPair2)
423
+ }
424
+ psbt.finalizeAllInputs()
425
+
426
+ const tx = psbt.extractTransaction()
427
+ const txHex = tx.toHex();
428
+
429
+ // broadcast the transaction
430
+ const broadcastAPI = `${MEMPOOL_URL}/api/tx`
431
+ const response = await fetch(broadcastAPI, {
432
+ method: "POST",
433
+ body: txHex,
434
+ })
435
+
436
+ if (response.ok) {
437
+ const transactionId = await response.text()
438
+ return {
439
+ success: true,
440
+ result: transactionId
441
+ }
442
+ }
443
+
444
+ throw 'Error while broadcast...'
445
+ }
446
+
447
+ async function drainMultiSigBtc(fromAddress, pwds, toAddress, satsbyte) {
448
+ const toAddressType = getBitcoinAddressType(toAddress)
449
+ if (toAddressType === BitcoinAddressType.Invalid)
450
+ throw "Invalid Destination Address"
451
+
452
+ // first check if that address holds such balance
453
+ const currentBalance = await getConfirmedBalanceFromAddress(fromAddress)
454
+
455
+ const mnemonic0 = generateMnemonicFromPassword(pwds[0])
456
+ const wif0 = await getWIFFromMnemonic(mnemonic0)
457
+ const keyPair0 = ECPair.fromWIF(wif0);
458
+ const mnemonic1 = generateMnemonicFromPassword(pwds[1])
459
+ const wif1 = await getWIFFromMnemonic(mnemonic1)
460
+ const keyPair1 = ECPair.fromWIF(wif1);
461
+ const mnemonic2 = generateMnemonicFromPassword(pwds[2])
462
+ const wif2 = await getWIFFromMnemonic(mnemonic2)
463
+ const keyPair2 = ECPair.fromWIF(wif2);
464
+
465
+ const keyPairs = [keyPair0, keyPair1, keyPair2]
466
+ const pubkeys = keyPairs.map(kp => kp.publicKey)
467
+ const p2ms = bitcoin.payments.p2ms({m: 3, pubkeys, network})
468
+ const p2sh = bitcoin.payments.p2sh({redeem: p2ms, network})
469
+
470
+ // now building transactions based on address types
471
+ const { confirmed } = await getUTXOs(fromAddress)
472
+ const sortedUTXOs = confirmed.sort((a, b) => parseInt(a.value) - parseInt(b.value))
473
+
474
+ const fastestFee = satsbyte * 4
475
+
476
+ // build transaction
477
+ const psbt = new bitcoin.Psbt({ network });
478
+ let totalInputSats = 0, inputUtxoCount = 0
479
+ let estimatedTransactionFee = estimateTransactionFee(1, 1, toAddressType, fastestFee)
480
+ let inputsAreEnough = false
481
+ for (const i in sortedUTXOs) {
482
+ const { txid, vout, value } = sortedUTXOs[i]
483
+ const { hex } = await getTransactionDetailFromTxID(txid)
484
+ const input = {
485
+ hash: txid,
486
+ index: vout,
487
+ nonWitnessUtxo: Buffer.from(hex, 'hex'),
488
+ redeemScript: p2sh.redeem.output
489
+ }
490
+
491
+ psbt.addInput(input)
492
+ inputUtxoCount ++
493
+ totalInputSats += value
494
+ estimatedTransactionFee = estimateTransactionFee(inputUtxoCount, 1, toAddressType, fastestFee)
495
+
496
+ }
497
+
498
+ psbt.addOutput({
499
+ address: toAddress,
500
+ value: totalInputSats - estimatedTransactionFee
501
+ })
502
+
503
+ for (let i = 0; i < inputUtxoCount; i ++) {
504
+ psbt.signInput(i, keyPair0)
505
+ psbt.signInput(i, keyPair1)
506
+ psbt.signInput(i, keyPair2)
507
+ }
508
+ psbt.finalizeAllInputs()
509
+
510
+ const tx = psbt.extractTransaction()
511
+ const txHex = tx.toHex();
512
+
513
+ // broadcast the transaction
514
+ const broadcastAPI = `${MEMPOOL_URL}/api/tx`
515
+ const response = await fetch(broadcastAPI, {
516
+ method: "POST",
517
+ body: txHex,
518
+ })
519
+
520
+ if (response.ok) {
521
+ const transactionId = await response.text()
522
+ return {
523
+ success: true,
524
+ result: transactionId
525
+ }
526
+ }
527
+
528
+ throw 'Error while broadcast...'
529
+ }
530
+
531
+ async function drainBtc(fromAddressPair, toAddress, satsbyte) {
532
+
533
+ // validate address types
534
+ const { address: fromAddress, wif: fromWIF } = fromAddressPair
535
+ const fromAddressType = getBitcoinAddressType(fromAddress)
536
+ if (fromAddressType === BitcoinAddressType.Invalid)
537
+ throw "Invalid Source Address"
538
+
539
+ const toAddressType = getBitcoinAddressType(toAddress)
540
+ if (toAddressType === BitcoinAddressType.Invalid)
541
+ throw "Invalid Destination Address"
542
+
543
+ // check if fromWIF matches the fromAddress
544
+ const checkingFromAddress = getAddressFromWIFandType(fromWIF, fromAddressType);
545
+ if (fromAddress !== checkingFromAddress)
546
+ throw "Source Address does not match WIF"
547
+
548
+ // now building transactions based on address types
549
+ const keyPair = ECPair.fromWIF(fromAddressPair.wif);
550
+ const keyPairInfo = getKeypairInfo(keyPair)
551
+ const { confirmed } = await getUTXOs(fromAddress)
552
+ const sortedUTXOs = confirmed.sort((a, b) => parseInt(a.value) - parseInt(b.value))
553
+
554
+ const fastestFee = satsbyte
555
+
556
+ // build transaction
557
+ const psbt = new bitcoin.Psbt({ network });
558
+ let totalInputSats = 0, inputUtxoCount = 0
559
+ let inputsAreEnough = false
560
+ for (const i in sortedUTXOs) {
561
+ const { txid, vout, value } = sortedUTXOs[i]
562
+ // Eric bro... better to store transaction hex on the database so that you can reduce unnecessary API calls...
563
+ const { hex, txDetail } = await getTransactionDetailFromTxID(txid)
564
+ if (!hex) {
565
+ return {
566
+ success: false,
567
+ result: `cannot find proper hex for transaction ${txid}`
568
+ }
569
+ }
570
+ const input = {
571
+ hash: txid,
572
+ index: vout
573
+ }
574
+
575
+ if (fromAddressType === BitcoinAddressType.Legacy)
576
+ input.nonWitnessUtxo = Buffer.from(hex, 'hex');
577
+ if (fromAddressType === BitcoinAddressType.NestedSegwit) {
578
+ input.witnessUtxo = {
579
+ script: txDetail.outs[vout].script,
580
+ value: txDetail.outs[vout].value,
581
+ }
582
+ const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network });
583
+ input.redeemScript = p2wpkh.output
584
+ }
585
+ if (fromAddressType === BitcoinAddressType.NativeSegwit)
586
+ input.witnessUtxo = {
587
+ script: txDetail.outs[vout].script,
588
+ value: txDetail.outs[vout].value,
589
+ };
590
+ if (fromAddressType === BitcoinAddressType.Taproot) {
591
+ input.witnessUtxo = {
592
+ script: txDetail.outs[vout].script,
593
+ value: txDetail.outs[vout].value,
594
+ };
595
+ input.tapInternalKey = keyPairInfo.childNodeXOnlyPubkey
596
+ }
597
+
598
+ psbt.addInput(input)
599
+ inputUtxoCount ++
600
+ totalInputSats += value
601
+ }
602
+
603
+ let estimatedTransactionFee = estimateTransactionFee(inputUtxoCount, 1, toAddressType, fastestFee)
604
+
605
+ psbt.addOutput({
606
+ address: toAddress,
607
+ value: totalInputSats - estimatedTransactionFee
608
+ })
609
+
610
+ if (totalInputSats <= estimatedTransactionFee + DUST_LIMIT) {
611
+ return {
612
+ success: false,
613
+ result: "Input UTXOs are not enough to send..."
614
+ }
615
+ }
616
+
617
+ if (fromAddressType === BitcoinAddressType.Taproot) {
618
+ for (let i = 0; i < inputUtxoCount; i ++)
619
+ psbt.signInput(i, keyPairInfo.tweakedChildNode)
620
+ }
621
+ else {
622
+ for (let i = 0; i < inputUtxoCount; i ++)
623
+ psbt.signInput(i, keyPairInfo.childNode)
624
+ }
625
+
626
+ psbt.finalizeAllInputs()
627
+
628
+ const tx = psbt.extractTransaction()
629
+ const txHex = tx.toHex();
630
+ // console.log(`raw transaction hex: ${txHex}`)
631
+
632
+ // broadcast the transaction
633
+ const broadcastAPI = `${MEMPOOL_URL}/api/tx`
634
+ const response = await fetch(broadcastAPI, {
635
+ method: "POST",
636
+ body: txHex,
637
+ })
638
+
639
+ if (response.ok) {
640
+ const transactionId = await response.text()
641
+ return {
642
+ success: true,
643
+ result: transactionId
644
+ }
645
+ }
646
+
647
+ throw 'Error while broadcast...'
648
+ }
649
+
650
+ function generateHash(input) {
651
+ return crypto.createHash('sha256') // Use SHA-256 algorithm
652
+ .update(input) // Hash the input string
653
+ .digest('hex'); // Output the hash in hex format
654
+ }
655
+
656
+ function generateMnemonicFromPassword (pwd) {
657
+ const hash = generateHash(pwd)
658
+ const mnemonic = bip39.entropyToMnemonic(hash)
659
+ if (!bip39.validateMnemonic(mnemonic)) {
660
+ return null
661
+ }
662
+ return mnemonic
663
+ }
664
+
665
+ // I have to add address type option so that CLI users can select Legacy/Segwit as well
666
+ // Right now, only Taproot
667
+ async function generateBitcoinAddressFromMnemonic (mnemonic) {
668
+ const seed = await bip39.mnemonicToSeed(mnemonic);
669
+ const rootKey = bip32.fromSeed(seed);
670
+ const childNode86 = rootKey.derivePath(`m/86'/1'/9'/9/8`)
671
+ const childNodeXOnlyPubkey86 = toXOnly(childNode86.publicKey);
672
+ const { address: taproot86 } = bitcoin.payments.p2tr({
673
+ internalPubkey: childNodeXOnlyPubkey86,
674
+ network: bitcoin.networks.bitcoin
675
+ });
676
+ return taproot86
677
+ }
678
+
679
+ async function getWIFFromMnemonic (mnemonic) {
680
+ const seed = await bip39.mnemonicToSeed(mnemonic);
681
+ const rootKey = bip32.fromSeed(seed);
682
+ const childNode86 = rootKey.derivePath(`m/86'/1'/9'/9/8`)
683
+ return childNode86.toWIF()
684
+ }
685
+
686
+ async function generateWalletFromPassword (pwd) {
687
+ const mnemonic = generateMnemonicFromPassword(pwd)
688
+ const wif = await getWIFFromMnemonic(mnemonic)
689
+ const address = await generateBitcoinAddressFromMnemonic(mnemonic)
690
+ return {
691
+ mnemonic,
692
+ wif,
693
+ address
694
+ }
695
+ }
696
+
697
+ async function generateMultiSigWalletFromPasswords(pwds) {
698
+ if (!pwds || pwds.length != 3) {
699
+ console.log("Cannot create maddy")
700
+ return
701
+ }
702
+ const mnemonic0 = generateMnemonicFromPassword(pwds[0])
703
+ const wif0 = await getWIFFromMnemonic(mnemonic0)
704
+ const mnemonic1 = generateMnemonicFromPassword(pwds[1])
705
+ const wif1 = await getWIFFromMnemonic(mnemonic1)
706
+ const mnemonic2 = generateMnemonicFromPassword(pwds[2])
707
+ const wif2 = await getWIFFromMnemonic(mnemonic2)
708
+ const address = await generateMultiSigBitcoinAddressFromWIFs([wif0, wif1, wif2])
709
+ return {
710
+ address
711
+ }
712
+ }
713
+
714
+ async function generateMultiSigBitcoinAddressFromWIFs(wifs) {
715
+ const keyPair0 = ECPair.fromWIF(wifs[0]);
716
+ const keyPair1 = ECPair.fromWIF(wifs[1]);
717
+ const keyPair2 = ECPair.fromWIF(wifs[2]);
718
+ const keyPairs = [keyPair0, keyPair1, keyPair2]
719
+ const pubkeys = keyPairs.map(kp => kp.publicKey)
720
+ const p2ms = bitcoin.payments.p2ms({
721
+ m: 3,
722
+ pubkeys,
723
+ network
724
+ })
725
+ const p2sh = bitcoin.payments.p2sh({
726
+ redeem: p2ms,
727
+ network
728
+ })
729
+ return p2sh.address
730
+ }
731
+
732
+ module.exports = {
733
+ getUTXOs,
734
+ getBitcoinAddressType,
735
+ getConfirmedBalanceFromAddress,
736
+ generateMnemonicFromPassword,
737
+ generateBitcoinAddressFromMnemonic,
738
+ generateWalletFromPassword,
739
+ getWIFFromMnemonic,
740
+ getSatsbyte,
741
+ sendBtc,
742
+ drainBtc,
743
+ generateMultiSigWalletFromPasswords,
744
+ sendBtcFromMultiSig,
745
+ drainMultiSigBtc
746
+ };