@btc-vision/transaction 1.7.19 → 1.7.23

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 (139) hide show
  1. package/.babelrc +10 -1
  2. package/LICENSE +190 -21
  3. package/README.md +1 -1
  4. package/browser/_version.d.ts +1 -1
  5. package/browser/bip39.js +204 -0
  6. package/browser/bitcoin-utils.js +3172 -0
  7. package/browser/btc-vision-bip32.js +805 -0
  8. package/browser/btc-vision-bitcoin.js +4179 -0
  9. package/browser/btc-vision-logger.js +273 -0
  10. package/browser/btc-vision-post-quantum.js +542 -0
  11. package/browser/chain/ChainData.d.ts +1 -1
  12. package/browser/crypto/crypto.d.ts +1 -1
  13. package/browser/generators/AddressGenerator.d.ts +1 -1
  14. package/browser/generators/Generator.d.ts +1 -1
  15. package/browser/generators/MLDSAData.d.ts +1 -1
  16. package/browser/generators/builders/CalldataGenerator.d.ts +1 -1
  17. package/browser/generators/builders/CustomGenerator.d.ts +1 -1
  18. package/browser/generators/builders/DeploymentGenerator.d.ts +1 -1
  19. package/browser/generators/builders/HashCommitmentGenerator.d.ts +49 -0
  20. package/browser/generators/builders/LegacyCalldataGenerator.d.ts +1 -1
  21. package/browser/generators/builders/P2WDAGenerator.d.ts +1 -1
  22. package/browser/index.js +10775 -2
  23. package/browser/keypair/Address.d.ts +5 -3
  24. package/browser/keypair/AddressVerificator.d.ts +2 -2
  25. package/browser/keypair/EcKeyPair.d.ts +2 -2
  26. package/browser/keypair/MessageSigner.d.ts +2 -2
  27. package/browser/keypair/Wallet.d.ts +2 -2
  28. package/browser/metadata/ContractBaseMetadata.d.ts +1 -1
  29. package/browser/mnemonic/Mnemonic.d.ts +2 -2
  30. package/browser/noble-curves.js +3316 -0
  31. package/browser/noble-hashes.js +1608 -0
  32. package/browser/opnet.d.ts +15 -2
  33. package/browser/p2wda/P2WDADetector.d.ts +2 -2
  34. package/browser/polyfills.js +4590 -0
  35. package/browser/scure-base.js +410 -0
  36. package/browser/signer/AddressRotation.d.ts +12 -0
  37. package/browser/signer/SignerUtils.d.ts +1 -1
  38. package/browser/signer/TweakedSigner.d.ts +1 -1
  39. package/browser/transaction/TransactionFactory.d.ts +15 -1
  40. package/browser/transaction/browser/BrowserSignerBase.d.ts +1 -1
  41. package/browser/transaction/browser/Web3Provider.d.ts +1 -1
  42. package/browser/transaction/browser/extensions/UnisatSigner.d.ts +1 -1
  43. package/browser/transaction/browser/extensions/XverseSigner.d.ts +1 -1
  44. package/browser/transaction/builders/CancelTransaction.d.ts +1 -1
  45. package/browser/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
  46. package/browser/transaction/builders/CustomScriptTransaction.d.ts +1 -1
  47. package/browser/transaction/builders/DeploymentTransaction.d.ts +1 -1
  48. package/browser/transaction/builders/FundingTransaction.d.ts +1 -1
  49. package/browser/transaction/builders/InteractionTransaction.d.ts +1 -1
  50. package/browser/transaction/builders/InteractionTransactionP2WDA.d.ts +2 -2
  51. package/browser/transaction/builders/MultiSignTransaction.d.ts +1 -1
  52. package/browser/transaction/builders/SharedInteractionTransaction.d.ts +1 -1
  53. package/browser/transaction/builders/TransactionBuilder.d.ts +1 -1
  54. package/browser/transaction/enums/TransactionType.d.ts +3 -1
  55. package/browser/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
  56. package/browser/transaction/interfaces/ITransactionParameters.d.ts +3 -1
  57. package/browser/transaction/interfaces/Tap.d.ts +1 -1
  58. package/browser/transaction/mineable/TimelockGenerator.d.ts +1 -1
  59. package/browser/transaction/offline/OfflineTransactionManager.d.ts +69 -0
  60. package/browser/transaction/offline/TransactionReconstructor.d.ts +28 -0
  61. package/browser/transaction/offline/TransactionSerializer.d.ts +50 -0
  62. package/browser/transaction/offline/TransactionStateCapture.d.ts +52 -0
  63. package/browser/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
  64. package/browser/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
  65. package/browser/transaction/processor/PsbtTransaction.d.ts +1 -1
  66. package/browser/transaction/shared/P2TR_MS.d.ts +1 -1
  67. package/browser/transaction/shared/TweakedTransaction.d.ts +15 -4
  68. package/browser/utxo/OPNetLimitedProvider.d.ts +1 -1
  69. package/browser/utxo/interfaces/IUTXO.d.ts +2 -0
  70. package/browser/valibot.js +4948 -0
  71. package/browser/vendors.js +12913 -0
  72. package/browser/verification/TapscriptVerificator.d.ts +1 -1
  73. package/build/_version.d.ts +1 -1
  74. package/build/_version.js +1 -1
  75. package/build/generators/builders/HashCommitmentGenerator.d.ts +49 -0
  76. package/build/generators/builders/HashCommitmentGenerator.js +229 -0
  77. package/build/keypair/Address.d.ts +3 -1
  78. package/build/keypair/Address.js +87 -54
  79. package/build/opnet.d.ts +14 -1
  80. package/build/opnet.js +11 -1
  81. package/build/signer/AddressRotation.d.ts +12 -0
  82. package/build/signer/AddressRotation.js +16 -0
  83. package/build/transaction/TransactionFactory.d.ts +14 -0
  84. package/build/transaction/TransactionFactory.js +36 -0
  85. package/build/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
  86. package/build/transaction/builders/ConsolidatedInteractionTransaction.js +259 -0
  87. package/build/transaction/builders/TransactionBuilder.js +2 -0
  88. package/build/transaction/enums/TransactionType.d.ts +3 -1
  89. package/build/transaction/enums/TransactionType.js +2 -0
  90. package/build/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
  91. package/build/transaction/interfaces/IConsolidatedTransactionParameters.js +1 -0
  92. package/build/transaction/interfaces/ITransactionParameters.d.ts +2 -0
  93. package/build/transaction/offline/OfflineTransactionManager.d.ts +69 -0
  94. package/build/transaction/offline/OfflineTransactionManager.js +255 -0
  95. package/build/transaction/offline/TransactionReconstructor.d.ts +28 -0
  96. package/build/transaction/offline/TransactionReconstructor.js +243 -0
  97. package/build/transaction/offline/TransactionSerializer.d.ts +50 -0
  98. package/build/transaction/offline/TransactionSerializer.js +700 -0
  99. package/build/transaction/offline/TransactionStateCapture.d.ts +52 -0
  100. package/build/transaction/offline/TransactionStateCapture.js +275 -0
  101. package/build/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
  102. package/build/transaction/offline/interfaces/ISerializableState.js +2 -0
  103. package/build/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
  104. package/build/transaction/offline/interfaces/ITypeSpecificData.js +19 -0
  105. package/build/transaction/shared/TweakedTransaction.d.ts +12 -1
  106. package/build/transaction/shared/TweakedTransaction.js +75 -8
  107. package/build/utxo/interfaces/IUTXO.d.ts +2 -0
  108. package/documentation/README.md +5 -0
  109. package/documentation/offline-transaction-signing.md +650 -0
  110. package/documentation/transaction-building.md +603 -0
  111. package/package.json +62 -4
  112. package/src/_version.ts +1 -1
  113. package/src/generators/builders/HashCommitmentGenerator.ts +495 -0
  114. package/src/keypair/Address.ts +123 -70
  115. package/src/opnet.ts +16 -1
  116. package/src/signer/AddressRotation.ts +72 -0
  117. package/src/transaction/TransactionFactory.ts +87 -0
  118. package/src/transaction/builders/CancelTransaction.ts +4 -2
  119. package/src/transaction/builders/ConsolidatedInteractionTransaction.ts +561 -0
  120. package/src/transaction/builders/CustomScriptTransaction.ts +4 -2
  121. package/src/transaction/builders/MultiSignTransaction.ts +4 -2
  122. package/src/transaction/builders/TransactionBuilder.ts +8 -2
  123. package/src/transaction/enums/TransactionType.ts +2 -0
  124. package/src/transaction/interfaces/IConsolidatedTransactionParameters.ts +78 -0
  125. package/src/transaction/interfaces/ITransactionParameters.ts +8 -0
  126. package/src/transaction/offline/OfflineTransactionManager.ts +630 -0
  127. package/src/transaction/offline/TransactionReconstructor.ts +402 -0
  128. package/src/transaction/offline/TransactionSerializer.ts +920 -0
  129. package/src/transaction/offline/TransactionStateCapture.ts +469 -0
  130. package/src/transaction/offline/interfaces/ISerializableState.ts +141 -0
  131. package/src/transaction/offline/interfaces/ITypeSpecificData.ts +172 -0
  132. package/src/transaction/shared/TweakedTransaction.ts +156 -9
  133. package/src/utxo/interfaces/IUTXO.ts +8 -0
  134. package/test/address-rotation.test.ts +553 -0
  135. package/test/offline-transaction.test.ts +2065 -0
  136. package/vite.config.browser.ts +92 -0
  137. package/webpack.config.js +143 -2
  138. package/browser/crypto/crypto-browser.d.ts +0 -11
  139. package/browser/index.js.LICENSE.txt +0 -29
@@ -0,0 +1,630 @@
1
+ import { Psbt, Signer } from '@btc-vision/bitcoin';
2
+ import { ECPairInterface } from 'ecpair';
3
+ import { TransactionType } from '../enums/TransactionType.js';
4
+ import { TransactionBuilder } from '../builders/TransactionBuilder.js';
5
+ import { MultiSignTransaction } from '../builders/MultiSignTransaction.js';
6
+ import { ISerializableTransactionState, PrecomputedData } from './interfaces/ISerializableState.js';
7
+ import { TransactionSerializer } from './TransactionSerializer.js';
8
+ import { ReconstructionOptions, TransactionReconstructor } from './TransactionReconstructor.js';
9
+ import { TransactionStateCapture } from './TransactionStateCapture.js';
10
+ import { isMultiSigSpecificData } from './interfaces/ITypeSpecificData.js';
11
+ import {
12
+ IDeploymentParameters,
13
+ IFundingTransactionParameters,
14
+ IInteractionParameters,
15
+ ITransactionParameters,
16
+ } from '../interfaces/ITransactionParameters.js';
17
+
18
+ /**
19
+ * Export options for offline transaction signing
20
+ */
21
+ export interface ExportOptions {
22
+ /** The original transaction parameters */
23
+ params: ITransactionParameters;
24
+ /** Transaction type */
25
+ type: TransactionType;
26
+ /** Precomputed data from the builder */
27
+ precomputed?: Partial<PrecomputedData>;
28
+ }
29
+
30
+ /**
31
+ * Main entry point for offline transaction signing workflow.
32
+ *
33
+ * This class provides a complete API for:
34
+ * 1. Phase 1 (Online): Building transactions and exporting state for offline signing
35
+ * 2. Phase 2 (Offline): Importing state, providing signers, and signing transactions
36
+ *
37
+ * Also supports fee bumping by allowing reconstruction with new fee parameters.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * // Phase 1 (Online environment)
42
+ * const params: IFundingTransactionParameters = { ... };
43
+ * const state = OfflineTransactionManager.exportFunding(params);
44
+ * // Send state to offline environment
45
+ *
46
+ * // Phase 2 (Offline environment)
47
+ * const signedTxHex = await OfflineTransactionManager.importSignAndExport(state, {
48
+ * signer: offlineSigner,
49
+ * });
50
+ * // Send signedTxHex back to online environment for broadcast
51
+ * ```
52
+ */
53
+ export class OfflineTransactionManager {
54
+ /**
55
+ * Export a FundingTransaction for offline signing
56
+ * @param params - Funding transaction parameters
57
+ * @param precomputed - Optional precomputed data
58
+ * @returns Base64-encoded serialized state
59
+ */
60
+ public static exportFunding(
61
+ params: IFundingTransactionParameters,
62
+ precomputed?: Partial<PrecomputedData>,
63
+ ): string {
64
+ const state = TransactionStateCapture.fromFunding(params, precomputed);
65
+ return TransactionSerializer.toBase64(state);
66
+ }
67
+
68
+ /**
69
+ * Export a DeploymentTransaction for offline signing
70
+ * @param params - Deployment transaction parameters
71
+ * @param precomputed - Required precomputed data (randomBytes, compiledTargetScript)
72
+ * @returns Base64-encoded serialized state
73
+ */
74
+ public static exportDeployment(
75
+ params: IDeploymentParameters,
76
+ precomputed: Partial<PrecomputedData> & {
77
+ compiledTargetScript: string;
78
+ randomBytes: string;
79
+ },
80
+ ): string {
81
+ const state = TransactionStateCapture.fromDeployment(params, precomputed);
82
+ return TransactionSerializer.toBase64(state);
83
+ }
84
+
85
+ /**
86
+ * Export an InteractionTransaction for offline signing
87
+ * @param params - Interaction transaction parameters
88
+ * @param precomputed - Required precomputed data (randomBytes, compiledTargetScript)
89
+ * @returns Base64-encoded serialized state
90
+ */
91
+ public static exportInteraction(
92
+ params: IInteractionParameters,
93
+ precomputed: Partial<PrecomputedData> & {
94
+ compiledTargetScript: string;
95
+ randomBytes: string;
96
+ },
97
+ ): string {
98
+ const state = TransactionStateCapture.fromInteraction(params, precomputed);
99
+ return TransactionSerializer.toBase64(state);
100
+ }
101
+
102
+ /**
103
+ * Export a MultiSignTransaction for offline signing
104
+ * @param params - MultiSig transaction parameters
105
+ * @param precomputed - Optional precomputed data
106
+ * @returns Base64-encoded serialized state
107
+ */
108
+ public static exportMultiSig(
109
+ params: ITransactionParameters & {
110
+ pubkeys: Buffer[];
111
+ minimumSignatures: number;
112
+ receiver: string;
113
+ requestedAmount: bigint;
114
+ refundVault: string;
115
+ originalInputCount?: number;
116
+ existingPsbtBase64?: string;
117
+ },
118
+ precomputed?: Partial<PrecomputedData>,
119
+ ): string {
120
+ const state = TransactionStateCapture.fromMultiSig(params, precomputed);
121
+ return TransactionSerializer.toBase64(state);
122
+ }
123
+
124
+ /**
125
+ * Export a CustomScriptTransaction for offline signing
126
+ * @param params - Custom script transaction parameters
127
+ * @param precomputed - Optional precomputed data
128
+ * @returns Base64-encoded serialized state
129
+ */
130
+ public static exportCustomScript(
131
+ params: ITransactionParameters & {
132
+ scriptElements: (Buffer | number)[];
133
+ witnesses: Buffer[];
134
+ annex?: Buffer;
135
+ },
136
+ precomputed?: Partial<PrecomputedData>,
137
+ ): string {
138
+ const state = TransactionStateCapture.fromCustomScript(params, precomputed);
139
+ return TransactionSerializer.toBase64(state);
140
+ }
141
+
142
+ /**
143
+ * Export a CancelTransaction for offline signing
144
+ * @param params - Cancel transaction parameters
145
+ * @param precomputed - Optional precomputed data
146
+ * @returns Base64-encoded serialized state
147
+ */
148
+ public static exportCancel(
149
+ params: ITransactionParameters & {
150
+ compiledTargetScript: Buffer | string;
151
+ },
152
+ precomputed?: Partial<PrecomputedData>,
153
+ ): string {
154
+ const state = TransactionStateCapture.fromCancel(params, precomputed);
155
+ return TransactionSerializer.toBase64(state);
156
+ }
157
+
158
+ /**
159
+ * Export transaction state from a builder instance.
160
+ * The builder must have been built but not yet signed.
161
+ * @param builder - Transaction builder instance
162
+ * @param params - Original construction parameters
163
+ * @param precomputed - Precomputed data from the builder
164
+ * @returns Base64-encoded serialized state
165
+ */
166
+ public static exportFromBuilder<T extends TransactionType>(
167
+ builder: TransactionBuilder<T>,
168
+ params: ITransactionParameters,
169
+ precomputed?: Partial<PrecomputedData>,
170
+ ): string {
171
+ const type = builder.type;
172
+ let state: ISerializableTransactionState;
173
+
174
+ switch (type) {
175
+ case TransactionType.FUNDING:
176
+ state = TransactionStateCapture.fromFunding(
177
+ params as IFundingTransactionParameters,
178
+ precomputed,
179
+ );
180
+ break;
181
+ case TransactionType.DEPLOYMENT:
182
+ state = TransactionStateCapture.fromDeployment(
183
+ params as IDeploymentParameters,
184
+ precomputed as Partial<PrecomputedData> & {
185
+ compiledTargetScript: string;
186
+ randomBytes: string;
187
+ },
188
+ );
189
+ break;
190
+ case TransactionType.INTERACTION:
191
+ state = TransactionStateCapture.fromInteraction(
192
+ params as IInteractionParameters,
193
+ precomputed as Partial<PrecomputedData> & {
194
+ compiledTargetScript: string;
195
+ randomBytes: string;
196
+ },
197
+ );
198
+ break;
199
+ default:
200
+ throw new Error(`Unsupported transaction type for export: ${type}`);
201
+ }
202
+
203
+ return TransactionSerializer.toBase64(state);
204
+ }
205
+
206
+ /**
207
+ * Import and reconstruct transaction for signing
208
+ * @param serializedState - Base64-encoded state from Phase 1
209
+ * @param options - Signer(s) and optional fee overrides
210
+ * @returns Reconstructed transaction builder ready for signing
211
+ */
212
+ public static importForSigning(
213
+ serializedState: string,
214
+ options: ReconstructionOptions,
215
+ ): TransactionBuilder<TransactionType> {
216
+ const state = TransactionSerializer.fromBase64(serializedState);
217
+ return TransactionReconstructor.reconstruct(state, options);
218
+ }
219
+
220
+ /**
221
+ * Complete signing and export signed transaction
222
+ * @param builder - Reconstructed builder from importForSigning
223
+ * @returns Signed transaction hex ready for broadcast
224
+ */
225
+ public static async signAndExport(
226
+ builder: TransactionBuilder<TransactionType>,
227
+ ): Promise<string> {
228
+ const tx = await builder.signTransaction();
229
+ return tx.toHex();
230
+ }
231
+
232
+ /**
233
+ * Convenience: Full Phase 2 in one call - import, sign, and export
234
+ * @param serializedState - Base64-encoded state
235
+ * @param options - Signer(s) and optional fee overrides
236
+ * @returns Signed transaction hex ready for broadcast
237
+ */
238
+ public static async importSignAndExport(
239
+ serializedState: string,
240
+ options: ReconstructionOptions,
241
+ ): Promise<string> {
242
+ const builder = this.importForSigning(serializedState, options);
243
+ return this.signAndExport(builder);
244
+ }
245
+
246
+ /**
247
+ * Rebuild transaction with new fee rate (fee bumping)
248
+ * @param serializedState - Original state
249
+ * @param newFeeRate - New fee rate in sat/vB
250
+ * @returns New serialized state with updated fees (not signed yet)
251
+ */
252
+ public static rebuildWithNewFees(serializedState: string, newFeeRate: number): string {
253
+ // Parse the existing state
254
+ const state = TransactionSerializer.fromBase64(serializedState);
255
+
256
+ // Create a new state with updated fee rate
257
+ const newState: ISerializableTransactionState = {
258
+ ...state,
259
+ baseParams: {
260
+ ...state.baseParams,
261
+ feeRate: newFeeRate,
262
+ },
263
+ };
264
+
265
+ return TransactionSerializer.toBase64(newState);
266
+ }
267
+
268
+ /**
269
+ * Rebuild and immediately sign with new fee rate
270
+ * @param serializedState - Original state
271
+ * @param newFeeRate - New fee rate in sat/vB
272
+ * @param options - Signer options
273
+ * @returns Signed transaction hex with new fees
274
+ */
275
+ public static async rebuildSignAndExport(
276
+ serializedState: string,
277
+ newFeeRate: number,
278
+ options: ReconstructionOptions,
279
+ ): Promise<string> {
280
+ const builder = this.importForSigning(serializedState, {
281
+ ...options,
282
+ newFeeRate,
283
+ });
284
+ return this.signAndExport(builder);
285
+ }
286
+
287
+ /**
288
+ * Inspect serialized state without signing
289
+ * @param serializedState - Base64-encoded state
290
+ * @returns Parsed state object for inspection
291
+ */
292
+ public static inspect(serializedState: string): ISerializableTransactionState {
293
+ return TransactionSerializer.fromBase64(serializedState);
294
+ }
295
+
296
+ /**
297
+ * Validate serialized state integrity
298
+ * @param serializedState - Base64-encoded state
299
+ * @returns True if checksum and format are valid
300
+ */
301
+ public static validate(serializedState: string): boolean {
302
+ try {
303
+ TransactionSerializer.fromBase64(serializedState);
304
+ return true;
305
+ } catch {
306
+ return false;
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Get transaction type from serialized state
312
+ * @param serializedState - Base64-encoded state
313
+ * @returns Transaction type enum value
314
+ */
315
+ public static getType(serializedState: string): TransactionType {
316
+ const state = TransactionSerializer.fromBase64(serializedState);
317
+ return state.header.transactionType;
318
+ }
319
+
320
+ /**
321
+ * Parse base64-encoded state into state object
322
+ * @param base64State - Base64-encoded state
323
+ * @returns Parsed state object
324
+ */
325
+ public static fromBase64(base64State: string): ISerializableTransactionState {
326
+ return TransactionSerializer.fromBase64(base64State);
327
+ }
328
+
329
+ /**
330
+ * Serialize state object to base64
331
+ * @param state - State object to serialize
332
+ * @returns Base64-encoded state
333
+ */
334
+ public static toBase64(state: ISerializableTransactionState): string {
335
+ return TransactionSerializer.toBase64(state);
336
+ }
337
+
338
+ /**
339
+ * Convert serialized state to hex format
340
+ * @param serializedState - Base64-encoded state
341
+ * @returns Hex-encoded state
342
+ */
343
+ public static toHex(serializedState: string): string {
344
+ const state = TransactionSerializer.fromBase64(serializedState);
345
+ return TransactionSerializer.toHex(state);
346
+ }
347
+
348
+ /**
349
+ * Convert hex format back to base64
350
+ * @param hexState - Hex-encoded state
351
+ * @returns Base64-encoded state
352
+ */
353
+ public static fromHex(hexState: string): string {
354
+ const state = TransactionSerializer.fromHex(hexState);
355
+ return TransactionSerializer.toBase64(state);
356
+ }
357
+
358
+ /**
359
+ * Add a partial signature to a multisig transaction state.
360
+ * This method signs the transaction with the provided signer and returns
361
+ * updated state with the new signature included.
362
+ *
363
+ * @param serializedState - Base64-encoded multisig state
364
+ * @param signer - The signer to add a signature with
365
+ * @returns Updated state with new signature, and signing result
366
+ */
367
+ public static async multiSigAddSignature(
368
+ serializedState: string,
369
+ signer: Signer | ECPairInterface,
370
+ ): Promise<{
371
+ state: string;
372
+ signed: boolean;
373
+ final: boolean;
374
+ psbtBase64: string;
375
+ }> {
376
+ const state = TransactionSerializer.fromBase64(serializedState);
377
+
378
+ if (!isMultiSigSpecificData(state.typeSpecificData)) {
379
+ throw new Error('State is not a multisig transaction');
380
+ }
381
+
382
+ const typeData = state.typeSpecificData;
383
+ const pubkeys = typeData.pubkeys.map((pk) => Buffer.from(pk, 'hex'));
384
+
385
+ // Parse existing PSBT or create new one
386
+ let psbt: Psbt;
387
+ const network = TransactionReconstructor['nameToNetwork'](state.baseParams.networkName);
388
+
389
+ if (typeData.existingPsbtBase64) {
390
+ psbt = Psbt.fromBase64(typeData.existingPsbtBase64, { network });
391
+ } else {
392
+ // Need to build the transaction first
393
+ const builder = this.importForSigning(serializedState, {
394
+ signer,
395
+ }) as MultiSignTransaction;
396
+ psbt = await builder.signPSBT();
397
+ }
398
+
399
+ // Calculate minimums array for each input
400
+ const minimums: number[] = [];
401
+ for (let i = typeData.originalInputCount; i < psbt.data.inputs.length; i++) {
402
+ minimums.push(typeData.minimumSignatures);
403
+ }
404
+
405
+ // Sign the PSBT
406
+ const result = MultiSignTransaction.signPartial(
407
+ psbt,
408
+ signer,
409
+ typeData.originalInputCount,
410
+ minimums,
411
+ );
412
+
413
+ // Finalize inputs (partial finalization to preserve signatures)
414
+ const orderedPubKeys: Buffer[][] = [];
415
+ for (let i = typeData.originalInputCount; i < psbt.data.inputs.length; i++) {
416
+ orderedPubKeys.push(pubkeys);
417
+ }
418
+
419
+ MultiSignTransaction.attemptFinalizeInputs(
420
+ psbt,
421
+ typeData.originalInputCount,
422
+ orderedPubKeys,
423
+ result.final,
424
+ );
425
+
426
+ const newPsbtBase64 = psbt.toBase64();
427
+
428
+ // Update the state with new PSBT
429
+ const newState: ISerializableTransactionState = {
430
+ ...state,
431
+ typeSpecificData: {
432
+ ...typeData,
433
+ existingPsbtBase64: newPsbtBase64,
434
+ },
435
+ };
436
+
437
+ return {
438
+ state: TransactionSerializer.toBase64(newState),
439
+ signed: result.signed,
440
+ final: result.final,
441
+ psbtBase64: newPsbtBase64,
442
+ };
443
+ }
444
+
445
+ /**
446
+ * Check if a public key has already signed a multisig transaction
447
+ *
448
+ * @param serializedState - Base64-encoded multisig state
449
+ * @param signerPubKey - Public key to check (Buffer or hex string)
450
+ * @returns True if the public key has already signed
451
+ */
452
+ public static multiSigHasSigned(
453
+ serializedState: string,
454
+ signerPubKey: Buffer | string,
455
+ ): boolean {
456
+ const state = TransactionSerializer.fromBase64(serializedState);
457
+
458
+ if (!isMultiSigSpecificData(state.typeSpecificData)) {
459
+ throw new Error('State is not a multisig transaction');
460
+ }
461
+
462
+ const typeData = state.typeSpecificData;
463
+
464
+ if (!typeData.existingPsbtBase64) {
465
+ return false;
466
+ }
467
+
468
+ const network = TransactionReconstructor['nameToNetwork'](state.baseParams.networkName);
469
+ const psbt = Psbt.fromBase64(typeData.existingPsbtBase64, { network });
470
+
471
+ const pubKeyBuffer = Buffer.isBuffer(signerPubKey)
472
+ ? signerPubKey
473
+ : Buffer.from(signerPubKey, 'hex');
474
+
475
+ return MultiSignTransaction.verifyIfSigned(psbt, pubKeyBuffer);
476
+ }
477
+
478
+ /**
479
+ * Get the current signature count for a multisig transaction
480
+ *
481
+ * @param serializedState - Base64-encoded multisig state
482
+ * @returns Object with signature count info
483
+ */
484
+ public static multiSigGetSignatureStatus(serializedState: string): {
485
+ required: number;
486
+ collected: number;
487
+ isComplete: boolean;
488
+ signers: string[];
489
+ } {
490
+ const state = TransactionSerializer.fromBase64(serializedState);
491
+
492
+ if (!isMultiSigSpecificData(state.typeSpecificData)) {
493
+ throw new Error('State is not a multisig transaction');
494
+ }
495
+
496
+ const typeData = state.typeSpecificData;
497
+ const required = typeData.minimumSignatures;
498
+
499
+ if (!typeData.existingPsbtBase64) {
500
+ return {
501
+ required,
502
+ collected: 0,
503
+ isComplete: false,
504
+ signers: [],
505
+ };
506
+ }
507
+
508
+ const network = TransactionReconstructor['nameToNetwork'](state.baseParams.networkName);
509
+ const psbt = Psbt.fromBase64(typeData.existingPsbtBase64, { network });
510
+
511
+ // Collect signers from all inputs
512
+ const signerSet = new Set<string>();
513
+
514
+ for (let i = typeData.originalInputCount; i < psbt.data.inputs.length; i++) {
515
+ const input = psbt.data.inputs[i];
516
+
517
+ if (input.tapScriptSig) {
518
+ for (const sig of input.tapScriptSig) {
519
+ signerSet.add(sig.pubkey.toString('hex'));
520
+ }
521
+ }
522
+
523
+ if (input.finalScriptWitness) {
524
+ const decoded = TransactionBuilder.readScriptWitnessToWitnessStack(
525
+ input.finalScriptWitness,
526
+ );
527
+
528
+ for (let j = 0; j < decoded.length - 2; j += 3) {
529
+ const pubKey = decoded[j + 2];
530
+ signerSet.add(pubKey.toString('hex'));
531
+ }
532
+ }
533
+ }
534
+
535
+ const signers = Array.from(signerSet);
536
+
537
+ return {
538
+ required,
539
+ collected: signers.length,
540
+ isComplete: signers.length >= required,
541
+ signers,
542
+ };
543
+ }
544
+
545
+ /**
546
+ * Finalize a multisig transaction and extract the signed transaction hex.
547
+ * Only call this when all required signatures have been collected.
548
+ *
549
+ * @param serializedState - Base64-encoded multisig state with all signatures
550
+ * @returns Signed transaction hex ready for broadcast
551
+ */
552
+ public static multiSigFinalize(serializedState: string): string {
553
+ const state = TransactionSerializer.fromBase64(serializedState);
554
+
555
+ if (!isMultiSigSpecificData(state.typeSpecificData)) {
556
+ throw new Error('State is not a multisig transaction');
557
+ }
558
+
559
+ const typeData = state.typeSpecificData;
560
+
561
+ if (!typeData.existingPsbtBase64) {
562
+ throw new Error('No PSBT found in state - transaction has not been signed');
563
+ }
564
+
565
+ const network = TransactionReconstructor['nameToNetwork'](state.baseParams.networkName);
566
+ const psbt = Psbt.fromBase64(typeData.existingPsbtBase64, { network });
567
+
568
+ const pubkeys = typeData.pubkeys.map((pk) => Buffer.from(pk, 'hex'));
569
+ const orderedPubKeys: Buffer[][] = [];
570
+
571
+ for (let i = typeData.originalInputCount; i < psbt.data.inputs.length; i++) {
572
+ orderedPubKeys.push(pubkeys);
573
+ }
574
+
575
+ // Final finalization
576
+ const success = MultiSignTransaction.attemptFinalizeInputs(
577
+ psbt,
578
+ typeData.originalInputCount,
579
+ orderedPubKeys,
580
+ true, // isFinal = true
581
+ );
582
+
583
+ if (!success) {
584
+ throw new Error('Failed to finalize multisig transaction - not enough signatures');
585
+ }
586
+
587
+ return psbt.extractTransaction(true, true).toHex();
588
+ }
589
+
590
+ /**
591
+ * Get the PSBT from a multisig state (for external signing tools)
592
+ *
593
+ * @param serializedState - Base64-encoded multisig state
594
+ * @returns PSBT in base64 format, or null if not yet built
595
+ */
596
+ public static multiSigGetPsbt(serializedState: string): string | null {
597
+ const state = TransactionSerializer.fromBase64(serializedState);
598
+
599
+ if (!isMultiSigSpecificData(state.typeSpecificData)) {
600
+ throw new Error('State is not a multisig transaction');
601
+ }
602
+
603
+ return state.typeSpecificData.existingPsbtBase64 || null;
604
+ }
605
+
606
+ /**
607
+ * Update the PSBT in a multisig state (after external signing)
608
+ *
609
+ * @param serializedState - Base64-encoded multisig state
610
+ * @param psbtBase64 - New PSBT with additional signatures
611
+ * @returns Updated state
612
+ */
613
+ public static multiSigUpdatePsbt(serializedState: string, psbtBase64: string): string {
614
+ const state = TransactionSerializer.fromBase64(serializedState);
615
+
616
+ if (!isMultiSigSpecificData(state.typeSpecificData)) {
617
+ throw new Error('State is not a multisig transaction');
618
+ }
619
+
620
+ const newState: ISerializableTransactionState = {
621
+ ...state,
622
+ typeSpecificData: {
623
+ ...state.typeSpecificData,
624
+ existingPsbtBase64: psbtBase64,
625
+ },
626
+ };
627
+
628
+ return TransactionSerializer.toBase64(newState);
629
+ }
630
+ }