@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,16 +1,18 @@
|
|
|
1
|
-
import { initEccLib, opcodes, Psbt, script, Transaction, varuint, } from '@btc-vision/bitcoin';
|
|
1
|
+
import bitcoin, { getFinalScripts, initEccLib, opcodes, Psbt, script, Transaction, varuint, } from '@btc-vision/bitcoin';
|
|
2
2
|
import * as ecc from '@bitcoinerlab/secp256k1';
|
|
3
3
|
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
4
4
|
import { AddressVerificator } from '../../keypair/AddressVerificator.js';
|
|
5
5
|
import { TweakedTransaction } from '../shared/TweakedTransaction.js';
|
|
6
|
+
import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
|
|
6
7
|
initEccLib(ecc);
|
|
7
|
-
export const MINIMUM_AMOUNT_REWARD =
|
|
8
|
+
export const MINIMUM_AMOUNT_REWARD = 330n;
|
|
8
9
|
export const MINIMUM_AMOUNT_CA = 297n;
|
|
9
10
|
export const ANCHOR_SCRIPT = Buffer.from('51024e73', 'hex');
|
|
10
11
|
export class TransactionBuilder extends TweakedTransaction {
|
|
11
12
|
constructor(parameters) {
|
|
12
13
|
super(parameters);
|
|
13
14
|
this.logColor = '#785def';
|
|
15
|
+
this.debugFees = false;
|
|
14
16
|
this.overflowFees = 0n;
|
|
15
17
|
this.transactionFee = 0n;
|
|
16
18
|
this.estimatedFees = 0n;
|
|
@@ -18,6 +20,7 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
18
20
|
this.outputs = [];
|
|
19
21
|
this.feeOutput = null;
|
|
20
22
|
this._maximumFeeRate = 100000000;
|
|
23
|
+
this.optionalOutputsAdded = false;
|
|
21
24
|
if (parameters.estimatedFees) {
|
|
22
25
|
this.estimatedFees = parameters.estimatedFees;
|
|
23
26
|
}
|
|
@@ -29,6 +32,7 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
29
32
|
this.utxos = parameters.utxos;
|
|
30
33
|
this.optionalInputs = parameters.optionalInputs || [];
|
|
31
34
|
this.to = parameters.to || undefined;
|
|
35
|
+
this.debugFees = parameters.debugFees || false;
|
|
32
36
|
if (parameters.note) {
|
|
33
37
|
if (typeof parameters.note === 'string') {
|
|
34
38
|
this.note = Buffer.from(parameters.note, 'utf8');
|
|
@@ -164,7 +168,7 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
164
168
|
addInput(input) {
|
|
165
169
|
this.inputs.push(input);
|
|
166
170
|
}
|
|
167
|
-
addOutput(output) {
|
|
171
|
+
addOutput(output, bypassMinCheck = false) {
|
|
168
172
|
if (output.value === 0) {
|
|
169
173
|
const script = output;
|
|
170
174
|
if (!script.script || script.script.length === 0) {
|
|
@@ -177,11 +181,14 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
177
181
|
throw new Error('Output script must start with OP_RETURN or be an ANCHOR when value is 0');
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
|
-
else if (output.value < TransactionBuilder.MINIMUM_DUST) {
|
|
184
|
+
else if (!bypassMinCheck && output.value < TransactionBuilder.MINIMUM_DUST) {
|
|
181
185
|
throw new Error(`Output value is less than the minimum dust ${output.value} < ${TransactionBuilder.MINIMUM_DUST}`);
|
|
182
186
|
}
|
|
183
187
|
this.outputs.push(output);
|
|
184
188
|
}
|
|
189
|
+
getTotalOutputValue() {
|
|
190
|
+
return this.outputs.reduce((total, output) => total + BigInt(output.value), 0n);
|
|
191
|
+
}
|
|
185
192
|
toAddress() {
|
|
186
193
|
return this.to;
|
|
187
194
|
}
|
|
@@ -189,25 +196,181 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
189
196
|
return this.tapData?.address;
|
|
190
197
|
}
|
|
191
198
|
async estimateTransactionFees() {
|
|
192
|
-
|
|
193
|
-
|
|
199
|
+
await Promise.resolve();
|
|
200
|
+
const fakeTx = new Psbt({ network: this.network });
|
|
201
|
+
const inputs = this.getInputs();
|
|
202
|
+
const outputs = this.getOutputs();
|
|
203
|
+
fakeTx.addInputs(inputs);
|
|
204
|
+
fakeTx.addOutputs(outputs);
|
|
205
|
+
const dummySchnorrSig = Buffer.alloc(64, 0);
|
|
206
|
+
const dummyEcdsaSig = Buffer.alloc(72, 0);
|
|
207
|
+
const dummyCompressedPubkey = Buffer.alloc(33, 2);
|
|
208
|
+
const finalizer = (inputIndex, input) => {
|
|
209
|
+
if (input.isPayToAnchor || this.anchorInputIndices.has(inputIndex)) {
|
|
210
|
+
return {
|
|
211
|
+
finalScriptSig: undefined,
|
|
212
|
+
finalScriptWitness: Buffer.from([0]),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (input.witnessScript && P2WDADetector.isP2WDAWitnessScript(input.witnessScript)) {
|
|
216
|
+
const dummyDataSlots = [];
|
|
217
|
+
for (let i = 0; i < 10; i++) {
|
|
218
|
+
dummyDataSlots.push(Buffer.alloc(0));
|
|
219
|
+
}
|
|
220
|
+
const dummyEcdsaSig = Buffer.alloc(72, 0);
|
|
221
|
+
return {
|
|
222
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
223
|
+
...dummyDataSlots,
|
|
224
|
+
dummyEcdsaSig,
|
|
225
|
+
input.witnessScript,
|
|
226
|
+
]),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (inputIndex === 0 && this.tapLeafScript) {
|
|
230
|
+
const dummySecret = Buffer.alloc(32, 0);
|
|
231
|
+
const dummyScript = this.tapLeafScript.script;
|
|
232
|
+
const dummyControlBlock = Buffer.alloc(1 + 32 + 32, 0);
|
|
233
|
+
return {
|
|
234
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
235
|
+
dummySecret,
|
|
236
|
+
dummySchnorrSig,
|
|
237
|
+
dummySchnorrSig,
|
|
238
|
+
dummyScript,
|
|
239
|
+
dummyControlBlock,
|
|
240
|
+
]),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (input.witnessUtxo) {
|
|
244
|
+
const script = input.witnessUtxo.script;
|
|
245
|
+
const decompiled = bitcoin.script.decompile(script);
|
|
246
|
+
if (decompiled &&
|
|
247
|
+
decompiled.length === 5 &&
|
|
248
|
+
decompiled[0] === opcodes.OP_DUP &&
|
|
249
|
+
decompiled[1] === opcodes.OP_HASH160 &&
|
|
250
|
+
decompiled[3] === opcodes.OP_EQUALVERIFY &&
|
|
251
|
+
decompiled[4] === opcodes.OP_CHECKSIG) {
|
|
252
|
+
return {
|
|
253
|
+
finalScriptSig: bitcoin.script.compile([
|
|
254
|
+
dummyEcdsaSig,
|
|
255
|
+
dummyCompressedPubkey,
|
|
256
|
+
]),
|
|
257
|
+
finalScriptWitness: undefined,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (input.witnessScript) {
|
|
262
|
+
if (this.csvInputIndices.has(inputIndex)) {
|
|
263
|
+
return {
|
|
264
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
265
|
+
dummyEcdsaSig,
|
|
266
|
+
input.witnessScript,
|
|
267
|
+
]),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (input.redeemScript) {
|
|
271
|
+
const dummyWitness = [dummyEcdsaSig, input.witnessScript];
|
|
272
|
+
return {
|
|
273
|
+
finalScriptSig: input.redeemScript,
|
|
274
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(dummyWitness),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const decompiled = bitcoin.script.decompile(input.witnessScript);
|
|
278
|
+
if (decompiled && decompiled.length >= 4) {
|
|
279
|
+
const firstOp = decompiled[0];
|
|
280
|
+
const lastOp = decompiled[decompiled.length - 1];
|
|
281
|
+
if (typeof firstOp === 'number' &&
|
|
282
|
+
firstOp >= opcodes.OP_1 &&
|
|
283
|
+
lastOp === opcodes.OP_CHECKMULTISIG) {
|
|
284
|
+
const m = firstOp - opcodes.OP_1 + 1;
|
|
285
|
+
const signatures = [];
|
|
286
|
+
for (let i = 0; i < m; i++) {
|
|
287
|
+
signatures.push(dummyEcdsaSig);
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
291
|
+
Buffer.alloc(0),
|
|
292
|
+
...signatures,
|
|
293
|
+
input.witnessScript,
|
|
294
|
+
]),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
300
|
+
dummyEcdsaSig,
|
|
301
|
+
input.witnessScript,
|
|
302
|
+
]),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
else if (input.redeemScript) {
|
|
306
|
+
const decompiled = bitcoin.script.decompile(input.redeemScript);
|
|
307
|
+
if (decompiled &&
|
|
308
|
+
decompiled.length === 2 &&
|
|
309
|
+
decompiled[0] === opcodes.OP_0 &&
|
|
310
|
+
Buffer.isBuffer(decompiled[1]) &&
|
|
311
|
+
decompiled[1].length === 20) {
|
|
312
|
+
return {
|
|
313
|
+
finalScriptSig: input.redeemScript,
|
|
314
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
315
|
+
dummyEcdsaSig,
|
|
316
|
+
dummyCompressedPubkey,
|
|
317
|
+
]),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (input.redeemScript && !input.witnessScript && !input.witnessUtxo) {
|
|
322
|
+
return {
|
|
323
|
+
finalScriptSig: bitcoin.script.compile([dummyEcdsaSig, input.redeemScript]),
|
|
324
|
+
finalScriptWitness: undefined,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const script = input.witnessUtxo?.script;
|
|
328
|
+
if (!script)
|
|
329
|
+
return { finalScriptSig: undefined, finalScriptWitness: undefined };
|
|
330
|
+
if (input.tapInternalKey) {
|
|
331
|
+
return {
|
|
332
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
333
|
+
dummySchnorrSig,
|
|
334
|
+
]),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (script.length === 22 && script[0] === opcodes.OP_0) {
|
|
338
|
+
return {
|
|
339
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
340
|
+
dummyEcdsaSig,
|
|
341
|
+
dummyCompressedPubkey,
|
|
342
|
+
]),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (input.redeemScript?.length === 22 && input.redeemScript[0] === opcodes.OP_0) {
|
|
346
|
+
return {
|
|
347
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
348
|
+
dummyEcdsaSig,
|
|
349
|
+
dummyCompressedPubkey,
|
|
350
|
+
]),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return getFinalScripts(inputIndex, input, script, true, !!input.redeemScript, !!input.witnessScript);
|
|
354
|
+
};
|
|
355
|
+
try {
|
|
356
|
+
for (let i = 0; i < fakeTx.data.inputs.length; i++) {
|
|
357
|
+
const fullInput = inputs[i];
|
|
358
|
+
if (fullInput) {
|
|
359
|
+
fakeTx.finalizeInput(i, (idx) => finalizer(idx, fullInput));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
194
362
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const fakeTx = new Psbt({
|
|
198
|
-
network: this.network,
|
|
199
|
-
});
|
|
200
|
-
const builtTx = await this.internalBuildTransaction(fakeTx);
|
|
201
|
-
if (builtTx) {
|
|
202
|
-
const tx = fakeTx.extractTransaction(true, true);
|
|
203
|
-
const size = tx.virtualSize();
|
|
204
|
-
const fee = this.feeRate * size;
|
|
205
|
-
this.estimatedFees = BigInt(Math.ceil(fee) + 1);
|
|
206
|
-
return this.estimatedFees;
|
|
363
|
+
catch (e) {
|
|
364
|
+
this.warn(`Could not finalize dummy tx: ${e.message}`);
|
|
207
365
|
}
|
|
208
|
-
|
|
209
|
-
|
|
366
|
+
const tx = fakeTx.extractTransaction(true, true);
|
|
367
|
+
const size = tx.virtualSize();
|
|
368
|
+
const fee = this.feeRate * size;
|
|
369
|
+
const finalFee = BigInt(Math.ceil(fee));
|
|
370
|
+
if (this.debugFees) {
|
|
371
|
+
this.log(`Estimating fees: feeRate=${this.feeRate}, accurate_vSize=${size}, fee=${finalFee}n`);
|
|
210
372
|
}
|
|
373
|
+
return finalFee;
|
|
211
374
|
}
|
|
212
375
|
async rebuildFromBase64(base64) {
|
|
213
376
|
this.transaction = Psbt.fromBase64(base64, {
|
|
@@ -246,34 +409,63 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
246
409
|
if (this.anchor) {
|
|
247
410
|
this.addAnchor();
|
|
248
411
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
412
|
+
let previousFee = -1n;
|
|
413
|
+
let estimatedFee = 0n;
|
|
414
|
+
let iterations = 0;
|
|
415
|
+
const maxIterations = 5;
|
|
416
|
+
while (iterations < maxIterations && estimatedFee !== previousFee) {
|
|
417
|
+
previousFee = estimatedFee;
|
|
418
|
+
estimatedFee = await this.estimateTransactionFees();
|
|
419
|
+
const totalSpent = amountSpent + estimatedFee;
|
|
420
|
+
const sendBackAmount = this.totalInputAmount - totalSpent;
|
|
421
|
+
if (this.debugFees) {
|
|
422
|
+
this.log(`Iteration ${iterations + 1}: inputAmount=${this.totalInputAmount}, totalSpent=${totalSpent}, sendBackAmount=${sendBackAmount}`);
|
|
423
|
+
}
|
|
424
|
+
if (sendBackAmount >= TransactionBuilder.MINIMUM_DUST) {
|
|
425
|
+
if (AddressVerificator.isValidP2TRAddress(this.from, this.network)) {
|
|
426
|
+
this.feeOutput = {
|
|
427
|
+
value: Number(sendBackAmount),
|
|
428
|
+
address: this.from,
|
|
429
|
+
tapInternalKey: this.internalPubKeyToXOnly(),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
else if (AddressVerificator.isValidPublicKey(this.from, this.network)) {
|
|
433
|
+
const pubKeyScript = script.compile([
|
|
434
|
+
Buffer.from(this.from.replace('0x', ''), 'hex'),
|
|
435
|
+
opcodes.OP_CHECKSIG,
|
|
436
|
+
]);
|
|
437
|
+
this.feeOutput = {
|
|
438
|
+
value: Number(sendBackAmount),
|
|
439
|
+
script: pubKeyScript,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
this.feeOutput = {
|
|
444
|
+
value: Number(sendBackAmount),
|
|
445
|
+
address: this.from,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
this.overflowFees = sendBackAmount;
|
|
267
449
|
}
|
|
268
450
|
else {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
451
|
+
this.feeOutput = null;
|
|
452
|
+
this.overflowFees = 0n;
|
|
453
|
+
if (sendBackAmount < 0n) {
|
|
454
|
+
throw new Error(`Insufficient funds: need ${totalSpent} sats but only have ${this.totalInputAmount} sats`);
|
|
455
|
+
}
|
|
456
|
+
if (this.debugFees) {
|
|
457
|
+
this.warn(`Amount to send back (${sendBackAmount} sat) is less than minimum dust...`);
|
|
458
|
+
}
|
|
273
459
|
}
|
|
274
|
-
|
|
460
|
+
iterations++;
|
|
461
|
+
}
|
|
462
|
+
if (iterations >= maxIterations) {
|
|
463
|
+
this.warn(`Fee calculation did not stabilize after ${maxIterations} iterations`);
|
|
464
|
+
}
|
|
465
|
+
this.transactionFee = estimatedFee;
|
|
466
|
+
if (this.debugFees) {
|
|
467
|
+
this.log(`Final fee: ${estimatedFee} sats, Change output: ${this.feeOutput ? `${this.feeOutput.value} sats` : 'none'}`);
|
|
275
468
|
}
|
|
276
|
-
this.warn(`Amount to send back (${sendBackAmount} sat) is less than the minimum dust (${TransactionBuilder.MINIMUM_DUST} sat), it will be consumed in fees instead.`);
|
|
277
469
|
}
|
|
278
470
|
addValueToToOutput(value) {
|
|
279
471
|
if (value < TransactionBuilder.MINIMUM_DUST) {
|
|
@@ -315,13 +507,14 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
315
507
|
return total;
|
|
316
508
|
}
|
|
317
509
|
addOptionalOutputsAndGetAmount() {
|
|
318
|
-
if (!this.optionalOutputs)
|
|
510
|
+
if (!this.optionalOutputs || this.optionalOutputsAdded)
|
|
319
511
|
return 0n;
|
|
320
512
|
let refundedFromOptionalOutputs = 0n;
|
|
321
513
|
for (let i = 0; i < this.optionalOutputs.length; i++) {
|
|
322
514
|
this.addOutput(this.optionalOutputs[i]);
|
|
323
515
|
refundedFromOptionalOutputs += BigInt(this.optionalOutputs[i].value);
|
|
324
516
|
}
|
|
517
|
+
this.optionalOutputsAdded = true;
|
|
325
518
|
return refundedFromOptionalOutputs;
|
|
326
519
|
}
|
|
327
520
|
addInputsFromUTXO() {
|
|
@@ -350,6 +543,35 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
350
543
|
updateInput(input) {
|
|
351
544
|
this.updateInputs.push(input);
|
|
352
545
|
}
|
|
546
|
+
addFeeToOutput(amountSpent, contractAddress, epochChallenge, addContractOutput) {
|
|
547
|
+
if (addContractOutput) {
|
|
548
|
+
let amountToCA;
|
|
549
|
+
if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
|
|
550
|
+
amountToCA = MINIMUM_AMOUNT_CA;
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
amountToCA = amountSpent;
|
|
554
|
+
}
|
|
555
|
+
this.addOutput({
|
|
556
|
+
value: Number(amountToCA),
|
|
557
|
+
address: contractAddress,
|
|
558
|
+
}, true);
|
|
559
|
+
if (amountToCA === MINIMUM_AMOUNT_CA &&
|
|
560
|
+
amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD) {
|
|
561
|
+
this.addOutput({
|
|
562
|
+
value: Number(amountSpent - amountToCA),
|
|
563
|
+
address: epochChallenge.address,
|
|
564
|
+
}, true);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
const amountToEpoch = amountSpent < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : amountSpent;
|
|
569
|
+
this.addOutput({
|
|
570
|
+
value: Number(amountToEpoch),
|
|
571
|
+
address: epochChallenge.address,
|
|
572
|
+
}, true);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
353
575
|
getWitness() {
|
|
354
576
|
if (!this.tapData || !this.tapData.witness) {
|
|
355
577
|
throw new Error('Witness is required');
|
|
@@ -379,28 +601,45 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
379
601
|
}
|
|
380
602
|
async setFeeOutput(output) {
|
|
381
603
|
const initialValue = output.value;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
604
|
+
this.feeOutput = null;
|
|
605
|
+
let estimatedFee = 0n;
|
|
606
|
+
let lastFee = -1n;
|
|
607
|
+
this.log(`setFeeOutput: Starting fee calculation for change. Initial available value: ${initialValue} sats.`);
|
|
608
|
+
for (let i = 0; i < 3 && estimatedFee !== lastFee; i++) {
|
|
609
|
+
lastFee = estimatedFee;
|
|
610
|
+
estimatedFee = await this.estimateTransactionFees();
|
|
611
|
+
const valueLeft = BigInt(initialValue) - estimatedFee;
|
|
612
|
+
if (this.debugFees) {
|
|
613
|
+
this.log(` -> Iteration ${i + 1}: Estimated fee is ${estimatedFee} sats. Value left for change: ${valueLeft} sats.`);
|
|
388
614
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const fee = await this.estimateTransactionFees();
|
|
393
|
-
if (fee > BigInt(initialValue)) {
|
|
394
|
-
throw new Error(`estimateTransactionFees: Insufficient funds to pay the fees. Fee: ${fee} > Value: ${initialValue}. Total input: ${this.totalInputAmount} sat`);
|
|
615
|
+
if (valueLeft >= TransactionBuilder.MINIMUM_DUST) {
|
|
616
|
+
this.feeOutput = { ...output, value: Number(valueLeft) };
|
|
617
|
+
this.overflowFees = valueLeft;
|
|
395
618
|
}
|
|
396
|
-
|
|
397
|
-
if (valueLeft < TransactionBuilder.MINIMUM_DUST) {
|
|
619
|
+
else {
|
|
398
620
|
this.feeOutput = null;
|
|
621
|
+
this.overflowFees = 0n;
|
|
622
|
+
estimatedFee = await this.estimateTransactionFees();
|
|
623
|
+
if (this.debugFees) {
|
|
624
|
+
this.log(` -> Change is less than dust. Final fee without change output: ${estimatedFee} sats.`);
|
|
625
|
+
}
|
|
399
626
|
}
|
|
400
|
-
|
|
401
|
-
|
|
627
|
+
}
|
|
628
|
+
const finalValueLeft = BigInt(initialValue) - estimatedFee;
|
|
629
|
+
if (finalValueLeft < 0) {
|
|
630
|
+
throw new Error(`setFeeOutput: Insufficient funds to pay the fees. Required fee: ${estimatedFee}, Available: ${initialValue}. Total input: ${this.totalInputAmount} sat`);
|
|
631
|
+
}
|
|
632
|
+
if (finalValueLeft >= TransactionBuilder.MINIMUM_DUST) {
|
|
633
|
+
this.feeOutput = { ...output, value: Number(finalValueLeft) };
|
|
634
|
+
this.overflowFees = finalValueLeft;
|
|
635
|
+
if (this.debugFees) {
|
|
636
|
+
this.log(`setFeeOutput: Final change output set to ${finalValueLeft} sats. Final fee: ${estimatedFee} sats.`);
|
|
402
637
|
}
|
|
403
|
-
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
this.warn(`Amount to send back (${finalValueLeft} sat) is less than the minimum dust (${TransactionBuilder.MINIMUM_DUST} sat), it will be consumed in fees instead.`);
|
|
641
|
+
this.feeOutput = null;
|
|
642
|
+
this.overflowFees = 0n;
|
|
404
643
|
}
|
|
405
644
|
}
|
|
406
645
|
async internalBuildTransaction(transaction, checkPartialSigs = false) {
|
|
@@ -432,4 +671,4 @@ TransactionBuilder.LOCK_LEAF_SCRIPT = script.compile([
|
|
|
432
671
|
opcodes.OP_FALSE,
|
|
433
672
|
opcodes.OP_VERIFY,
|
|
434
673
|
]);
|
|
435
|
-
TransactionBuilder.MINIMUM_DUST =
|
|
674
|
+
TransactionBuilder.MINIMUM_DUST = 330n;
|
|
@@ -9,6 +9,7 @@ export interface LoadedStorage {
|
|
|
9
9
|
export interface ITransactionParameters extends ITweakedTransactionData {
|
|
10
10
|
readonly from?: string;
|
|
11
11
|
readonly to?: string;
|
|
12
|
+
readonly debugFees?: boolean;
|
|
12
13
|
utxos: UTXO[];
|
|
13
14
|
nonWitnessUtxo?: Buffer;
|
|
14
15
|
estimatedFees?: bigint;
|
package/package.json
CHANGED
package/src/_version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.6.
|
|
1
|
+
export const version = '1.6.8';
|