@btc-vision/transaction 1.6.6 → 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/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 +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 +432 -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,169 @@ 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 && input.nonWitnessUtxo) {
|
|
244
|
+
return {
|
|
245
|
+
finalScriptSig: bitcoin.script.compile([dummyEcdsaSig, dummyCompressedPubkey]),
|
|
246
|
+
finalScriptWitness: undefined,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (input.witnessScript) {
|
|
250
|
+
if (this.csvInputIndices.has(inputIndex)) {
|
|
251
|
+
return {
|
|
252
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
253
|
+
dummyEcdsaSig,
|
|
254
|
+
input.witnessScript,
|
|
255
|
+
]),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if (input.redeemScript) {
|
|
259
|
+
const dummyWitness = [dummyEcdsaSig, input.witnessScript];
|
|
260
|
+
return {
|
|
261
|
+
finalScriptSig: input.redeemScript,
|
|
262
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(dummyWitness),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const decompiled = bitcoin.script.decompile(input.witnessScript);
|
|
266
|
+
if (decompiled && decompiled.length >= 4) {
|
|
267
|
+
const firstOp = decompiled[0];
|
|
268
|
+
const lastOp = decompiled[decompiled.length - 1];
|
|
269
|
+
if (typeof firstOp === 'number' &&
|
|
270
|
+
firstOp >= opcodes.OP_1 &&
|
|
271
|
+
lastOp === opcodes.OP_CHECKMULTISIG) {
|
|
272
|
+
const m = firstOp - opcodes.OP_1 + 1;
|
|
273
|
+
const signatures = [];
|
|
274
|
+
for (let i = 0; i < m; i++) {
|
|
275
|
+
signatures.push(dummyEcdsaSig);
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
279
|
+
Buffer.alloc(0),
|
|
280
|
+
...signatures,
|
|
281
|
+
input.witnessScript,
|
|
282
|
+
]),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
288
|
+
dummyEcdsaSig,
|
|
289
|
+
input.witnessScript,
|
|
290
|
+
]),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
else if (input.redeemScript) {
|
|
294
|
+
const decompiled = bitcoin.script.decompile(input.redeemScript);
|
|
295
|
+
if (decompiled &&
|
|
296
|
+
decompiled.length === 2 &&
|
|
297
|
+
decompiled[0] === opcodes.OP_0 &&
|
|
298
|
+
Buffer.isBuffer(decompiled[1]) &&
|
|
299
|
+
decompiled[1].length === 20) {
|
|
300
|
+
return {
|
|
301
|
+
finalScriptSig: input.redeemScript,
|
|
302
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
303
|
+
dummyEcdsaSig,
|
|
304
|
+
dummyCompressedPubkey,
|
|
305
|
+
]),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (input.redeemScript && !input.witnessScript && !input.witnessUtxo) {
|
|
310
|
+
return {
|
|
311
|
+
finalScriptSig: bitcoin.script.compile([dummyEcdsaSig, input.redeemScript]),
|
|
312
|
+
finalScriptWitness: undefined,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const script = input.witnessUtxo?.script;
|
|
316
|
+
if (!script)
|
|
317
|
+
return { finalScriptSig: undefined, finalScriptWitness: undefined };
|
|
318
|
+
if (input.tapInternalKey) {
|
|
319
|
+
return {
|
|
320
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
321
|
+
dummySchnorrSig,
|
|
322
|
+
]),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (script.length === 22 && script[0] === opcodes.OP_0) {
|
|
326
|
+
return {
|
|
327
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
328
|
+
dummyEcdsaSig,
|
|
329
|
+
dummyCompressedPubkey,
|
|
330
|
+
]),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (input.redeemScript?.length === 22 && input.redeemScript[0] === opcodes.OP_0) {
|
|
334
|
+
return {
|
|
335
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness([
|
|
336
|
+
dummyEcdsaSig,
|
|
337
|
+
dummyCompressedPubkey,
|
|
338
|
+
]),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return getFinalScripts(inputIndex, input, script, true, !!input.redeemScript, !!input.witnessScript);
|
|
342
|
+
};
|
|
343
|
+
try {
|
|
344
|
+
for (let i = 0; i < fakeTx.data.inputs.length; i++) {
|
|
345
|
+
const fullInput = inputs[i];
|
|
346
|
+
if (fullInput) {
|
|
347
|
+
fakeTx.finalizeInput(i, (idx) => finalizer(idx, fullInput));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
194
350
|
}
|
|
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;
|
|
351
|
+
catch (e) {
|
|
352
|
+
this.warn(`Could not finalize dummy tx: ${e.message}`);
|
|
207
353
|
}
|
|
208
|
-
|
|
209
|
-
|
|
354
|
+
const tx = fakeTx.extractTransaction(true, true);
|
|
355
|
+
const size = tx.virtualSize();
|
|
356
|
+
const fee = this.feeRate * size;
|
|
357
|
+
const finalFee = BigInt(Math.ceil(fee));
|
|
358
|
+
if (this.debugFees) {
|
|
359
|
+
this.log(`Estimating fees: feeRate=${this.feeRate}, accurate_vSize=${size}, fee=${finalFee}n`);
|
|
210
360
|
}
|
|
361
|
+
return finalFee;
|
|
211
362
|
}
|
|
212
363
|
async rebuildFromBase64(base64) {
|
|
213
364
|
this.transaction = Psbt.fromBase64(base64, {
|
|
@@ -246,34 +397,63 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
246
397
|
if (this.anchor) {
|
|
247
398
|
this.addAnchor();
|
|
248
399
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
400
|
+
let previousFee = -1n;
|
|
401
|
+
let estimatedFee = 0n;
|
|
402
|
+
let iterations = 0;
|
|
403
|
+
const maxIterations = 5;
|
|
404
|
+
while (iterations < maxIterations && estimatedFee !== previousFee) {
|
|
405
|
+
previousFee = estimatedFee;
|
|
406
|
+
estimatedFee = await this.estimateTransactionFees();
|
|
407
|
+
const totalSpent = amountSpent + estimatedFee;
|
|
408
|
+
const sendBackAmount = this.totalInputAmount - totalSpent;
|
|
409
|
+
if (this.debugFees) {
|
|
410
|
+
this.log(`Iteration ${iterations + 1}: inputAmount=${this.totalInputAmount}, totalSpent=${totalSpent}, sendBackAmount=${sendBackAmount}`);
|
|
411
|
+
}
|
|
412
|
+
if (sendBackAmount >= TransactionBuilder.MINIMUM_DUST) {
|
|
413
|
+
if (AddressVerificator.isValidP2TRAddress(this.from, this.network)) {
|
|
414
|
+
this.feeOutput = {
|
|
415
|
+
value: Number(sendBackAmount),
|
|
416
|
+
address: this.from,
|
|
417
|
+
tapInternalKey: this.internalPubKeyToXOnly(),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
else if (AddressVerificator.isValidPublicKey(this.from, this.network)) {
|
|
421
|
+
const pubKeyScript = script.compile([
|
|
422
|
+
Buffer.from(this.from.replace('0x', ''), 'hex'),
|
|
423
|
+
opcodes.OP_CHECKSIG,
|
|
424
|
+
]);
|
|
425
|
+
this.feeOutput = {
|
|
426
|
+
value: Number(sendBackAmount),
|
|
427
|
+
script: pubKeyScript,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
this.feeOutput = {
|
|
432
|
+
value: Number(sendBackAmount),
|
|
433
|
+
address: this.from,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
this.overflowFees = sendBackAmount;
|
|
267
437
|
}
|
|
268
438
|
else {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
439
|
+
this.feeOutput = null;
|
|
440
|
+
this.overflowFees = 0n;
|
|
441
|
+
if (sendBackAmount < 0n) {
|
|
442
|
+
throw new Error(`Insufficient funds: need ${totalSpent} sats but only have ${this.totalInputAmount} sats`);
|
|
443
|
+
}
|
|
444
|
+
if (this.debugFees) {
|
|
445
|
+
this.warn(`Amount to send back (${sendBackAmount} sat) is less than minimum dust...`);
|
|
446
|
+
}
|
|
273
447
|
}
|
|
274
|
-
|
|
448
|
+
iterations++;
|
|
449
|
+
}
|
|
450
|
+
if (iterations >= maxIterations) {
|
|
451
|
+
this.warn(`Fee calculation did not stabilize after ${maxIterations} iterations`);
|
|
452
|
+
}
|
|
453
|
+
this.transactionFee = estimatedFee;
|
|
454
|
+
if (this.debugFees) {
|
|
455
|
+
this.log(`Final fee: ${estimatedFee} sats, Change output: ${this.feeOutput ? `${this.feeOutput.value} sats` : 'none'}`);
|
|
275
456
|
}
|
|
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
457
|
}
|
|
278
458
|
addValueToToOutput(value) {
|
|
279
459
|
if (value < TransactionBuilder.MINIMUM_DUST) {
|
|
@@ -315,13 +495,14 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
315
495
|
return total;
|
|
316
496
|
}
|
|
317
497
|
addOptionalOutputsAndGetAmount() {
|
|
318
|
-
if (!this.optionalOutputs)
|
|
498
|
+
if (!this.optionalOutputs || this.optionalOutputsAdded)
|
|
319
499
|
return 0n;
|
|
320
500
|
let refundedFromOptionalOutputs = 0n;
|
|
321
501
|
for (let i = 0; i < this.optionalOutputs.length; i++) {
|
|
322
502
|
this.addOutput(this.optionalOutputs[i]);
|
|
323
503
|
refundedFromOptionalOutputs += BigInt(this.optionalOutputs[i].value);
|
|
324
504
|
}
|
|
505
|
+
this.optionalOutputsAdded = true;
|
|
325
506
|
return refundedFromOptionalOutputs;
|
|
326
507
|
}
|
|
327
508
|
addInputsFromUTXO() {
|
|
@@ -350,6 +531,35 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
350
531
|
updateInput(input) {
|
|
351
532
|
this.updateInputs.push(input);
|
|
352
533
|
}
|
|
534
|
+
addFeeToOutput(amountSpent, contractAddress, epochChallenge, addContractOutput) {
|
|
535
|
+
if (addContractOutput) {
|
|
536
|
+
let amountToCA;
|
|
537
|
+
if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
|
|
538
|
+
amountToCA = MINIMUM_AMOUNT_CA;
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
amountToCA = amountSpent;
|
|
542
|
+
}
|
|
543
|
+
this.addOutput({
|
|
544
|
+
value: Number(amountToCA),
|
|
545
|
+
address: contractAddress,
|
|
546
|
+
}, true);
|
|
547
|
+
if (amountToCA === MINIMUM_AMOUNT_CA &&
|
|
548
|
+
amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD) {
|
|
549
|
+
this.addOutput({
|
|
550
|
+
value: Number(amountSpent - amountToCA),
|
|
551
|
+
address: epochChallenge.address,
|
|
552
|
+
}, true);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
const amountToEpoch = amountSpent < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : amountSpent;
|
|
557
|
+
this.addOutput({
|
|
558
|
+
value: Number(amountToEpoch),
|
|
559
|
+
address: epochChallenge.address,
|
|
560
|
+
}, true);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
353
563
|
getWitness() {
|
|
354
564
|
if (!this.tapData || !this.tapData.witness) {
|
|
355
565
|
throw new Error('Witness is required');
|
|
@@ -379,28 +589,45 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
379
589
|
}
|
|
380
590
|
async setFeeOutput(output) {
|
|
381
591
|
const initialValue = output.value;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
592
|
+
this.feeOutput = null;
|
|
593
|
+
let estimatedFee = 0n;
|
|
594
|
+
let lastFee = -1n;
|
|
595
|
+
this.log(`setFeeOutput: Starting fee calculation for change. Initial available value: ${initialValue} sats.`);
|
|
596
|
+
for (let i = 0; i < 3 && estimatedFee !== lastFee; i++) {
|
|
597
|
+
lastFee = estimatedFee;
|
|
598
|
+
estimatedFee = await this.estimateTransactionFees();
|
|
599
|
+
const valueLeft = BigInt(initialValue) - estimatedFee;
|
|
600
|
+
if (this.debugFees) {
|
|
601
|
+
this.log(` -> Iteration ${i + 1}: Estimated fee is ${estimatedFee} sats. Value left for change: ${valueLeft} sats.`);
|
|
388
602
|
}
|
|
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`);
|
|
603
|
+
if (valueLeft >= TransactionBuilder.MINIMUM_DUST) {
|
|
604
|
+
this.feeOutput = { ...output, value: Number(valueLeft) };
|
|
605
|
+
this.overflowFees = valueLeft;
|
|
395
606
|
}
|
|
396
|
-
|
|
397
|
-
if (valueLeft < TransactionBuilder.MINIMUM_DUST) {
|
|
607
|
+
else {
|
|
398
608
|
this.feeOutput = null;
|
|
609
|
+
this.overflowFees = 0n;
|
|
610
|
+
estimatedFee = await this.estimateTransactionFees();
|
|
611
|
+
if (this.debugFees) {
|
|
612
|
+
this.log(` -> Change is less than dust. Final fee without change output: ${estimatedFee} sats.`);
|
|
613
|
+
}
|
|
399
614
|
}
|
|
400
|
-
|
|
401
|
-
|
|
615
|
+
}
|
|
616
|
+
const finalValueLeft = BigInt(initialValue) - estimatedFee;
|
|
617
|
+
if (finalValueLeft < 0) {
|
|
618
|
+
throw new Error(`setFeeOutput: Insufficient funds to pay the fees. Required fee: ${estimatedFee}, Available: ${initialValue}. Total input: ${this.totalInputAmount} sat`);
|
|
619
|
+
}
|
|
620
|
+
if (finalValueLeft >= TransactionBuilder.MINIMUM_DUST) {
|
|
621
|
+
this.feeOutput = { ...output, value: Number(finalValueLeft) };
|
|
622
|
+
this.overflowFees = finalValueLeft;
|
|
623
|
+
if (this.debugFees) {
|
|
624
|
+
this.log(`setFeeOutput: Final change output set to ${finalValueLeft} sats. Final fee: ${estimatedFee} sats.`);
|
|
402
625
|
}
|
|
403
|
-
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
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.`);
|
|
629
|
+
this.feeOutput = null;
|
|
630
|
+
this.overflowFees = 0n;
|
|
404
631
|
}
|
|
405
632
|
}
|
|
406
633
|
async internalBuildTransaction(transaction, checkPartialSigs = false) {
|
|
@@ -432,4 +659,4 @@ TransactionBuilder.LOCK_LEAF_SCRIPT = script.compile([
|
|
|
432
659
|
opcodes.OP_FALSE,
|
|
433
660
|
opcodes.OP_VERIFY,
|
|
434
661
|
]);
|
|
435
|
-
TransactionBuilder.MINIMUM_DUST =
|
|
662
|
+
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.7';
|