@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
|
@@ -25,6 +25,9 @@ import { WindowWithWallets } from './browser/extensions/UnisatSigner.js';
|
|
|
25
25
|
import { RawChallenge } from '../epoch/interfaces/IChallengeSolution.js';
|
|
26
26
|
import { P2WDADetector } from '../p2wda/P2WDADetector.js';
|
|
27
27
|
import { InteractionTransactionP2WDA } from './builders/InteractionTransactionP2WDA.js';
|
|
28
|
+
import { ChallengeSolution } from '../epoch/ChallengeSolution.js';
|
|
29
|
+
import { Address } from '../keypair/Address.js';
|
|
30
|
+
import { BitcoinUtils } from '../utils/BitcoinUtils.js';
|
|
28
31
|
|
|
29
32
|
export interface DeploymentResult {
|
|
30
33
|
readonly transaction: [string, string];
|
|
@@ -62,6 +65,16 @@ export interface BitcoinTransferResponse extends BitcoinTransferBase {
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
export class TransactionFactory {
|
|
68
|
+
public debug: boolean = false;
|
|
69
|
+
|
|
70
|
+
private readonly DUMMY_PUBKEY = Buffer.alloc(32, 1);
|
|
71
|
+
private readonly P2TR_SCRIPT = Buffer.concat([
|
|
72
|
+
Buffer.from([0x51, 0x20]), // OP_1 + 32 bytes
|
|
73
|
+
this.DUMMY_PUBKEY,
|
|
74
|
+
]);
|
|
75
|
+
private readonly INITIAL_FUNDING_ESTIMATE = 2000n;
|
|
76
|
+
private readonly MAX_ITERATIONS = 10;
|
|
77
|
+
|
|
65
78
|
/**
|
|
66
79
|
* @description Generate a transaction with a custom script.
|
|
67
80
|
* @returns {Promise<[string, string]>} - The signed transaction
|
|
@@ -72,49 +85,49 @@ export class TransactionFactory {
|
|
|
72
85
|
if (!interactionParameters.to) {
|
|
73
86
|
throw new Error('Field "to" not provided.');
|
|
74
87
|
}
|
|
75
|
-
|
|
76
88
|
if (!interactionParameters.from) {
|
|
77
89
|
throw new Error('Field "from" not provided.');
|
|
78
90
|
}
|
|
79
|
-
|
|
80
91
|
if (!interactionParameters.utxos[0]) {
|
|
81
92
|
throw new Error('Missing at least one UTXO.');
|
|
82
93
|
}
|
|
83
|
-
|
|
84
94
|
if (!('signer' in interactionParameters)) {
|
|
85
95
|
throw new Error('Field "signer" not provided, OP_WALLET not detected.');
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
|
|
89
|
-
const preTransaction: CustomScriptTransaction = new CustomScriptTransaction({
|
|
90
|
-
...interactionParameters,
|
|
91
|
-
utxos: [interactionParameters.utxos[0]], // we simulate one input here.
|
|
92
|
-
optionalInputs: inputs,
|
|
93
|
-
});
|
|
94
99
|
|
|
95
|
-
//
|
|
96
|
-
await
|
|
100
|
+
// Use common iteration logic
|
|
101
|
+
const { finalTransaction, estimatedAmount, challenge } = await this.iterateFundingAmount(
|
|
102
|
+
{ ...interactionParameters, optionalInputs: inputs },
|
|
103
|
+
CustomScriptTransaction,
|
|
104
|
+
async (tx) => {
|
|
105
|
+
const fee = await tx.estimateTransactionFees();
|
|
106
|
+
const priorityFee = this.getPriorityFee(interactionParameters);
|
|
107
|
+
const optionalValue = tx.getOptionalOutputValue();
|
|
108
|
+
return fee + priorityFee + optionalValue;
|
|
109
|
+
},
|
|
110
|
+
'CustomScript',
|
|
111
|
+
);
|
|
97
112
|
|
|
98
113
|
const parameters: IFundingTransactionParameters =
|
|
99
|
-
await
|
|
114
|
+
await finalTransaction.getFundingTransactionParameters();
|
|
100
115
|
|
|
101
116
|
parameters.utxos = interactionParameters.utxos;
|
|
102
|
-
parameters.amount =
|
|
103
|
-
(await preTransaction.estimateTransactionFees()) +
|
|
104
|
-
this.getPriorityFee(interactionParameters) +
|
|
105
|
-
preTransaction.getOptionalOutputValue();
|
|
117
|
+
parameters.amount = estimatedAmount;
|
|
106
118
|
|
|
107
|
-
|
|
119
|
+
// Create funding transaction
|
|
120
|
+
const feeEstimationFunding = await this.createFundTransaction({
|
|
108
121
|
...parameters,
|
|
109
122
|
optionalOutputs: [],
|
|
110
123
|
optionalInputs: [],
|
|
111
124
|
});
|
|
112
125
|
|
|
113
|
-
if (!
|
|
126
|
+
if (!feeEstimationFunding) {
|
|
114
127
|
throw new Error('Could not sign funding transaction.');
|
|
115
128
|
}
|
|
116
129
|
|
|
117
|
-
parameters.estimatedFees =
|
|
130
|
+
parameters.estimatedFees = feeEstimationFunding.estimatedFees;
|
|
118
131
|
|
|
119
132
|
const signedTransaction = await this.createFundTransaction({
|
|
120
133
|
...parameters,
|
|
@@ -126,34 +139,29 @@ export class TransactionFactory {
|
|
|
126
139
|
throw new Error('Could not sign funding transaction.');
|
|
127
140
|
}
|
|
128
141
|
|
|
129
|
-
interactionParameters.utxos = this.getUTXOAsTransaction(
|
|
130
|
-
signedTransaction.tx,
|
|
131
|
-
interactionParameters.to,
|
|
132
|
-
0,
|
|
133
|
-
);
|
|
134
|
-
|
|
135
142
|
const newParams: ICustomTransactionParameters = {
|
|
136
143
|
...interactionParameters,
|
|
137
|
-
utxos:
|
|
138
|
-
|
|
139
|
-
], // always 0
|
|
140
|
-
randomBytes: preTransaction.getRndBytes(),
|
|
144
|
+
utxos: this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0),
|
|
145
|
+
randomBytes: finalTransaction.getRndBytes(),
|
|
141
146
|
nonWitnessUtxo: signedTransaction.tx.toBuffer(),
|
|
142
|
-
estimatedFees:
|
|
147
|
+
estimatedFees: finalTransaction.estimatedFees,
|
|
143
148
|
optionalInputs: inputs,
|
|
144
149
|
};
|
|
145
150
|
|
|
146
|
-
const
|
|
151
|
+
const customTransaction = new CustomScriptTransaction(newParams);
|
|
152
|
+
const outTx = await customTransaction.signTransaction();
|
|
147
153
|
|
|
148
|
-
// We have to regenerate using the new utxo
|
|
149
|
-
const outTx: Transaction = await finalTransaction.signTransaction();
|
|
150
154
|
return [
|
|
151
155
|
signedTransaction.tx.toHex(),
|
|
152
156
|
outTx.toHex(),
|
|
153
|
-
this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.from, 1),
|
|
157
|
+
this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.from, 1),
|
|
154
158
|
];
|
|
155
159
|
}
|
|
156
160
|
|
|
161
|
+
/**
|
|
162
|
+
* @description Generates the required transactions.
|
|
163
|
+
* @returns {Promise<InteractionResponse>} - The signed transaction
|
|
164
|
+
*/
|
|
157
165
|
/**
|
|
158
166
|
* @description Generates the required transactions.
|
|
159
167
|
* @returns {Promise<InteractionResponse>} - The signed transaction
|
|
@@ -164,16 +172,13 @@ export class TransactionFactory {
|
|
|
164
172
|
if (!interactionParameters.to) {
|
|
165
173
|
throw new Error('Field "to" not provided.');
|
|
166
174
|
}
|
|
167
|
-
|
|
168
175
|
if (!interactionParameters.from) {
|
|
169
176
|
throw new Error('Field "from" not provided.');
|
|
170
177
|
}
|
|
171
|
-
|
|
172
178
|
if (!interactionParameters.utxos[0]) {
|
|
173
179
|
throw new Error('Missing at least one UTXO.');
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
// If OP_WALLET is used...
|
|
177
182
|
const opWalletInteraction = await this.detectInteractionOPWallet(interactionParameters);
|
|
178
183
|
if (opWalletInteraction) {
|
|
179
184
|
return opWalletInteraction;
|
|
@@ -189,35 +194,40 @@ export class TransactionFactory {
|
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
|
|
192
|
-
const preTransaction: InteractionTransaction = new InteractionTransaction({
|
|
193
|
-
...interactionParameters,
|
|
194
|
-
utxos: [interactionParameters.utxos[0]], // we simulate one input here.
|
|
195
|
-
optionalInputs: inputs,
|
|
196
|
-
});
|
|
197
197
|
|
|
198
|
-
//
|
|
199
|
-
await
|
|
198
|
+
// Use common iteration logic
|
|
199
|
+
const { finalTransaction, estimatedAmount, challenge } = await this.iterateFundingAmount(
|
|
200
|
+
{ ...interactionParameters, optionalInputs: inputs },
|
|
201
|
+
InteractionTransaction,
|
|
202
|
+
async (tx) => {
|
|
203
|
+
const fee = await tx.estimateTransactionFees();
|
|
204
|
+
const outputsValue = tx.getTotalOutputValue();
|
|
205
|
+
return fee + outputsValue;
|
|
206
|
+
},
|
|
207
|
+
'Interaction',
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (!challenge) {
|
|
211
|
+
throw new Error('Failed to get challenge from interaction transaction');
|
|
212
|
+
}
|
|
200
213
|
|
|
201
214
|
const parameters: IFundingTransactionParameters =
|
|
202
|
-
await
|
|
215
|
+
await finalTransaction.getFundingTransactionParameters();
|
|
203
216
|
|
|
204
217
|
parameters.utxos = interactionParameters.utxos;
|
|
205
|
-
parameters.amount =
|
|
206
|
-
(await preTransaction.estimateTransactionFees()) +
|
|
207
|
-
this.getPriorityFee(interactionParameters) +
|
|
208
|
-
preTransaction.getOptionalOutputValue();
|
|
218
|
+
parameters.amount = estimatedAmount;
|
|
209
219
|
|
|
210
|
-
const
|
|
220
|
+
const feeEstimationFunding = await this.createFundTransaction({
|
|
211
221
|
...parameters,
|
|
212
222
|
optionalOutputs: [],
|
|
213
223
|
optionalInputs: [],
|
|
214
224
|
});
|
|
215
225
|
|
|
216
|
-
if (!
|
|
226
|
+
if (!feeEstimationFunding) {
|
|
217
227
|
throw new Error('Could not sign funding transaction.');
|
|
218
228
|
}
|
|
219
229
|
|
|
220
|
-
parameters.estimatedFees =
|
|
230
|
+
parameters.estimatedFees = feeEstimationFunding.estimatedFees;
|
|
221
231
|
|
|
222
232
|
const signedTransaction = await this.createFundTransaction({
|
|
223
233
|
...parameters,
|
|
@@ -229,38 +239,33 @@ export class TransactionFactory {
|
|
|
229
239
|
throw new Error('Could not sign funding transaction.');
|
|
230
240
|
}
|
|
231
241
|
|
|
232
|
-
interactionParameters.utxos = this.getUTXOAsTransaction(
|
|
233
|
-
signedTransaction.tx,
|
|
234
|
-
interactionParameters.to,
|
|
235
|
-
0,
|
|
236
|
-
);
|
|
237
|
-
|
|
238
242
|
const newParams: IInteractionParameters = {
|
|
239
243
|
...interactionParameters,
|
|
240
|
-
utxos:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
utxos: this.getUTXOAsTransaction(
|
|
245
|
+
signedTransaction.tx,
|
|
246
|
+
finalTransaction.getScriptAddress(),
|
|
247
|
+
0,
|
|
248
|
+
),
|
|
249
|
+
randomBytes: finalTransaction.getRndBytes(),
|
|
250
|
+
challenge: challenge,
|
|
245
251
|
nonWitnessUtxo: signedTransaction.tx.toBuffer(),
|
|
246
|
-
estimatedFees:
|
|
252
|
+
estimatedFees: finalTransaction.estimatedFees,
|
|
247
253
|
optionalInputs: inputs,
|
|
248
254
|
};
|
|
249
255
|
|
|
250
|
-
const
|
|
256
|
+
const interactionTx = new InteractionTransaction(newParams);
|
|
257
|
+
const outTx = await interactionTx.signTransaction();
|
|
251
258
|
|
|
252
|
-
// We have to regenerate using the new utxo
|
|
253
|
-
const outTx: Transaction = await finalTransaction.signTransaction();
|
|
254
259
|
return {
|
|
255
260
|
fundingTransaction: signedTransaction.tx.toHex(),
|
|
256
261
|
interactionTransaction: outTx.toHex(),
|
|
257
|
-
estimatedFees:
|
|
262
|
+
estimatedFees: interactionTx.transactionFee,
|
|
258
263
|
nextUTXOs: this.getUTXOAsTransaction(
|
|
259
264
|
signedTransaction.tx,
|
|
260
265
|
interactionParameters.from,
|
|
261
266
|
1,
|
|
262
|
-
),
|
|
263
|
-
challenge:
|
|
267
|
+
),
|
|
268
|
+
challenge: challenge.toRaw(),
|
|
264
269
|
};
|
|
265
270
|
}
|
|
266
271
|
|
|
@@ -282,77 +287,81 @@ export class TransactionFactory {
|
|
|
282
287
|
}
|
|
283
288
|
|
|
284
289
|
const inputs = this.parseOptionalInputs(deploymentParameters.optionalInputs);
|
|
285
|
-
const preTransaction: DeploymentTransaction = new DeploymentTransaction({
|
|
286
|
-
...deploymentParameters,
|
|
287
|
-
utxos: [deploymentParameters.utxos[0]], // we simulate one input here.
|
|
288
|
-
optionalInputs: inputs,
|
|
289
|
-
});
|
|
290
290
|
|
|
291
|
-
//
|
|
292
|
-
await
|
|
291
|
+
// Use common iteration logic
|
|
292
|
+
const { finalTransaction, estimatedAmount, challenge } = await this.iterateFundingAmount(
|
|
293
|
+
{ ...deploymentParameters, optionalInputs: inputs },
|
|
294
|
+
DeploymentTransaction,
|
|
295
|
+
async (tx) => {
|
|
296
|
+
const fee = await tx.estimateTransactionFees();
|
|
297
|
+
const priorityFee = this.getPriorityFee(deploymentParameters);
|
|
298
|
+
const optionalValue = tx.getOptionalOutputValue();
|
|
299
|
+
return fee + priorityFee + optionalValue;
|
|
300
|
+
},
|
|
301
|
+
'Deployment',
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (!challenge) {
|
|
305
|
+
throw new Error('Failed to get challenge from deployment transaction');
|
|
306
|
+
}
|
|
293
307
|
|
|
294
308
|
const parameters: IFundingTransactionParameters =
|
|
295
|
-
await
|
|
309
|
+
await finalTransaction.getFundingTransactionParameters();
|
|
296
310
|
|
|
297
311
|
parameters.utxos = deploymentParameters.utxos;
|
|
298
|
-
parameters.amount =
|
|
299
|
-
(await preTransaction.estimateTransactionFees()) +
|
|
300
|
-
this.getPriorityFee(deploymentParameters) +
|
|
301
|
-
preTransaction.getOptionalOutputValue();
|
|
312
|
+
parameters.amount = estimatedAmount;
|
|
302
313
|
|
|
303
|
-
const
|
|
314
|
+
const feeEstimationFunding = await this.createFundTransaction({
|
|
304
315
|
...parameters,
|
|
305
316
|
optionalOutputs: [],
|
|
306
317
|
optionalInputs: [],
|
|
307
318
|
});
|
|
308
319
|
|
|
309
|
-
if (!
|
|
320
|
+
if (!feeEstimationFunding) {
|
|
310
321
|
throw new Error('Could not sign funding transaction.');
|
|
311
322
|
}
|
|
312
323
|
|
|
313
|
-
parameters.estimatedFees =
|
|
324
|
+
parameters.estimatedFees = feeEstimationFunding.estimatedFees;
|
|
314
325
|
|
|
315
|
-
const fundingTransaction
|
|
326
|
+
const fundingTransaction = new FundingTransaction({
|
|
316
327
|
...parameters,
|
|
317
328
|
optionalInputs: [],
|
|
318
329
|
optionalOutputs: [],
|
|
319
330
|
});
|
|
320
331
|
|
|
321
|
-
const signedTransaction
|
|
332
|
+
const signedTransaction = await fundingTransaction.signTransaction();
|
|
322
333
|
if (!signedTransaction) {
|
|
323
334
|
throw new Error('Could not sign funding transaction.');
|
|
324
335
|
}
|
|
325
336
|
|
|
326
|
-
const out
|
|
337
|
+
const out = signedTransaction.outs[0];
|
|
327
338
|
const newUtxo: UTXO = {
|
|
328
339
|
transactionId: signedTransaction.getId(),
|
|
329
|
-
outputIndex: 0,
|
|
340
|
+
outputIndex: 0,
|
|
330
341
|
scriptPubKey: {
|
|
331
342
|
hex: out.script.toString('hex'),
|
|
332
|
-
address:
|
|
343
|
+
address: finalTransaction.getScriptAddress(),
|
|
333
344
|
},
|
|
334
345
|
value: BigInt(out.value),
|
|
335
346
|
};
|
|
336
347
|
|
|
337
348
|
const newParams: IDeploymentParameters = {
|
|
338
349
|
...deploymentParameters,
|
|
339
|
-
utxos: [newUtxo],
|
|
340
|
-
randomBytes:
|
|
341
|
-
challenge:
|
|
350
|
+
utxos: [newUtxo],
|
|
351
|
+
randomBytes: finalTransaction.getRndBytes(),
|
|
352
|
+
challenge: challenge,
|
|
342
353
|
nonWitnessUtxo: signedTransaction.toBuffer(),
|
|
343
|
-
estimatedFees:
|
|
354
|
+
estimatedFees: finalTransaction.estimatedFees,
|
|
344
355
|
optionalInputs: inputs,
|
|
345
356
|
};
|
|
346
357
|
|
|
347
|
-
const
|
|
358
|
+
const deploymentTx = new DeploymentTransaction(newParams);
|
|
359
|
+
const outTx = await deploymentTx.signTransaction();
|
|
348
360
|
|
|
349
|
-
|
|
350
|
-
const outTx: Transaction = await finalTransaction.signTransaction();
|
|
351
|
-
|
|
352
|
-
const out2: TxOutput = signedTransaction.outs[1];
|
|
361
|
+
const out2 = signedTransaction.outs[1];
|
|
353
362
|
const refundUTXO: UTXO = {
|
|
354
363
|
transactionId: signedTransaction.getId(),
|
|
355
|
-
outputIndex: 1,
|
|
364
|
+
outputIndex: 1,
|
|
356
365
|
scriptPubKey: {
|
|
357
366
|
hex: out2.script.toString('hex'),
|
|
358
367
|
address: deploymentParameters.from,
|
|
@@ -362,10 +371,10 @@ export class TransactionFactory {
|
|
|
362
371
|
|
|
363
372
|
return {
|
|
364
373
|
transaction: [signedTransaction.toHex(), outTx.toHex()],
|
|
365
|
-
contractAddress:
|
|
366
|
-
contractPubKey:
|
|
374
|
+
contractAddress: deploymentTx.getContractAddress(),
|
|
375
|
+
contractPubKey: deploymentTx.contractPubKey,
|
|
367
376
|
utxos: [refundUTXO],
|
|
368
|
-
challenge:
|
|
377
|
+
challenge: challenge.toRaw(),
|
|
369
378
|
};
|
|
370
379
|
}
|
|
371
380
|
|
|
@@ -599,6 +608,127 @@ export class TransactionFactory {
|
|
|
599
608
|
return totalFee;
|
|
600
609
|
}
|
|
601
610
|
|
|
611
|
+
/**
|
|
612
|
+
* Common iteration logic for finding the correct funding amount
|
|
613
|
+
*/
|
|
614
|
+
private async iterateFundingAmount<
|
|
615
|
+
T extends InteractionTransaction | DeploymentTransaction | CustomScriptTransaction,
|
|
616
|
+
P extends IInteractionParameters | IDeploymentParameters | ICustomTransactionParameters,
|
|
617
|
+
>(
|
|
618
|
+
params: P,
|
|
619
|
+
TransactionClass: new (params: P) => T,
|
|
620
|
+
calculateAmount: (tx: T) => Promise<bigint>,
|
|
621
|
+
debugPrefix: string,
|
|
622
|
+
): Promise<{
|
|
623
|
+
finalTransaction: T;
|
|
624
|
+
estimatedAmount: bigint;
|
|
625
|
+
challenge: ChallengeSolution | null;
|
|
626
|
+
}> {
|
|
627
|
+
const randomBytes =
|
|
628
|
+
'randomBytes' in params
|
|
629
|
+
? (params.randomBytes ?? BitcoinUtils.rndBytes())
|
|
630
|
+
: BitcoinUtils.rndBytes();
|
|
631
|
+
|
|
632
|
+
const dummyAddress = Address.dead().p2tr(params.network);
|
|
633
|
+
|
|
634
|
+
let estimatedFundingAmount = this.INITIAL_FUNDING_ESTIMATE;
|
|
635
|
+
let previousAmount = 0n;
|
|
636
|
+
let iterations = 0;
|
|
637
|
+
let finalPreTransaction: T | null = null;
|
|
638
|
+
let challenge: ChallengeSolution | null = null;
|
|
639
|
+
|
|
640
|
+
while (iterations < this.MAX_ITERATIONS && estimatedFundingAmount !== previousAmount) {
|
|
641
|
+
previousAmount = estimatedFundingAmount;
|
|
642
|
+
|
|
643
|
+
const dummyTx = new Transaction();
|
|
644
|
+
dummyTx.addOutput(this.P2TR_SCRIPT, Number(estimatedFundingAmount));
|
|
645
|
+
|
|
646
|
+
const simulatedFundedUtxo: UTXO = {
|
|
647
|
+
transactionId: Buffer.alloc(32, 0).toString('hex'),
|
|
648
|
+
outputIndex: 0,
|
|
649
|
+
scriptPubKey: {
|
|
650
|
+
hex: this.P2TR_SCRIPT.toString('hex'),
|
|
651
|
+
address: dummyAddress,
|
|
652
|
+
},
|
|
653
|
+
value: estimatedFundingAmount,
|
|
654
|
+
nonWitnessUtxo: dummyTx.toBuffer(),
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// Build transaction params - TypeScript needs explicit typing here
|
|
658
|
+
let txParams: P;
|
|
659
|
+
if ('challenge' in params && params.challenge) {
|
|
660
|
+
const withChallenge = {
|
|
661
|
+
...params,
|
|
662
|
+
utxos: [simulatedFundedUtxo],
|
|
663
|
+
randomBytes: randomBytes,
|
|
664
|
+
challenge: challenge ?? params.challenge, // Use existing or original
|
|
665
|
+
};
|
|
666
|
+
txParams = withChallenge as P;
|
|
667
|
+
} else {
|
|
668
|
+
const withoutChallenge = {
|
|
669
|
+
...params,
|
|
670
|
+
utxos: [simulatedFundedUtxo],
|
|
671
|
+
randomBytes: randomBytes,
|
|
672
|
+
};
|
|
673
|
+
txParams = withoutChallenge as P;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const preTransaction: T = new TransactionClass(txParams);
|
|
677
|
+
|
|
678
|
+
try {
|
|
679
|
+
await preTransaction.generateTransactionMinimalSignatures();
|
|
680
|
+
estimatedFundingAmount = await calculateAmount(preTransaction);
|
|
681
|
+
} catch (error: unknown) {
|
|
682
|
+
if (error instanceof Error) {
|
|
683
|
+
const match = error.message.match(/need (\d+) sats but only have (\d+) sats/);
|
|
684
|
+
if (match) {
|
|
685
|
+
estimatedFundingAmount = BigInt(match[1]);
|
|
686
|
+
if (this.debug) {
|
|
687
|
+
console.log(
|
|
688
|
+
`${debugPrefix}: Caught insufficient funds, updating to ${estimatedFundingAmount}`,
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
} else {
|
|
692
|
+
throw error;
|
|
693
|
+
}
|
|
694
|
+
} else {
|
|
695
|
+
throw new Error('Unknown error during fee estimation');
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
finalPreTransaction = preTransaction;
|
|
700
|
+
|
|
701
|
+
// Extract challenge with explicit typing
|
|
702
|
+
if (
|
|
703
|
+
'getChallenge' in preTransaction &&
|
|
704
|
+
typeof preTransaction.getChallenge === 'function'
|
|
705
|
+
) {
|
|
706
|
+
const result = preTransaction.getChallenge();
|
|
707
|
+
if (result instanceof ChallengeSolution) {
|
|
708
|
+
challenge = result;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
iterations++;
|
|
713
|
+
|
|
714
|
+
if (this.debug) {
|
|
715
|
+
console.log(
|
|
716
|
+
`${debugPrefix} Iteration ${iterations}: Previous=${previousAmount}, New=${estimatedFundingAmount}`,
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (!finalPreTransaction) {
|
|
722
|
+
throw new Error(`Failed to converge on ${debugPrefix} funding amount`);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return {
|
|
726
|
+
finalTransaction: finalPreTransaction,
|
|
727
|
+
estimatedAmount: estimatedFundingAmount,
|
|
728
|
+
challenge,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
602
732
|
private getUTXOAsTransaction(tx: Transaction, to: string, index: number): UTXO[] {
|
|
603
733
|
if (!tx.outs[index]) return [];
|
|
604
734
|
|
|
@@ -10,11 +10,7 @@ import {
|
|
|
10
10
|
Taptree,
|
|
11
11
|
toXOnly,
|
|
12
12
|
} from '@btc-vision/bitcoin';
|
|
13
|
-
import {
|
|
14
|
-
MINIMUM_AMOUNT_CA,
|
|
15
|
-
MINIMUM_AMOUNT_REWARD,
|
|
16
|
-
TransactionBuilder,
|
|
17
|
-
} from './TransactionBuilder.js';
|
|
13
|
+
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
18
14
|
import { TapLeafScript } from '../interfaces/Tap.js';
|
|
19
15
|
import {
|
|
20
16
|
DeploymentGenerator,
|
|
@@ -255,30 +251,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
255
251
|
this.addInputsFromUTXO();
|
|
256
252
|
|
|
257
253
|
const amountSpent: bigint = this.getTransactionOPNetFee();
|
|
258
|
-
|
|
259
|
-
let amountToCA: bigint;
|
|
260
|
-
if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
|
|
261
|
-
amountToCA = MINIMUM_AMOUNT_CA;
|
|
262
|
-
} else {
|
|
263
|
-
amountToCA = amountSpent;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// ALWAYS THE FIRST INPUT.
|
|
267
|
-
this.addOutput({
|
|
268
|
-
value: Number(amountToCA),
|
|
269
|
-
address: this.getContractAddress(),
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// ALWAYS SECOND.
|
|
273
|
-
if (
|
|
274
|
-
amountToCA === MINIMUM_AMOUNT_CA &&
|
|
275
|
-
amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD
|
|
276
|
-
) {
|
|
277
|
-
this.addOutput({
|
|
278
|
-
value: Number(amountSpent - amountToCA),
|
|
279
|
-
address: this.epochChallenge.address,
|
|
280
|
-
});
|
|
281
|
-
}
|
|
254
|
+
this.addFeeToOutput(amountSpent, this.getContractAddress(), this.epochChallenge, true);
|
|
282
255
|
|
|
283
256
|
await this.addRefundOutput(amountSpent + this.addOptionalOutputsAndGetAmount());
|
|
284
257
|
}
|
|
@@ -26,6 +26,7 @@ export class FundingTransaction extends TransactionBuilder<TransactionType.FUNDI
|
|
|
26
26
|
|
|
27
27
|
this.addInputsFromUTXO();
|
|
28
28
|
|
|
29
|
+
// Add the primary output(s) first
|
|
29
30
|
if (this.splitInputsInto > 1) {
|
|
30
31
|
this.splitInputs(this.amount);
|
|
31
32
|
} else if (this.isPubKeyDestination) {
|
|
@@ -45,7 +46,11 @@ export class FundingTransaction extends TransactionBuilder<TransactionType.FUNDI
|
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
// Calculate total amount needed for all outputs (including optional)
|
|
50
|
+
const totalOutputAmount = this.amount + this.addOptionalOutputsAndGetAmount();
|
|
51
|
+
|
|
52
|
+
// Add refund output - this will handle fee calculation properly
|
|
53
|
+
await this.addRefundOutput(totalOutputAmount);
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
protected splitInputs(amountSpent: bigint): void {
|
|
@@ -2,11 +2,7 @@ import { Buffer } from 'buffer';
|
|
|
2
2
|
import { Psbt, PsbtInput, toXOnly } from '@btc-vision/bitcoin';
|
|
3
3
|
import { TransactionType } from '../enums/TransactionType.js';
|
|
4
4
|
import { IInteractionParameters } from '../interfaces/ITransactionParameters.js';
|
|
5
|
-
import {
|
|
6
|
-
MINIMUM_AMOUNT_CA,
|
|
7
|
-
MINIMUM_AMOUNT_REWARD,
|
|
8
|
-
TransactionBuilder,
|
|
9
|
-
} from './TransactionBuilder.js';
|
|
5
|
+
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
10
6
|
import { MessageSigner } from '../../keypair/MessageSigner.js';
|
|
11
7
|
import { Compressor } from '../../bytecode/Compressor.js';
|
|
12
8
|
import { P2WDAGenerator } from '../../generators/builders/P2WDAGenerator.js';
|
|
@@ -153,29 +149,7 @@ export class InteractionTransactionP2WDA extends TransactionBuilder<TransactionT
|
|
|
153
149
|
|
|
154
150
|
const amountSpent: bigint = this.getTransactionOPNetFee();
|
|
155
151
|
|
|
156
|
-
|
|
157
|
-
if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
|
|
158
|
-
amountToCA = MINIMUM_AMOUNT_CA;
|
|
159
|
-
} else {
|
|
160
|
-
amountToCA = amountSpent;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ALWAYS THE FIRST INPUT.
|
|
164
|
-
this.addOutput({
|
|
165
|
-
value: Number(amountToCA),
|
|
166
|
-
address: this.to,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// ALWAYS SECOND.
|
|
170
|
-
if (
|
|
171
|
-
amountToCA === MINIMUM_AMOUNT_CA &&
|
|
172
|
-
amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD
|
|
173
|
-
) {
|
|
174
|
-
this.addOutput({
|
|
175
|
-
value: Number(amountSpent - amountToCA),
|
|
176
|
-
address: this.epochChallenge.address,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
152
|
+
this.addFeeToOutput(amountSpent, this.to, this.epochChallenge, false);
|
|
179
153
|
|
|
180
154
|
const amount = this.addOptionalOutputsAndGetAmount();
|
|
181
155
|
if (!this.disableAutoRefund) {
|
|
@@ -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
|
|