@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.
- package/browser/_version.d.ts +1 -1
- package/browser/index.js +1 -1
- package/browser/transaction/TransactionFactory.d.ts +6 -0
- package/browser/transaction/builders/TransactionBuilder.d.ts +6 -1
- package/browser/transaction/interfaces/ITransactionParameters.d.ts +1 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/transaction/TransactionFactory.d.ts +6 -0
- package/build/transaction/TransactionFactory.js +160 -70
- package/build/transaction/builders/DeploymentTransaction.js +2 -19
- package/build/transaction/builders/FundingTransaction.js +2 -1
- package/build/transaction/builders/InteractionTransactionP2WDA.js +2 -19
- package/build/transaction/builders/SharedInteractionTransaction.js +6 -22
- package/build/transaction/builders/TransactionBuilder.d.ts +6 -1
- package/build/transaction/builders/TransactionBuilder.js +302 -63
- package/build/transaction/interfaces/ITransactionParameters.d.ts +1 -0
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/transaction/TransactionFactory.ts +232 -102
- package/src/transaction/builders/DeploymentTransaction.ts +2 -29
- package/src/transaction/builders/FundingTransaction.ts +6 -1
- package/src/transaction/builders/InteractionTransactionP2WDA.ts +2 -28
- package/src/transaction/builders/SharedInteractionTransaction.ts +10 -26
- package/src/transaction/builders/TransactionBuilder.ts +446 -64
- package/src/transaction/interfaces/ITransactionParameters.ts +1 -0
|
@@ -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 =
|
|
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>}
|
|
468
|
+
* @returns {Promise<bigint>}
|
|
455
469
|
*/
|
|
456
470
|
public async estimateTransactionFees(): Promise<bigint> {
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
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
|
-
|
|
464
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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
|
-
|
|
820
|
+
iterations++;
|
|
574
821
|
}
|
|
575
822
|
|
|
576
|
-
|
|
577
|
-
`
|
|
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.
|