@btc-vision/transaction 1.6.5 → 1.6.7
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/MultiSignTransaction.js +2 -2
- package/build/transaction/builders/SharedInteractionTransaction.js +6 -22
- package/build/transaction/builders/TransactionBuilder.d.ts +6 -1
- package/build/transaction/builders/TransactionBuilder.js +290 -63
- package/build/transaction/interfaces/ITransactionParameters.d.ts +1 -0
- package/package.json +9 -9
- 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 -24
- package/src/transaction/builders/MultiSignTransaction.ts +2 -2
- package/src/transaction/builders/SharedInteractionTransaction.ts +10 -26
- package/src/transaction/builders/TransactionBuilder.ts +432 -64
- package/src/transaction/interfaces/ITransactionParameters.ts +1 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { P2TRPayment, PaymentType, Psbt, PsbtInput, Signer, Taptree, toXOnly, } from '@btc-vision/bitcoin';
|
|
2
2
|
import { ECPairInterface } from 'ecpair';
|
|
3
|
-
import {
|
|
3
|
+
import { MINIMUM_AMOUNT_REWARD, TransactionBuilder } from './TransactionBuilder.js';
|
|
4
4
|
import { TransactionType } from '../enums/TransactionType.js';
|
|
5
5
|
import { CalldataGenerator } from '../../generators/builders/CalldataGenerator.js';
|
|
6
6
|
import { SharedInteractionParameters } from '../interfaces/ITransactionParameters.js';
|
|
@@ -344,35 +344,19 @@ export abstract class SharedInteractionTransaction<
|
|
|
344
344
|
protected async createMineableRewardOutputs(): Promise<void> {
|
|
345
345
|
if (!this.to) throw new Error('To address is required');
|
|
346
346
|
|
|
347
|
-
const
|
|
347
|
+
const opnetFee = this.getTransactionOPNetFee();
|
|
348
348
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
amountToCA = MINIMUM_AMOUNT_CA;
|
|
352
|
-
} else {
|
|
353
|
-
amountToCA = amountSpent;
|
|
354
|
-
}
|
|
349
|
+
// Add the output to challenge address
|
|
350
|
+
this.addFeeToOutput(opnetFee, this.to, this.epochChallenge, false);
|
|
355
351
|
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
// ALWAYS SECOND.
|
|
363
|
-
if (
|
|
364
|
-
amountToCA === MINIMUM_AMOUNT_CA &&
|
|
365
|
-
amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD
|
|
366
|
-
) {
|
|
367
|
-
this.addOutput({
|
|
368
|
-
value: Number(amountSpent - amountToCA),
|
|
369
|
-
address: this.epochChallenge.address,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
352
|
+
// Get the actual amount added to outputs (might be MINIMUM_AMOUNT_REWARD if opnetFee is too small)
|
|
353
|
+
const actualOutputAmount = opnetFee < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : opnetFee;
|
|
354
|
+
|
|
355
|
+
const optionalAmount = this.addOptionalOutputsAndGetAmount();
|
|
372
356
|
|
|
373
|
-
const amount = this.addOptionalOutputsAndGetAmount();
|
|
374
357
|
if (!this.disableAutoRefund) {
|
|
375
|
-
|
|
358
|
+
// Pass the TOTAL amount spent: actual output amount + optional outputs
|
|
359
|
+
await this.addRefundOutput(actualOutputAmount + optionalAmount);
|
|
376
360
|
}
|
|
377
361
|
}
|
|
378
362
|
|
|
@@ -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,215 @@ 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 && input.nonWitnessUtxo) {
|
|
527
|
+
return {
|
|
528
|
+
finalScriptSig: bitcoin.script.compile([dummyEcdsaSig, dummyCompressedPubkey]),
|
|
529
|
+
finalScriptWitness: undefined,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
472
532
|
|
|
473
|
-
|
|
533
|
+
if (input.witnessScript) {
|
|
534
|
+
if (this.csvInputIndices.has(inputIndex)) {
|
|
535
|
+
// CSV P2WSH needs: [signature, witnessScript]
|
|
536
|
+
return {
|
|
537
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
538
|
+
dummyEcdsaSig,
|
|
539
|
+
input.witnessScript,
|
|
540
|
+
]),
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (input.redeemScript) {
|
|
545
|
+
// P2SH-P2WSH needs redeemScript in scriptSig and witness data
|
|
546
|
+
const dummyWitness = [dummyEcdsaSig, input.witnessScript];
|
|
547
|
+
return {
|
|
548
|
+
finalScriptSig: input.redeemScript,
|
|
549
|
+
finalScriptWitness:
|
|
550
|
+
TransactionBuilder.witnessStackToScriptWitness(dummyWitness),
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const decompiled = bitcoin.script.decompile(input.witnessScript);
|
|
555
|
+
if (decompiled && decompiled.length >= 4) {
|
|
556
|
+
const firstOp = decompiled[0];
|
|
557
|
+
const lastOp = decompiled[decompiled.length - 1];
|
|
558
|
+
// Check if it's M-of-N multisig
|
|
559
|
+
if (
|
|
560
|
+
typeof firstOp === 'number' &&
|
|
561
|
+
firstOp >= opcodes.OP_1 &&
|
|
562
|
+
lastOp === opcodes.OP_CHECKMULTISIG
|
|
563
|
+
) {
|
|
564
|
+
const m = firstOp - opcodes.OP_1 + 1;
|
|
565
|
+
const signatures: Buffer[] = [];
|
|
566
|
+
for (let i = 0; i < m; i++) {
|
|
567
|
+
signatures.push(dummyEcdsaSig);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
572
|
+
Buffer.alloc(0), // OP_0 due to multisig bug
|
|
573
|
+
...signatures,
|
|
574
|
+
input.witnessScript,
|
|
575
|
+
]),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
582
|
+
dummyEcdsaSig,
|
|
583
|
+
input.witnessScript,
|
|
584
|
+
]),
|
|
585
|
+
};
|
|
586
|
+
} else if (input.redeemScript) {
|
|
587
|
+
const decompiled = bitcoin.script.decompile(input.redeemScript);
|
|
588
|
+
if (
|
|
589
|
+
decompiled &&
|
|
590
|
+
decompiled.length === 2 &&
|
|
591
|
+
decompiled[0] === opcodes.OP_0 &&
|
|
592
|
+
Buffer.isBuffer(decompiled[1]) &&
|
|
593
|
+
decompiled[1].length === 20
|
|
594
|
+
) {
|
|
595
|
+
// P2SH-P2WPKH
|
|
596
|
+
return {
|
|
597
|
+
finalScriptSig: input.redeemScript,
|
|
598
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
599
|
+
dummyEcdsaSig,
|
|
600
|
+
dummyCompressedPubkey,
|
|
601
|
+
]),
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
474
605
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
606
|
+
if (input.redeemScript && !input.witnessScript && !input.witnessUtxo) {
|
|
607
|
+
// Pure P2SH needs signatures + redeemScript in scriptSig
|
|
608
|
+
return {
|
|
609
|
+
finalScriptSig: bitcoin.script.compile([dummyEcdsaSig, input.redeemScript]),
|
|
610
|
+
finalScriptWitness: undefined,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const script = input.witnessUtxo?.script;
|
|
615
|
+
if (!script) return { finalScriptSig: undefined, finalScriptWitness: undefined };
|
|
616
|
+
|
|
617
|
+
if (input.tapInternalKey) {
|
|
618
|
+
return {
|
|
619
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
620
|
+
dummySchnorrSig,
|
|
621
|
+
]),
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (script.length === 22 && script[0] === opcodes.OP_0) {
|
|
626
|
+
return {
|
|
627
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
628
|
+
dummyEcdsaSig,
|
|
629
|
+
dummyCompressedPubkey,
|
|
630
|
+
]),
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (input.redeemScript?.length === 22 && input.redeemScript[0] === opcodes.OP_0) {
|
|
635
|
+
return {
|
|
636
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
637
|
+
dummyEcdsaSig,
|
|
638
|
+
dummyCompressedPubkey,
|
|
639
|
+
]),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return getFinalScripts(
|
|
644
|
+
inputIndex,
|
|
645
|
+
input,
|
|
646
|
+
script,
|
|
647
|
+
true,
|
|
648
|
+
!!input.redeemScript,
|
|
649
|
+
!!input.witnessScript,
|
|
650
|
+
);
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
try {
|
|
654
|
+
for (let i = 0; i < fakeTx.data.inputs.length; i++) {
|
|
655
|
+
const fullInput = inputs[i];
|
|
656
|
+
if (fullInput) {
|
|
657
|
+
fakeTx.finalizeInput(i, (idx: number) => finalizer(idx, fullInput));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
} catch (e) {
|
|
661
|
+
this.warn(`Could not finalize dummy tx: ${(e as Error).message}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const tx = fakeTx.extractTransaction(true, true);
|
|
665
|
+
const size = tx.virtualSize();
|
|
666
|
+
const fee = this.feeRate * size;
|
|
667
|
+
const finalFee = BigInt(Math.ceil(fee));
|
|
668
|
+
|
|
669
|
+
if (this.debugFees) {
|
|
670
|
+
this.log(
|
|
671
|
+
`Estimating fees: feeRate=${this.feeRate}, accurate_vSize=${size}, fee=${finalFee}n`,
|
|
479
672
|
);
|
|
480
673
|
}
|
|
674
|
+
return finalFee;
|
|
481
675
|
}
|
|
482
676
|
|
|
483
677
|
public async rebuildFromBase64(base64: string): Promise<Psbt> {
|
|
@@ -529,12 +723,6 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
529
723
|
return total;
|
|
530
724
|
}
|
|
531
725
|
|
|
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
726
|
protected async addRefundOutput(amountSpent: bigint): Promise<void> {
|
|
539
727
|
if (this.note) {
|
|
540
728
|
this.addOPReturn(this.note);
|
|
@@ -544,38 +732,92 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
544
732
|
this.addAnchor();
|
|
545
733
|
}
|
|
546
734
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
735
|
+
// Initialize variables for iteration
|
|
736
|
+
let previousFee = -1n;
|
|
737
|
+
let estimatedFee = 0n;
|
|
738
|
+
let iterations = 0;
|
|
739
|
+
const maxIterations = 5; // Prevent infinite loops
|
|
740
|
+
|
|
741
|
+
// Iterate until fee stabilizes
|
|
742
|
+
while (iterations < maxIterations && estimatedFee !== previousFee) {
|
|
743
|
+
previousFee = estimatedFee;
|
|
744
|
+
|
|
745
|
+
// Calculate the fee with current outputs
|
|
746
|
+
estimatedFee = await this.estimateTransactionFees();
|
|
747
|
+
|
|
748
|
+
// Total amount that needs to be spent (outputs + fee)
|
|
749
|
+
const totalSpent = amountSpent + estimatedFee;
|
|
750
|
+
|
|
751
|
+
// Calculate refund
|
|
752
|
+
const sendBackAmount = this.totalInputAmount - totalSpent;
|
|
753
|
+
|
|
754
|
+
if (this.debugFees) {
|
|
755
|
+
this.log(
|
|
756
|
+
`Iteration ${iterations + 1}: inputAmount=${this.totalInputAmount}, totalSpent=${totalSpent}, sendBackAmount=${sendBackAmount}`,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Determine if we should add a change output
|
|
761
|
+
if (sendBackAmount >= TransactionBuilder.MINIMUM_DUST) {
|
|
762
|
+
// Create the appropriate change output
|
|
763
|
+
if (AddressVerificator.isValidP2TRAddress(this.from, this.network)) {
|
|
764
|
+
this.feeOutput = {
|
|
765
|
+
value: Number(sendBackAmount),
|
|
766
|
+
address: this.from,
|
|
767
|
+
tapInternalKey: this.internalPubKeyToXOnly(),
|
|
768
|
+
};
|
|
769
|
+
} else if (AddressVerificator.isValidPublicKey(this.from, this.network)) {
|
|
770
|
+
const pubKeyScript = script.compile([
|
|
771
|
+
Buffer.from(this.from.replace('0x', ''), 'hex'),
|
|
772
|
+
opcodes.OP_CHECKSIG,
|
|
773
|
+
]);
|
|
774
|
+
|
|
775
|
+
this.feeOutput = {
|
|
776
|
+
value: Number(sendBackAmount),
|
|
777
|
+
script: pubKeyScript,
|
|
778
|
+
};
|
|
779
|
+
} else {
|
|
780
|
+
this.feeOutput = {
|
|
781
|
+
value: Number(sendBackAmount),
|
|
782
|
+
address: this.from,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Set overflowFees when we have a change output
|
|
787
|
+
this.overflowFees = sendBackAmount;
|
|
566
788
|
} else {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
789
|
+
// No change output if below dust
|
|
790
|
+
this.feeOutput = null;
|
|
791
|
+
this.overflowFees = 0n;
|
|
792
|
+
|
|
793
|
+
if (sendBackAmount < 0n) {
|
|
794
|
+
throw new Error(
|
|
795
|
+
`Insufficient funds: need ${totalSpent} sats but only have ${this.totalInputAmount} sats`,
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (this.debugFees) {
|
|
800
|
+
this.warn(
|
|
801
|
+
`Amount to send back (${sendBackAmount} sat) is less than minimum dust...`,
|
|
802
|
+
);
|
|
803
|
+
}
|
|
571
804
|
}
|
|
572
805
|
|
|
573
|
-
|
|
806
|
+
iterations++;
|
|
574
807
|
}
|
|
575
808
|
|
|
576
|
-
|
|
577
|
-
`
|
|
578
|
-
|
|
809
|
+
if (iterations >= maxIterations) {
|
|
810
|
+
this.warn(`Fee calculation did not stabilize after ${maxIterations} iterations`);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Store the final fee
|
|
814
|
+
this.transactionFee = estimatedFee;
|
|
815
|
+
|
|
816
|
+
if (this.debugFees) {
|
|
817
|
+
this.log(
|
|
818
|
+
`Final fee: ${estimatedFee} sats, Change output: ${this.feeOutput ? `${this.feeOutput.value} sats` : 'none'}`,
|
|
819
|
+
);
|
|
820
|
+
}
|
|
579
821
|
}
|
|
580
822
|
|
|
581
823
|
/**
|
|
@@ -657,14 +899,17 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
657
899
|
* @returns {bigint}
|
|
658
900
|
*/
|
|
659
901
|
protected addOptionalOutputsAndGetAmount(): bigint {
|
|
660
|
-
if (!this.optionalOutputs) return 0n;
|
|
902
|
+
if (!this.optionalOutputs || this.optionalOutputsAdded) return 0n;
|
|
661
903
|
|
|
662
|
-
let refundedFromOptionalOutputs = 0n;
|
|
904
|
+
let refundedFromOptionalOutputs: bigint = 0n;
|
|
663
905
|
|
|
664
906
|
for (let i = 0; i < this.optionalOutputs.length; i++) {
|
|
665
907
|
this.addOutput(this.optionalOutputs[i]);
|
|
666
908
|
refundedFromOptionalOutputs += BigInt(this.optionalOutputs[i].value);
|
|
667
909
|
}
|
|
910
|
+
|
|
911
|
+
this.optionalOutputsAdded = true;
|
|
912
|
+
|
|
668
913
|
return refundedFromOptionalOutputs;
|
|
669
914
|
}
|
|
670
915
|
|
|
@@ -732,6 +977,66 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
732
977
|
this.updateInputs.push(input);
|
|
733
978
|
}
|
|
734
979
|
|
|
980
|
+
/**
|
|
981
|
+
* Adds the fee to the output.
|
|
982
|
+
* @param amountSpent
|
|
983
|
+
* @param contractAddress
|
|
984
|
+
* @param epochChallenge
|
|
985
|
+
* @param addContractOutput
|
|
986
|
+
* @protected
|
|
987
|
+
*/
|
|
988
|
+
protected addFeeToOutput(
|
|
989
|
+
amountSpent: bigint,
|
|
990
|
+
contractAddress: string,
|
|
991
|
+
epochChallenge: IP2WSHAddress,
|
|
992
|
+
addContractOutput: boolean,
|
|
993
|
+
): void {
|
|
994
|
+
if (addContractOutput) {
|
|
995
|
+
let amountToCA: bigint;
|
|
996
|
+
if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
|
|
997
|
+
amountToCA = MINIMUM_AMOUNT_CA;
|
|
998
|
+
} else {
|
|
999
|
+
amountToCA = amountSpent;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// ALWAYS THE FIRST INPUT.
|
|
1003
|
+
this.addOutput(
|
|
1004
|
+
{
|
|
1005
|
+
value: Number(amountToCA),
|
|
1006
|
+
address: contractAddress,
|
|
1007
|
+
},
|
|
1008
|
+
true,
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
// ALWAYS SECOND.
|
|
1012
|
+
if (
|
|
1013
|
+
amountToCA === MINIMUM_AMOUNT_CA &&
|
|
1014
|
+
amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD
|
|
1015
|
+
) {
|
|
1016
|
+
this.addOutput(
|
|
1017
|
+
{
|
|
1018
|
+
value: Number(amountSpent - amountToCA),
|
|
1019
|
+
address: epochChallenge.address,
|
|
1020
|
+
},
|
|
1021
|
+
true,
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
} else {
|
|
1025
|
+
// When SEND_AMOUNT_TO_CA is false, always send to epochChallenge
|
|
1026
|
+
// Use the maximum of amountSpent or MINIMUM_AMOUNT_REWARD
|
|
1027
|
+
const amountToEpoch =
|
|
1028
|
+
amountSpent < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : amountSpent;
|
|
1029
|
+
|
|
1030
|
+
this.addOutput(
|
|
1031
|
+
{
|
|
1032
|
+
value: Number(amountToEpoch),
|
|
1033
|
+
address: epochChallenge.address,
|
|
1034
|
+
},
|
|
1035
|
+
true,
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
735
1040
|
/**
|
|
736
1041
|
* Returns the witness of the tap transaction.
|
|
737
1042
|
* @protected
|
|
@@ -788,6 +1093,69 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
788
1093
|
*/
|
|
789
1094
|
protected async setFeeOutput(output: PsbtOutputExtended): Promise<void> {
|
|
790
1095
|
const initialValue = output.value;
|
|
1096
|
+
this.feeOutput = null; // Start with no fee output
|
|
1097
|
+
|
|
1098
|
+
let estimatedFee = 0n;
|
|
1099
|
+
let lastFee = -1n;
|
|
1100
|
+
|
|
1101
|
+
this.log(
|
|
1102
|
+
`setFeeOutput: Starting fee calculation for change. Initial available value: ${initialValue} sats.`,
|
|
1103
|
+
);
|
|
1104
|
+
|
|
1105
|
+
for (let i = 0; i < 3 && estimatedFee !== lastFee; i++) {
|
|
1106
|
+
lastFee = estimatedFee;
|
|
1107
|
+
estimatedFee = await this.estimateTransactionFees();
|
|
1108
|
+
const valueLeft = BigInt(initialValue) - estimatedFee;
|
|
1109
|
+
|
|
1110
|
+
if (this.debugFees) {
|
|
1111
|
+
this.log(
|
|
1112
|
+
` -> Iteration ${i + 1}: Estimated fee is ${estimatedFee} sats. Value left for change: ${valueLeft} sats.`,
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
if (valueLeft >= TransactionBuilder.MINIMUM_DUST) {
|
|
1117
|
+
this.feeOutput = { ...output, value: Number(valueLeft) };
|
|
1118
|
+
this.overflowFees = valueLeft;
|
|
1119
|
+
} else {
|
|
1120
|
+
this.feeOutput = null;
|
|
1121
|
+
this.overflowFees = 0n;
|
|
1122
|
+
// Re-estimate fee one last time without the change output
|
|
1123
|
+
estimatedFee = await this.estimateTransactionFees();
|
|
1124
|
+
|
|
1125
|
+
if (this.debugFees) {
|
|
1126
|
+
this.log(
|
|
1127
|
+
` -> Change is less than dust. Final fee without change output: ${estimatedFee} sats.`,
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const finalValueLeft = BigInt(initialValue) - estimatedFee;
|
|
1134
|
+
|
|
1135
|
+
if (finalValueLeft < 0) {
|
|
1136
|
+
throw new Error(
|
|
1137
|
+
`setFeeOutput: Insufficient funds to pay the fees. Required fee: ${estimatedFee}, Available: ${initialValue}. Total input: ${this.totalInputAmount} sat`,
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
if (finalValueLeft >= TransactionBuilder.MINIMUM_DUST) {
|
|
1142
|
+
this.feeOutput = { ...output, value: Number(finalValueLeft) };
|
|
1143
|
+
this.overflowFees = finalValueLeft;
|
|
1144
|
+
if (this.debugFees) {
|
|
1145
|
+
this.log(
|
|
1146
|
+
`setFeeOutput: Final change output set to ${finalValueLeft} sats. Final fee: ${estimatedFee} sats.`,
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
} else {
|
|
1150
|
+
this.warn(
|
|
1151
|
+
`Amount to send back (${finalValueLeft} sat) is less than the minimum dust (${TransactionBuilder.MINIMUM_DUST} sat), it will be consumed in fees instead.`,
|
|
1152
|
+
);
|
|
1153
|
+
this.feeOutput = null;
|
|
1154
|
+
this.overflowFees = 0n;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
/*protected async setFeeOutput(output: PsbtOutputExtended): Promise<void> {
|
|
1158
|
+
const initialValue = output.value;
|
|
791
1159
|
|
|
792
1160
|
const fee = await this.estimateTransactionFees();
|
|
793
1161
|
output.value = initialValue - Number(fee);
|
|
@@ -819,7 +1187,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
819
1187
|
|
|
820
1188
|
this.overflowFees = BigInt(valueLeft);
|
|
821
1189
|
}
|
|
822
|
-
}
|
|
1190
|
+
}*/
|
|
823
1191
|
|
|
824
1192
|
/**
|
|
825
1193
|
* Builds the transaction.
|