@btc-vision/transaction 1.6.1 → 1.6.5

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 (75) hide show
  1. package/browser/_version.d.ts +1 -1
  2. package/browser/epoch/ChallengeSolution.d.ts +3 -3
  3. package/browser/epoch/validator/EpochValidator.d.ts +5 -6
  4. package/browser/generators/builders/P2WDAGenerator.d.ts +13 -0
  5. package/browser/index.js +1 -1
  6. package/browser/keypair/Address.d.ts +3 -2
  7. package/browser/keypair/AddressVerificator.d.ts +13 -1
  8. package/browser/keypair/Wallet.d.ts +3 -0
  9. package/browser/opnet.d.ts +4 -0
  10. package/browser/p2wda/P2WDADetector.d.ts +16 -0
  11. package/browser/transaction/TransactionFactory.d.ts +3 -1
  12. package/browser/transaction/builders/DeploymentTransaction.d.ts +3 -3
  13. package/browser/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
  14. package/browser/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
  15. package/browser/transaction/builders/TransactionBuilder.d.ts +3 -0
  16. package/browser/transaction/interfaces/ITransactionParameters.d.ts +1 -0
  17. package/browser/transaction/mineable/IP2WSHAddress.d.ts +4 -0
  18. package/browser/transaction/mineable/TimelockGenerator.d.ts +2 -5
  19. package/browser/transaction/shared/TweakedTransaction.d.ts +23 -0
  20. package/build/_version.d.ts +1 -1
  21. package/build/_version.js +1 -1
  22. package/build/epoch/ChallengeSolution.d.ts +3 -3
  23. package/build/epoch/ChallengeSolution.js +3 -3
  24. package/build/epoch/validator/EpochValidator.d.ts +5 -6
  25. package/build/epoch/validator/EpochValidator.js +11 -12
  26. package/build/generators/builders/P2WDAGenerator.d.ts +13 -0
  27. package/build/generators/builders/P2WDAGenerator.js +62 -0
  28. package/build/keypair/Address.d.ts +3 -2
  29. package/build/keypair/Address.js +28 -2
  30. package/build/keypair/AddressVerificator.d.ts +13 -1
  31. package/build/keypair/AddressVerificator.js +82 -1
  32. package/build/keypair/Wallet.d.ts +3 -0
  33. package/build/keypair/Wallet.js +4 -0
  34. package/build/opnet.d.ts +4 -0
  35. package/build/opnet.js +4 -0
  36. package/build/p2wda/P2WDADetector.d.ts +16 -0
  37. package/build/p2wda/P2WDADetector.js +97 -0
  38. package/build/transaction/TransactionFactory.d.ts +3 -1
  39. package/build/transaction/TransactionFactory.js +35 -4
  40. package/build/transaction/builders/DeploymentTransaction.d.ts +3 -3
  41. package/build/transaction/builders/DeploymentTransaction.js +1 -1
  42. package/build/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
  43. package/build/transaction/builders/InteractionTransactionP2WDA.js +205 -0
  44. package/build/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
  45. package/build/transaction/builders/SharedInteractionTransaction.js +3 -3
  46. package/build/transaction/builders/TransactionBuilder.d.ts +3 -0
  47. package/build/transaction/builders/TransactionBuilder.js +18 -3
  48. package/build/transaction/interfaces/ITransactionParameters.d.ts +1 -0
  49. package/build/transaction/mineable/IP2WSHAddress.d.ts +4 -0
  50. package/build/transaction/mineable/IP2WSHAddress.js +1 -0
  51. package/build/transaction/mineable/TimelockGenerator.d.ts +2 -5
  52. package/build/transaction/shared/TweakedTransaction.d.ts +23 -0
  53. package/build/transaction/shared/TweakedTransaction.js +154 -18
  54. package/doc/README.md +0 -0
  55. package/doc/addresses/P2OP.md +1 -0
  56. package/doc/addresses/P2WDA.md +240 -0
  57. package/package.json +2 -2
  58. package/src/_version.ts +1 -1
  59. package/src/epoch/ChallengeSolution.ts +4 -4
  60. package/src/epoch/validator/EpochValidator.ts +12 -16
  61. package/src/generators/builders/P2WDAGenerator.ts +174 -0
  62. package/src/keypair/Address.ts +58 -3
  63. package/src/keypair/AddressVerificator.ts +147 -2
  64. package/src/keypair/Wallet.ts +16 -0
  65. package/src/opnet.ts +4 -0
  66. package/src/p2wda/P2WDADetector.ts +218 -0
  67. package/src/transaction/TransactionFactory.ts +79 -5
  68. package/src/transaction/builders/DeploymentTransaction.ts +4 -3
  69. package/src/transaction/builders/InteractionTransactionP2WDA.ts +376 -0
  70. package/src/transaction/builders/SharedInteractionTransaction.ts +7 -6
  71. package/src/transaction/builders/TransactionBuilder.ts +30 -3
  72. package/src/transaction/interfaces/ITransactionParameters.ts +1 -0
  73. package/src/transaction/mineable/IP2WSHAddress.ts +4 -0
  74. package/src/transaction/mineable/TimelockGenerator.ts +2 -6
  75. package/src/transaction/shared/TweakedTransaction.ts +246 -23
@@ -0,0 +1,376 @@
1
+ import { Buffer } from 'buffer';
2
+ import { Psbt, PsbtInput, toXOnly } from '@btc-vision/bitcoin';
3
+ import { TransactionType } from '../enums/TransactionType.js';
4
+ import { IInteractionParameters } from '../interfaces/ITransactionParameters.js';
5
+ import { MINIMUM_AMOUNT_CA, MINIMUM_AMOUNT_REWARD, TransactionBuilder, } from './TransactionBuilder.js';
6
+ import { MessageSigner } from '../../keypair/MessageSigner.js';
7
+ import { Compressor } from '../../bytecode/Compressor.js';
8
+ import { P2WDAGenerator } from '../../generators/builders/P2WDAGenerator.js';
9
+ import { Feature, Features } from '../../generators/Features.js';
10
+ import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
11
+ import { EcKeyPair } from '../../keypair/EcKeyPair.js';
12
+ import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
13
+ import { ECPairInterface } from 'ecpair';
14
+ import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
15
+ import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
16
+ import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
17
+
18
+ /**
19
+ * P2WDA Interaction Transaction
20
+ *
21
+ * This transaction type uses the exact same operation data as regular interactions
22
+ * (via CalldataGenerator), but embeds it in the witness field instead of a taproot script.
23
+ * This achieves 75% cost reduction through the witness discount.
24
+ */
25
+ export class InteractionTransactionP2WDA extends TransactionBuilder<TransactionType.INTERACTION> {
26
+ private static readonly MAX_WITNESS_FIELDS = 10;
27
+ private static readonly MAX_BYTES_PER_WITNESS = 80;
28
+
29
+ public readonly type: TransactionType.INTERACTION = TransactionType.INTERACTION;
30
+ protected readonly epochChallenge: IP2WSHAddress;
31
+ /**
32
+ * Disable auto refund
33
+ * @protected
34
+ */
35
+ protected readonly disableAutoRefund: boolean;
36
+ private readonly contractAddress: string;
37
+ private readonly contractSecret: Buffer;
38
+ private readonly calldata: Buffer;
39
+ private readonly challenge: ChallengeSolution;
40
+ private readonly randomBytes: Buffer;
41
+ private p2wdaGenerator: P2WDAGenerator;
42
+ private scriptSigner: ECPairInterface;
43
+ private p2wdaInputIndices: Set<number> = new Set();
44
+ /**
45
+ * The compiled operation data from CalldataGenerator
46
+ * This is exactly what would go in a taproot script, but we put it in witness instead
47
+ */
48
+ private readonly compiledOperationData: Buffer | null = null;
49
+
50
+ public constructor(parameters: IInteractionParameters) {
51
+ super(parameters);
52
+
53
+ if (!parameters.to) {
54
+ throw new Error('Contract address (to) is required');
55
+ }
56
+
57
+ if (!parameters.contract) {
58
+ throw new Error('Contract secret is required');
59
+ }
60
+
61
+ if (!parameters.calldata) {
62
+ throw new Error('Calldata is required');
63
+ }
64
+
65
+ if (!parameters.challenge) {
66
+ throw new Error('Challenge solution is required');
67
+ }
68
+
69
+ this.disableAutoRefund = parameters.disableAutoRefund || false;
70
+ this.contractAddress = parameters.to;
71
+ this.contractSecret = Buffer.from(parameters.contract.replace('0x', ''), 'hex');
72
+ this.calldata = Compressor.compress(parameters.calldata);
73
+ this.challenge = parameters.challenge;
74
+ this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
75
+
76
+ // Create the script signer (same as SharedInteractionTransaction does)
77
+ this.scriptSigner = this.generateKeyPairFromSeed();
78
+
79
+ // Create the P2WDA generator instead of CalldataGenerator
80
+ // P2WDA needs a different data format optimized for witness embedding
81
+ this.p2wdaGenerator = new P2WDAGenerator(
82
+ Buffer.from(this.signer.publicKey),
83
+ this.scriptSignerXOnlyPubKey(),
84
+ this.network,
85
+ );
86
+
87
+ // Validate contract secret
88
+ if (this.contractSecret.length !== 32) {
89
+ throw new Error('Invalid contract secret length. Expected 32 bytes.');
90
+ }
91
+
92
+ this.epochChallenge = TimeLockGenerator.generateTimeLockAddress(
93
+ this.challenge.publicKey.originalPublicKeyBuffer(),
94
+ this.network,
95
+ );
96
+
97
+ // Validate P2WDA inputs
98
+ this.validateP2WDAInputs();
99
+
100
+ this.compiledOperationData = this.p2wdaGenerator.compile(
101
+ this.calldata,
102
+ this.contractSecret,
103
+ this.challenge,
104
+ this.priorityFee,
105
+ this.generateFeatures(parameters),
106
+ );
107
+
108
+ // Validate size early
109
+ this.validateOperationDataSize();
110
+
111
+ this.internalInit();
112
+ }
113
+
114
+ /**
115
+ * Get random bytes (for compatibility if needed elsewhere)
116
+ */
117
+ public getRndBytes(): Buffer {
118
+ return this.randomBytes;
119
+ }
120
+
121
+ /**
122
+ * Get the challenge (for compatibility if needed elsewhere)
123
+ */
124
+ public getChallenge(): ChallengeSolution {
125
+ return this.challenge;
126
+ }
127
+
128
+ /**
129
+ * Get contract secret (for compatibility if needed elsewhere)
130
+ */
131
+ public getContractSecret(): Buffer {
132
+ return this.contractSecret;
133
+ }
134
+
135
+ /**
136
+ * Build the transaction
137
+ */
138
+ protected async buildTransaction(): Promise<void> {
139
+ if (!this.regenerated) {
140
+ this.addInputsFromUTXO();
141
+ }
142
+
143
+ // Add refund
144
+ await this.createMineableRewardOutputs();
145
+ }
146
+
147
+ protected async createMineableRewardOutputs(): Promise<void> {
148
+ if (!this.to) throw new Error('To address is required');
149
+
150
+ const amountSpent: bigint = this.getTransactionOPNetFee();
151
+
152
+ let amountToCA: bigint;
153
+ if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
154
+ amountToCA = MINIMUM_AMOUNT_CA;
155
+ } else {
156
+ amountToCA = amountSpent;
157
+ }
158
+
159
+ // ALWAYS THE FIRST INPUT.
160
+ this.addOutput({
161
+ value: Number(amountToCA),
162
+ address: this.to,
163
+ });
164
+
165
+ // ALWAYS SECOND.
166
+ if (
167
+ amountToCA === MINIMUM_AMOUNT_CA &&
168
+ amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD
169
+ ) {
170
+ this.addOutput({
171
+ value: Number(amountSpent - amountToCA),
172
+ address: this.epochChallenge.address,
173
+ });
174
+ }
175
+
176
+ const amount = this.addOptionalOutputsAndGetAmount();
177
+ if (!this.disableAutoRefund) {
178
+ await this.addRefundOutput(amountSpent + amount);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Sign inputs with P2WDA-specific handling
184
+ */
185
+ protected override async signInputs(transaction: Psbt): Promise<void> {
186
+ // Sign all inputs
187
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
188
+ await this.signInput(transaction, transaction.data.inputs[i], i, this.signer);
189
+ }
190
+
191
+ // Finalize with appropriate finalizers
192
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
193
+ if (this.p2wdaInputIndices.has(i)) {
194
+ if (i === 0) {
195
+ transaction.finalizeInput(i, this.finalizePrimaryP2WDA.bind(this));
196
+ } else {
197
+ transaction.finalizeInput(i, this.finalizeSecondaryP2WDA.bind(this));
198
+ }
199
+ } else {
200
+ transaction.finalizeInput(i, this.customFinalizerP2SH.bind(this));
201
+ }
202
+ }
203
+
204
+ this.finalized = true;
205
+ }
206
+
207
+ /**
208
+ * Generate features array (same as InteractionTransaction)
209
+ */
210
+ private generateFeatures(parameters: IInteractionParameters): Feature<Features>[] {
211
+ const features: Feature<Features>[] = [];
212
+
213
+ if (parameters.loadedStorage) {
214
+ features.push({
215
+ opcode: Features.ACCESS_LIST,
216
+ data: parameters.loadedStorage,
217
+ });
218
+ }
219
+
220
+ const submission = parameters.challenge.getSubmission();
221
+ if (submission) {
222
+ features.push({
223
+ opcode: Features.EPOCH_SUBMISSION,
224
+ data: submission,
225
+ });
226
+ }
227
+
228
+ return features;
229
+ }
230
+
231
+ /**
232
+ * Generate keypair from seed (same as SharedInteractionTransaction)
233
+ */
234
+ private generateKeyPairFromSeed(): ECPairInterface {
235
+ return EcKeyPair.fromSeedKeyPair(this.randomBytes, this.network);
236
+ }
237
+
238
+ /**
239
+ * Get script signer x-only pubkey (same as SharedInteractionTransaction)
240
+ */
241
+ private scriptSignerXOnlyPubKey(): Buffer {
242
+ return toXOnly(Buffer.from(this.scriptSigner.publicKey));
243
+ }
244
+
245
+ /**
246
+ * Validate that input 0 is P2WDA
247
+ */
248
+ private validateP2WDAInputs(): void {
249
+ if (this.utxos.length === 0 || !P2WDADetector.isP2WDAUTXO(this.utxos[0])) {
250
+ throw new Error('Input 0 must be a P2WDA UTXO');
251
+ }
252
+
253
+ // Track all P2WDA inputs
254
+ for (let i = 0; i < this.utxos.length; i++) {
255
+ if (P2WDADetector.isP2WDAUTXO(this.utxos[i])) {
256
+ this.p2wdaInputIndices.add(i);
257
+ }
258
+ }
259
+
260
+ for (let i = 0; i < this.optionalInputs.length; i++) {
261
+ const actualIndex = this.utxos.length + i;
262
+ if (P2WDADetector.isP2WDAUTXO(this.optionalInputs[i])) {
263
+ this.p2wdaInputIndices.add(actualIndex);
264
+ }
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Validate the compiled operation data will fit in witness fields
270
+ */
271
+ private validateOperationDataSize(): void {
272
+ if (!this.compiledOperationData) {
273
+ throw new Error('Operation data not compiled');
274
+ }
275
+
276
+ // The data that goes in witness: COMPRESS(signature + compiledOperationData)
277
+ // Signature is 64 bytes
278
+ const estimatedSize = this.compiledOperationData.length;
279
+
280
+ if (!P2WDAGenerator.validateWitnessSize(estimatedSize)) {
281
+ const signatureSize = 64;
282
+ const totalSize = estimatedSize + signatureSize;
283
+ const compressedEstimate = Math.ceil(totalSize * 0.7);
284
+ const requiredFields = Math.ceil(
285
+ compressedEstimate / InteractionTransactionP2WDA.MAX_BYTES_PER_WITNESS,
286
+ );
287
+
288
+ throw new Error(
289
+ `Please dont use P2WDA for this operation. Data too large. Raw size: ${estimatedSize} bytes, ` +
290
+ `estimated compressed: ${compressedEstimate} bytes, ` +
291
+ `needs ${requiredFields} witness fields, max is ${InteractionTransactionP2WDA.MAX_WITNESS_FIELDS}`,
292
+ );
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Finalize primary P2WDA input with the operation data
298
+ * This is where we create the signature and compress everything
299
+ */
300
+ private finalizePrimaryP2WDA(
301
+ inputIndex: number,
302
+ input: PsbtInput,
303
+ ): {
304
+ finalScriptSig: Buffer | undefined;
305
+ finalScriptWitness: Buffer | undefined;
306
+ } {
307
+ if (!input.partialSig || input.partialSig.length === 0) {
308
+ throw new Error(`No signature for P2WDA input #${inputIndex}`);
309
+ }
310
+
311
+ if (!input.witnessScript) {
312
+ throw new Error(`No witness script for P2WDA input #${inputIndex}`);
313
+ }
314
+
315
+ if (!this.compiledOperationData) {
316
+ throw new Error('Operation data not compiled');
317
+ }
318
+
319
+ const txSignature = input.partialSig[0].signature;
320
+ const messageToSign = Buffer.concat([txSignature, this.compiledOperationData]);
321
+ const signedMessage = MessageSigner.signMessage(
322
+ this.signer as ECPairInterface,
323
+ messageToSign,
324
+ );
325
+
326
+ const schnorrSignature = Buffer.from(signedMessage.signature);
327
+
328
+ // Combine and compress: COMPRESS(signature + compiledOperationData)
329
+ const fullData = Buffer.concat([schnorrSignature, this.compiledOperationData]);
330
+ const compressedData = Compressor.compress(fullData);
331
+
332
+ // Split into chunks
333
+ const chunks = this.splitIntoWitnessChunks(compressedData);
334
+
335
+ if (chunks.length > InteractionTransactionP2WDA.MAX_WITNESS_FIELDS) {
336
+ throw new Error(
337
+ `Compressed data needs ${chunks.length} witness fields, max is ${InteractionTransactionP2WDA.MAX_WITNESS_FIELDS}`,
338
+ );
339
+ }
340
+
341
+ // Build witness stack
342
+ const witnessStack: Buffer[] = [txSignature];
343
+
344
+ // Add exactly 10 data fields
345
+ // Bitcoin stack is reversed!
346
+ for (let i = 0; i < InteractionTransactionP2WDA.MAX_WITNESS_FIELDS; i++) {
347
+ witnessStack.push(i < chunks.length ? chunks[i] : Buffer.alloc(0));
348
+ }
349
+
350
+ witnessStack.push(input.witnessScript);
351
+
352
+ return {
353
+ finalScriptSig: undefined,
354
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
355
+ };
356
+ }
357
+
358
+ /**
359
+ * Split data into 80-byte chunks
360
+ */
361
+ private splitIntoWitnessChunks(data: Buffer): Buffer[] {
362
+ const chunks: Buffer[] = [];
363
+ let offset = 0;
364
+
365
+ while (offset < data.length) {
366
+ const size = Math.min(
367
+ InteractionTransactionP2WDA.MAX_BYTES_PER_WITNESS,
368
+ data.length - offset,
369
+ );
370
+ chunks.push(data.subarray(offset, offset + size));
371
+ offset += size;
372
+ }
373
+
374
+ return chunks;
375
+ }
376
+ }
@@ -1,6 +1,6 @@
1
- import { P2TRPayment, PaymentType, Psbt, PsbtInput, Signer, Taptree, toXOnly } from '@btc-vision/bitcoin';
1
+ import { P2TRPayment, PaymentType, Psbt, PsbtInput, Signer, Taptree, toXOnly, } from '@btc-vision/bitcoin';
2
2
  import { ECPairInterface } from 'ecpair';
3
- import { MINIMUM_AMOUNT_CA, MINIMUM_AMOUNT_REWARD, TransactionBuilder } from './TransactionBuilder.js';
3
+ import { MINIMUM_AMOUNT_CA, 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';
@@ -8,8 +8,9 @@ import { Compressor } from '../../bytecode/Compressor.js';
8
8
  import { EcKeyPair } from '../../keypair/EcKeyPair.js';
9
9
  import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
10
10
  import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
11
- import { ITimeLockOutput, TimeLockGenerator } from '../mineable/TimelockGenerator.js';
11
+ import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
12
12
  import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
13
+ import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
13
14
 
14
15
  /**
15
16
  * Shared interaction transaction
@@ -33,7 +34,7 @@ export abstract class SharedInteractionTransaction<
33
34
  protected abstract readonly scriptTree: Taptree;
34
35
 
35
36
  protected readonly challenge: ChallengeSolution;
36
- protected readonly epochChallenge: ITimeLockOutput;
37
+ protected readonly epochChallenge: IP2WSHAddress;
37
38
 
38
39
  protected calldataGenerator: CalldataGenerator;
39
40
 
@@ -111,7 +112,7 @@ export abstract class SharedInteractionTransaction<
111
112
  /**
112
113
  * Get the preimage
113
114
  */
114
- public getPreimage(): ChallengeSolution {
115
+ public getChallenge(): ChallengeSolution {
115
116
  return this.challenge;
116
117
  }
117
118
 
@@ -340,7 +341,7 @@ export abstract class SharedInteractionTransaction<
340
341
  }
341
342
  }
342
343
 
343
- private async createMineableRewardOutputs(): Promise<void> {
344
+ protected async createMineableRewardOutputs(): Promise<void> {
344
345
  if (!this.to) throw new Error('To address is required');
345
346
 
346
347
  const amountSpent: bigint = this.getTransactionOPNetFee();
@@ -28,6 +28,7 @@ initEccLib(ecc);
28
28
 
29
29
  export const MINIMUM_AMOUNT_REWARD: bigint = 540n;
30
30
  export const MINIMUM_AMOUNT_CA: bigint = 297n;
31
+ export const ANCHOR_SCRIPT = Buffer.from('51024e73', 'hex');
31
32
 
32
33
  /**
33
34
  * Allows to build a transaction like you would on Ethereum.
@@ -148,6 +149,12 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
148
149
  */
149
150
  protected isPubKeyDestination: boolean;
150
151
 
152
+ /**
153
+ * @description If the transaction need an anchor output
154
+ * @protected
155
+ */
156
+ protected anchor: boolean;
157
+
151
158
  protected note?: Buffer;
152
159
 
153
160
  protected constructor(parameters: ITransactionParameters) {
@@ -174,6 +181,8 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
174
181
  }
175
182
  }
176
183
 
184
+ this.anchor = parameters.anchor ?? false;
185
+
177
186
  this.isPubKeyDestination = this.to
178
187
  ? AddressVerificator.isValidPublicKey(this.to, this.network)
179
188
  : false;
@@ -191,6 +200,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
191
200
 
192
201
  this.transaction = new Psbt({
193
202
  network: this.network,
203
+ version: this.txVersion,
194
204
  });
195
205
  }
196
206
 
@@ -247,6 +257,13 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
247
257
  });
248
258
  }
249
259
 
260
+ public addAnchor(): void {
261
+ this.addOutput({
262
+ value: 0,
263
+ script: ANCHOR_SCRIPT,
264
+ });
265
+ }
266
+
250
267
  public async getFundingTransactionParameters(): Promise<IFundingTransactionParameters> {
251
268
  if (!this.estimatedFees) {
252
269
  this.estimatedFees = await this.estimateTransactionFees();
@@ -400,8 +417,10 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
400
417
  throw new Error('Output script is too short');
401
418
  }
402
419
 
403
- if (script.script[0] !== opcodes.OP_RETURN) {
404
- throw new Error('Output script must start with OP_RETURN when value is 0');
420
+ if (script.script[0] !== opcodes.OP_RETURN && !script.script.equals(ANCHOR_SCRIPT)) {
421
+ throw new Error(
422
+ 'Output script must start with OP_RETURN or be an ANCHOR when value is 0',
423
+ );
405
424
  }
406
425
  } else if (output.value < TransactionBuilder.MINIMUM_DUST) {
407
426
  throw new Error(
@@ -462,7 +481,11 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
462
481
  }
463
482
 
464
483
  public async rebuildFromBase64(base64: string): Promise<Psbt> {
465
- this.transaction = Psbt.fromBase64(base64, { network: this.network });
484
+ this.transaction = Psbt.fromBase64(base64, {
485
+ network: this.network,
486
+ version: this.txVersion,
487
+ });
488
+
466
489
  this.signed = false;
467
490
 
468
491
  this.sighashTypes = [Transaction.SIGHASH_ANYONECANPAY, Transaction.SIGHASH_ALL];
@@ -517,6 +540,10 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
517
540
  this.addOPReturn(this.note);
518
541
  }
519
542
 
543
+ if (this.anchor) {
544
+ this.addAnchor();
545
+ }
546
+
520
547
  /** Add the refund output */
521
548
  const sendBackAmount: bigint = this.totalInputAmount - amountSpent;
522
549
  if (sendBackAmount >= TransactionBuilder.MINIMUM_DUST) {
@@ -24,6 +24,7 @@ export interface ITransactionParameters extends ITweakedTransactionData {
24
24
  noSignatures?: boolean;
25
25
 
26
26
  readonly note?: string | Buffer;
27
+ readonly anchor?: boolean;
27
28
 
28
29
  readonly feeRate: number;
29
30
  readonly priorityFee: bigint;
@@ -0,0 +1,4 @@
1
+ export interface IP2WSHAddress {
2
+ address: string;
3
+ witnessScript: Buffer;
4
+ }
@@ -1,9 +1,5 @@
1
1
  import bitcoin, { Network, networks, opcodes, script } from '@btc-vision/bitcoin';
2
-
3
- export interface ITimeLockOutput {
4
- address: string;
5
- witnessScript: Buffer;
6
- }
2
+ import { IP2WSHAddress } from './IP2WSHAddress.js';
7
3
 
8
4
  export class TimeLockGenerator {
9
5
  private static readonly CSV_BLOCKS = 75;
@@ -16,7 +12,7 @@ export class TimeLockGenerator {
16
12
  publicKey: Buffer,
17
13
  network: Network = networks.bitcoin,
18
14
  csvBlocks: number = TimeLockGenerator.CSV_BLOCKS,
19
- ): ITimeLockOutput {
15
+ ): IP2WSHAddress {
20
16
  const witnessScript = script.compile([
21
17
  script.number.encode(csvBlocks),
22
18
  opcodes.OP_CHECKSEQUENCEVERIFY,