@btc-vision/transaction 1.6.10 → 1.6.12
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/opnet.d.ts +1 -0
- package/browser/transaction/TransactionFactory.d.ts +3 -0
- package/browser/transaction/builders/CancelTransaction.d.ts +28 -0
- package/browser/transaction/builders/SharedInteractionTransaction.d.ts +1 -0
- package/browser/transaction/builders/TransactionBuilder.d.ts +3 -2
- package/browser/transaction/enums/TransactionType.d.ts +1 -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/opnet.d.ts +1 -0
- package/build/opnet.js +1 -0
- package/build/transaction/TransactionFactory.d.ts +3 -0
- package/build/transaction/TransactionFactory.js +8 -1
- package/build/transaction/builders/CancelTransaction.d.ts +28 -0
- package/build/transaction/builders/CancelTransaction.js +154 -0
- package/build/transaction/builders/CustomScriptTransaction.js +2 -1
- package/build/transaction/builders/DeploymentTransaction.js +2 -1
- package/build/transaction/builders/SharedInteractionTransaction.d.ts +1 -0
- package/build/transaction/builders/SharedInteractionTransaction.js +7 -2
- package/build/transaction/builders/TransactionBuilder.d.ts +3 -2
- package/build/transaction/builders/TransactionBuilder.js +12 -8
- package/build/transaction/enums/TransactionType.d.ts +1 -1
- package/build/transaction/enums/TransactionType.js +1 -1
- package/build/transaction/interfaces/ITransactionParameters.d.ts +1 -0
- package/build/verification/TapscriptVerificator.js +11 -4
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/opnet.ts +1 -0
- package/src/transaction/TransactionFactory.ts +15 -9
- package/src/transaction/builders/CancelTransaction.ts +266 -0
- package/src/transaction/builders/CustomScriptTransaction.ts +2 -1
- package/src/transaction/builders/DeploymentTransaction.ts +2 -1
- package/src/transaction/builders/SharedInteractionTransaction.ts +12 -3
- package/src/transaction/builders/TransactionBuilder.ts +27 -10
- package/src/transaction/enums/TransactionType.ts +1 -1
- package/src/transaction/interfaces/ITransactionParameters.ts +1 -0
- package/src/verification/TapscriptVerificator.ts +14 -3
|
@@ -57,7 +57,10 @@ export interface InteractionResponse {
|
|
|
57
57
|
readonly interactionTransaction: string;
|
|
58
58
|
readonly estimatedFees: bigint;
|
|
59
59
|
readonly nextUTXOs: UTXO[];
|
|
60
|
+
readonly fundingUTXOs: UTXO[];
|
|
60
61
|
readonly challenge: RawChallenge;
|
|
62
|
+
readonly interactionAddress: string | null;
|
|
63
|
+
readonly compiledTargetScript: string | null;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
export interface BitcoinTransferResponse extends BitcoinTransferBase {
|
|
@@ -158,10 +161,6 @@ export class TransactionFactory {
|
|
|
158
161
|
];
|
|
159
162
|
}
|
|
160
163
|
|
|
161
|
-
/**
|
|
162
|
-
* @description Generates the required transactions.
|
|
163
|
-
* @returns {Promise<InteractionResponse>} - The signed transaction
|
|
164
|
-
*/
|
|
165
164
|
/**
|
|
166
165
|
* @description Generates the required transactions.
|
|
167
166
|
* @returns {Promise<InteractionResponse>} - The signed transaction
|
|
@@ -239,13 +238,14 @@ export class TransactionFactory {
|
|
|
239
238
|
throw new Error('Could not sign funding transaction.');
|
|
240
239
|
}
|
|
241
240
|
|
|
241
|
+
const fundingUTXO = this.getUTXOAsTransaction(
|
|
242
|
+
signedTransaction.tx,
|
|
243
|
+
finalTransaction.getScriptAddress(),
|
|
244
|
+
0,
|
|
245
|
+
);
|
|
242
246
|
const newParams: IInteractionParameters = {
|
|
243
247
|
...interactionParameters,
|
|
244
|
-
utxos:
|
|
245
|
-
signedTransaction.tx,
|
|
246
|
-
finalTransaction.getScriptAddress(),
|
|
247
|
-
0,
|
|
248
|
-
),
|
|
248
|
+
utxos: fundingUTXO,
|
|
249
249
|
randomBytes: finalTransaction.getRndBytes(),
|
|
250
250
|
challenge: challenge,
|
|
251
251
|
nonWitnessUtxo: signedTransaction.tx.toBuffer(),
|
|
@@ -257,6 +257,7 @@ export class TransactionFactory {
|
|
|
257
257
|
const outTx = await interactionTx.signTransaction();
|
|
258
258
|
|
|
259
259
|
return {
|
|
260
|
+
interactionAddress: finalTransaction.getScriptAddress(),
|
|
260
261
|
fundingTransaction: signedTransaction.tx.toHex(),
|
|
261
262
|
interactionTransaction: outTx.toHex(),
|
|
262
263
|
estimatedFees: interactionTx.transactionFee,
|
|
@@ -266,6 +267,8 @@ export class TransactionFactory {
|
|
|
266
267
|
1,
|
|
267
268
|
),
|
|
268
269
|
challenge: challenge.toRaw(),
|
|
270
|
+
fundingUTXOs: fundingUTXO,
|
|
271
|
+
compiledTargetScript: interactionTx.exportCompiledTargetScript().toString('hex'),
|
|
269
272
|
};
|
|
270
273
|
}
|
|
271
274
|
|
|
@@ -587,6 +590,7 @@ export class TransactionFactory {
|
|
|
587
590
|
const txHex = signedTx.toHex();
|
|
588
591
|
|
|
589
592
|
return {
|
|
593
|
+
interactionAddress: null,
|
|
590
594
|
fundingTransaction: null,
|
|
591
595
|
interactionTransaction: txHex,
|
|
592
596
|
estimatedFees: p2wdaTransaction.estimatedFees,
|
|
@@ -595,7 +599,9 @@ export class TransactionFactory {
|
|
|
595
599
|
interactionParameters.from,
|
|
596
600
|
signedTx.outs.length - 1, // Last output is typically the change
|
|
597
601
|
),
|
|
602
|
+
fundingUTXOs: [...interactionParameters.utxos, ...inputs],
|
|
598
603
|
challenge: interactionParameters.challenge.toRaw(),
|
|
604
|
+
compiledTargetScript: null,
|
|
599
605
|
};
|
|
600
606
|
}
|
|
601
607
|
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { TransactionType } from '../enums/TransactionType.js';
|
|
2
|
+
import { P2TRPayment, PaymentType, Psbt, PsbtInput, Taptree } from '@btc-vision/bitcoin';
|
|
3
|
+
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
4
|
+
import { TapLeafScript } from '../interfaces/Tap.js';
|
|
5
|
+
import { ITransactionParameters, SharedInteractionParameters, } from '../interfaces/ITransactionParameters.js';
|
|
6
|
+
import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
|
|
7
|
+
|
|
8
|
+
export interface ICancelTransactionParameters
|
|
9
|
+
extends Omit<ITransactionParameters, 'priorityFee' | 'gasSatFee'> {
|
|
10
|
+
readonly compiledTargetScript: string | Buffer;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class CancelTransaction extends TransactionBuilder<TransactionType.CANCEL> {
|
|
14
|
+
public type: TransactionType.CANCEL = TransactionType.CANCEL;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The tap leaf script for spending
|
|
18
|
+
*/
|
|
19
|
+
protected tapLeafScript: TapLeafScript | null = null;
|
|
20
|
+
|
|
21
|
+
protected readonly compiledTargetScript: Buffer;
|
|
22
|
+
protected readonly scriptTree: Taptree;
|
|
23
|
+
|
|
24
|
+
protected readonly contractSecret: Buffer;
|
|
25
|
+
protected leftOverFundsScriptRedeem: P2TRPayment | null = null;
|
|
26
|
+
|
|
27
|
+
public constructor(parameters: ICancelTransactionParameters) {
|
|
28
|
+
super({
|
|
29
|
+
...parameters,
|
|
30
|
+
gasSatFee: 1n,
|
|
31
|
+
isCancellation: true,
|
|
32
|
+
priorityFee: 1n,
|
|
33
|
+
calldata: Buffer.alloc(0),
|
|
34
|
+
} as unknown as SharedInteractionParameters);
|
|
35
|
+
|
|
36
|
+
this.contractSecret = Buffer.alloc(0);
|
|
37
|
+
|
|
38
|
+
if (Buffer.isBuffer(parameters.compiledTargetScript)) {
|
|
39
|
+
this.compiledTargetScript = parameters.compiledTargetScript;
|
|
40
|
+
} else {
|
|
41
|
+
this.compiledTargetScript = Buffer.from(parameters.compiledTargetScript, 'hex');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Generate the minimal script tree needed for recovery
|
|
45
|
+
this.scriptTree = this.getMinimalScriptTree();
|
|
46
|
+
|
|
47
|
+
this.internalInit();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected override async buildTransaction(): Promise<void> {
|
|
51
|
+
if (!this.from) {
|
|
52
|
+
throw new Error('From address is required');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!this.leftOverFundsScriptRedeem) {
|
|
56
|
+
throw new Error('Left over funds script redeem is required');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!this.leftOverFundsScriptRedeem.redeemVersion) {
|
|
60
|
+
throw new Error('Left over funds script redeem version is required');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!this.leftOverFundsScriptRedeem.output) {
|
|
64
|
+
throw new Error('Left over funds script redeem output is required');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Set up the tap leaf script for spending
|
|
68
|
+
this.tapLeafScript = {
|
|
69
|
+
leafVersion: this.leftOverFundsScriptRedeem.redeemVersion,
|
|
70
|
+
script: this.leftOverFundsScriptRedeem.output,
|
|
71
|
+
controlBlock: this.getWitness(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
this.addInputsFromUTXO();
|
|
75
|
+
|
|
76
|
+
await this.addRefundOutput(0n, true);
|
|
77
|
+
|
|
78
|
+
if (!this.feeOutput) {
|
|
79
|
+
throw new Error('Must add extra UTXOs to cancel this transaction');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/*protected override async buildTransaction(): Promise<void> {
|
|
84
|
+
if (!this.from) {
|
|
85
|
+
throw new Error('From address is required');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// For key-path spend, we don't need the tap leaf script
|
|
89
|
+
this.tapLeafScript = null;
|
|
90
|
+
|
|
91
|
+
this.addInputsFromUTXO();
|
|
92
|
+
await this.addRefundOutput(0n);
|
|
93
|
+
}*/
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Sign the inputs
|
|
97
|
+
* @param {Psbt} transaction The transaction to sign
|
|
98
|
+
* @protected
|
|
99
|
+
*/
|
|
100
|
+
/*protected async signInputs(transaction: Psbt): Promise<void> {
|
|
101
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
102
|
+
if (i === 0) {
|
|
103
|
+
transaction.signInput(0, this.getSignerKey());
|
|
104
|
+
|
|
105
|
+
transaction.finalizeInput(0, this.customFinalizer.bind(this));
|
|
106
|
+
} else {
|
|
107
|
+
await super.signInputs(transaction);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}*/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generate the script address (for verification purposes)
|
|
114
|
+
*/
|
|
115
|
+
protected override generateScriptAddress(): P2TRPayment {
|
|
116
|
+
return {
|
|
117
|
+
internalPubkey: this.internalPubKeyToXOnly(),
|
|
118
|
+
network: this.network,
|
|
119
|
+
scriptTree: this.scriptTree,
|
|
120
|
+
name: PaymentType.P2TR,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Generate the tap data for spending
|
|
126
|
+
*/
|
|
127
|
+
/*protected override generateTapData(): P2TRPayment {
|
|
128
|
+
const internalPubkey = this.internalPubKeyToXOnly();
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
name: PaymentType.P2TR,
|
|
132
|
+
internalPubkey: internalPubkey,
|
|
133
|
+
network: this.network,
|
|
134
|
+
scriptTree: this.scriptTree, // This is crucial for the tweak
|
|
135
|
+
};
|
|
136
|
+
}*/
|
|
137
|
+
protected override generateTapData(): P2TRPayment {
|
|
138
|
+
const selectedRedeem = this.leftOverFundsScriptRedeem;
|
|
139
|
+
|
|
140
|
+
if (!selectedRedeem) {
|
|
141
|
+
throw new Error('Left over funds script redeem is required');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!this.scriptTree) {
|
|
145
|
+
throw new Error('Script tree is required');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
internalPubkey: this.internalPubKeyToXOnly(),
|
|
150
|
+
network: this.network,
|
|
151
|
+
scriptTree: this.scriptTree,
|
|
152
|
+
redeem: selectedRedeem,
|
|
153
|
+
name: PaymentType.P2TR,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Custom finalizer for the tap script spend
|
|
159
|
+
*/
|
|
160
|
+
protected customFinalizer = (_inputIndex: number, input: PsbtInput) => {
|
|
161
|
+
if (!this.tapLeafScript) {
|
|
162
|
+
throw new Error('Tap leaf script is required');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!input.tapScriptSig || input.tapScriptSig.length === 0) {
|
|
166
|
+
throw new Error('Tap script signature is required');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// For the simple lock script, we only need the signature
|
|
170
|
+
const scriptSolution = [input.tapScriptSig[0].signature];
|
|
171
|
+
|
|
172
|
+
const witness = scriptSolution
|
|
173
|
+
.concat(this.tapLeafScript.script)
|
|
174
|
+
.concat(this.tapLeafScript.controlBlock);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witness),
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
protected override async signInputs(transaction: Psbt): Promise<void> {
|
|
182
|
+
if ('multiSignPsbt' in this.signer) {
|
|
183
|
+
await this.signInputsWalletBased(transaction);
|
|
184
|
+
} else {
|
|
185
|
+
await this.signInputsNonWalletBased(transaction);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
protected override async signInputsWalletBased(transaction: Psbt): Promise<void> {
|
|
190
|
+
const signer: UnisatSigner = this.signer as UnisatSigner;
|
|
191
|
+
|
|
192
|
+
// then, we sign all the remaining inputs with the wallet signer.
|
|
193
|
+
await signer.multiSignPsbt([transaction]);
|
|
194
|
+
|
|
195
|
+
// Then, we finalize every input.
|
|
196
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
197
|
+
if (i === 0) {
|
|
198
|
+
transaction.finalizeInput(i, this.customFinalizer.bind(this));
|
|
199
|
+
} else {
|
|
200
|
+
try {
|
|
201
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH.bind(this));
|
|
202
|
+
} catch (e) {
|
|
203
|
+
transaction.finalizeInput(i);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
protected override async signInputsNonWalletBased(transaction: Psbt): Promise<void> {
|
|
210
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
211
|
+
if (i === 0) {
|
|
212
|
+
await this.signInput(
|
|
213
|
+
transaction,
|
|
214
|
+
transaction.data.inputs[i],
|
|
215
|
+
i,
|
|
216
|
+
this.getSignerKey(),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
transaction.finalizeInput(0, this.customFinalizer.bind(this));
|
|
220
|
+
} else {
|
|
221
|
+
await this.signInput(transaction, transaction.data.inputs[i], i, this.signer);
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH.bind(this));
|
|
225
|
+
} catch (e) {
|
|
226
|
+
transaction.finalizeInput(i);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Generate the minimal script tree needed for recovery
|
|
234
|
+
* This only includes the leftover funds script
|
|
235
|
+
*/
|
|
236
|
+
private getMinimalScriptTree(): Taptree {
|
|
237
|
+
this.generateLeftoverFundsRedeem();
|
|
238
|
+
|
|
239
|
+
if (!this.leftOverFundsScriptRedeem || !this.leftOverFundsScriptRedeem.output) {
|
|
240
|
+
throw new Error('Failed to generate leftover funds redeem script');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return [
|
|
244
|
+
{
|
|
245
|
+
output: this.compiledTargetScript,
|
|
246
|
+
version: 192,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
output: this.leftOverFundsScriptRedeem.output,
|
|
250
|
+
version: 192,
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Generate the leftover funds redeem script
|
|
257
|
+
*/
|
|
258
|
+
private generateLeftoverFundsRedeem(): void {
|
|
259
|
+
// Use the same LOCK_LEAF_SCRIPT from the parent class
|
|
260
|
+
this.leftOverFundsScriptRedeem = {
|
|
261
|
+
name: PaymentType.P2TR,
|
|
262
|
+
output: this.LOCK_LEAF_SCRIPT,
|
|
263
|
+
redeemVersion: 192,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -107,6 +107,7 @@ export class CustomScriptTransaction extends TransactionBuilder<TransactionType.
|
|
|
107
107
|
|
|
108
108
|
this.witnesses = parameters.witnesses;
|
|
109
109
|
this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
|
|
110
|
+
this.LOCK_LEAF_SCRIPT = this.defineLockScript();
|
|
110
111
|
|
|
111
112
|
this.scriptSeed = this.getContractSeed();
|
|
112
113
|
this.contractSigner = EcKeyPair.fromSeedKeyPair(this.scriptSeed, this.network);
|
|
@@ -353,7 +354,7 @@ export class CustomScriptTransaction extends TransactionBuilder<TransactionType.
|
|
|
353
354
|
* @private
|
|
354
355
|
*/
|
|
355
356
|
private getLeafScript(): Buffer {
|
|
356
|
-
return
|
|
357
|
+
return this.LOCK_LEAF_SCRIPT;
|
|
357
358
|
}
|
|
358
359
|
|
|
359
360
|
/**
|
|
@@ -127,6 +127,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
127
127
|
this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
|
|
128
128
|
this.challenge = parameters.challenge;
|
|
129
129
|
|
|
130
|
+
this.LOCK_LEAF_SCRIPT = this.defineLockScript();
|
|
130
131
|
this.epochChallenge = TimeLockGenerator.generateTimeLockAddress(
|
|
131
132
|
this.challenge.publicKey.originalPublicKeyBuffer(),
|
|
132
133
|
this.network,
|
|
@@ -471,7 +472,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
471
472
|
* @private
|
|
472
473
|
*/
|
|
473
474
|
private getLeafScript(): Buffer {
|
|
474
|
-
return
|
|
475
|
+
return this.LOCK_LEAF_SCRIPT;
|
|
475
476
|
}
|
|
476
477
|
|
|
477
478
|
/**
|
|
@@ -75,6 +75,8 @@ export abstract class SharedInteractionTransaction<
|
|
|
75
75
|
|
|
76
76
|
this.challenge = parameters.challenge;
|
|
77
77
|
|
|
78
|
+
this.LOCK_LEAF_SCRIPT = this.defineLockScript();
|
|
79
|
+
|
|
78
80
|
this.disableAutoRefund = parameters.disableAutoRefund || false;
|
|
79
81
|
this.epochChallenge = TimeLockGenerator.generateTimeLockAddress(
|
|
80
82
|
this.challenge.publicKey.originalPublicKeyBuffer(),
|
|
@@ -93,6 +95,10 @@ export abstract class SharedInteractionTransaction<
|
|
|
93
95
|
);
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
public exportCompiledTargetScript(): Buffer {
|
|
99
|
+
return this.compiledTargetScript;
|
|
100
|
+
}
|
|
101
|
+
|
|
96
102
|
/**
|
|
97
103
|
* Get the contract secret
|
|
98
104
|
* @returns {Buffer} The contract secret
|
|
@@ -215,6 +221,8 @@ export abstract class SharedInteractionTransaction<
|
|
|
215
221
|
throw new Error('Script tree is required');
|
|
216
222
|
}
|
|
217
223
|
|
|
224
|
+
console.log('internalPubkey', this.internalPubKeyToXOnly().toString('hex'));
|
|
225
|
+
|
|
218
226
|
return {
|
|
219
227
|
internalPubkey: this.internalPubKeyToXOnly(),
|
|
220
228
|
network: this.network,
|
|
@@ -262,7 +270,7 @@ export abstract class SharedInteractionTransaction<
|
|
|
262
270
|
version: 192,
|
|
263
271
|
},
|
|
264
272
|
{
|
|
265
|
-
output:
|
|
273
|
+
output: this.LOCK_LEAF_SCRIPT,
|
|
266
274
|
version: 192,
|
|
267
275
|
},
|
|
268
276
|
];
|
|
@@ -350,7 +358,8 @@ export abstract class SharedInteractionTransaction<
|
|
|
350
358
|
this.addFeeToOutput(opnetFee, this.to, this.epochChallenge, false);
|
|
351
359
|
|
|
352
360
|
// Get the actual amount added to outputs (might be MINIMUM_AMOUNT_REWARD if opnetFee is too small)
|
|
353
|
-
const actualOutputAmount =
|
|
361
|
+
const actualOutputAmount =
|
|
362
|
+
opnetFee < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : opnetFee;
|
|
354
363
|
|
|
355
364
|
const optionalAmount = this.addOptionalOutputsAndGetAmount();
|
|
356
365
|
|
|
@@ -395,7 +404,7 @@ export abstract class SharedInteractionTransaction<
|
|
|
395
404
|
|
|
396
405
|
this.leftOverFundsScriptRedeem = {
|
|
397
406
|
name: PaymentType.P2TR,
|
|
398
|
-
output:
|
|
407
|
+
output: this.LOCK_LEAF_SCRIPT,
|
|
399
408
|
redeemVersion: 192,
|
|
400
409
|
};
|
|
401
410
|
}
|
|
@@ -8,13 +8,17 @@ import bitcoin, {
|
|
|
8
8
|
PsbtOutputExtended,
|
|
9
9
|
script,
|
|
10
10
|
Signer,
|
|
11
|
+
toXOnly,
|
|
11
12
|
Transaction,
|
|
12
13
|
varuint,
|
|
13
14
|
} from '@btc-vision/bitcoin';
|
|
14
15
|
import * as ecc from '@bitcoinerlab/secp256k1';
|
|
15
16
|
import { UpdateInput } from '../interfaces/Tap.js';
|
|
16
17
|
import { TransactionType } from '../enums/TransactionType.js';
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
IFundingTransactionParameters,
|
|
20
|
+
ITransactionParameters,
|
|
21
|
+
} from '../interfaces/ITransactionParameters.js';
|
|
18
22
|
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
19
23
|
import { UTXO } from '../../utxo/interfaces/IUTXO.js';
|
|
20
24
|
import { ECPairInterface } from 'ecpair';
|
|
@@ -37,18 +41,15 @@ export const ANCHOR_SCRIPT = Buffer.from('51024e73', 'hex');
|
|
|
37
41
|
* @class TransactionBuilder
|
|
38
42
|
*/
|
|
39
43
|
export abstract class TransactionBuilder<T extends TransactionType> extends TweakedTransaction {
|
|
40
|
-
// Cancel script
|
|
41
|
-
public static readonly LOCK_LEAF_SCRIPT: Buffer = script.compile([
|
|
42
|
-
opcodes.OP_FALSE,
|
|
43
|
-
opcodes.OP_VERIFY,
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
44
|
public static readonly MINIMUM_DUST: bigint = 330n;
|
|
47
45
|
|
|
48
46
|
public abstract readonly type: T;
|
|
49
47
|
public readonly logColor: string = '#785def';
|
|
50
48
|
public debugFees: boolean = false;
|
|
51
49
|
|
|
50
|
+
// Cancel script
|
|
51
|
+
public LOCK_LEAF_SCRIPT: Buffer;
|
|
52
|
+
|
|
52
53
|
/**
|
|
53
54
|
* @description The overflow fees of the transaction
|
|
54
55
|
* @public
|
|
@@ -177,6 +178,8 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
177
178
|
this.to = parameters.to || undefined;
|
|
178
179
|
this.debugFees = parameters.debugFees || false;
|
|
179
180
|
|
|
181
|
+
this.LOCK_LEAF_SCRIPT = this.defineLockScript();
|
|
182
|
+
|
|
180
183
|
if (parameters.note) {
|
|
181
184
|
if (typeof parameters.note === 'string') {
|
|
182
185
|
this.note = Buffer.from(parameters.note, 'utf8');
|
|
@@ -737,7 +740,10 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
737
740
|
return total;
|
|
738
741
|
}
|
|
739
742
|
|
|
740
|
-
protected async addRefundOutput(
|
|
743
|
+
protected async addRefundOutput(
|
|
744
|
+
amountSpent: bigint,
|
|
745
|
+
expectRefund: boolean = false,
|
|
746
|
+
): Promise<void> {
|
|
741
747
|
if (this.note) {
|
|
742
748
|
this.addOPReturn(this.note);
|
|
743
749
|
}
|
|
@@ -752,6 +758,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
752
758
|
let iterations = 0;
|
|
753
759
|
const maxIterations = 5; // Prevent infinite loops
|
|
754
760
|
|
|
761
|
+
let sendBackAmount = 0n;
|
|
755
762
|
// Iterate until fee stabilizes
|
|
756
763
|
while (iterations < maxIterations && estimatedFee !== previousFee) {
|
|
757
764
|
previousFee = estimatedFee;
|
|
@@ -763,7 +770,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
763
770
|
const totalSpent = amountSpent + estimatedFee;
|
|
764
771
|
|
|
765
772
|
// Calculate refund
|
|
766
|
-
|
|
773
|
+
sendBackAmount = this.totalInputAmount - totalSpent;
|
|
767
774
|
|
|
768
775
|
if (this.debugFees) {
|
|
769
776
|
this.log(
|
|
@@ -804,7 +811,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
804
811
|
this.feeOutput = null;
|
|
805
812
|
this.overflowFees = 0n;
|
|
806
813
|
|
|
807
|
-
if (sendBackAmount < 0n) {
|
|
814
|
+
if (sendBackAmount < 0n && iterations === maxIterations) {
|
|
808
815
|
throw new Error(
|
|
809
816
|
`Insufficient funds: need ${totalSpent} sats but only have ${this.totalInputAmount} sats`,
|
|
810
817
|
);
|
|
@@ -820,6 +827,12 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
820
827
|
iterations++;
|
|
821
828
|
}
|
|
822
829
|
|
|
830
|
+
if (expectRefund && sendBackAmount < 0n) {
|
|
831
|
+
throw new Error(
|
|
832
|
+
`Insufficient funds: need at least ${-sendBackAmount} more sats to cover fees.`,
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
|
|
823
836
|
if (iterations >= maxIterations) {
|
|
824
837
|
this.warn(`Fee calculation did not stabilize after ${maxIterations} iterations`);
|
|
825
838
|
}
|
|
@@ -834,6 +847,10 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
834
847
|
}
|
|
835
848
|
}
|
|
836
849
|
|
|
850
|
+
protected defineLockScript(): Buffer {
|
|
851
|
+
return script.compile([toXOnly(Buffer.from(this.signer.publicKey)), opcodes.OP_CHECKSIG]);
|
|
852
|
+
}
|
|
853
|
+
|
|
837
854
|
/**
|
|
838
855
|
* @description Adds the value to the output
|
|
839
856
|
* @param {number | bigint} value - The value to add
|
|
@@ -46,6 +46,7 @@ export interface SharedInteractionParameters extends ITransactionParameters {
|
|
|
46
46
|
readonly randomBytes?: Buffer;
|
|
47
47
|
|
|
48
48
|
readonly loadedStorage?: LoadedStorage;
|
|
49
|
+
readonly isCancellation?: boolean;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export interface IInteractionParameters extends SharedInteractionParameters {
|
|
@@ -2,13 +2,14 @@ import {
|
|
|
2
2
|
crypto as bitCrypto,
|
|
3
3
|
Network,
|
|
4
4
|
networks,
|
|
5
|
+
opcodes,
|
|
5
6
|
Payment,
|
|
6
7
|
payments,
|
|
8
|
+
script,
|
|
7
9
|
Taptree,
|
|
8
10
|
toXOnly,
|
|
9
11
|
} from '@btc-vision/bitcoin';
|
|
10
12
|
import { DeploymentGenerator } from '../generators/builders/DeploymentGenerator.js';
|
|
11
|
-
import { TransactionBuilder } from '../transaction/builders/TransactionBuilder.js';
|
|
12
13
|
import { ChallengeSolution } from '../epoch/ChallengeSolution.js';
|
|
13
14
|
import { Feature, Features } from '../generators/Features.js';
|
|
14
15
|
|
|
@@ -46,13 +47,18 @@ export class TapscriptVerificator {
|
|
|
46
47
|
params.features,
|
|
47
48
|
);
|
|
48
49
|
|
|
50
|
+
const lockLeafScript = script.compile([
|
|
51
|
+
toXOnly(params.deployerPubKey),
|
|
52
|
+
opcodes.OP_CHECKSIG,
|
|
53
|
+
]);
|
|
54
|
+
|
|
49
55
|
const scriptTree: Taptree = [
|
|
50
56
|
{
|
|
51
57
|
output: compiledTargetScript,
|
|
52
58
|
version: TapscriptVerificator.TAP_SCRIPT_VERSION,
|
|
53
59
|
},
|
|
54
60
|
{
|
|
55
|
-
output:
|
|
61
|
+
output: lockLeafScript,
|
|
56
62
|
version: TapscriptVerificator.TAP_SCRIPT_VERSION,
|
|
57
63
|
},
|
|
58
64
|
];
|
|
@@ -80,13 +86,18 @@ export class TapscriptVerificator {
|
|
|
80
86
|
params.features,
|
|
81
87
|
);
|
|
82
88
|
|
|
89
|
+
const lockLeafScript = script.compile([
|
|
90
|
+
toXOnly(params.deployerPubKey),
|
|
91
|
+
opcodes.OP_CHECKSIG,
|
|
92
|
+
]);
|
|
93
|
+
|
|
83
94
|
const scriptTree: Taptree = [
|
|
84
95
|
{
|
|
85
96
|
output: compiledTargetScript,
|
|
86
97
|
version: TapscriptVerificator.TAP_SCRIPT_VERSION,
|
|
87
98
|
},
|
|
88
99
|
{
|
|
89
|
-
output:
|
|
100
|
+
output: lockLeafScript,
|
|
90
101
|
version: TapscriptVerificator.TAP_SCRIPT_VERSION,
|
|
91
102
|
},
|
|
92
103
|
];
|