@btc-vision/transaction 1.6.6 → 1.6.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.
@@ -1,4 +1,5 @@
1
- import {
1
+ import bitcoin, {
2
+ getFinalScripts,
2
3
  initEccLib,
3
4
  Network,
4
5
  opcodes,
@@ -13,20 +14,19 @@ import {
13
14
  import * as ecc from '@bitcoinerlab/secp256k1';
14
15
  import { UpdateInput } from '../interfaces/Tap.js';
15
16
  import { TransactionType } from '../enums/TransactionType.js';
16
- import {
17
- IFundingTransactionParameters,
18
- ITransactionParameters,
19
- } from '../interfaces/ITransactionParameters.js';
17
+ import { IFundingTransactionParameters, ITransactionParameters, } from '../interfaces/ITransactionParameters.js';
20
18
  import { EcKeyPair } from '../../keypair/EcKeyPair.js';
21
19
  import { UTXO } from '../../utxo/interfaces/IUTXO.js';
22
20
  import { ECPairInterface } from 'ecpair';
23
21
  import { AddressVerificator } from '../../keypair/AddressVerificator.js';
24
22
  import { TweakedTransaction } from '../shared/TweakedTransaction.js';
25
23
  import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
24
+ import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
25
+ import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
26
26
 
27
27
  initEccLib(ecc);
28
28
 
29
- export const MINIMUM_AMOUNT_REWARD: bigint = 540n;
29
+ export const MINIMUM_AMOUNT_REWARD: bigint = 330n; //540n;
30
30
  export const MINIMUM_AMOUNT_CA: bigint = 297n;
31
31
  export const ANCHOR_SCRIPT = Buffer.from('51024e73', 'hex');
32
32
 
@@ -43,10 +43,11 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
43
43
  opcodes.OP_VERIFY,
44
44
  ]);
45
45
 
46
- public static readonly MINIMUM_DUST: bigint = 50n;
46
+ public static readonly MINIMUM_DUST: bigint = 330n;
47
47
 
48
48
  public abstract readonly type: T;
49
49
  public readonly logColor: string = '#785def';
50
+ public debugFees: boolean = false;
50
51
 
51
52
  /**
52
53
  * @description The overflow fees of the transaction
@@ -157,6 +158,8 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
157
158
 
158
159
  protected note?: Buffer;
159
160
 
161
+ private optionalOutputsAdded: boolean = false;
162
+
160
163
  protected constructor(parameters: ITransactionParameters) {
161
164
  super(parameters);
162
165
 
@@ -172,6 +175,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
172
175
  this.utxos = parameters.utxos;
173
176
  this.optionalInputs = parameters.optionalInputs || [];
174
177
  this.to = parameters.to || undefined;
178
+ this.debugFees = parameters.debugFees || false;
175
179
 
176
180
  if (parameters.note) {
177
181
  if (typeof parameters.note === 'string') {
@@ -400,10 +404,11 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
400
404
  /**
401
405
  * Add an output to the transaction.
402
406
  * @param {PsbtOutputExtended} output - The output to add
407
+ * @param bypassMinCheck
403
408
  * @public
404
409
  * @returns {void}
405
410
  */
406
- public addOutput(output: PsbtOutputExtended): void {
411
+ public addOutput(output: PsbtOutputExtended, bypassMinCheck: boolean = false): void {
407
412
  if (output.value === 0) {
408
413
  const script = output as {
409
414
  script: Buffer;
@@ -422,7 +427,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
422
427
  'Output script must start with OP_RETURN or be an ANCHOR when value is 0',
423
428
  );
424
429
  }
425
- } else if (output.value < TransactionBuilder.MINIMUM_DUST) {
430
+ } else if (!bypassMinCheck && output.value < TransactionBuilder.MINIMUM_DUST) {
426
431
  throw new Error(
427
432
  `Output value is less than the minimum dust ${output.value} < ${TransactionBuilder.MINIMUM_DUST}`,
428
433
  );
@@ -431,6 +436,15 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
431
436
  this.outputs.push(output);
432
437
  }
433
438
 
439
+ /**
440
+ * Returns the total value of all outputs added so far (excluding the fee/change output).
441
+ * @public
442
+ * @returns {bigint}
443
+ */
444
+ public getTotalOutputValue(): bigint {
445
+ return this.outputs.reduce((total, output) => total + BigInt(output.value), 0n);
446
+ }
447
+
434
448
  /**
435
449
  * Receiver address.
436
450
  * @public
@@ -449,35 +463,229 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
449
463
  }
450
464
 
451
465
  /**
452
- * Estimates the transaction fees.
466
+ * Estimates the transaction fees with accurate size calculation.
453
467
  * @public
454
- * @returns {Promise<bigint>} - The estimated transaction fees
468
+ * @returns {Promise<bigint>}
455
469
  */
456
470
  public async estimateTransactionFees(): Promise<bigint> {
457
- if (!this.utxos.length) {
458
- throw new Error('No UTXOs specified');
459
- }
471
+ await Promise.resolve();
472
+
473
+ const fakeTx = new Psbt({ network: this.network });
474
+ const inputs = this.getInputs();
475
+ const outputs = this.getOutputs();
476
+ fakeTx.addInputs(inputs);
477
+ fakeTx.addOutputs(outputs);
478
+
479
+ const dummySchnorrSig = Buffer.alloc(64, 0);
480
+ const dummyEcdsaSig = Buffer.alloc(72, 0);
481
+ const dummyCompressedPubkey = Buffer.alloc(33, 2);
482
+
483
+ const finalizer = (inputIndex: number, input: PsbtInputExtended) => {
484
+ if (input.isPayToAnchor || this.anchorInputIndices.has(inputIndex)) {
485
+ return {
486
+ finalScriptSig: undefined,
487
+ finalScriptWitness: Buffer.from([0]),
488
+ };
489
+ }
460
490
 
461
- if (this.estimatedFees) return this.estimatedFees;
491
+ if (input.witnessScript && P2WDADetector.isP2WDAWitnessScript(input.witnessScript)) {
492
+ // Create dummy witness stack for P2WDA
493
+ const dummyDataSlots: Buffer[] = [];
494
+ for (let i = 0; i < 10; i++) {
495
+ dummyDataSlots.push(Buffer.alloc(0));
496
+ }
497
+
498
+ const dummyEcdsaSig = Buffer.alloc(72, 0);
499
+ return {
500
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
501
+ ...dummyDataSlots,
502
+ dummyEcdsaSig,
503
+ input.witnessScript,
504
+ ]),
505
+ };
506
+ }
462
507
 
463
- const fakeTx = new Psbt({
464
- network: this.network,
465
- });
508
+ if (inputIndex === 0 && this.tapLeafScript) {
509
+ const dummySecret = Buffer.alloc(32, 0);
510
+ const dummyScript = this.tapLeafScript.script;
511
+
512
+ // A control block for a 2-leaf tree contains one 32-byte hash.
513
+ const dummyControlBlock = Buffer.alloc(1 + 32 + 32, 0);
514
+
515
+ return {
516
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
517
+ dummySecret,
518
+ dummySchnorrSig, // It's a tapScriptSig, which is Schnorr
519
+ dummySchnorrSig, // Second Schnorr signature
520
+ dummyScript,
521
+ dummyControlBlock,
522
+ ]),
523
+ };
524
+ }
466
525
 
467
- const builtTx = await this.internalBuildTransaction(fakeTx);
468
- if (builtTx) {
469
- const tx = fakeTx.extractTransaction(true, true);
470
- const size = tx.virtualSize();
471
- const fee: number = this.feeRate * size;
526
+ if (input.witnessUtxo) {
527
+ const script = input.witnessUtxo.script;
528
+ const decompiled = bitcoin.script.decompile(script);
529
+ if (
530
+ decompiled &&
531
+ decompiled.length === 5 &&
532
+ decompiled[0] === opcodes.OP_DUP &&
533
+ decompiled[1] === opcodes.OP_HASH160 &&
534
+ decompiled[3] === opcodes.OP_EQUALVERIFY &&
535
+ decompiled[4] === opcodes.OP_CHECKSIG
536
+ ) {
537
+ return {
538
+ finalScriptSig: bitcoin.script.compile([
539
+ dummyEcdsaSig,
540
+ dummyCompressedPubkey,
541
+ ]),
542
+ finalScriptWitness: undefined,
543
+ };
544
+ }
545
+ }
472
546
 
473
- this.estimatedFees = BigInt(Math.ceil(fee) + 1);
547
+ if (input.witnessScript) {
548
+ if (this.csvInputIndices.has(inputIndex)) {
549
+ // CSV P2WSH needs: [signature, witnessScript]
550
+ return {
551
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
552
+ dummyEcdsaSig,
553
+ input.witnessScript,
554
+ ]),
555
+ };
556
+ }
557
+
558
+ if (input.redeemScript) {
559
+ // P2SH-P2WSH needs redeemScript in scriptSig and witness data
560
+ const dummyWitness = [dummyEcdsaSig, input.witnessScript];
561
+ return {
562
+ finalScriptSig: input.redeemScript,
563
+ finalScriptWitness:
564
+ TransactionBuilder.witnessStackToScriptWitness(dummyWitness),
565
+ };
566
+ }
567
+
568
+ const decompiled = bitcoin.script.decompile(input.witnessScript);
569
+ if (decompiled && decompiled.length >= 4) {
570
+ const firstOp = decompiled[0];
571
+ const lastOp = decompiled[decompiled.length - 1];
572
+ // Check if it's M-of-N multisig
573
+ if (
574
+ typeof firstOp === 'number' &&
575
+ firstOp >= opcodes.OP_1 &&
576
+ lastOp === opcodes.OP_CHECKMULTISIG
577
+ ) {
578
+ const m = firstOp - opcodes.OP_1 + 1;
579
+ const signatures: Buffer[] = [];
580
+ for (let i = 0; i < m; i++) {
581
+ signatures.push(dummyEcdsaSig);
582
+ }
583
+
584
+ return {
585
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
586
+ Buffer.alloc(0), // OP_0 due to multisig bug
587
+ ...signatures,
588
+ input.witnessScript,
589
+ ]),
590
+ };
591
+ }
592
+ }
593
+
594
+ return {
595
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
596
+ dummyEcdsaSig,
597
+ input.witnessScript,
598
+ ]),
599
+ };
600
+ } else if (input.redeemScript) {
601
+ const decompiled = bitcoin.script.decompile(input.redeemScript);
602
+ if (
603
+ decompiled &&
604
+ decompiled.length === 2 &&
605
+ decompiled[0] === opcodes.OP_0 &&
606
+ Buffer.isBuffer(decompiled[1]) &&
607
+ decompiled[1].length === 20
608
+ ) {
609
+ // P2SH-P2WPKH
610
+ return {
611
+ finalScriptSig: input.redeemScript,
612
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
613
+ dummyEcdsaSig,
614
+ dummyCompressedPubkey,
615
+ ]),
616
+ };
617
+ }
618
+ }
474
619
 
475
- return this.estimatedFees;
476
- } else {
477
- throw new Error(
478
- `Could not build transaction to estimate fee. Something went wrong while building the transaction.`,
620
+ if (input.redeemScript && !input.witnessScript && !input.witnessUtxo) {
621
+ // Pure P2SH needs signatures + redeemScript in scriptSig
622
+ return {
623
+ finalScriptSig: bitcoin.script.compile([dummyEcdsaSig, input.redeemScript]),
624
+ finalScriptWitness: undefined,
625
+ };
626
+ }
627
+
628
+ const script = input.witnessUtxo?.script;
629
+ if (!script) return { finalScriptSig: undefined, finalScriptWitness: undefined };
630
+
631
+ if (input.tapInternalKey) {
632
+ return {
633
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
634
+ dummySchnorrSig,
635
+ ]),
636
+ };
637
+ }
638
+
639
+ if (script.length === 22 && script[0] === opcodes.OP_0) {
640
+ return {
641
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
642
+ dummyEcdsaSig,
643
+ dummyCompressedPubkey,
644
+ ]),
645
+ };
646
+ }
647
+
648
+ if (input.redeemScript?.length === 22 && input.redeemScript[0] === opcodes.OP_0) {
649
+ return {
650
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
651
+ dummyEcdsaSig,
652
+ dummyCompressedPubkey,
653
+ ]),
654
+ };
655
+ }
656
+
657
+ return getFinalScripts(
658
+ inputIndex,
659
+ input,
660
+ script,
661
+ true,
662
+ !!input.redeemScript,
663
+ !!input.witnessScript,
664
+ );
665
+ };
666
+
667
+ try {
668
+ for (let i = 0; i < fakeTx.data.inputs.length; i++) {
669
+ const fullInput = inputs[i];
670
+ if (fullInput) {
671
+ fakeTx.finalizeInput(i, (idx: number) => finalizer(idx, fullInput));
672
+ }
673
+ }
674
+ } catch (e) {
675
+ this.warn(`Could not finalize dummy tx: ${(e as Error).message}`);
676
+ }
677
+
678
+ const tx = fakeTx.extractTransaction(true, true);
679
+ const size = tx.virtualSize();
680
+ const fee = this.feeRate * size;
681
+ const finalFee = BigInt(Math.ceil(fee));
682
+
683
+ if (this.debugFees) {
684
+ this.log(
685
+ `Estimating fees: feeRate=${this.feeRate}, accurate_vSize=${size}, fee=${finalFee}n`,
479
686
  );
480
687
  }
688
+ return finalFee;
481
689
  }
482
690
 
483
691
  public async rebuildFromBase64(base64: string): Promise<Psbt> {
@@ -529,12 +737,6 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
529
737
  return total;
530
738
  }
531
739
 
532
- /**
533
- * @description Adds the refund output to the transaction
534
- * @param {bigint} amountSpent - The amount spent
535
- * @protected
536
- * @returns {Promise<void>}
537
- */
538
740
  protected async addRefundOutput(amountSpent: bigint): Promise<void> {
539
741
  if (this.note) {
540
742
  this.addOPReturn(this.note);
@@ -544,38 +746,92 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
544
746
  this.addAnchor();
545
747
  }
546
748
 
547
- /** Add the refund output */
548
- const sendBackAmount: bigint = this.totalInputAmount - amountSpent;
549
- if (sendBackAmount >= TransactionBuilder.MINIMUM_DUST) {
550
- if (AddressVerificator.isValidP2TRAddress(this.from, this.network)) {
551
- await this.setFeeOutput({
552
- value: Number(sendBackAmount),
553
- address: this.from,
554
- tapInternalKey: this.internalPubKeyToXOnly(),
555
- });
556
- } else if (AddressVerificator.isValidPublicKey(this.from, this.network)) {
557
- const pubKeyScript = script.compile([
558
- Buffer.from(this.from.replace('0x', ''), 'hex'),
559
- opcodes.OP_CHECKSIG,
560
- ]);
561
-
562
- await this.setFeeOutput({
563
- value: Number(sendBackAmount),
564
- script: pubKeyScript,
565
- });
749
+ // Initialize variables for iteration
750
+ let previousFee = -1n;
751
+ let estimatedFee = 0n;
752
+ let iterations = 0;
753
+ const maxIterations = 5; // Prevent infinite loops
754
+
755
+ // Iterate until fee stabilizes
756
+ while (iterations < maxIterations && estimatedFee !== previousFee) {
757
+ previousFee = estimatedFee;
758
+
759
+ // Calculate the fee with current outputs
760
+ estimatedFee = await this.estimateTransactionFees();
761
+
762
+ // Total amount that needs to be spent (outputs + fee)
763
+ const totalSpent = amountSpent + estimatedFee;
764
+
765
+ // Calculate refund
766
+ const sendBackAmount = this.totalInputAmount - totalSpent;
767
+
768
+ if (this.debugFees) {
769
+ this.log(
770
+ `Iteration ${iterations + 1}: inputAmount=${this.totalInputAmount}, totalSpent=${totalSpent}, sendBackAmount=${sendBackAmount}`,
771
+ );
772
+ }
773
+
774
+ // Determine if we should add a change output
775
+ if (sendBackAmount >= TransactionBuilder.MINIMUM_DUST) {
776
+ // Create the appropriate change output
777
+ if (AddressVerificator.isValidP2TRAddress(this.from, this.network)) {
778
+ this.feeOutput = {
779
+ value: Number(sendBackAmount),
780
+ address: this.from,
781
+ tapInternalKey: this.internalPubKeyToXOnly(),
782
+ };
783
+ } else if (AddressVerificator.isValidPublicKey(this.from, this.network)) {
784
+ const pubKeyScript = script.compile([
785
+ Buffer.from(this.from.replace('0x', ''), 'hex'),
786
+ opcodes.OP_CHECKSIG,
787
+ ]);
788
+
789
+ this.feeOutput = {
790
+ value: Number(sendBackAmount),
791
+ script: pubKeyScript,
792
+ };
793
+ } else {
794
+ this.feeOutput = {
795
+ value: Number(sendBackAmount),
796
+ address: this.from,
797
+ };
798
+ }
799
+
800
+ // Set overflowFees when we have a change output
801
+ this.overflowFees = sendBackAmount;
566
802
  } else {
567
- await this.setFeeOutput({
568
- value: Number(sendBackAmount),
569
- address: this.from,
570
- });
803
+ // No change output if below dust
804
+ this.feeOutput = null;
805
+ this.overflowFees = 0n;
806
+
807
+ if (sendBackAmount < 0n) {
808
+ throw new Error(
809
+ `Insufficient funds: need ${totalSpent} sats but only have ${this.totalInputAmount} sats`,
810
+ );
811
+ }
812
+
813
+ if (this.debugFees) {
814
+ this.warn(
815
+ `Amount to send back (${sendBackAmount} sat) is less than minimum dust...`,
816
+ );
817
+ }
571
818
  }
572
819
 
573
- return;
820
+ iterations++;
574
821
  }
575
822
 
576
- this.warn(
577
- `Amount to send back (${sendBackAmount} sat) is less than the minimum dust (${TransactionBuilder.MINIMUM_DUST} sat), it will be consumed in fees instead.`,
578
- );
823
+ if (iterations >= maxIterations) {
824
+ this.warn(`Fee calculation did not stabilize after ${maxIterations} iterations`);
825
+ }
826
+
827
+ // Store the final fee
828
+ this.transactionFee = estimatedFee;
829
+
830
+ if (this.debugFees) {
831
+ this.log(
832
+ `Final fee: ${estimatedFee} sats, Change output: ${this.feeOutput ? `${this.feeOutput.value} sats` : 'none'}`,
833
+ );
834
+ }
579
835
  }
580
836
 
581
837
  /**
@@ -657,14 +913,17 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
657
913
  * @returns {bigint}
658
914
  */
659
915
  protected addOptionalOutputsAndGetAmount(): bigint {
660
- if (!this.optionalOutputs) return 0n;
916
+ if (!this.optionalOutputs || this.optionalOutputsAdded) return 0n;
661
917
 
662
- let refundedFromOptionalOutputs = 0n;
918
+ let refundedFromOptionalOutputs: bigint = 0n;
663
919
 
664
920
  for (let i = 0; i < this.optionalOutputs.length; i++) {
665
921
  this.addOutput(this.optionalOutputs[i]);
666
922
  refundedFromOptionalOutputs += BigInt(this.optionalOutputs[i].value);
667
923
  }
924
+
925
+ this.optionalOutputsAdded = true;
926
+
668
927
  return refundedFromOptionalOutputs;
669
928
  }
670
929
 
@@ -732,6 +991,66 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
732
991
  this.updateInputs.push(input);
733
992
  }
734
993
 
994
+ /**
995
+ * Adds the fee to the output.
996
+ * @param amountSpent
997
+ * @param contractAddress
998
+ * @param epochChallenge
999
+ * @param addContractOutput
1000
+ * @protected
1001
+ */
1002
+ protected addFeeToOutput(
1003
+ amountSpent: bigint,
1004
+ contractAddress: string,
1005
+ epochChallenge: IP2WSHAddress,
1006
+ addContractOutput: boolean,
1007
+ ): void {
1008
+ if (addContractOutput) {
1009
+ let amountToCA: bigint;
1010
+ if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
1011
+ amountToCA = MINIMUM_AMOUNT_CA;
1012
+ } else {
1013
+ amountToCA = amountSpent;
1014
+ }
1015
+
1016
+ // ALWAYS THE FIRST INPUT.
1017
+ this.addOutput(
1018
+ {
1019
+ value: Number(amountToCA),
1020
+ address: contractAddress,
1021
+ },
1022
+ true,
1023
+ );
1024
+
1025
+ // ALWAYS SECOND.
1026
+ if (
1027
+ amountToCA === MINIMUM_AMOUNT_CA &&
1028
+ amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD
1029
+ ) {
1030
+ this.addOutput(
1031
+ {
1032
+ value: Number(amountSpent - amountToCA),
1033
+ address: epochChallenge.address,
1034
+ },
1035
+ true,
1036
+ );
1037
+ }
1038
+ } else {
1039
+ // When SEND_AMOUNT_TO_CA is false, always send to epochChallenge
1040
+ // Use the maximum of amountSpent or MINIMUM_AMOUNT_REWARD
1041
+ const amountToEpoch =
1042
+ amountSpent < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : amountSpent;
1043
+
1044
+ this.addOutput(
1045
+ {
1046
+ value: Number(amountToEpoch),
1047
+ address: epochChallenge.address,
1048
+ },
1049
+ true,
1050
+ );
1051
+ }
1052
+ }
1053
+
735
1054
  /**
736
1055
  * Returns the witness of the tap transaction.
737
1056
  * @protected
@@ -788,6 +1107,69 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
788
1107
  */
789
1108
  protected async setFeeOutput(output: PsbtOutputExtended): Promise<void> {
790
1109
  const initialValue = output.value;
1110
+ this.feeOutput = null; // Start with no fee output
1111
+
1112
+ let estimatedFee = 0n;
1113
+ let lastFee = -1n;
1114
+
1115
+ this.log(
1116
+ `setFeeOutput: Starting fee calculation for change. Initial available value: ${initialValue} sats.`,
1117
+ );
1118
+
1119
+ for (let i = 0; i < 3 && estimatedFee !== lastFee; i++) {
1120
+ lastFee = estimatedFee;
1121
+ estimatedFee = await this.estimateTransactionFees();
1122
+ const valueLeft = BigInt(initialValue) - estimatedFee;
1123
+
1124
+ if (this.debugFees) {
1125
+ this.log(
1126
+ ` -> Iteration ${i + 1}: Estimated fee is ${estimatedFee} sats. Value left for change: ${valueLeft} sats.`,
1127
+ );
1128
+ }
1129
+
1130
+ if (valueLeft >= TransactionBuilder.MINIMUM_DUST) {
1131
+ this.feeOutput = { ...output, value: Number(valueLeft) };
1132
+ this.overflowFees = valueLeft;
1133
+ } else {
1134
+ this.feeOutput = null;
1135
+ this.overflowFees = 0n;
1136
+ // Re-estimate fee one last time without the change output
1137
+ estimatedFee = await this.estimateTransactionFees();
1138
+
1139
+ if (this.debugFees) {
1140
+ this.log(
1141
+ ` -> Change is less than dust. Final fee without change output: ${estimatedFee} sats.`,
1142
+ );
1143
+ }
1144
+ }
1145
+ }
1146
+
1147
+ const finalValueLeft = BigInt(initialValue) - estimatedFee;
1148
+
1149
+ if (finalValueLeft < 0) {
1150
+ throw new Error(
1151
+ `setFeeOutput: Insufficient funds to pay the fees. Required fee: ${estimatedFee}, Available: ${initialValue}. Total input: ${this.totalInputAmount} sat`,
1152
+ );
1153
+ }
1154
+
1155
+ if (finalValueLeft >= TransactionBuilder.MINIMUM_DUST) {
1156
+ this.feeOutput = { ...output, value: Number(finalValueLeft) };
1157
+ this.overflowFees = finalValueLeft;
1158
+ if (this.debugFees) {
1159
+ this.log(
1160
+ `setFeeOutput: Final change output set to ${finalValueLeft} sats. Final fee: ${estimatedFee} sats.`,
1161
+ );
1162
+ }
1163
+ } else {
1164
+ this.warn(
1165
+ `Amount to send back (${finalValueLeft} sat) is less than the minimum dust (${TransactionBuilder.MINIMUM_DUST} sat), it will be consumed in fees instead.`,
1166
+ );
1167
+ this.feeOutput = null;
1168
+ this.overflowFees = 0n;
1169
+ }
1170
+ }
1171
+ /*protected async setFeeOutput(output: PsbtOutputExtended): Promise<void> {
1172
+ const initialValue = output.value;
791
1173
 
792
1174
  const fee = await this.estimateTransactionFees();
793
1175
  output.value = initialValue - Number(fee);
@@ -819,7 +1201,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
819
1201
 
820
1202
  this.overflowFees = BigInt(valueLeft);
821
1203
  }
822
- }
1204
+ }*/
823
1205
 
824
1206
  /**
825
1207
  * Builds the transaction.
@@ -11,6 +11,7 @@ export interface LoadedStorage {
11
11
  export interface ITransactionParameters extends ITweakedTransactionData {
12
12
  readonly from?: string;
13
13
  readonly to?: string;
14
+ readonly debugFees?: boolean;
14
15
 
15
16
  utxos: UTXO[];
16
17