@btc-vision/transaction 1.0.94 → 1.0.96

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.
Files changed (32) hide show
  1. package/browser/_version.d.ts +1 -1
  2. package/browser/generators/builders/MultiSignGenerator.d.ts +1 -0
  3. package/browser/index.js +1 -1
  4. package/browser/opnet.d.ts +0 -1
  5. package/browser/transaction/browser/Web3Provider.d.ts +2 -2
  6. package/browser/transaction/builders/DeploymentTransaction.d.ts +1 -0
  7. package/browser/transaction/processor/PsbtTransaction.d.ts +0 -1
  8. package/browser/utils/BitcoinUtils.d.ts +1 -1
  9. package/build/_version.d.ts +1 -1
  10. package/build/_version.js +1 -1
  11. package/build/generators/builders/MultisignGenerator.d.ts +1 -0
  12. package/build/generators/builders/MultisignGenerator.js +4 -2
  13. package/build/opnet.d.ts +0 -1
  14. package/build/opnet.js +0 -1
  15. package/build/transaction/TransactionFactory.js +7 -2
  16. package/build/transaction/browser/Web3Provider.d.ts +2 -2
  17. package/build/transaction/builders/DeploymentTransaction.d.ts +1 -0
  18. package/build/transaction/builders/DeploymentTransaction.js +3 -0
  19. package/build/transaction/processor/PsbtTransaction.d.ts +0 -1
  20. package/build/transaction/processor/PsbtTransaction.js +0 -14
  21. package/build/utils/BitcoinUtils.d.ts +1 -1
  22. package/build/utils/BitcoinUtils.js +2 -2
  23. package/package.json +4 -5
  24. package/src/_version.ts +1 -1
  25. package/src/generators/builders/MultiSignGenerator.ts +9 -2
  26. package/src/opnet.ts +0 -3
  27. package/src/transaction/TransactionFactory.ts +559 -544
  28. package/src/transaction/browser/Web3Provider.ts +2 -2
  29. package/src/transaction/builders/DeploymentTransaction.ts +3 -0
  30. package/src/transaction/processor/PsbtTransaction.ts +0 -19
  31. package/src/utils/BitcoinUtils.ts +2 -2
  32. package/src/network/NetworkInformation.ts +0 -7
@@ -1,544 +1,559 @@
1
- import { Psbt, Transaction } from 'bitcoinjs-lib';
2
- import {
3
- IDeploymentParameters,
4
- IFundingTransactionParameters,
5
- IInteractionParameters,
6
- IUnwrapParameters,
7
- IWrapParameters,
8
- } from './interfaces/ITransactionParameters.js';
9
- import { FundingTransaction } from './builders/FundingTransaction.js';
10
- import { Output } from 'bitcoinjs-lib/src/transaction.js';
11
- import { UTXO } from '../utxo/interfaces/IUTXO.js';
12
- import { InteractionTransaction } from './builders/InteractionTransaction.js';
13
- import { DeploymentTransaction } from './builders/DeploymentTransaction.js';
14
- import { Address } from '@btc-vision/bsi-binary';
15
- import { wBTC } from '../metadata/contracts/wBTC.js';
16
- import { WrapTransaction } from './builders/WrapTransaction.js';
17
- import { PSBTTypes } from './psbt/PSBTTypes.js';
18
- import { VaultUTXOs } from './processor/PsbtTransaction.js';
19
- import { UnwrapSegwitTransaction } from './builders/UnwrapSegwitTransaction.js';
20
- import { UnwrapTransaction } from './builders/UnwrapTransaction.js';
21
- import { currentConsensus, currentConsensusConfig } from '../consensus/ConsensusConfig.js';
22
- import { TransactionBuilder } from './builders/TransactionBuilder.js';
23
- import { TransactionType } from './enums/TransactionType.js';
24
-
25
- export interface DeploymentResult {
26
- readonly transaction: [string, string];
27
-
28
- readonly contractAddress: Address;
29
- readonly p2trAddress: Address;
30
-
31
- readonly utxos: UTXO[];
32
- }
33
-
34
- export interface WrapResult {
35
- readonly transaction: [string, string];
36
- readonly vaultAddress: Address;
37
- readonly amount: bigint;
38
- readonly receiverAddress: Address;
39
- readonly utxos: UTXO[];
40
- }
41
-
42
- export interface FundingTransactionResponse {
43
- readonly tx: Transaction;
44
- readonly original: FundingTransaction;
45
- readonly estimatedFees: bigint;
46
- readonly nextUTXOs: UTXO[];
47
- }
48
-
49
- export interface BitcoinTransferResponse {
50
- readonly tx: string;
51
- readonly original: FundingTransaction;
52
- readonly estimatedFees: bigint;
53
- readonly nextUTXOs: UTXO[];
54
- }
55
-
56
- export interface UnwrapResult {
57
- readonly fundingTransaction: string;
58
- readonly psbt: string;
59
-
60
- /**
61
- * @description The fee refund or loss.
62
- * @description If the amount is negative, it means that the user has to pay the difference. The difference is deducted from the amount.
63
- * @description If the amount is positive, it means that the user will be refunded the difference.
64
- * @type {bigint}
65
- */
66
- readonly feeRefundOrLoss: bigint;
67
-
68
- readonly utxos: UTXO[];
69
- }
70
-
71
- export class TransactionFactory {
72
- constructor() {}
73
-
74
- /**
75
- * @description Generates the required transactions.
76
- * @returns {Promise<[string, string]>} - The signed transaction
77
- */
78
- public async signInteraction(
79
- interactionParameters: IInteractionParameters,
80
- ): Promise<[string, string, UTXO[]]> {
81
- if (!interactionParameters.to) {
82
- throw new Error('Field "to" not provided.');
83
- }
84
-
85
- if (!interactionParameters.from) {
86
- throw new Error('Field "from" not provided.');
87
- }
88
-
89
- const preTransaction: InteractionTransaction = new InteractionTransaction({
90
- ...interactionParameters,
91
- utxos: [interactionParameters.utxos[0]], // we simulate one input here.
92
- });
93
-
94
- // we don't sign that transaction, we just need the parameters.
95
-
96
- await preTransaction.generateTransactionMinimalSignatures();
97
-
98
- const parameters: IFundingTransactionParameters =
99
- await preTransaction.getFundingTransactionParameters();
100
-
101
- parameters.utxos = interactionParameters.utxos;
102
- parameters.amount = await preTransaction.estimateTransactionFees();
103
-
104
- const feeEstimationFundingTransaction = await this.createFundTransaction({ ...parameters });
105
- if (!feeEstimationFundingTransaction) {
106
- throw new Error('Could not sign funding transaction.');
107
- }
108
-
109
- parameters.estimatedFees = feeEstimationFundingTransaction.estimatedFees;
110
-
111
- const signedTransaction = await this.createFundTransaction(parameters);
112
- if (!signedTransaction) {
113
- throw new Error('Could not sign funding transaction.');
114
- }
115
-
116
- interactionParameters.utxos = this.getUTXOAsTransaction(
117
- signedTransaction.tx,
118
- interactionParameters.to,
119
- 0,
120
- );
121
-
122
- const newParams: IInteractionParameters = {
123
- ...interactionParameters,
124
- utxos: this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0), // always 0
125
- randomBytes: preTransaction.getRndBytes(),
126
- nonWitnessUtxo: signedTransaction.tx.toBuffer(),
127
- estimatedFees: preTransaction.estimatedFees,
128
- };
129
-
130
- const finalTransaction: InteractionTransaction = new InteractionTransaction(newParams);
131
-
132
- // We have to regenerate using the new utxo
133
- const outTx: Transaction = await finalTransaction.signTransaction();
134
-
135
- return [
136
- signedTransaction.tx.toHex(),
137
- outTx.toHex(),
138
- this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.from, 1), // always 1
139
- ];
140
- }
141
-
142
- /**
143
- * @description Generates the required transactions.
144
- * @param {IDeploymentParameters} deploymentParameters - The deployment parameters
145
- * @returns {Promise<DeploymentResult>} - The signed transaction
146
- */
147
- public async signDeployment(
148
- deploymentParameters: IDeploymentParameters,
149
- ): Promise<DeploymentResult> {
150
- const preTransaction: DeploymentTransaction = new DeploymentTransaction(
151
- deploymentParameters,
152
- );
153
-
154
- // Initial generation
155
- await preTransaction.signTransaction();
156
-
157
- const parameters: IFundingTransactionParameters =
158
- await preTransaction.getFundingTransactionParameters();
159
-
160
- const fundingTransaction: FundingTransaction = new FundingTransaction(parameters);
161
- const signedTransaction: Transaction = await fundingTransaction.signTransaction();
162
- if (!signedTransaction) {
163
- throw new Error('Could not sign funding transaction.');
164
- }
165
-
166
- const out: Output = signedTransaction.outs[0];
167
- const newUtxo: UTXO = {
168
- transactionId: signedTransaction.getId(),
169
- outputIndex: 0, // always 0
170
- scriptPubKey: {
171
- hex: out.script.toString('hex'),
172
- address: preTransaction.getScriptAddress(),
173
- },
174
- value: BigInt(out.value),
175
- };
176
-
177
- const newParams: IDeploymentParameters = {
178
- ...deploymentParameters,
179
- utxos: [newUtxo],
180
- randomBytes: preTransaction.getRndBytes(),
181
- nonWitnessUtxo: signedTransaction.toBuffer(),
182
- };
183
-
184
- const finalTransaction: DeploymentTransaction = new DeploymentTransaction(newParams);
185
-
186
- // We have to regenerate using the new utxo
187
- const outTx: Transaction = await finalTransaction.signTransaction();
188
-
189
- const out2: Output = signedTransaction.outs[1];
190
- const refundUTXO: UTXO = {
191
- transactionId: signedTransaction.getId(),
192
- outputIndex: 1, // always 1
193
- scriptPubKey: {
194
- hex: out2.script.toString('hex'),
195
- address: deploymentParameters.from,
196
- },
197
- value: BigInt(out2.value),
198
- };
199
-
200
- return {
201
- transaction: [signedTransaction.toHex(), outTx.toHex()],
202
- contractAddress: finalTransaction.contractAddress,
203
- p2trAddress: finalTransaction.p2trAddress,
204
- utxos: [refundUTXO],
205
- };
206
- }
207
-
208
- /**
209
- * Basically it's fun to manage UTXOs.
210
- * @param {IWrapParameters} wrapParameters - The wrap parameters
211
- * @returns {Promise<WrapResult>} - The signed transaction
212
- * @throws {Error} - If the transaction could not be signed
213
- */
214
- public async wrap(wrapParameters: Omit<IWrapParameters, 'calldata'>): Promise<WrapResult> {
215
- if (wrapParameters.amount < currentConsensusConfig.VAULT_MINIMUM_AMOUNT) {
216
- throw new Error(
217
- `Amount is too low. Minimum consolidation is ${currentConsensusConfig.VAULT_MINIMUM_AMOUNT} sat. Received ${wrapParameters.amount} sat. Make sure that you cover the unwrap consolidation fees of ${currentConsensusConfig.UNWRAP_CONSOLIDATION_PREPAID_FEES_SAT}sat.`,
218
- );
219
- }
220
-
221
- const childTransactionRequiredValue: bigint =
222
- wrapParameters.amount +
223
- currentConsensusConfig.UNWRAP_CONSOLIDATION_PREPAID_FEES_SAT +
224
- (wrapParameters.priorityFee || 300n);
225
-
226
- const wbtc: wBTC = new wBTC(wrapParameters.network, wrapParameters.chainId);
227
- const to = wbtc.getAddress();
228
- const fundingParameters: IFundingTransactionParameters = {
229
- ...wrapParameters,
230
- amount: childTransactionRequiredValue,
231
- to: wrapParameters.to ?? to,
232
- };
233
-
234
- const preFundingTransaction = await this.createFundTransaction(fundingParameters);
235
- wrapParameters.utxos = this.getUTXOAsTransaction(preFundingTransaction.tx, to, 0);
236
-
237
- const preTransaction: WrapTransaction = new WrapTransaction(wrapParameters);
238
-
239
- // Initial generation
240
- await preTransaction.signTransaction();
241
-
242
- const parameters: IFundingTransactionParameters =
243
- await preTransaction.getFundingTransactionParameters();
244
-
245
- // We add the amount
246
- parameters.amount += childTransactionRequiredValue;
247
- parameters.utxos = fundingParameters.utxos;
248
-
249
- const signedTransaction = await this.createFundTransaction(parameters);
250
- if (!signedTransaction) {
251
- throw new Error('Could not sign funding transaction.');
252
- }
253
-
254
- const newParams: IWrapParameters = {
255
- ...wrapParameters,
256
- utxos: this.getUTXOAsTransaction(signedTransaction.tx, to, 0), // always 0
257
- randomBytes: preTransaction.getRndBytes(),
258
- nonWitnessUtxo: signedTransaction.tx.toBuffer(),
259
- };
260
-
261
- const finalTransaction: WrapTransaction = new WrapTransaction(newParams);
262
-
263
- // We have to regenerate using the new utxo
264
- const outTx: Transaction = await finalTransaction.signTransaction();
265
- return {
266
- transaction: [signedTransaction.tx.toHex(), outTx.toHex()],
267
- vaultAddress: finalTransaction.vault,
268
- amount: finalTransaction.amount,
269
- receiverAddress: finalTransaction.receiver,
270
- utxos: this.getUTXOAsTransaction(signedTransaction.tx, wrapParameters.from, 1),
271
- };
272
- }
273
-
274
- /**
275
- * Unwrap bitcoin.
276
- * @param {IUnwrapParameters} unwrapParameters - The unwrap parameters
277
- * @returns {Promise<UnwrapResult>} - The signed transaction
278
- * @throws {Error} - If the transaction could not be signed
279
- * @deprecated
280
- */
281
- public async unwrapSegwit(unwrapParameters: IUnwrapParameters): Promise<UnwrapResult> {
282
- console.error('The "unwrap" method is deprecated. Use unwrapTap instead.');
283
-
284
- const transaction: UnwrapSegwitTransaction = new UnwrapSegwitTransaction(unwrapParameters);
285
- await transaction.signTransaction();
286
-
287
- const to = transaction.toAddress();
288
- if (!to) throw new Error('To address is required');
289
-
290
- // Initial generation
291
- const estimatedGas = await transaction.estimateTransactionFees();
292
- const estimatedFees = transaction.preEstimateTransactionFees(
293
- BigInt(unwrapParameters.feeRate),
294
- this.calculateNumInputs(unwrapParameters.unwrapUTXOs),
295
- 2n,
296
- this.calculateNumSignatures(unwrapParameters.unwrapUTXOs),
297
- this.maxPubKeySize(unwrapParameters.unwrapUTXOs),
298
- );
299
-
300
- const fundingParameters: IFundingTransactionParameters = {
301
- ...unwrapParameters,
302
- amount: estimatedGas + estimatedFees,
303
- to: to,
304
- };
305
-
306
- const preFundingTransaction = await this.createFundTransaction(fundingParameters);
307
- unwrapParameters.utxos = this.getUTXOAsTransaction(preFundingTransaction.tx, to, 0);
308
-
309
- const preTransaction: UnwrapSegwitTransaction = new UnwrapSegwitTransaction({
310
- ...unwrapParameters,
311
- randomBytes: transaction.getRndBytes(),
312
- });
313
-
314
- // Initial generation
315
- await preTransaction.signTransaction();
316
-
317
- const parameters: IFundingTransactionParameters =
318
- await preTransaction.getFundingTransactionParameters();
319
-
320
- parameters.utxos = fundingParameters.utxos;
321
- parameters.amount = (await preTransaction.estimateTransactionFees()) + estimatedFees;
322
-
323
- const signedTransaction = await this.createFundTransaction(parameters);
324
- if (!signedTransaction) {
325
- throw new Error('Could not sign funding transaction.');
326
- }
327
-
328
- const newParams: IUnwrapParameters = {
329
- ...unwrapParameters,
330
- utxos: this.getUTXOAsTransaction(signedTransaction.tx, to, 0), // always 0
331
- randomBytes: preTransaction.getRndBytes(),
332
- nonWitnessUtxo: signedTransaction.tx.toBuffer(),
333
- };
334
-
335
- const finalTransaction: UnwrapSegwitTransaction = new UnwrapSegwitTransaction(newParams);
336
-
337
- // We have to regenerate using the new utxo
338
- const outTx: Psbt = await finalTransaction.signPSBT();
339
- const asBase64 = outTx.toBase64();
340
- const psbt = this.writePSBTHeader(PSBTTypes.UNWRAP, asBase64);
341
-
342
- return {
343
- fundingTransaction: signedTransaction.tx.toHex(),
344
- psbt: psbt,
345
- feeRefundOrLoss: estimatedFees,
346
- utxos: [],
347
- };
348
- }
349
-
350
- /**
351
- * Unwrap bitcoin via taproot.
352
- * @param {IUnwrapParameters} unwrapParameters - The unwrap parameters
353
- * @returns {Promise<UnwrapResult>} - The signed transaction
354
- * @throws {Error} - If the transaction could not be signed
355
- */
356
- public async unwrap(unwrapParameters: IUnwrapParameters): Promise<UnwrapResult> {
357
- if (!unwrapParameters.from) {
358
- throw new Error('Field "from" not provided.');
359
- }
360
-
361
- const transaction: UnwrapTransaction = new UnwrapTransaction(unwrapParameters);
362
- await transaction.signTransaction();
363
-
364
- const to = transaction.toAddress();
365
- if (!to) throw new Error('To address is required');
366
-
367
- // Initial generation
368
- const estimatedGas = await transaction.estimateTransactionFees();
369
- const fundingParameters: IFundingTransactionParameters = {
370
- ...unwrapParameters,
371
- amount: estimatedGas,
372
- to: to,
373
- };
374
-
375
- const preFundingTransaction = await this.createFundTransaction(fundingParameters);
376
- unwrapParameters.utxos = this.getUTXOAsTransaction(preFundingTransaction.tx, to, 0);
377
-
378
- const preTransaction: UnwrapTransaction = new UnwrapTransaction({
379
- ...unwrapParameters,
380
- randomBytes: transaction.getRndBytes(),
381
- });
382
-
383
- // Initial generation
384
- await preTransaction.signTransaction();
385
-
386
- const parameters: IFundingTransactionParameters =
387
- await preTransaction.getFundingTransactionParameters();
388
-
389
- parameters.utxos = fundingParameters.utxos;
390
- parameters.amount = await preTransaction.estimateTransactionFees();
391
-
392
- const signedTransaction = await this.createFundTransaction(parameters);
393
- if (!signedTransaction) {
394
- throw new Error('Could not sign funding transaction.');
395
- }
396
-
397
- const newParams: IUnwrapParameters = {
398
- ...unwrapParameters,
399
- utxos: this.getUTXOAsTransaction(signedTransaction.tx, to, 0), // always 0
400
- randomBytes: preTransaction.getRndBytes(),
401
- nonWitnessUtxo: signedTransaction.tx.toBuffer(),
402
- };
403
-
404
- const finalTransaction: UnwrapTransaction = new UnwrapTransaction(newParams);
405
-
406
- // We have to regenerate using the new utxo
407
- const outTx: Psbt = await finalTransaction.signPSBT();
408
- const asBase64 = outTx.toBase64();
409
- const psbt = this.writePSBTHeader(PSBTTypes.UNWRAP, asBase64);
410
-
411
- return {
412
- fundingTransaction: signedTransaction.tx.toHex(),
413
- psbt: psbt,
414
- feeRefundOrLoss: finalTransaction.getFeeLossOrRefund(),
415
- utxos: this.getUTXOAsTransaction(signedTransaction.tx, unwrapParameters.from, 1),
416
- };
417
- }
418
-
419
- /**
420
- * @description Creates a funding transaction.
421
- * @param {IFundingTransactionParameters} parameters - The funding transaction parameters
422
- * @returns {Promise<{ estimatedFees: bigint; tx: string }>} - The signed transaction
423
- */
424
- public async createBTCTransfer(
425
- parameters: IFundingTransactionParameters,
426
- ): Promise<BitcoinTransferResponse> {
427
- if (!parameters.from) {
428
- throw new Error('Field "from" not provided.');
429
- }
430
-
431
- const resp = await this.createFundTransaction(parameters);
432
-
433
- return {
434
- estimatedFees: resp.estimatedFees,
435
- original: resp.original,
436
- tx: resp.tx.toHex(),
437
- nextUTXOs: this.getAllNewUTXOs(resp.original, resp.tx, parameters.from),
438
- };
439
- }
440
-
441
- /**
442
- * Get all new UTXOs of a generated transaction.
443
- * @param {TransactionBuilder<TransactionType>} original - The original transaction
444
- * @param {Transaction} tx - The transaction
445
- * @param {Address} to - The address to filter
446
- */
447
- public getAllNewUTXOs(
448
- original: TransactionBuilder<TransactionType>,
449
- tx: Transaction,
450
- to: Address,
451
- ): UTXO[] {
452
- const outputs = original.getOutputs();
453
-
454
- const utxos: UTXO[] = [];
455
- for (let i = 0; i < tx.outs.length; i++) {
456
- const output = outputs[i];
457
- if ('address' in output) {
458
- if (output.address !== to) continue;
459
- } else {
460
- continue;
461
- }
462
-
463
- utxos.push(...this.getUTXOAsTransaction(tx, to, i));
464
- }
465
-
466
- return utxos;
467
- }
468
-
469
- private async createFundTransaction(
470
- parameters: IFundingTransactionParameters,
471
- ): Promise<FundingTransactionResponse> {
472
- if (!parameters.to) throw new Error('Field "to" not provided.');
473
-
474
- const fundingTransaction: FundingTransaction = new FundingTransaction(parameters);
475
- const signedTransaction: Transaction = await fundingTransaction.signTransaction();
476
- if (!signedTransaction) {
477
- throw new Error('Could not sign funding transaction.');
478
- }
479
-
480
- return {
481
- tx: signedTransaction,
482
- original: fundingTransaction,
483
- estimatedFees: fundingTransaction.estimatedFees,
484
- nextUTXOs: this.getUTXOAsTransaction(signedTransaction, parameters.to, 0),
485
- };
486
- }
487
-
488
- private calculateNumSignatures(vault: VaultUTXOs[]): bigint {
489
- let numSignatures = 0n;
490
-
491
- for (const v of vault) {
492
- numSignatures += BigInt(v.minimum * v.utxos.length);
493
- }
494
-
495
- return numSignatures;
496
- }
497
-
498
- private calculateNumInputs(vault: VaultUTXOs[]): bigint {
499
- let numSignatures = 0n;
500
-
501
- for (const v of vault) {
502
- numSignatures += BigInt(v.utxos.length);
503
- }
504
-
505
- return numSignatures;
506
- }
507
-
508
- private maxPubKeySize(vault: VaultUTXOs[]): bigint {
509
- let size = 0;
510
-
511
- for (const v of vault) {
512
- size = Math.max(size, v.publicKeys.length);
513
- }
514
-
515
- return BigInt(size);
516
- }
517
-
518
- private writePSBTHeader(type: PSBTTypes, psbt: string): string {
519
- const buf = Buffer.from(psbt, 'base64');
520
-
521
- const header = Buffer.alloc(2);
522
- header.writeUInt8(type, 0);
523
- header.writeUInt8(currentConsensus, 1);
524
-
525
- return Buffer.concat([header, buf]).toString('hex');
526
- }
527
-
528
- private getUTXOAsTransaction(tx: Transaction, to: Address, index: number): UTXO[] {
529
- if (!tx.outs[index]) return [];
530
-
531
- const out: Output = tx.outs[index];
532
- const newUtxo: UTXO = {
533
- transactionId: tx.getId(),
534
- outputIndex: index,
535
- scriptPubKey: {
536
- hex: out.script.toString('hex'),
537
- address: to,
538
- },
539
- value: BigInt(out.value),
540
- };
541
-
542
- return [newUtxo];
543
- }
544
- }
1
+ import { Psbt, Transaction } from 'bitcoinjs-lib';
2
+ import {
3
+ IDeploymentParameters,
4
+ IFundingTransactionParameters,
5
+ IInteractionParameters,
6
+ IUnwrapParameters,
7
+ IWrapParameters,
8
+ } from './interfaces/ITransactionParameters.js';
9
+ import { FundingTransaction } from './builders/FundingTransaction.js';
10
+ import { Output } from 'bitcoinjs-lib/src/transaction.js';
11
+ import { UTXO } from '../utxo/interfaces/IUTXO.js';
12
+ import { InteractionTransaction } from './builders/InteractionTransaction.js';
13
+ import { DeploymentTransaction } from './builders/DeploymentTransaction.js';
14
+ import { Address } from '@btc-vision/bsi-binary';
15
+ import { wBTC } from '../metadata/contracts/wBTC.js';
16
+ import { WrapTransaction } from './builders/WrapTransaction.js';
17
+ import { PSBTTypes } from './psbt/PSBTTypes.js';
18
+ import { VaultUTXOs } from './processor/PsbtTransaction.js';
19
+ import { UnwrapSegwitTransaction } from './builders/UnwrapSegwitTransaction.js';
20
+ import { UnwrapTransaction } from './builders/UnwrapTransaction.js';
21
+ import { currentConsensus, currentConsensusConfig } from '../consensus/ConsensusConfig.js';
22
+ import { TransactionBuilder } from './builders/TransactionBuilder.js';
23
+ import { TransactionType } from './enums/TransactionType.js';
24
+
25
+ export interface DeploymentResult {
26
+ readonly transaction: [string, string];
27
+
28
+ readonly contractAddress: Address;
29
+ readonly p2trAddress: Address;
30
+
31
+ readonly utxos: UTXO[];
32
+ }
33
+
34
+ export interface WrapResult {
35
+ readonly transaction: [string, string];
36
+ readonly vaultAddress: Address;
37
+ readonly amount: bigint;
38
+ readonly receiverAddress: Address;
39
+ readonly utxos: UTXO[];
40
+ }
41
+
42
+ export interface FundingTransactionResponse {
43
+ readonly tx: Transaction;
44
+ readonly original: FundingTransaction;
45
+ readonly estimatedFees: bigint;
46
+ readonly nextUTXOs: UTXO[];
47
+ }
48
+
49
+ export interface BitcoinTransferResponse {
50
+ readonly tx: string;
51
+ readonly original: FundingTransaction;
52
+ readonly estimatedFees: bigint;
53
+ readonly nextUTXOs: UTXO[];
54
+ }
55
+
56
+ export interface UnwrapResult {
57
+ readonly fundingTransaction: string;
58
+ readonly psbt: string;
59
+
60
+ /**
61
+ * @description The fee refund or loss.
62
+ * @description If the amount is negative, it means that the user has to pay the difference. The difference is deducted from the amount.
63
+ * @description If the amount is positive, it means that the user will be refunded the difference.
64
+ * @type {bigint}
65
+ */
66
+ readonly feeRefundOrLoss: bigint;
67
+
68
+ readonly utxos: UTXO[];
69
+ }
70
+
71
+ export class TransactionFactory {
72
+ constructor() {}
73
+
74
+ /**
75
+ * @description Generates the required transactions.
76
+ * @returns {Promise<[string, string]>} - The signed transaction
77
+ */
78
+ public async signInteraction(
79
+ interactionParameters: IInteractionParameters,
80
+ ): Promise<[string, string, UTXO[]]> {
81
+ if (!interactionParameters.to) {
82
+ throw new Error('Field "to" not provided.');
83
+ }
84
+
85
+ if (!interactionParameters.from) {
86
+ throw new Error('Field "from" not provided.');
87
+ }
88
+
89
+ const preTransaction: InteractionTransaction = new InteractionTransaction({
90
+ ...interactionParameters,
91
+ utxos: [interactionParameters.utxos[0]], // we simulate one input here.
92
+ });
93
+
94
+ // we don't sign that transaction, we just need the parameters.
95
+
96
+ await preTransaction.generateTransactionMinimalSignatures();
97
+
98
+ const parameters: IFundingTransactionParameters =
99
+ await preTransaction.getFundingTransactionParameters();
100
+
101
+ parameters.utxos = interactionParameters.utxos;
102
+ parameters.amount =
103
+ (await preTransaction.estimateTransactionFees()) + interactionParameters.priorityFee;
104
+
105
+ const feeEstimationFundingTransaction = await this.createFundTransaction({ ...parameters });
106
+ if (!feeEstimationFundingTransaction) {
107
+ throw new Error('Could not sign funding transaction.');
108
+ }
109
+
110
+ parameters.estimatedFees = feeEstimationFundingTransaction.estimatedFees;
111
+
112
+ const signedTransaction = await this.createFundTransaction(parameters);
113
+ if (!signedTransaction) {
114
+ throw new Error('Could not sign funding transaction.');
115
+ }
116
+
117
+ interactionParameters.utxos = this.getUTXOAsTransaction(
118
+ signedTransaction.tx,
119
+ interactionParameters.to,
120
+ 0,
121
+ );
122
+
123
+ const newParams: IInteractionParameters = {
124
+ ...interactionParameters,
125
+ utxos: this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0), // always 0
126
+ randomBytes: preTransaction.getRndBytes(),
127
+ nonWitnessUtxo: signedTransaction.tx.toBuffer(),
128
+ estimatedFees: preTransaction.estimatedFees,
129
+ };
130
+
131
+ const finalTransaction: InteractionTransaction = new InteractionTransaction(newParams);
132
+
133
+ // We have to regenerate using the new utxo
134
+ const outTx: Transaction = await finalTransaction.signTransaction();
135
+
136
+ return [
137
+ signedTransaction.tx.toHex(),
138
+ outTx.toHex(),
139
+ this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.from, 1), // always 1
140
+ ];
141
+ }
142
+
143
+ /**
144
+ * @description Generates the required transactions.
145
+ * @param {IDeploymentParameters} deploymentParameters - The deployment parameters
146
+ * @returns {Promise<DeploymentResult>} - The signed transaction
147
+ */
148
+ public async signDeployment(
149
+ deploymentParameters: IDeploymentParameters,
150
+ ): Promise<DeploymentResult> {
151
+ const preTransaction: DeploymentTransaction = new DeploymentTransaction(
152
+ deploymentParameters,
153
+ );
154
+
155
+ // Initial generation
156
+ await preTransaction.signTransaction();
157
+
158
+ const parameters: IFundingTransactionParameters =
159
+ await preTransaction.getFundingTransactionParameters();
160
+
161
+ const fundingTransaction: FundingTransaction = new FundingTransaction(parameters);
162
+ const signedTransaction: Transaction = await fundingTransaction.signTransaction();
163
+ if (!signedTransaction) {
164
+ throw new Error('Could not sign funding transaction.');
165
+ }
166
+
167
+ const out: Output = signedTransaction.outs[0];
168
+ const newUtxo: UTXO = {
169
+ transactionId: signedTransaction.getId(),
170
+ outputIndex: 0, // always 0
171
+ scriptPubKey: {
172
+ hex: out.script.toString('hex'),
173
+ address: preTransaction.getScriptAddress(),
174
+ },
175
+ value: BigInt(out.value),
176
+ };
177
+
178
+ const newParams: IDeploymentParameters = {
179
+ ...deploymentParameters,
180
+ utxos: [newUtxo],
181
+ randomBytes: preTransaction.getRndBytes(),
182
+ nonWitnessUtxo: signedTransaction.toBuffer(),
183
+ };
184
+
185
+ const finalTransaction: DeploymentTransaction = new DeploymentTransaction(newParams);
186
+
187
+ // We have to regenerate using the new utxo
188
+ const outTx: Transaction = await finalTransaction.signTransaction();
189
+
190
+ const out2: Output = signedTransaction.outs[1];
191
+ const refundUTXO: UTXO = {
192
+ transactionId: signedTransaction.getId(),
193
+ outputIndex: 1, // always 1
194
+ scriptPubKey: {
195
+ hex: out2.script.toString('hex'),
196
+ address: deploymentParameters.from,
197
+ },
198
+ value: BigInt(out2.value),
199
+ };
200
+
201
+ return {
202
+ transaction: [signedTransaction.toHex(), outTx.toHex()],
203
+ contractAddress: finalTransaction.contractAddress,
204
+ p2trAddress: finalTransaction.p2trAddress,
205
+ utxos: [refundUTXO],
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Basically it's fun to manage UTXOs.
211
+ * @param {IWrapParameters} wrapParameters - The wrap parameters
212
+ * @returns {Promise<WrapResult>} - The signed transaction
213
+ * @throws {Error} - If the transaction could not be signed
214
+ */
215
+ public async wrap(wrapParameters: Omit<IWrapParameters, 'calldata'>): Promise<WrapResult> {
216
+ if (wrapParameters.amount < currentConsensusConfig.VAULT_MINIMUM_AMOUNT) {
217
+ throw new Error(
218
+ `Amount is too low. Minimum consolidation is ${currentConsensusConfig.VAULT_MINIMUM_AMOUNT} sat. Received ${wrapParameters.amount} sat. Make sure that you cover the unwrap consolidation fees of ${currentConsensusConfig.UNWRAP_CONSOLIDATION_PREPAID_FEES_SAT}sat.`,
219
+ );
220
+ }
221
+
222
+ const childTransactionRequiredValue: bigint =
223
+ wrapParameters.amount +
224
+ currentConsensusConfig.UNWRAP_CONSOLIDATION_PREPAID_FEES_SAT +
225
+ (wrapParameters.priorityFee || 300n);
226
+
227
+ const wbtc: wBTC = new wBTC(wrapParameters.network, wrapParameters.chainId);
228
+ const to = wbtc.getAddress();
229
+ const fundingParameters: IFundingTransactionParameters = {
230
+ ...wrapParameters,
231
+ amount: childTransactionRequiredValue,
232
+ to: wrapParameters.to ?? to,
233
+ };
234
+
235
+ const preFundingTransaction = await this.createFundTransaction(fundingParameters);
236
+ wrapParameters.utxos = this.getUTXOAsTransaction(preFundingTransaction.tx, to, 0);
237
+
238
+ const preTransaction: WrapTransaction = new WrapTransaction(wrapParameters);
239
+
240
+ // Initial generation
241
+ await preTransaction.signTransaction();
242
+
243
+ const parameters: IFundingTransactionParameters =
244
+ await preTransaction.getFundingTransactionParameters();
245
+
246
+ console.log('wrap parameters', parameters);
247
+
248
+ // We add the amount
249
+ parameters.amount += childTransactionRequiredValue;
250
+ parameters.utxos = fundingParameters.utxos;
251
+
252
+ const signedTransaction = await this.createFundTransaction(parameters);
253
+ if (!signedTransaction) {
254
+ throw new Error('Could not sign funding transaction.');
255
+ }
256
+
257
+ const newParams: IWrapParameters = {
258
+ ...wrapParameters,
259
+ utxos: this.getUTXOAsTransaction(signedTransaction.tx, to, 0), // always 0
260
+ randomBytes: preTransaction.getRndBytes(),
261
+ nonWitnessUtxo: signedTransaction.tx.toBuffer(),
262
+ };
263
+
264
+ const finalTransaction: WrapTransaction = new WrapTransaction(newParams);
265
+
266
+ // We have to regenerate using the new utxo
267
+ const outTx: Transaction = await finalTransaction.signTransaction();
268
+ return {
269
+ transaction: [signedTransaction.tx.toHex(), outTx.toHex()],
270
+ vaultAddress: finalTransaction.vault,
271
+ amount: finalTransaction.amount,
272
+ receiverAddress: finalTransaction.receiver,
273
+ utxos: this.getUTXOAsTransaction(signedTransaction.tx, wrapParameters.from, 1),
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Unwrap bitcoin.
279
+ * @param {IUnwrapParameters} unwrapParameters - The unwrap parameters
280
+ * @returns {Promise<UnwrapResult>} - The signed transaction
281
+ * @throws {Error} - If the transaction could not be signed
282
+ * @deprecated
283
+ */
284
+ public async unwrapSegwit(unwrapParameters: IUnwrapParameters): Promise<UnwrapResult> {
285
+ console.error('The "unwrap" method is deprecated. Use unwrapTap instead.');
286
+
287
+ const transaction: UnwrapSegwitTransaction = new UnwrapSegwitTransaction(unwrapParameters);
288
+ await transaction.signTransaction();
289
+
290
+ const to = transaction.toAddress();
291
+ if (!to) throw new Error('To address is required');
292
+
293
+ // Initial generation
294
+ const estimatedGas = await transaction.estimateTransactionFees();
295
+ const estimatedFees = transaction.preEstimateTransactionFees(
296
+ BigInt(unwrapParameters.feeRate),
297
+ this.calculateNumInputs(unwrapParameters.unwrapUTXOs),
298
+ 2n,
299
+ this.calculateNumSignatures(unwrapParameters.unwrapUTXOs),
300
+ this.maxPubKeySize(unwrapParameters.unwrapUTXOs),
301
+ );
302
+
303
+ console.log(
304
+ 'estimatedFees',
305
+ estimatedFees,
306
+ 'estimatedGas',
307
+ estimatedGas,
308
+ 'priority',
309
+ unwrapParameters.priorityFee,
310
+ );
311
+
312
+ const fundingParameters: IFundingTransactionParameters = {
313
+ ...unwrapParameters,
314
+ amount: estimatedGas + estimatedFees,
315
+ to: to,
316
+ };
317
+
318
+ const preFundingTransaction = await this.createFundTransaction(fundingParameters);
319
+ unwrapParameters.utxos = this.getUTXOAsTransaction(preFundingTransaction.tx, to, 0);
320
+
321
+ const preTransaction: UnwrapSegwitTransaction = new UnwrapSegwitTransaction({
322
+ ...unwrapParameters,
323
+ randomBytes: transaction.getRndBytes(),
324
+ });
325
+
326
+ // Initial generation
327
+ await preTransaction.signTransaction();
328
+
329
+ const parameters: IFundingTransactionParameters =
330
+ await preTransaction.getFundingTransactionParameters();
331
+
332
+ parameters.utxos = fundingParameters.utxos;
333
+ parameters.amount = (await preTransaction.estimateTransactionFees()) + estimatedFees;
334
+
335
+ const signedTransaction = await this.createFundTransaction(parameters);
336
+ if (!signedTransaction) {
337
+ throw new Error('Could not sign funding transaction.');
338
+ }
339
+
340
+ const newParams: IUnwrapParameters = {
341
+ ...unwrapParameters,
342
+ utxos: this.getUTXOAsTransaction(signedTransaction.tx, to, 0), // always 0
343
+ randomBytes: preTransaction.getRndBytes(),
344
+ nonWitnessUtxo: signedTransaction.tx.toBuffer(),
345
+ };
346
+
347
+ const finalTransaction: UnwrapSegwitTransaction = new UnwrapSegwitTransaction(newParams);
348
+
349
+ // We have to regenerate using the new utxo
350
+ const outTx: Psbt = await finalTransaction.signPSBT();
351
+ const asBase64 = outTx.toBase64();
352
+ const psbt = this.writePSBTHeader(PSBTTypes.UNWRAP, asBase64);
353
+
354
+ return {
355
+ fundingTransaction: signedTransaction.tx.toHex(),
356
+ psbt: psbt,
357
+ feeRefundOrLoss: estimatedFees,
358
+ utxos: [],
359
+ };
360
+ }
361
+
362
+ /**
363
+ * Unwrap bitcoin via taproot.
364
+ * @param {IUnwrapParameters} unwrapParameters - The unwrap parameters
365
+ * @returns {Promise<UnwrapResult>} - The signed transaction
366
+ * @throws {Error} - If the transaction could not be signed
367
+ */
368
+ public async unwrap(unwrapParameters: IUnwrapParameters): Promise<UnwrapResult> {
369
+ if (!unwrapParameters.from) {
370
+ throw new Error('Field "from" not provided.');
371
+ }
372
+
373
+ const transaction: UnwrapTransaction = new UnwrapTransaction(unwrapParameters);
374
+ await transaction.signTransaction();
375
+
376
+ const to = transaction.toAddress();
377
+ if (!to) throw new Error('To address is required');
378
+
379
+ // Initial generation
380
+ const estimatedGas = await transaction.estimateTransactionFees();
381
+ const fundingParameters: IFundingTransactionParameters = {
382
+ ...unwrapParameters,
383
+ amount: estimatedGas,
384
+ to: to,
385
+ };
386
+
387
+ const preFundingTransaction = await this.createFundTransaction(fundingParameters);
388
+ unwrapParameters.utxos = this.getUTXOAsTransaction(preFundingTransaction.tx, to, 0);
389
+
390
+ const preTransaction: UnwrapTransaction = new UnwrapTransaction({
391
+ ...unwrapParameters,
392
+ randomBytes: transaction.getRndBytes(),
393
+ });
394
+
395
+ // Initial generation
396
+ await preTransaction.signTransaction();
397
+
398
+ const parameters: IFundingTransactionParameters =
399
+ await preTransaction.getFundingTransactionParameters();
400
+
401
+ parameters.utxos = fundingParameters.utxos;
402
+ parameters.amount =
403
+ (await preTransaction.estimateTransactionFees()) + unwrapParameters.priorityFee;
404
+
405
+ console.log('parameters', parameters);
406
+
407
+ const signedTransaction = await this.createFundTransaction(parameters);
408
+ if (!signedTransaction) {
409
+ throw new Error('Could not sign funding transaction.');
410
+ }
411
+
412
+ const newParams: IUnwrapParameters = {
413
+ ...unwrapParameters,
414
+ utxos: this.getUTXOAsTransaction(signedTransaction.tx, to, 0), // always 0
415
+ randomBytes: preTransaction.getRndBytes(),
416
+ nonWitnessUtxo: signedTransaction.tx.toBuffer(),
417
+ };
418
+
419
+ const finalTransaction: UnwrapTransaction = new UnwrapTransaction(newParams);
420
+
421
+ // We have to regenerate using the new utxo
422
+ const outTx: Psbt = await finalTransaction.signPSBT();
423
+ const asBase64 = outTx.toBase64();
424
+ const psbt = this.writePSBTHeader(PSBTTypes.UNWRAP, asBase64);
425
+
426
+ return {
427
+ fundingTransaction: signedTransaction.tx.toHex(),
428
+ psbt: psbt,
429
+ feeRefundOrLoss: finalTransaction.getFeeLossOrRefund(),
430
+ utxos: this.getUTXOAsTransaction(signedTransaction.tx, unwrapParameters.from, 1),
431
+ };
432
+ }
433
+
434
+ /**
435
+ * @description Creates a funding transaction.
436
+ * @param {IFundingTransactionParameters} parameters - The funding transaction parameters
437
+ * @returns {Promise<{ estimatedFees: bigint; tx: string }>} - The signed transaction
438
+ */
439
+ public async createBTCTransfer(
440
+ parameters: IFundingTransactionParameters,
441
+ ): Promise<BitcoinTransferResponse> {
442
+ if (!parameters.from) {
443
+ throw new Error('Field "from" not provided.');
444
+ }
445
+
446
+ const resp = await this.createFundTransaction(parameters);
447
+
448
+ return {
449
+ estimatedFees: resp.estimatedFees,
450
+ original: resp.original,
451
+ tx: resp.tx.toHex(),
452
+ nextUTXOs: this.getAllNewUTXOs(resp.original, resp.tx, parameters.from),
453
+ };
454
+ }
455
+
456
+ /**
457
+ * Get all new UTXOs of a generated transaction.
458
+ * @param {TransactionBuilder<TransactionType>} original - The original transaction
459
+ * @param {Transaction} tx - The transaction
460
+ * @param {Address} to - The address to filter
461
+ */
462
+ public getAllNewUTXOs(
463
+ original: TransactionBuilder<TransactionType>,
464
+ tx: Transaction,
465
+ to: Address,
466
+ ): UTXO[] {
467
+ const outputs = original.getOutputs();
468
+
469
+ const utxos: UTXO[] = [];
470
+ for (let i = 0; i < tx.outs.length; i++) {
471
+ const output = outputs[i];
472
+ if ('address' in output) {
473
+ if (output.address !== to) continue;
474
+ } else {
475
+ continue;
476
+ }
477
+
478
+ utxos.push(...this.getUTXOAsTransaction(tx, to, i));
479
+ }
480
+
481
+ return utxos;
482
+ }
483
+
484
+ private async createFundTransaction(
485
+ parameters: IFundingTransactionParameters,
486
+ ): Promise<FundingTransactionResponse> {
487
+ if (!parameters.to) throw new Error('Field "to" not provided.');
488
+
489
+ const fundingTransaction: FundingTransaction = new FundingTransaction(parameters);
490
+ const signedTransaction: Transaction = await fundingTransaction.signTransaction();
491
+ if (!signedTransaction) {
492
+ throw new Error('Could not sign funding transaction.');
493
+ }
494
+
495
+ return {
496
+ tx: signedTransaction,
497
+ original: fundingTransaction,
498
+ estimatedFees: fundingTransaction.estimatedFees,
499
+ nextUTXOs: this.getUTXOAsTransaction(signedTransaction, parameters.to, 0),
500
+ };
501
+ }
502
+
503
+ private calculateNumSignatures(vault: VaultUTXOs[]): bigint {
504
+ let numSignatures = 0n;
505
+
506
+ for (const v of vault) {
507
+ numSignatures += BigInt(v.minimum * v.utxos.length);
508
+ }
509
+
510
+ return numSignatures;
511
+ }
512
+
513
+ private calculateNumInputs(vault: VaultUTXOs[]): bigint {
514
+ let numSignatures = 0n;
515
+
516
+ for (const v of vault) {
517
+ numSignatures += BigInt(v.utxos.length);
518
+ }
519
+
520
+ return numSignatures;
521
+ }
522
+
523
+ private maxPubKeySize(vault: VaultUTXOs[]): bigint {
524
+ let size = 0;
525
+
526
+ for (const v of vault) {
527
+ size = Math.max(size, v.publicKeys.length);
528
+ }
529
+
530
+ return BigInt(size);
531
+ }
532
+
533
+ private writePSBTHeader(type: PSBTTypes, psbt: string): string {
534
+ const buf = Buffer.from(psbt, 'base64');
535
+
536
+ const header = Buffer.alloc(2);
537
+ header.writeUInt8(type, 0);
538
+ header.writeUInt8(currentConsensus, 1);
539
+
540
+ return Buffer.concat([header, buf]).toString('hex');
541
+ }
542
+
543
+ private getUTXOAsTransaction(tx: Transaction, to: Address, index: number): UTXO[] {
544
+ if (!tx.outs[index]) return [];
545
+
546
+ const out: Output = tx.outs[index];
547
+ const newUtxo: UTXO = {
548
+ transactionId: tx.getId(),
549
+ outputIndex: index,
550
+ scriptPubKey: {
551
+ hex: out.script.toString('hex'),
552
+ address: to,
553
+ },
554
+ value: BigInt(out.value),
555
+ };
556
+
557
+ return [newUtxo];
558
+ }
559
+ }