@btc-vision/transaction 1.6.1 → 1.6.4

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 (29) 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/index.js +1 -1
  5. package/browser/keypair/AddressVerificator.d.ts +2 -1
  6. package/browser/transaction/builders/TransactionBuilder.d.ts +3 -0
  7. package/browser/transaction/interfaces/ITransactionParameters.d.ts +1 -0
  8. package/browser/transaction/shared/TweakedTransaction.d.ts +18 -0
  9. package/build/_version.d.ts +1 -1
  10. package/build/_version.js +1 -1
  11. package/build/epoch/ChallengeSolution.d.ts +3 -3
  12. package/build/epoch/ChallengeSolution.js +3 -3
  13. package/build/epoch/validator/EpochValidator.d.ts +5 -6
  14. package/build/epoch/validator/EpochValidator.js +11 -12
  15. package/build/keypair/AddressVerificator.d.ts +2 -1
  16. package/build/keypair/AddressVerificator.js +4 -0
  17. package/build/transaction/builders/TransactionBuilder.d.ts +3 -0
  18. package/build/transaction/builders/TransactionBuilder.js +18 -3
  19. package/build/transaction/interfaces/ITransactionParameters.d.ts +1 -0
  20. package/build/transaction/shared/TweakedTransaction.d.ts +18 -0
  21. package/build/transaction/shared/TweakedTransaction.js +135 -18
  22. package/package.json +2 -2
  23. package/src/_version.ts +1 -1
  24. package/src/epoch/ChallengeSolution.ts +4 -4
  25. package/src/epoch/validator/EpochValidator.ts +12 -16
  26. package/src/keypair/AddressVerificator.ts +7 -1
  27. package/src/transaction/builders/TransactionBuilder.ts +30 -3
  28. package/src/transaction/interfaces/ITransactionParameters.ts +1 -0
  29. package/src/transaction/shared/TweakedTransaction.ts +210 -23
@@ -1,12 +1,19 @@
1
1
  import { Logger } from '@btc-vision/logger';
2
- import { address as bitAddress, crypto as bitCrypto, getFinalScripts, isP2MS, isP2PK, isP2PKH, isP2SHScript, isP2TR, isP2WPKH, isP2WSHScript, isUnknownSegwitVersion, opcodes, payments, PaymentType, script, toXOnly, varuint, } from '@btc-vision/bitcoin';
2
+ import { address as bitAddress, crypto as bitCrypto, getFinalScripts, isP2A, isP2MS, isP2PK, isP2PKH, isP2SHScript, isP2TR, isP2WPKH, isP2WSHScript, isUnknownSegwitVersion, opcodes, payments, PaymentType, script, toXOnly, varuint, } from '@btc-vision/bitcoin';
3
3
  import { TweakedSigner } from '../../signer/TweakedSigner.js';
4
4
  import { canSignNonTaprootInput, isTaprootInput, pubkeyInScript, } from '../../signer/SignerUtils.js';
5
+ import { TransactionBuilder } from '../builders/TransactionBuilder.js';
5
6
  export var TransactionSequence;
6
7
  (function (TransactionSequence) {
7
8
  TransactionSequence[TransactionSequence["REPLACE_BY_FEE"] = 4294967293] = "REPLACE_BY_FEE";
8
9
  TransactionSequence[TransactionSequence["FINAL"] = 4294967295] = "FINAL";
9
10
  })(TransactionSequence || (TransactionSequence = {}));
11
+ export var CSVModes;
12
+ (function (CSVModes) {
13
+ CSVModes[CSVModes["BLOCKS"] = 0] = "BLOCKS";
14
+ CSVModes[CSVModes["TIMESTAMPS"] = 1] = "TIMESTAMPS";
15
+ })(CSVModes || (CSVModes = {}));
16
+ const CSV_ENABLED_BLOCKS_MASK = 0x3fffffff;
10
17
  export class TweakedTransaction extends Logger {
11
18
  constructor(data) {
12
19
  super();
@@ -19,9 +26,12 @@ export class TweakedTransaction extends Logger {
19
26
  this.sequence = TransactionSequence.REPLACE_BY_FEE;
20
27
  this.tapLeafScript = null;
21
28
  this.isBrowser = false;
29
+ this.csvInputIndices = new Set();
30
+ this.anchorInputIndices = new Set();
22
31
  this.regenerated = false;
23
32
  this.ignoreSignatureErrors = false;
24
33
  this.noSignatures = false;
34
+ this.txVersion = 2;
25
35
  this.customFinalizerP2SH = (inputIndex, input, scriptA, isSegwit, isP2SH, isP2WSH) => {
26
36
  const inputDecoded = this.inputs[inputIndex];
27
37
  if (isP2SH && input.partialSig && inputDecoded && inputDecoded.redeemScript) {
@@ -32,6 +42,25 @@ export class TweakedTransaction extends Logger {
32
42
  finalScriptWitness: undefined,
33
43
  };
34
44
  }
45
+ if (this.anchorInputIndices.has(inputIndex)) {
46
+ return {
47
+ finalScriptSig: undefined,
48
+ finalScriptWitness: Buffer.from([0]),
49
+ };
50
+ }
51
+ if (isP2WSH && isSegwit && input.witnessScript) {
52
+ if (!input.partialSig || input.partialSig.length === 0) {
53
+ throw new Error(`No signatures for P2WSH input #${inputIndex}`);
54
+ }
55
+ const isCSVInput = this.csvInputIndices.has(inputIndex);
56
+ if (isCSVInput) {
57
+ const witnessStack = [input.partialSig[0].signature, input.witnessScript];
58
+ return {
59
+ finalScriptSig: undefined,
60
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
61
+ };
62
+ }
63
+ }
35
64
  return getFinalScripts(inputIndex, input, scriptA, isSegwit, isP2SH, isP2WSH, true, this.unlockScript);
36
65
  };
37
66
  this.signer = data.signer;
@@ -40,6 +69,9 @@ export class TweakedTransaction extends Logger {
40
69
  this.nonWitnessUtxo = data.nonWitnessUtxo;
41
70
  this.unlockScript = data.unlockScript;
42
71
  this.isBrowser = typeof window !== 'undefined';
72
+ if (data.txVersion) {
73
+ this.txVersion = data.txVersion;
74
+ }
43
75
  }
44
76
  static readScriptWitnessToWitnessStack(Buffer) {
45
77
  let offset = 0;
@@ -120,6 +152,9 @@ export class TweakedTransaction extends Logger {
120
152
  throw new Error('Transaction is already signed');
121
153
  this.sequence = TransactionSequence.FINAL;
122
154
  for (const input of this.inputs) {
155
+ if (this.csvInputIndices.has(this.inputs.indexOf(input))) {
156
+ continue;
157
+ }
123
158
  input.sequence = TransactionSequence.FINAL;
124
159
  }
125
160
  }
@@ -157,6 +192,8 @@ export class TweakedTransaction extends Logger {
157
192
  return this.signer;
158
193
  }
159
194
  async signInput(transaction, input, i, signer, reverse = false, errored = false) {
195
+ if (this.anchorInputIndices.has(i))
196
+ return;
160
197
  const publicKey = signer.publicKey;
161
198
  let isTaproot = isTaprootInput(input);
162
199
  if (reverse) {
@@ -335,17 +372,17 @@ export class TweakedTransaction extends Logger {
335
372
  return;
336
373
  }
337
374
  generatePsbtInputExtended(utxo, i, _extra = false) {
338
- const script = Buffer.from(utxo.scriptPubKey.hex, 'hex');
375
+ const scriptPub = Buffer.from(utxo.scriptPubKey.hex, 'hex');
339
376
  const input = {
340
377
  hash: utxo.transactionId,
341
378
  index: utxo.outputIndex,
342
379
  sequence: this.sequence,
343
380
  witnessUtxo: {
344
381
  value: Number(utxo.value),
345
- script,
382
+ script: scriptPub,
346
383
  },
347
384
  };
348
- if (isP2PKH(script)) {
385
+ if (isP2PKH(scriptPub)) {
349
386
  if (utxo.nonWitnessUtxo) {
350
387
  input.nonWitnessUtxo = Buffer.isBuffer(utxo.nonWitnessUtxo)
351
388
  ? utxo.nonWitnessUtxo
@@ -355,17 +392,12 @@ export class TweakedTransaction extends Logger {
355
392
  throw new Error('Missing nonWitnessUtxo for P2PKH UTXO');
356
393
  }
357
394
  }
358
- else if (isP2WPKH(script) || isUnknownSegwitVersion(script)) {
395
+ else if (isP2WPKH(scriptPub) || isUnknownSegwitVersion(scriptPub)) {
359
396
  }
360
- else if (isP2WSHScript(script)) {
361
- if (!utxo.witnessScript) {
362
- throw new Error('Missing witnessScript for P2WSH UTXO');
363
- }
364
- input.witnessScript = Buffer.isBuffer(utxo.witnessScript)
365
- ? utxo.witnessScript
366
- : Buffer.from(utxo.witnessScript, 'hex');
397
+ else if (isP2WSHScript(scriptPub)) {
398
+ this.processP2WSHInput(utxo, input, i);
367
399
  }
368
- else if (isP2SHScript(script)) {
400
+ else if (isP2SHScript(scriptPub)) {
369
401
  let redeemScriptBuf;
370
402
  if (utxo.redeemScript) {
371
403
  redeemScriptBuf = Buffer.isBuffer(utxo.redeemScript)
@@ -401,15 +433,13 @@ export class TweakedTransaction extends Logger {
401
433
  }
402
434
  else if (isP2WSHScript(redeemOutput)) {
403
435
  delete input.nonWitnessUtxo;
404
- if (!input.witnessScript) {
405
- throw new Error('Missing witnessScript for P2SH-P2WSH UTXO');
406
- }
436
+ this.processP2WSHInput(utxo, input, i);
407
437
  }
408
438
  else {
409
439
  delete input.witnessUtxo;
410
440
  }
411
441
  }
412
- else if (isP2TR(script)) {
442
+ else if (isP2TR(scriptPub)) {
413
443
  if (this.sighashTypes) {
414
444
  const inputSign = TweakedTransaction.calculateSignHash(this.sighashTypes);
415
445
  if (inputSign)
@@ -418,7 +448,11 @@ export class TweakedTransaction extends Logger {
418
448
  this.tweakSigner();
419
449
  input.tapInternalKey = this.internalPubKeyToXOnly();
420
450
  }
421
- else if (isP2PK(script) || isP2MS(script)) {
451
+ else if (isP2A(scriptPub)) {
452
+ this.anchorInputIndices.add(i);
453
+ input.isPayToAnchor = true;
454
+ }
455
+ else if (isP2PK(scriptPub) || isP2MS(scriptPub)) {
422
456
  if (utxo.nonWitnessUtxo) {
423
457
  input.nonWitnessUtxo = Buffer.isBuffer(utxo.nonWitnessUtxo)
424
458
  ? utxo.nonWitnessUtxo
@@ -441,6 +475,40 @@ export class TweakedTransaction extends Logger {
441
475
  }
442
476
  return input;
443
477
  }
478
+ processP2WSHInput(utxo, input, i) {
479
+ if (!utxo.witnessScript) {
480
+ throw new Error('Missing witnessScript for P2WSH UTXO');
481
+ }
482
+ input.witnessScript = Buffer.isBuffer(utxo.witnessScript)
483
+ ? utxo.witnessScript
484
+ : Buffer.from(utxo.witnessScript, 'hex');
485
+ const decompiled = script.decompile(input.witnessScript);
486
+ if (decompiled && this.isCSVScript(decompiled)) {
487
+ const decompiled = script.decompile(input.witnessScript);
488
+ if (decompiled && this.isCSVScript(decompiled)) {
489
+ this.csvInputIndices.add(i);
490
+ const csvBlocks = this.extractCSVBlocks(decompiled);
491
+ console.log('csvBlocks', csvBlocks);
492
+ input.sequence = this.setCSVSequence(csvBlocks, this.sequence);
493
+ }
494
+ }
495
+ }
496
+ secondsToCSVTimeUnits(seconds) {
497
+ return Math.floor(seconds / 512);
498
+ }
499
+ createTimeBasedCSV(seconds) {
500
+ const timeUnits = this.secondsToCSVTimeUnits(seconds);
501
+ if (timeUnits > 0xffff) {
502
+ throw new Error(`Time units ${timeUnits} exceeds maximum of 65,535`);
503
+ }
504
+ return timeUnits | (1 << 22);
505
+ }
506
+ isCSVEnabled(sequence) {
507
+ return (sequence & (1 << 31)) === 0;
508
+ }
509
+ extractCSVValue(sequence) {
510
+ return sequence & 0x0000ffff;
511
+ }
444
512
  async signInputsWalletBased(transaction) {
445
513
  const signer = this.signer;
446
514
  await signer.multiSignPsbt([transaction]);
@@ -449,6 +517,55 @@ export class TweakedTransaction extends Logger {
449
517
  }
450
518
  this.finalized = true;
451
519
  }
520
+ isCSVScript(decompiled) {
521
+ return decompiled.some((op) => op === opcodes.OP_CHECKSEQUENCEVERIFY);
522
+ }
523
+ setCSVSequence(csvBlocks, currentSequence) {
524
+ if (this.txVersion < 2) {
525
+ throw new Error('CSV requires transaction version 2 or higher');
526
+ }
527
+ if (csvBlocks > 0xffff) {
528
+ throw new Error(`CSV blocks ${csvBlocks} exceeds maximum of 65,535`);
529
+ }
530
+ const isTimeBased = (csvBlocks & (1 << 22)) !== 0;
531
+ let sequence = csvBlocks & 0x0000ffff;
532
+ if (isTimeBased) {
533
+ sequence |= 1 << 22;
534
+ }
535
+ if (currentSequence === TransactionSequence.REPLACE_BY_FEE) {
536
+ sequence |= 1 << 25;
537
+ }
538
+ sequence = sequence & 0x7fffffff;
539
+ return sequence;
540
+ }
541
+ getCSVType(csvValue) {
542
+ return csvValue & (1 << 22) ? CSVModes.TIMESTAMPS : CSVModes.BLOCKS;
543
+ }
544
+ extractCSVBlocks(decompiled) {
545
+ for (let i = 0; i < decompiled.length; i++) {
546
+ if (decompiled[i] === opcodes.OP_CHECKSEQUENCEVERIFY && i > 0) {
547
+ const csvValue = decompiled[i - 1];
548
+ if (Buffer.isBuffer(csvValue)) {
549
+ return script.number.decode(csvValue);
550
+ }
551
+ else if (typeof csvValue === 'number') {
552
+ if (csvValue === opcodes.OP_0 || csvValue === opcodes.OP_FALSE) {
553
+ return 0;
554
+ }
555
+ else if (csvValue === opcodes.OP_1NEGATE) {
556
+ return -1;
557
+ }
558
+ else if (csvValue >= opcodes.OP_1 && csvValue <= opcodes.OP_16) {
559
+ return csvValue - opcodes.OP_1 + 1;
560
+ }
561
+ else {
562
+ throw new Error(`Unexpected raw number in script: ${csvValue}`);
563
+ }
564
+ }
565
+ }
566
+ }
567
+ return 0;
568
+ }
452
569
  async attemptSignTaproot(transaction, input, i, signer, publicKey) {
453
570
  const isScriptSpend = this.isTaprootScriptSpend(input, publicKey);
454
571
  if (isScriptSpend) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@btc-vision/transaction",
3
3
  "type": "module",
4
- "version": "1.6.1",
4
+ "version": "1.6.4",
5
5
  "author": "BlobMaster41",
6
6
  "description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
7
7
  "engines": {
@@ -89,7 +89,7 @@
89
89
  "dependencies": {
90
90
  "@babel/plugin-proposal-object-rest-spread": "^7.21.4-esm",
91
91
  "@bitcoinerlab/secp256k1": "^1.2.0",
92
- "@btc-vision/bitcoin": "^6.4.6",
92
+ "@btc-vision/bitcoin": "^6.4.8",
93
93
  "@btc-vision/bitcoin-rpc": "^1.0.2",
94
94
  "@btc-vision/logger": "^1.0.6",
95
95
  "@eslint/js": "^9.32.0",
package/src/_version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '1.6.1';
1
+ export const version = '1.6.4';
@@ -90,7 +90,7 @@ export class ChallengeSolution implements IChallengeSolution {
90
90
  /**
91
91
  * Static method to validate from raw data directly
92
92
  */
93
- public static async validateRaw(data: RawChallenge): Promise<boolean> {
93
+ public static validateRaw(data: RawChallenge): boolean {
94
94
  return EpochValidator.validateEpochWinner(data);
95
95
  }
96
96
 
@@ -116,9 +116,9 @@ export class ChallengeSolution implements IChallengeSolution {
116
116
 
117
117
  /**
118
118
  * Verify this challenge
119
- * @returns {Promise<boolean>} True if the challenge is valid
119
+ * @returns {boolean} True if the challenge is valid
120
120
  */
121
- public async verify(): Promise<boolean> {
121
+ public verify(): boolean {
122
122
  return EpochValidator.validateChallengeSolution(this);
123
123
  }
124
124
 
@@ -165,7 +165,7 @@ export class ChallengeSolution implements IChallengeSolution {
165
165
  * Calculate the expected solution hash for this challenge
166
166
  * @returns {Promise<Buffer>} The calculated solution hash
167
167
  */
168
- public async calculateSolution(): Promise<Buffer> {
168
+ public calculateSolution(): Buffer {
169
169
  return EpochValidator.calculateSolution(
170
170
  this.verification.targetChecksum,
171
171
  this.publicKey.toBuffer(),
@@ -1,9 +1,9 @@
1
1
  import { IChallengeSolution, RawChallenge } from '../interfaces/IChallengeSolution.js';
2
2
  import { ChallengeSolution } from '../ChallengeSolution.js';
3
+ import { crypto } from '@btc-vision/bitcoin';
3
4
 
4
5
  export class EpochValidator {
5
6
  private static readonly BLOCKS_PER_EPOCH: bigint = 5n;
6
- private static readonly GRAFFITI_LENGTH: number = 16;
7
7
 
8
8
  /**
9
9
  * Convert Buffer to Uint8Array
@@ -22,9 +22,8 @@ export class EpochValidator {
22
22
  /**
23
23
  * Calculate SHA-1 hash
24
24
  */
25
- public static async sha1(data: Uint8Array): Promise<Uint8Array> {
26
- const hashBuffer = await crypto.subtle.digest('SHA-1', data);
27
- return new Uint8Array(hashBuffer);
25
+ public static sha1(data: Uint8Array | Buffer): Buffer {
26
+ return crypto.sha1(Buffer.isBuffer(data) ? data : Buffer.from(data));
28
27
  }
29
28
 
30
29
  /**
@@ -78,10 +77,7 @@ export class EpochValidator {
78
77
  /**
79
78
  * Verify an epoch solution using IPreimage
80
79
  */
81
- public static async verifySolution(
82
- challenge: IChallengeSolution,
83
- log: boolean = false,
84
- ): Promise<boolean> {
80
+ public static verifySolution(challenge: IChallengeSolution, log: boolean = false): boolean {
85
81
  try {
86
82
  const verification = challenge.verification;
87
83
  const calculatedPreimage = this.calculatePreimage(
@@ -90,7 +86,7 @@ export class EpochValidator {
90
86
  challenge.salt,
91
87
  );
92
88
 
93
- const computedSolution = await this.sha1(this.bufferToUint8Array(calculatedPreimage));
89
+ const computedSolution = this.sha1(calculatedPreimage);
94
90
  const computedSolutionBuffer = this.uint8ArrayToBuffer(computedSolution);
95
91
 
96
92
  if (!computedSolutionBuffer.equals(challenge.solution)) {
@@ -134,16 +130,16 @@ export class EpochValidator {
134
130
  /**
135
131
  * Validate epoch winner from raw data
136
132
  */
137
- public static async validateEpochWinner(epochData: RawChallenge): Promise<boolean> {
133
+ public static validateEpochWinner(epochData: RawChallenge): boolean {
138
134
  const preimage = new ChallengeSolution(epochData);
139
- return await this.verifySolution(preimage);
135
+ return this.verifySolution(preimage);
140
136
  }
141
137
 
142
138
  /**
143
139
  * Validate epoch winner from Preimage instance
144
140
  */
145
- public static async validateChallengeSolution(challenge: IChallengeSolution): Promise<boolean> {
146
- return await this.verifySolution(challenge);
141
+ public static validateChallengeSolution(challenge: IChallengeSolution): boolean {
142
+ return this.verifySolution(challenge);
147
143
  }
148
144
 
149
145
  /**
@@ -153,13 +149,13 @@ export class EpochValidator {
153
149
  * @param salt The salt buffer (32 bytes)
154
150
  * @returns The SHA-1 hash of the preimage
155
151
  */
156
- public static async calculateSolution(
152
+ public static calculateSolution(
157
153
  targetChecksum: Buffer,
158
154
  publicKey: Buffer,
159
155
  salt: Buffer,
160
- ): Promise<Buffer> {
156
+ ): Buffer {
161
157
  const preimage = this.calculatePreimage(targetChecksum, publicKey, salt);
162
- const hash = await this.sha1(this.bufferToUint8Array(preimage));
158
+ const hash = this.sha1(this.bufferToUint8Array(preimage));
163
159
  return this.uint8ArrayToBuffer(hash);
164
160
  }
165
161
 
@@ -12,6 +12,7 @@ export enum AddressTypes {
12
12
  P2PK = 'P2PK',
13
13
  P2TR = 'P2TR',
14
14
  P2WPKH = 'P2WPKH',
15
+ P2WSH = 'P2WSH',
15
16
  }
16
17
 
17
18
  export class AddressVerificator {
@@ -193,11 +194,16 @@ export class AddressVerificator {
193
194
  }
194
195
 
195
196
  if (decodedBech32.prefix === network.bech32) {
196
- // P2WPKH: SegWit address (starting with 'bc1q' for mainnet, 'tb1q' for testnet)
197
+ // P2WPKH: SegWit address (20 bytes)
197
198
  if (decodedBech32.version === 0 && decodedBech32.data.length === 20) {
198
199
  return AddressTypes.P2WPKH;
199
200
  }
200
201
 
202
+ // P2WSH: SegWit script hash (32 bytes)
203
+ if (decodedBech32.version === 0 && decodedBech32.data.length === 32) {
204
+ return AddressTypes.P2WSH;
205
+ }
206
+
201
207
  // P2TR: Taproot address (starting with 'bc1p' for mainnet, 'tb1p' for testnet)
202
208
  if (decodedBech32.version === 1 && decodedBech32.data.length === 32) {
203
209
  return AddressTypes.P2TR;
@@ -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;