@btc-vision/transaction 1.0.77 → 1.0.79

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.
@@ -1,537 +1,539 @@
1
- import { Logger } from '@btc-vision/logger';
2
- import { Network, Payment, payments, Psbt, Signer, Transaction } from 'bitcoinjs-lib';
3
- import { TweakedSigner, TweakSettings } from '../../signer/TweakedSigner.js';
4
- import { ECPairInterface } from 'ecpair';
5
- import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371.js';
6
- import { PsbtInput } from 'bip174/src/lib/interfaces.js';
7
- import { UTXO } from '../../utxo/interfaces/IUTXO.js';
8
- import { PsbtInputExtended, TapLeafScript } from '../interfaces/Tap.js';
9
- import { AddressVerificator } from '../../keypair/AddressVerificator.js';
10
- import { varuint } from 'bitcoinjs-lib/src/bufferutils.js';
11
-
12
- export interface ITweakedTransactionData {
13
- readonly signer: Signer;
14
- readonly network: Network;
15
- readonly nonWitnessUtxo?: Buffer;
16
- }
17
-
18
- /**
19
- * The transaction sequence
20
- */
21
- export enum TransactionSequence {
22
- REPLACE_BY_FEE = 0xfffffffd,
23
- FINAL = 0xffffffff,
24
- }
25
-
26
- /**
27
- * @description PSBT Transaction processor.
28
- * */
29
- export abstract class TweakedTransaction extends Logger {
30
- public readonly logColor: string = '#00ffe1';
31
- public finalized: boolean = false;
32
- /**
33
- * @description Was the transaction signed?
34
- */
35
- protected signer: Signer;
36
- /**
37
- * @description Tweaked signer
38
- */
39
- protected tweakedSigner?: Signer;
40
- /**
41
- * @description The network of the transaction
42
- */
43
- protected network: Network;
44
- /**
45
- * @description Was the transaction signed?
46
- */
47
- protected signed: boolean = false;
48
- /**
49
- * @description The transaction
50
- * @protected
51
- */
52
- protected abstract readonly transaction: Psbt;
53
- /**
54
- * @description The sighash types of the transaction
55
- * @protected
56
- */
57
- protected sighashTypes: number[] | undefined;
58
- /**
59
- * @description The script data of the transaction
60
- */
61
- protected scriptData: Payment | null = null;
62
- /**
63
- * @description The tap data of the transaction
64
- */
65
- protected tapData: Payment | null = null;
66
- /**
67
- * @description The inputs of the transaction
68
- */
69
- protected readonly inputs: PsbtInputExtended[] = [];
70
- /**
71
- * @description The sequence of the transaction
72
- * @protected
73
- */
74
- protected sequence: number = TransactionSequence.REPLACE_BY_FEE;
75
- /**
76
- * The tap leaf script
77
- * @protected
78
- */
79
- protected tapLeafScript: TapLeafScript | null = null;
80
- /**
81
- * Add a non-witness utxo to the transaction
82
- * @protected
83
- */
84
- protected nonWitnessUtxo?: Buffer;
85
-
86
- /**
87
- * Is the transaction being generated inside a browser?
88
- * @protected
89
- */
90
- protected readonly isBrowser: boolean = false;
91
-
92
- protected regenerated: boolean = false;
93
- protected ignoreSignatureErrors: boolean = false;
94
-
95
- protected constructor(data: ITweakedTransactionData) {
96
- super();
97
-
98
- this.signer = data.signer;
99
- this.network = data.network;
100
-
101
- this.nonWitnessUtxo = data.nonWitnessUtxo;
102
-
103
- this.isBrowser = typeof window !== 'undefined';
104
- }
105
-
106
- /**
107
- * Read witnesses
108
- * @protected
109
- */
110
- public static readScriptWitnessToWitnessStack(buffer: Buffer): Buffer[] {
111
- let offset = 0;
112
-
113
- function readSlice(n: number): Buffer {
114
- const slice = buffer.subarray(offset, offset + n);
115
- offset += n;
116
- return slice;
117
- }
118
-
119
- function readVarInt(): number {
120
- const varint = varuint.decode(buffer, offset);
121
- offset += varuint.decode.bytes;
122
- return varint;
123
- }
124
-
125
- function readVarSlice(): Buffer {
126
- const len = readVarInt();
127
- return readSlice(len);
128
- }
129
-
130
- function readVector(): Buffer[] {
131
- const count = readVarInt();
132
- const vector = [];
133
- for (let i = 0; i < count; i++) {
134
- vector.push(readVarSlice());
135
- }
136
- return vector;
137
- }
138
-
139
- return readVector();
140
- }
141
-
142
- /**
143
- * Pre-estimate the transaction fees for a Taproot transaction
144
- * @param {bigint} feeRate - The fee rate in satoshis per virtual byte
145
- * @param {bigint} numInputs - The number of inputs
146
- * @param {bigint} numOutputs - The number of outputs
147
- * @param {bigint} numWitnessElements - The number of witness elements (e.g., number of control blocks and witnesses)
148
- * @param {bigint} witnessElementSize - The average size of each witness element in bytes
149
- * @param {bigint} emptyWitness - The amount of empty witnesses
150
- * @param {bigint} [taprootControlWitnessSize=139n] - The size of the control block witness in bytes
151
- * @param {bigint} [taprootScriptSize=32n] - The size of the taproot script in bytes
152
- * @returns {bigint} - The estimated transaction fees
153
- */
154
- public static preEstimateTaprootTransactionFees(
155
- feeRate: bigint, // satoshis per virtual byte
156
- numInputs: bigint,
157
- numOutputs: bigint,
158
- numWitnessElements: bigint,
159
- witnessElementSize: bigint,
160
- emptyWitness: bigint,
161
- taprootControlWitnessSize: bigint = 32n,
162
- taprootScriptSize: bigint = 139n,
163
- ): bigint {
164
- const txHeaderSize = 10n;
165
- const inputBaseSize = 41n;
166
- const outputSize = 68n;
167
- const taprootWitnessBaseSize = 1n; // Base witness size per input (without signatures and control blocks)
168
-
169
- // Base transaction size (excluding witness data)
170
- const baseTxSize = txHeaderSize + inputBaseSize * numInputs + outputSize * numOutputs;
171
-
172
- // Witness data size for Taproot
173
- const witnessSize =
174
- numInputs * taprootWitnessBaseSize +
175
- numWitnessElements * witnessElementSize +
176
- taprootControlWitnessSize * numInputs +
177
- taprootScriptSize * numInputs +
178
- emptyWitness;
179
-
180
- // Total weight and virtual size
181
- const weight = baseTxSize * 3n + (baseTxSize + witnessSize);
182
- const vSize = weight / 4n;
183
-
184
- return vSize * feeRate;
185
- }
186
-
187
- protected static signInput(
188
- transaction: Psbt,
189
- input: PsbtInput,
190
- i: number,
191
- signer: Signer,
192
- sighashTypes: number[],
193
- ): void {
194
- if (sighashTypes && sighashTypes[0]) input.sighashType = sighashTypes[0];
195
-
196
- transaction.signInput(i, signer, sighashTypes.length ? sighashTypes : undefined);
197
- }
198
-
199
- /**
200
- * Calculate the sign hash number
201
- * @description Calculates the sign hash
202
- * @protected
203
- * @returns {number}
204
- */
205
- protected static calculateSignHash(sighashTypes: number[]): number {
206
- if (!sighashTypes) {
207
- throw new Error('Sighash types are required');
208
- }
209
-
210
- let signHash: number = 0;
211
- for (let sighashType of sighashTypes) {
212
- signHash |= sighashType;
213
- }
214
-
215
- return signHash || 0;
216
- }
217
-
218
- public ignoreSignatureError(): void {
219
- this.ignoreSignatureErrors = true;
220
- }
221
-
222
- /**
223
- * @description Returns the script address
224
- * @returns {string}
225
- */
226
- public getScriptAddress(): string {
227
- if (!this.scriptData || !this.scriptData.address) {
228
- throw new Error('Tap data is required');
229
- }
230
-
231
- return this.scriptData.address;
232
- }
233
-
234
- /**
235
- * @description Returns the transaction
236
- * @returns {Transaction}
237
- */
238
- public getTransaction(): Transaction {
239
- return this.transaction.extractTransaction(false);
240
- }
241
-
242
- /**
243
- * @description Returns the tap address
244
- * @returns {string}
245
- * @throws {Error} - If tap data is not set
246
- */
247
- public getTapAddress(): string {
248
- if (!this.tapData || !this.tapData.address) {
249
- throw new Error('Tap data is required');
250
- }
251
-
252
- return this.tapData.address;
253
- }
254
-
255
- /**
256
- * @description Disables replace by fee on the transaction
257
- */
258
- public disableRBF(): void {
259
- if (this.signed) throw new Error('Transaction is already signed');
260
-
261
- this.sequence = TransactionSequence.FINAL;
262
-
263
- for (let input of this.inputs) {
264
- input.sequence = TransactionSequence.FINAL;
265
- }
266
- }
267
-
268
- /**
269
- * Get the tweaked hash
270
- * @private
271
- *
272
- * @returns {Buffer | undefined} The tweaked hash
273
- */
274
- public getTweakerHash(): Buffer | undefined {
275
- return this.tapData?.hash;
276
- }
277
-
278
- /**
279
- * Pre-estimate the transaction fees
280
- * @param {bigint} feeRate - The fee rate
281
- * @param {bigint} numInputs - The number of inputs
282
- * @param {bigint} numOutputs - The number of outputs
283
- * @param {bigint} numSignatures - The number of signatures
284
- * @param {bigint} numPubkeys - The number of public keys
285
- * @returns {bigint} - The estimated transaction fees
286
- */
287
- public preEstimateTransactionFees(
288
- feeRate: bigint, // satoshis per byte
289
- numInputs: bigint,
290
- numOutputs: bigint,
291
- numSignatures: bigint,
292
- numPubkeys: bigint,
293
- ): bigint {
294
- const txHeaderSize = 10n;
295
- const inputBaseSize = 41n;
296
- const outputSize = 68n;
297
- const signatureSize = 144n;
298
- const pubkeySize = 34n;
299
-
300
- // Base transaction size (excluding witness data)
301
- const baseTxSize = txHeaderSize + inputBaseSize * numInputs + outputSize * numOutputs;
302
-
303
- // Witness data size
304
- const redeemScriptSize = 1n + numPubkeys * (1n + pubkeySize) + 1n + numSignatures;
305
- const witnessSize =
306
- numSignatures * signatureSize + numPubkeys * pubkeySize + redeemScriptSize;
307
-
308
- // Total weight and virtual size
309
- const weight = baseTxSize * 3n + (baseTxSize + witnessSize);
310
- const vSize = weight / 4n;
311
-
312
- return vSize * feeRate;
313
- }
314
-
315
- protected generateTapData(): Payment {
316
- return {
317
- internalPubkey: this.internalPubKeyToXOnly(),
318
- network: this.network,
319
- };
320
- }
321
-
322
- /**
323
- * Generates the script address.
324
- * @protected
325
- * @returns {Payment}
326
- */
327
- protected generateScriptAddress(): Payment {
328
- return {
329
- internalPubkey: this.internalPubKeyToXOnly(),
330
- network: this.network,
331
- };
332
- }
333
-
334
- /**
335
- * Returns the signer key.
336
- * @protected
337
- * @returns {Signer}
338
- */
339
- protected getSignerKey(): Signer {
340
- return this.signer;
341
- }
342
-
343
- /**
344
- * Signs an input of the transaction.
345
- * @param {Psbt} transaction - The transaction to sign
346
- * @param {PsbtInput} input - The input to sign
347
- * @param {number} i - The index of the input
348
- * @param {Signer} [signer] - The signer to use
349
- * @protected
350
- */
351
- protected async signInput(
352
- transaction: Psbt,
353
- input: PsbtInput,
354
- i: number,
355
- signer?: Signer,
356
- ): Promise<void> {
357
- const signHash =
358
- this.sighashTypes && this.sighashTypes.length
359
- ? [TweakedTransaction.calculateSignHash(this.sighashTypes)]
360
- : undefined;
361
-
362
- signer = signer || this.getSignerKey();
363
-
364
- let testedTap: boolean = false;
365
- if (input.tapInternalKey) {
366
- if (!this.tweakedSigner) this.tweakSigner();
367
-
368
- let tweakedSigner: Signer | undefined;
369
- if (signer !== this.signer) {
370
- tweakedSigner = this.getTweakedSigner(true, signer);
371
- } else {
372
- tweakedSigner = this.tweakedSigner;
373
- }
374
-
375
- if (tweakedSigner) {
376
- testedTap = true;
377
-
378
- try {
379
- if ('signTaprootInput' in signer) {
380
- // @ts-ignore
381
- return await signer.signTaprootInput(transaction, i, signHash);
382
- } else {
383
- transaction.signTaprootInput(i, tweakedSigner, undefined, signHash);
384
- }
385
-
386
- return;
387
- } catch (e) {}
388
- }
389
- }
390
-
391
- try {
392
- if ('signInput' in signer) {
393
- // @ts-ignore
394
- return await signer.signInput(transaction, i, signHash);
395
- } else {
396
- transaction.signInput(i, signer, signHash);
397
- }
398
- } catch (e) {
399
- if (!testedTap) {
400
- // and we try again taproot...
401
-
402
- if ('signTaprootInput' in signer) {
403
- // @ts-ignore
404
- return await signer.signTaprootInput(transaction, i, signHash);
405
- } else if (this.tweakedSigner) {
406
- transaction.signTaprootInput(i, this.tweakedSigner, undefined, signHash);
407
- } else {
408
- throw e;
409
- }
410
- }
411
- }
412
- }
413
-
414
- /**
415
- * Signs all the inputs of the transaction.
416
- * @param {Psbt} transaction - The transaction to sign
417
- * @protected
418
- * @returns {Promise<void>}
419
- */
420
- protected async signInputs(transaction: Psbt): Promise<void> {
421
- for (let i = 0; i < transaction.data.inputs.length; i++) {
422
- let input: PsbtInput = transaction.data.inputs[i];
423
-
424
- try {
425
- await this.signInput(transaction, input, i);
426
- } catch (e) {
427
- this.log(`Failed to sign input ${i}: ${(e as Error).stack}`);
428
- }
429
- }
430
-
431
- try {
432
- transaction.finalizeAllInputs();
433
-
434
- this.finalized = true;
435
- } catch (e) {
436
- this.finalized = false;
437
- }
438
- }
439
-
440
- /**
441
- * Converts the public key to x-only.
442
- * @protected
443
- * @returns {Buffer}
444
- */
445
- protected internalPubKeyToXOnly(): Buffer {
446
- return toXOnly(this.signer.publicKey);
447
- }
448
-
449
- /**
450
- * Internal init.
451
- * @protected
452
- */
453
- protected internalInit(): void {
454
- this.scriptData = payments.p2tr(this.generateScriptAddress());
455
- this.tapData = payments.p2tr(this.generateTapData());
456
- }
457
-
458
- /**
459
- * Tweak the signer for the interaction
460
- * @protected
461
- */
462
- protected tweakSigner(): void {
463
- if (this.tweakedSigner) return;
464
-
465
- // tweaked p2tr signer.
466
- this.tweakedSigner = this.getTweakedSigner(true);
467
- }
468
-
469
- /**
470
- * Get the tweaked signer
471
- * @private
472
- * @returns {Signer} The tweaked signer
473
- */
474
- protected getTweakedSigner(
475
- useTweakedHash: boolean = false,
476
- signer: Signer = this.signer,
477
- ): Signer | undefined {
478
- const settings: TweakSettings = {
479
- network: this.network,
480
- };
481
-
482
- if (useTweakedHash) {
483
- settings.tweakHash = this.getTweakerHash();
484
- }
485
-
486
- if (!('privateKey' in signer)) {
487
- return;
488
- }
489
-
490
- return TweakedSigner.tweakSigner(signer as unknown as ECPairInterface, settings) as Signer;
491
- }
492
-
493
- /**
494
- * Generate the PSBT input extended
495
- * @param {UTXO} utxo The UTXO
496
- * @param {number} i The index of the input
497
- * @protected
498
- * @returns {PsbtInputExtended} The PSBT input extended
499
- */
500
- protected generatePsbtInputExtended(utxo: UTXO, i: number): PsbtInputExtended {
501
- const input: PsbtInputExtended = {
502
- hash: utxo.transactionId,
503
- index: utxo.outputIndex,
504
- witnessUtxo: {
505
- value: Number(utxo.value),
506
- script: Buffer.from(utxo.scriptPubKey.hex, 'hex'),
507
- },
508
- sequence: this.sequence,
509
- };
510
-
511
- if (this.sighashTypes) {
512
- const inputSign = TweakedTransaction.calculateSignHash(this.sighashTypes);
513
- if (inputSign) input.sighashType = inputSign;
514
- }
515
-
516
- if (this.tapLeafScript) {
517
- input.tapLeafScript = [this.tapLeafScript];
518
- }
519
-
520
- if (i === 0 && this.nonWitnessUtxo) {
521
- input.nonWitnessUtxo = this.nonWitnessUtxo;
522
- this.log(`Using non-witness utxo for input ${i}`);
523
- }
524
-
525
- // automatically detect p2tr inputs.
526
- if (
527
- utxo.scriptPubKey.address &&
528
- AddressVerificator.isValidP2TRAddress(utxo.scriptPubKey.address, this.network)
529
- ) {
530
- this.tweakSigner();
531
-
532
- input.tapInternalKey = this.internalPubKeyToXOnly();
533
- }
534
-
535
- return input;
536
- }
537
- }
1
+ import { Logger } from '@btc-vision/logger';
2
+ import { Network, Payment, payments, Psbt, Signer, Transaction } from 'bitcoinjs-lib';
3
+ import { TweakedSigner, TweakSettings } from '../../signer/TweakedSigner.js';
4
+ import { ECPairInterface } from 'ecpair';
5
+ import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371.js';
6
+ import { PsbtInput } from 'bip174/src/lib/interfaces.js';
7
+ import { UTXO } from '../../utxo/interfaces/IUTXO.js';
8
+ import { PsbtInputExtended, TapLeafScript } from '../interfaces/Tap.js';
9
+ import { AddressVerificator } from '../../keypair/AddressVerificator.js';
10
+ import { varuint } from 'bitcoinjs-lib/src/bufferutils.js';
11
+ import { ChainId } from '../../network/ChainId.js';
12
+
13
+ export interface ITweakedTransactionData {
14
+ readonly signer: Signer;
15
+ readonly network: Network;
16
+ readonly chainId?: ChainId;
17
+ readonly nonWitnessUtxo?: Buffer;
18
+ }
19
+
20
+ /**
21
+ * The transaction sequence
22
+ */
23
+ export enum TransactionSequence {
24
+ REPLACE_BY_FEE = 0xfffffffd,
25
+ FINAL = 0xffffffff,
26
+ }
27
+
28
+ /**
29
+ * @description PSBT Transaction processor.
30
+ * */
31
+ export abstract class TweakedTransaction extends Logger {
32
+ public readonly logColor: string = '#00ffe1';
33
+ public finalized: boolean = false;
34
+ /**
35
+ * @description Was the transaction signed?
36
+ */
37
+ protected signer: Signer;
38
+ /**
39
+ * @description Tweaked signer
40
+ */
41
+ protected tweakedSigner?: Signer;
42
+ /**
43
+ * @description The network of the transaction
44
+ */
45
+ protected network: Network;
46
+ /**
47
+ * @description Was the transaction signed?
48
+ */
49
+ protected signed: boolean = false;
50
+ /**
51
+ * @description The transaction
52
+ * @protected
53
+ */
54
+ protected abstract readonly transaction: Psbt;
55
+ /**
56
+ * @description The sighash types of the transaction
57
+ * @protected
58
+ */
59
+ protected sighashTypes: number[] | undefined;
60
+ /**
61
+ * @description The script data of the transaction
62
+ */
63
+ protected scriptData: Payment | null = null;
64
+ /**
65
+ * @description The tap data of the transaction
66
+ */
67
+ protected tapData: Payment | null = null;
68
+ /**
69
+ * @description The inputs of the transaction
70
+ */
71
+ protected readonly inputs: PsbtInputExtended[] = [];
72
+ /**
73
+ * @description The sequence of the transaction
74
+ * @protected
75
+ */
76
+ protected sequence: number = TransactionSequence.REPLACE_BY_FEE;
77
+ /**
78
+ * The tap leaf script
79
+ * @protected
80
+ */
81
+ protected tapLeafScript: TapLeafScript | null = null;
82
+ /**
83
+ * Add a non-witness utxo to the transaction
84
+ * @protected
85
+ */
86
+ protected nonWitnessUtxo?: Buffer;
87
+
88
+ /**
89
+ * Is the transaction being generated inside a browser?
90
+ * @protected
91
+ */
92
+ protected readonly isBrowser: boolean = false;
93
+
94
+ protected regenerated: boolean = false;
95
+ protected ignoreSignatureErrors: boolean = false;
96
+
97
+ protected constructor(data: ITweakedTransactionData) {
98
+ super();
99
+
100
+ this.signer = data.signer;
101
+ this.network = data.network;
102
+
103
+ this.nonWitnessUtxo = data.nonWitnessUtxo;
104
+
105
+ this.isBrowser = typeof window !== 'undefined';
106
+ }
107
+
108
+ /**
109
+ * Read witnesses
110
+ * @protected
111
+ */
112
+ public static readScriptWitnessToWitnessStack(buffer: Buffer): Buffer[] {
113
+ let offset = 0;
114
+
115
+ function readSlice(n: number): Buffer {
116
+ const slice = buffer.subarray(offset, offset + n);
117
+ offset += n;
118
+ return slice;
119
+ }
120
+
121
+ function readVarInt(): number {
122
+ const varint = varuint.decode(buffer, offset);
123
+ offset += varuint.decode.bytes;
124
+ return varint;
125
+ }
126
+
127
+ function readVarSlice(): Buffer {
128
+ const len = readVarInt();
129
+ return readSlice(len);
130
+ }
131
+
132
+ function readVector(): Buffer[] {
133
+ const count = readVarInt();
134
+ const vector = [];
135
+ for (let i = 0; i < count; i++) {
136
+ vector.push(readVarSlice());
137
+ }
138
+ return vector;
139
+ }
140
+
141
+ return readVector();
142
+ }
143
+
144
+ /**
145
+ * Pre-estimate the transaction fees for a Taproot transaction
146
+ * @param {bigint} feeRate - The fee rate in satoshis per virtual byte
147
+ * @param {bigint} numInputs - The number of inputs
148
+ * @param {bigint} numOutputs - The number of outputs
149
+ * @param {bigint} numWitnessElements - The number of witness elements (e.g., number of control blocks and witnesses)
150
+ * @param {bigint} witnessElementSize - The average size of each witness element in bytes
151
+ * @param {bigint} emptyWitness - The amount of empty witnesses
152
+ * @param {bigint} [taprootControlWitnessSize=139n] - The size of the control block witness in bytes
153
+ * @param {bigint} [taprootScriptSize=32n] - The size of the taproot script in bytes
154
+ * @returns {bigint} - The estimated transaction fees
155
+ */
156
+ public static preEstimateTaprootTransactionFees(
157
+ feeRate: bigint, // satoshis per virtual byte
158
+ numInputs: bigint,
159
+ numOutputs: bigint,
160
+ numWitnessElements: bigint,
161
+ witnessElementSize: bigint,
162
+ emptyWitness: bigint,
163
+ taprootControlWitnessSize: bigint = 32n,
164
+ taprootScriptSize: bigint = 139n,
165
+ ): bigint {
166
+ const txHeaderSize = 10n;
167
+ const inputBaseSize = 41n;
168
+ const outputSize = 68n;
169
+ const taprootWitnessBaseSize = 1n; // Base witness size per input (without signatures and control blocks)
170
+
171
+ // Base transaction size (excluding witness data)
172
+ const baseTxSize = txHeaderSize + inputBaseSize * numInputs + outputSize * numOutputs;
173
+
174
+ // Witness data size for Taproot
175
+ const witnessSize =
176
+ numInputs * taprootWitnessBaseSize +
177
+ numWitnessElements * witnessElementSize +
178
+ taprootControlWitnessSize * numInputs +
179
+ taprootScriptSize * numInputs +
180
+ emptyWitness;
181
+
182
+ // Total weight and virtual size
183
+ const weight = baseTxSize * 3n + (baseTxSize + witnessSize);
184
+ const vSize = weight / 4n;
185
+
186
+ return vSize * feeRate;
187
+ }
188
+
189
+ protected static signInput(
190
+ transaction: Psbt,
191
+ input: PsbtInput,
192
+ i: number,
193
+ signer: Signer,
194
+ sighashTypes: number[],
195
+ ): void {
196
+ if (sighashTypes && sighashTypes[0]) input.sighashType = sighashTypes[0];
197
+
198
+ transaction.signInput(i, signer, sighashTypes.length ? sighashTypes : undefined);
199
+ }
200
+
201
+ /**
202
+ * Calculate the sign hash number
203
+ * @description Calculates the sign hash
204
+ * @protected
205
+ * @returns {number}
206
+ */
207
+ protected static calculateSignHash(sighashTypes: number[]): number {
208
+ if (!sighashTypes) {
209
+ throw new Error('Sighash types are required');
210
+ }
211
+
212
+ let signHash: number = 0;
213
+ for (let sighashType of sighashTypes) {
214
+ signHash |= sighashType;
215
+ }
216
+
217
+ return signHash || 0;
218
+ }
219
+
220
+ public ignoreSignatureError(): void {
221
+ this.ignoreSignatureErrors = true;
222
+ }
223
+
224
+ /**
225
+ * @description Returns the script address
226
+ * @returns {string}
227
+ */
228
+ public getScriptAddress(): string {
229
+ if (!this.scriptData || !this.scriptData.address) {
230
+ throw new Error('Tap data is required');
231
+ }
232
+
233
+ return this.scriptData.address;
234
+ }
235
+
236
+ /**
237
+ * @description Returns the transaction
238
+ * @returns {Transaction}
239
+ */
240
+ public getTransaction(): Transaction {
241
+ return this.transaction.extractTransaction(false);
242
+ }
243
+
244
+ /**
245
+ * @description Returns the tap address
246
+ * @returns {string}
247
+ * @throws {Error} - If tap data is not set
248
+ */
249
+ public getTapAddress(): string {
250
+ if (!this.tapData || !this.tapData.address) {
251
+ throw new Error('Tap data is required');
252
+ }
253
+
254
+ return this.tapData.address;
255
+ }
256
+
257
+ /**
258
+ * @description Disables replace by fee on the transaction
259
+ */
260
+ public disableRBF(): void {
261
+ if (this.signed) throw new Error('Transaction is already signed');
262
+
263
+ this.sequence = TransactionSequence.FINAL;
264
+
265
+ for (let input of this.inputs) {
266
+ input.sequence = TransactionSequence.FINAL;
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Get the tweaked hash
272
+ * @private
273
+ *
274
+ * @returns {Buffer | undefined} The tweaked hash
275
+ */
276
+ public getTweakerHash(): Buffer | undefined {
277
+ return this.tapData?.hash;
278
+ }
279
+
280
+ /**
281
+ * Pre-estimate the transaction fees
282
+ * @param {bigint} feeRate - The fee rate
283
+ * @param {bigint} numInputs - The number of inputs
284
+ * @param {bigint} numOutputs - The number of outputs
285
+ * @param {bigint} numSignatures - The number of signatures
286
+ * @param {bigint} numPubkeys - The number of public keys
287
+ * @returns {bigint} - The estimated transaction fees
288
+ */
289
+ public preEstimateTransactionFees(
290
+ feeRate: bigint, // satoshis per byte
291
+ numInputs: bigint,
292
+ numOutputs: bigint,
293
+ numSignatures: bigint,
294
+ numPubkeys: bigint,
295
+ ): bigint {
296
+ const txHeaderSize = 10n;
297
+ const inputBaseSize = 41n;
298
+ const outputSize = 68n;
299
+ const signatureSize = 144n;
300
+ const pubkeySize = 34n;
301
+
302
+ // Base transaction size (excluding witness data)
303
+ const baseTxSize = txHeaderSize + inputBaseSize * numInputs + outputSize * numOutputs;
304
+
305
+ // Witness data size
306
+ const redeemScriptSize = 1n + numPubkeys * (1n + pubkeySize) + 1n + numSignatures;
307
+ const witnessSize =
308
+ numSignatures * signatureSize + numPubkeys * pubkeySize + redeemScriptSize;
309
+
310
+ // Total weight and virtual size
311
+ const weight = baseTxSize * 3n + (baseTxSize + witnessSize);
312
+ const vSize = weight / 4n;
313
+
314
+ return vSize * feeRate;
315
+ }
316
+
317
+ protected generateTapData(): Payment {
318
+ return {
319
+ internalPubkey: this.internalPubKeyToXOnly(),
320
+ network: this.network,
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Generates the script address.
326
+ * @protected
327
+ * @returns {Payment}
328
+ */
329
+ protected generateScriptAddress(): Payment {
330
+ return {
331
+ internalPubkey: this.internalPubKeyToXOnly(),
332
+ network: this.network,
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Returns the signer key.
338
+ * @protected
339
+ * @returns {Signer}
340
+ */
341
+ protected getSignerKey(): Signer {
342
+ return this.signer;
343
+ }
344
+
345
+ /**
346
+ * Signs an input of the transaction.
347
+ * @param {Psbt} transaction - The transaction to sign
348
+ * @param {PsbtInput} input - The input to sign
349
+ * @param {number} i - The index of the input
350
+ * @param {Signer} [signer] - The signer to use
351
+ * @protected
352
+ */
353
+ protected async signInput(
354
+ transaction: Psbt,
355
+ input: PsbtInput,
356
+ i: number,
357
+ signer?: Signer,
358
+ ): Promise<void> {
359
+ const signHash =
360
+ this.sighashTypes && this.sighashTypes.length
361
+ ? [TweakedTransaction.calculateSignHash(this.sighashTypes)]
362
+ : undefined;
363
+
364
+ signer = signer || this.getSignerKey();
365
+
366
+ let testedTap: boolean = false;
367
+ if (input.tapInternalKey) {
368
+ if (!this.tweakedSigner) this.tweakSigner();
369
+
370
+ let tweakedSigner: Signer | undefined;
371
+ if (signer !== this.signer) {
372
+ tweakedSigner = this.getTweakedSigner(true, signer);
373
+ } else {
374
+ tweakedSigner = this.tweakedSigner;
375
+ }
376
+
377
+ if (tweakedSigner) {
378
+ testedTap = true;
379
+
380
+ try {
381
+ if ('signTaprootInput' in signer) {
382
+ // @ts-ignore
383
+ return await signer.signTaprootInput(transaction, i, signHash);
384
+ } else {
385
+ transaction.signTaprootInput(i, tweakedSigner, undefined, signHash);
386
+ }
387
+
388
+ return;
389
+ } catch (e) {}
390
+ }
391
+ }
392
+
393
+ try {
394
+ if ('signInput' in signer) {
395
+ // @ts-ignore
396
+ return await signer.signInput(transaction, i, signHash);
397
+ } else {
398
+ transaction.signInput(i, signer, signHash);
399
+ }
400
+ } catch (e) {
401
+ if (!testedTap) {
402
+ // and we try again taproot...
403
+
404
+ if ('signTaprootInput' in signer) {
405
+ // @ts-ignore
406
+ return await signer.signTaprootInput(transaction, i, signHash);
407
+ } else if (this.tweakedSigner) {
408
+ transaction.signTaprootInput(i, this.tweakedSigner, undefined, signHash);
409
+ } else {
410
+ throw e;
411
+ }
412
+ }
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Signs all the inputs of the transaction.
418
+ * @param {Psbt} transaction - The transaction to sign
419
+ * @protected
420
+ * @returns {Promise<void>}
421
+ */
422
+ protected async signInputs(transaction: Psbt): Promise<void> {
423
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
424
+ let input: PsbtInput = transaction.data.inputs[i];
425
+
426
+ try {
427
+ await this.signInput(transaction, input, i);
428
+ } catch (e) {
429
+ this.log(`Failed to sign input ${i}: ${(e as Error).stack}`);
430
+ }
431
+ }
432
+
433
+ try {
434
+ transaction.finalizeAllInputs();
435
+
436
+ this.finalized = true;
437
+ } catch (e) {
438
+ this.finalized = false;
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Converts the public key to x-only.
444
+ * @protected
445
+ * @returns {Buffer}
446
+ */
447
+ protected internalPubKeyToXOnly(): Buffer {
448
+ return toXOnly(this.signer.publicKey);
449
+ }
450
+
451
+ /**
452
+ * Internal init.
453
+ * @protected
454
+ */
455
+ protected internalInit(): void {
456
+ this.scriptData = payments.p2tr(this.generateScriptAddress());
457
+ this.tapData = payments.p2tr(this.generateTapData());
458
+ }
459
+
460
+ /**
461
+ * Tweak the signer for the interaction
462
+ * @protected
463
+ */
464
+ protected tweakSigner(): void {
465
+ if (this.tweakedSigner) return;
466
+
467
+ // tweaked p2tr signer.
468
+ this.tweakedSigner = this.getTweakedSigner(true);
469
+ }
470
+
471
+ /**
472
+ * Get the tweaked signer
473
+ * @private
474
+ * @returns {Signer} The tweaked signer
475
+ */
476
+ protected getTweakedSigner(
477
+ useTweakedHash: boolean = false,
478
+ signer: Signer = this.signer,
479
+ ): Signer | undefined {
480
+ const settings: TweakSettings = {
481
+ network: this.network,
482
+ };
483
+
484
+ if (useTweakedHash) {
485
+ settings.tweakHash = this.getTweakerHash();
486
+ }
487
+
488
+ if (!('privateKey' in signer)) {
489
+ return;
490
+ }
491
+
492
+ return TweakedSigner.tweakSigner(signer as unknown as ECPairInterface, settings) as Signer;
493
+ }
494
+
495
+ /**
496
+ * Generate the PSBT input extended
497
+ * @param {UTXO} utxo The UTXO
498
+ * @param {number} i The index of the input
499
+ * @protected
500
+ * @returns {PsbtInputExtended} The PSBT input extended
501
+ */
502
+ protected generatePsbtInputExtended(utxo: UTXO, i: number): PsbtInputExtended {
503
+ const input: PsbtInputExtended = {
504
+ hash: utxo.transactionId,
505
+ index: utxo.outputIndex,
506
+ witnessUtxo: {
507
+ value: Number(utxo.value),
508
+ script: Buffer.from(utxo.scriptPubKey.hex, 'hex'),
509
+ },
510
+ sequence: this.sequence,
511
+ };
512
+
513
+ if (this.sighashTypes) {
514
+ const inputSign = TweakedTransaction.calculateSignHash(this.sighashTypes);
515
+ if (inputSign) input.sighashType = inputSign;
516
+ }
517
+
518
+ if (this.tapLeafScript) {
519
+ input.tapLeafScript = [this.tapLeafScript];
520
+ }
521
+
522
+ if (i === 0 && this.nonWitnessUtxo) {
523
+ input.nonWitnessUtxo = this.nonWitnessUtxo;
524
+ this.log(`Using non-witness utxo for input ${i}`);
525
+ }
526
+
527
+ // automatically detect p2tr inputs.
528
+ if (
529
+ utxo.scriptPubKey.address &&
530
+ AddressVerificator.isValidP2TRAddress(utxo.scriptPubKey.address, this.network)
531
+ ) {
532
+ this.tweakSigner();
533
+
534
+ input.tapInternalKey = this.internalPubKeyToXOnly();
535
+ }
536
+
537
+ return input;
538
+ }
539
+ }