@btc-vision/transaction 1.6.4 → 1.6.6

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 (62) hide show
  1. package/browser/_version.d.ts +1 -1
  2. package/browser/generators/builders/P2WDAGenerator.d.ts +13 -0
  3. package/browser/index.js +1 -1
  4. package/browser/keypair/Address.d.ts +3 -2
  5. package/browser/keypair/AddressVerificator.d.ts +12 -1
  6. package/browser/keypair/Wallet.d.ts +3 -0
  7. package/browser/opnet.d.ts +4 -0
  8. package/browser/p2wda/P2WDADetector.d.ts +16 -0
  9. package/browser/transaction/TransactionFactory.d.ts +3 -1
  10. package/browser/transaction/builders/DeploymentTransaction.d.ts +3 -3
  11. package/browser/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
  12. package/browser/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
  13. package/browser/transaction/mineable/IP2WSHAddress.d.ts +4 -0
  14. package/browser/transaction/mineable/TimelockGenerator.d.ts +2 -5
  15. package/browser/transaction/shared/TweakedTransaction.d.ts +5 -0
  16. package/build/_version.d.ts +1 -1
  17. package/build/_version.js +1 -1
  18. package/build/generators/builders/P2WDAGenerator.d.ts +13 -0
  19. package/build/generators/builders/P2WDAGenerator.js +62 -0
  20. package/build/keypair/Address.d.ts +3 -2
  21. package/build/keypair/Address.js +28 -2
  22. package/build/keypair/AddressVerificator.d.ts +12 -1
  23. package/build/keypair/AddressVerificator.js +78 -1
  24. package/build/keypair/Wallet.d.ts +3 -0
  25. package/build/keypair/Wallet.js +4 -0
  26. package/build/opnet.d.ts +4 -0
  27. package/build/opnet.js +4 -0
  28. package/build/p2wda/P2WDADetector.d.ts +16 -0
  29. package/build/p2wda/P2WDADetector.js +97 -0
  30. package/build/transaction/TransactionFactory.d.ts +3 -1
  31. package/build/transaction/TransactionFactory.js +35 -4
  32. package/build/transaction/builders/DeploymentTransaction.d.ts +3 -3
  33. package/build/transaction/builders/DeploymentTransaction.js +1 -1
  34. package/build/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
  35. package/build/transaction/builders/InteractionTransactionP2WDA.js +205 -0
  36. package/build/transaction/builders/MultiSignTransaction.js +2 -2
  37. package/build/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
  38. package/build/transaction/builders/SharedInteractionTransaction.js +3 -3
  39. package/build/transaction/mineable/IP2WSHAddress.d.ts +4 -0
  40. package/build/transaction/mineable/IP2WSHAddress.js +1 -0
  41. package/build/transaction/mineable/TimelockGenerator.d.ts +2 -5
  42. package/build/transaction/shared/TweakedTransaction.d.ts +5 -0
  43. package/build/transaction/shared/TweakedTransaction.js +19 -0
  44. package/doc/README.md +0 -0
  45. package/doc/addresses/P2OP.md +1 -0
  46. package/doc/addresses/P2WDA.md +240 -0
  47. package/package.json +9 -9
  48. package/src/_version.ts +1 -1
  49. package/src/generators/builders/P2WDAGenerator.ts +174 -0
  50. package/src/keypair/Address.ts +58 -3
  51. package/src/keypair/AddressVerificator.ts +140 -1
  52. package/src/keypair/Wallet.ts +16 -0
  53. package/src/opnet.ts +4 -0
  54. package/src/p2wda/P2WDADetector.ts +218 -0
  55. package/src/transaction/TransactionFactory.ts +79 -5
  56. package/src/transaction/builders/DeploymentTransaction.ts +4 -3
  57. package/src/transaction/builders/InteractionTransactionP2WDA.ts +380 -0
  58. package/src/transaction/builders/MultiSignTransaction.ts +2 -2
  59. package/src/transaction/builders/SharedInteractionTransaction.ts +7 -6
  60. package/src/transaction/mineable/IP2WSHAddress.ts +4 -0
  61. package/src/transaction/mineable/TimelockGenerator.ts +2 -6
  62. package/src/transaction/shared/TweakedTransaction.ts +36 -0
@@ -0,0 +1,380 @@
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 {
6
+ MINIMUM_AMOUNT_CA,
7
+ MINIMUM_AMOUNT_REWARD,
8
+ TransactionBuilder,
9
+ } from './TransactionBuilder.js';
10
+ import { MessageSigner } from '../../keypair/MessageSigner.js';
11
+ import { Compressor } from '../../bytecode/Compressor.js';
12
+ import { P2WDAGenerator } from '../../generators/builders/P2WDAGenerator.js';
13
+ import { Feature, Features } from '../../generators/Features.js';
14
+ import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
15
+ import { EcKeyPair } from '../../keypair/EcKeyPair.js';
16
+ import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
17
+ import { ECPairInterface } from 'ecpair';
18
+ import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
19
+ import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
20
+ import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
21
+
22
+ /**
23
+ * P2WDA Interaction Transaction
24
+ *
25
+ * This transaction type uses the exact same operation data as regular interactions
26
+ * (via CalldataGenerator), but embeds it in the witness field instead of a taproot script.
27
+ * This achieves 75% cost reduction through the witness discount.
28
+ */
29
+ export class InteractionTransactionP2WDA extends TransactionBuilder<TransactionType.INTERACTION> {
30
+ private static readonly MAX_WITNESS_FIELDS = 10;
31
+ private static readonly MAX_BYTES_PER_WITNESS = 80;
32
+
33
+ public readonly type: TransactionType.INTERACTION = TransactionType.INTERACTION;
34
+ protected readonly epochChallenge: IP2WSHAddress;
35
+ /**
36
+ * Disable auto refund
37
+ * @protected
38
+ */
39
+ protected readonly disableAutoRefund: boolean;
40
+ private readonly contractAddress: string;
41
+ private readonly contractSecret: Buffer;
42
+ private readonly calldata: Buffer;
43
+ private readonly challenge: ChallengeSolution;
44
+ private readonly randomBytes: Buffer;
45
+ private p2wdaGenerator: P2WDAGenerator;
46
+ private scriptSigner: ECPairInterface;
47
+ private p2wdaInputIndices: Set<number> = new Set();
48
+ /**
49
+ * The compiled operation data from CalldataGenerator
50
+ * This is exactly what would go in a taproot script, but we put it in witness instead
51
+ */
52
+ private readonly compiledOperationData: Buffer | null = null;
53
+
54
+ public constructor(parameters: IInteractionParameters) {
55
+ super(parameters);
56
+
57
+ if (!parameters.to) {
58
+ throw new Error('Contract address (to) is required');
59
+ }
60
+
61
+ if (!parameters.contract) {
62
+ throw new Error('Contract secret is required');
63
+ }
64
+
65
+ if (!parameters.calldata) {
66
+ throw new Error('Calldata is required');
67
+ }
68
+
69
+ if (!parameters.challenge) {
70
+ throw new Error('Challenge solution is required');
71
+ }
72
+
73
+ this.disableAutoRefund = parameters.disableAutoRefund || false;
74
+ this.contractAddress = parameters.to;
75
+ this.contractSecret = Buffer.from(parameters.contract.replace('0x', ''), 'hex');
76
+ this.calldata = Compressor.compress(parameters.calldata);
77
+ this.challenge = parameters.challenge;
78
+ this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
79
+
80
+ // Create the script signer (same as SharedInteractionTransaction does)
81
+ this.scriptSigner = this.generateKeyPairFromSeed();
82
+
83
+ // Create the P2WDA generator instead of CalldataGenerator
84
+ // P2WDA needs a different data format optimized for witness embedding
85
+ this.p2wdaGenerator = new P2WDAGenerator(
86
+ Buffer.from(this.signer.publicKey),
87
+ this.scriptSignerXOnlyPubKey(),
88
+ this.network,
89
+ );
90
+
91
+ // Validate contract secret
92
+ if (this.contractSecret.length !== 32) {
93
+ throw new Error('Invalid contract secret length. Expected 32 bytes.');
94
+ }
95
+
96
+ this.epochChallenge = TimeLockGenerator.generateTimeLockAddress(
97
+ this.challenge.publicKey.originalPublicKeyBuffer(),
98
+ this.network,
99
+ );
100
+
101
+ // Validate P2WDA inputs
102
+ this.validateP2WDAInputs();
103
+
104
+ this.compiledOperationData = this.p2wdaGenerator.compile(
105
+ this.calldata,
106
+ this.contractSecret,
107
+ this.challenge,
108
+ this.priorityFee,
109
+ this.generateFeatures(parameters),
110
+ );
111
+
112
+ // Validate size early
113
+ this.validateOperationDataSize();
114
+
115
+ this.internalInit();
116
+ }
117
+
118
+ /**
119
+ * Get random bytes (for compatibility if needed elsewhere)
120
+ */
121
+ public getRndBytes(): Buffer {
122
+ return this.randomBytes;
123
+ }
124
+
125
+ /**
126
+ * Get the challenge (for compatibility if needed elsewhere)
127
+ */
128
+ public getChallenge(): ChallengeSolution {
129
+ return this.challenge;
130
+ }
131
+
132
+ /**
133
+ * Get contract secret (for compatibility if needed elsewhere)
134
+ */
135
+ public getContractSecret(): Buffer {
136
+ return this.contractSecret;
137
+ }
138
+
139
+ /**
140
+ * Build the transaction
141
+ */
142
+ protected async buildTransaction(): Promise<void> {
143
+ if (!this.regenerated) {
144
+ this.addInputsFromUTXO();
145
+ }
146
+
147
+ // Add refund
148
+ await this.createMineableRewardOutputs();
149
+ }
150
+
151
+ protected async createMineableRewardOutputs(): Promise<void> {
152
+ if (!this.to) throw new Error('To address is required');
153
+
154
+ const amountSpent: bigint = this.getTransactionOPNetFee();
155
+
156
+ let amountToCA: bigint;
157
+ if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
158
+ amountToCA = MINIMUM_AMOUNT_CA;
159
+ } else {
160
+ amountToCA = amountSpent;
161
+ }
162
+
163
+ // ALWAYS THE FIRST INPUT.
164
+ this.addOutput({
165
+ value: Number(amountToCA),
166
+ address: this.to,
167
+ });
168
+
169
+ // ALWAYS SECOND.
170
+ if (
171
+ amountToCA === MINIMUM_AMOUNT_CA &&
172
+ amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD
173
+ ) {
174
+ this.addOutput({
175
+ value: Number(amountSpent - amountToCA),
176
+ address: this.epochChallenge.address,
177
+ });
178
+ }
179
+
180
+ const amount = this.addOptionalOutputsAndGetAmount();
181
+ if (!this.disableAutoRefund) {
182
+ await this.addRefundOutput(amountSpent + amount);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Sign inputs with P2WDA-specific handling
188
+ */
189
+ protected override async signInputs(transaction: Psbt): Promise<void> {
190
+ // Sign all inputs
191
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
192
+ await this.signInput(transaction, transaction.data.inputs[i], i, this.signer);
193
+ }
194
+
195
+ // Finalize with appropriate finalizers
196
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
197
+ if (this.p2wdaInputIndices.has(i)) {
198
+ if (i === 0) {
199
+ transaction.finalizeInput(i, this.finalizePrimaryP2WDA.bind(this));
200
+ } else {
201
+ transaction.finalizeInput(i, this.finalizeSecondaryP2WDA.bind(this));
202
+ }
203
+ } else {
204
+ transaction.finalizeInput(i, this.customFinalizerP2SH.bind(this));
205
+ }
206
+ }
207
+
208
+ this.finalized = true;
209
+ }
210
+
211
+ /**
212
+ * Generate features array (same as InteractionTransaction)
213
+ */
214
+ private generateFeatures(parameters: IInteractionParameters): Feature<Features>[] {
215
+ const features: Feature<Features>[] = [];
216
+
217
+ if (parameters.loadedStorage) {
218
+ features.push({
219
+ opcode: Features.ACCESS_LIST,
220
+ data: parameters.loadedStorage,
221
+ });
222
+ }
223
+
224
+ const submission = parameters.challenge.getSubmission();
225
+ if (submission) {
226
+ features.push({
227
+ opcode: Features.EPOCH_SUBMISSION,
228
+ data: submission,
229
+ });
230
+ }
231
+
232
+ return features;
233
+ }
234
+
235
+ /**
236
+ * Generate keypair from seed (same as SharedInteractionTransaction)
237
+ */
238
+ private generateKeyPairFromSeed(): ECPairInterface {
239
+ return EcKeyPair.fromSeedKeyPair(this.randomBytes, this.network);
240
+ }
241
+
242
+ /**
243
+ * Get script signer x-only pubkey (same as SharedInteractionTransaction)
244
+ */
245
+ private scriptSignerXOnlyPubKey(): Buffer {
246
+ return toXOnly(Buffer.from(this.scriptSigner.publicKey));
247
+ }
248
+
249
+ /**
250
+ * Validate that input 0 is P2WDA
251
+ */
252
+ private validateP2WDAInputs(): void {
253
+ if (this.utxos.length === 0 || !P2WDADetector.isP2WDAUTXO(this.utxos[0])) {
254
+ throw new Error('Input 0 must be a P2WDA UTXO');
255
+ }
256
+
257
+ // Track all P2WDA inputs
258
+ for (let i = 0; i < this.utxos.length; i++) {
259
+ if (P2WDADetector.isP2WDAUTXO(this.utxos[i])) {
260
+ this.p2wdaInputIndices.add(i);
261
+ }
262
+ }
263
+
264
+ for (let i = 0; i < this.optionalInputs.length; i++) {
265
+ const actualIndex = this.utxos.length + i;
266
+ if (P2WDADetector.isP2WDAUTXO(this.optionalInputs[i])) {
267
+ this.p2wdaInputIndices.add(actualIndex);
268
+ }
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Validate the compiled operation data will fit in witness fields
274
+ */
275
+ private validateOperationDataSize(): void {
276
+ if (!this.compiledOperationData) {
277
+ throw new Error('Operation data not compiled');
278
+ }
279
+
280
+ // The data that goes in witness: COMPRESS(signature + compiledOperationData)
281
+ // Signature is 64 bytes
282
+ const estimatedSize = this.compiledOperationData.length;
283
+
284
+ if (!P2WDAGenerator.validateWitnessSize(estimatedSize)) {
285
+ const signatureSize = 64;
286
+ const totalSize = estimatedSize + signatureSize;
287
+ const compressedEstimate = Math.ceil(totalSize * 0.7);
288
+ const requiredFields = Math.ceil(
289
+ compressedEstimate / InteractionTransactionP2WDA.MAX_BYTES_PER_WITNESS,
290
+ );
291
+
292
+ throw new Error(
293
+ `Please dont use P2WDA for this operation. Data too large. Raw size: ${estimatedSize} bytes, ` +
294
+ `estimated compressed: ${compressedEstimate} bytes, ` +
295
+ `needs ${requiredFields} witness fields, max is ${InteractionTransactionP2WDA.MAX_WITNESS_FIELDS}`,
296
+ );
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Finalize primary P2WDA input with the operation data
302
+ * This is where we create the signature and compress everything
303
+ */
304
+ private finalizePrimaryP2WDA(
305
+ inputIndex: number,
306
+ input: PsbtInput,
307
+ ): {
308
+ finalScriptSig: Buffer | undefined;
309
+ finalScriptWitness: Buffer | undefined;
310
+ } {
311
+ if (!input.partialSig || input.partialSig.length === 0) {
312
+ throw new Error(`No signature for P2WDA input #${inputIndex}`);
313
+ }
314
+
315
+ if (!input.witnessScript) {
316
+ throw new Error(`No witness script for P2WDA input #${inputIndex}`);
317
+ }
318
+
319
+ if (!this.compiledOperationData) {
320
+ throw new Error('Operation data not compiled');
321
+ }
322
+
323
+ const txSignature = input.partialSig[0].signature;
324
+ const messageToSign = Buffer.concat([txSignature, this.compiledOperationData]);
325
+ const signedMessage = MessageSigner.signMessage(
326
+ this.signer as ECPairInterface,
327
+ messageToSign,
328
+ );
329
+
330
+ const schnorrSignature = Buffer.from(signedMessage.signature);
331
+
332
+ // Combine and compress: COMPRESS(signature + compiledOperationData)
333
+ const fullData = Buffer.concat([schnorrSignature, this.compiledOperationData]);
334
+ const compressedData = Compressor.compress(fullData);
335
+
336
+ // Split into chunks
337
+ const chunks = this.splitIntoWitnessChunks(compressedData);
338
+
339
+ if (chunks.length > InteractionTransactionP2WDA.MAX_WITNESS_FIELDS) {
340
+ throw new Error(
341
+ `Compressed data needs ${chunks.length} witness fields, max is ${InteractionTransactionP2WDA.MAX_WITNESS_FIELDS}`,
342
+ );
343
+ }
344
+
345
+ // Build witness stack
346
+ const witnessStack: Buffer[] = [txSignature];
347
+
348
+ // Add exactly 10 data fields
349
+ // Bitcoin stack is reversed!
350
+ for (let i = 0; i < InteractionTransactionP2WDA.MAX_WITNESS_FIELDS; i++) {
351
+ witnessStack.push(i < chunks.length ? chunks[i] : Buffer.alloc(0));
352
+ }
353
+
354
+ witnessStack.push(input.witnessScript);
355
+
356
+ return {
357
+ finalScriptSig: undefined,
358
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
359
+ };
360
+ }
361
+
362
+ /**
363
+ * Split data into 80-byte chunks
364
+ */
365
+ private splitIntoWitnessChunks(data: Buffer): Buffer[] {
366
+ const chunks: Buffer[] = [];
367
+ let offset = 0;
368
+
369
+ while (offset < data.length) {
370
+ const size = Math.min(
371
+ InteractionTransactionP2WDA.MAX_BYTES_PER_WITNESS,
372
+ data.length - offset,
373
+ );
374
+ chunks.push(data.subarray(offset, offset + size));
375
+ offset += size;
376
+ }
377
+
378
+ return chunks;
379
+ }
380
+ }
@@ -613,9 +613,9 @@ export class MultiSignTransaction extends TransactionBuilder<TransactionType.MUL
613
613
  }
614
614
 
615
615
  private getTotalOutputAmount(utxos: UTXO[]): bigint {
616
- let total = BigInt(0);
616
+ let total: bigint = 0n;
617
617
  for (const utxo of utxos) {
618
- total += BigInt(utxo.value);
618
+ total += utxo.value;
619
619
  }
620
620
 
621
621
  return total;
@@ -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();
@@ -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,
@@ -39,6 +39,8 @@ import {
39
39
  pubkeyInScript,
40
40
  } from '../../signer/SignerUtils.js';
41
41
  import { TransactionBuilder } from '../builders/TransactionBuilder.js';
42
+ import { Buffer } from 'buffer';
43
+ import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
42
44
 
43
45
  export type SupportedTransactionVersion = 1 | 2 | 3;
44
46
 
@@ -954,6 +956,11 @@ export abstract class TweakedTransaction extends Logger {
954
956
  throw new Error(`No signatures for P2WSH input #${inputIndex}`);
955
957
  }
956
958
 
959
+ const isP2WDA = P2WDADetector.isP2WDAWitnessScript(input.witnessScript);
960
+ if (isP2WDA) {
961
+ return this.finalizeSecondaryP2WDA(inputIndex, input);
962
+ }
963
+
957
964
  // Check if this is a CSV input
958
965
  const isCSVInput = this.csvInputIndices.has(inputIndex);
959
966
  if (isCSVInput) {
@@ -981,6 +988,35 @@ export abstract class TweakedTransaction extends Logger {
981
988
  );
982
989
  };
983
990
 
991
+ /**
992
+ * Finalize secondary P2WDA inputs with empty data
993
+ */
994
+ protected finalizeSecondaryP2WDA(
995
+ inputIndex: number,
996
+ input: PsbtInput,
997
+ ): {
998
+ finalScriptWitness: Buffer | undefined;
999
+ finalScriptSig: Buffer | undefined;
1000
+ } {
1001
+ if (!input.partialSig || input.partialSig.length === 0) {
1002
+ throw new Error(`No signature for P2WDA input #${inputIndex}`);
1003
+ }
1004
+
1005
+ if (!input.witnessScript) {
1006
+ throw new Error(`No witness script for P2WDA input #${inputIndex}`);
1007
+ }
1008
+
1009
+ const witnessStack = P2WDADetector.createSimpleP2WDAWitness(
1010
+ input.partialSig[0].signature,
1011
+ input.witnessScript,
1012
+ );
1013
+
1014
+ return {
1015
+ finalScriptSig: undefined,
1016
+ finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
1017
+ };
1018
+ }
1019
+
984
1020
  protected async signInputsWalletBased(transaction: Psbt): Promise<void> {
985
1021
  const signer: UnisatSigner = this.signer as UnisatSigner;
986
1022