@btc-vision/transaction 1.1.1 → 1.1.3

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 (30) hide show
  1. package/browser/_version.d.ts +1 -1
  2. package/browser/index.js +1 -1
  3. package/browser/transaction/browser/extensions/UnisatSigner.d.ts +4 -4
  4. package/browser/transaction/browser/types/Unisat.d.ts +1 -1
  5. package/browser/transaction/builders/DeploymentTransaction.d.ts +1 -0
  6. package/browser/transaction/builders/SharedInteractionTransaction.d.ts +2 -0
  7. package/browser/transaction/builders/TransactionBuilder.d.ts +2 -1
  8. package/browser/transaction/shared/TweakedTransaction.d.ts +14 -3
  9. package/build/_version.d.ts +1 -1
  10. package/build/_version.js +1 -1
  11. package/build/buffer/BinaryWriter.js +0 -1
  12. package/build/transaction/browser/extensions/UnisatSigner.d.ts +4 -4
  13. package/build/transaction/browser/extensions/UnisatSigner.js +103 -20
  14. package/build/transaction/browser/types/Unisat.d.ts +1 -1
  15. package/build/transaction/builders/DeploymentTransaction.d.ts +1 -0
  16. package/build/transaction/builders/DeploymentTransaction.js +17 -0
  17. package/build/transaction/builders/SharedInteractionTransaction.d.ts +2 -0
  18. package/build/transaction/builders/SharedInteractionTransaction.js +31 -10
  19. package/build/transaction/builders/TransactionBuilder.d.ts +2 -1
  20. package/build/transaction/shared/TweakedTransaction.d.ts +14 -3
  21. package/build/transaction/shared/TweakedTransaction.js +146 -23
  22. package/package.json +4 -1
  23. package/src/_version.ts +1 -1
  24. package/src/buffer/BinaryWriter.ts +0 -2
  25. package/src/transaction/browser/extensions/UnisatSigner.ts +139 -28
  26. package/src/transaction/browser/types/Unisat.ts +1 -1
  27. package/src/transaction/builders/DeploymentTransaction.ts +25 -0
  28. package/src/transaction/builders/SharedInteractionTransaction.ts +51 -21
  29. package/src/transaction/builders/TransactionBuilder.ts +2 -1
  30. package/src/transaction/shared/TweakedTransaction.ts +210 -113
@@ -4,6 +4,7 @@ import { TweakedSigner } from '../../signer/TweakedSigner.js';
4
4
  import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
5
5
  import { AddressTypes, AddressVerificator } from '../../keypair/AddressVerificator.js';
6
6
  import { varuint } from '@btc-vision/bitcoin/src/bufferutils.js';
7
+ import * as bscript from '@btc-vision/bitcoin/src/script.js';
7
8
  export var TransactionSequence;
8
9
  (function (TransactionSequence) {
9
10
  TransactionSequence[TransactionSequence["REPLACE_BY_FEE"] = 4294967293] = "REPLACE_BY_FEE";
@@ -27,10 +28,7 @@ export class TweakedTransaction extends Logger {
27
28
  const inputDecoded = this.inputs[inputIndex];
28
29
  if (isP2SH && input.partialSig && inputDecoded && inputDecoded.redeemScript) {
29
30
  const signatures = input.partialSig.map((sig) => sig.signature);
30
- const scriptSig = script.compile([
31
- ...signatures,
32
- inputDecoded.redeemScript,
33
- ]);
31
+ const scriptSig = script.compile([...signatures, inputDecoded.redeemScript]);
34
32
  return {
35
33
  finalScriptSig: scriptSig,
36
34
  finalScriptWitness: undefined,
@@ -156,22 +154,39 @@ export class TweakedTransaction extends Logger {
156
154
  getSignerKey() {
157
155
  return this.signer;
158
156
  }
159
- async signInput(transaction, _input, i, signer) {
160
- try {
161
- if ('signInput' in signer) {
162
- return await signer.signInput(transaction, i);
163
- }
164
- transaction.signInput(i, signer);
157
+ async signInput(transaction, input, i, signer, reverse = false) {
158
+ const publicKey = signer.publicKey;
159
+ let isTaproot = this.isTaprootInput(input);
160
+ if (reverse) {
161
+ isTaproot = !isTaproot;
165
162
  }
166
- catch {
163
+ let signed = false;
164
+ if (isTaproot) {
167
165
  try {
168
- if ('signTaprootInput' in signer) {
169
- return await signer.signTaprootInput(transaction, i);
166
+ await this.attemptSignTaproot(transaction, input, i, signer, publicKey);
167
+ signed = true;
168
+ }
169
+ catch (e) {
170
+ this.error(`Failed to sign Taproot script path input ${i}: ${e}`);
171
+ }
172
+ }
173
+ else {
174
+ if (!reverse ? this.canSignNonTaprootInput(input, publicKey) : true) {
175
+ try {
176
+ await this.signNonTaprootInput(signer, transaction, i);
177
+ signed = true;
170
178
  }
171
- transaction.signTaprootInput(i, signer);
179
+ catch (e) {
180
+ this.error(`Failed to sign non-Taproot input ${i}: ${e}`);
181
+ }
182
+ }
183
+ }
184
+ if (!signed) {
185
+ try {
186
+ await this.signInput(transaction, input, i, signer, true);
172
187
  }
173
188
  catch {
174
- throw new Error('Failed to sign input');
189
+ throw new Error(`Cannot sign input ${i} with the provided signer.`);
175
190
  }
176
191
  }
177
192
  }
@@ -186,6 +201,10 @@ export class TweakedTransaction extends Logger {
186
201
  return result;
187
202
  }
188
203
  async signInputs(transaction) {
204
+ if ('multiSignPsbt' in this.signer) {
205
+ await this.signInputsWalletBased(transaction);
206
+ return;
207
+ }
189
208
  const txs = transaction.data.inputs;
190
209
  const batchSize = 20;
191
210
  const batches = this.splitArray(txs, batchSize);
@@ -205,14 +224,10 @@ export class TweakedTransaction extends Logger {
205
224
  }
206
225
  await Promise.all(promises);
207
226
  }
208
- transaction.finalizeInput(0, this.customFinalizerP2SH);
209
- try {
210
- transaction.finalizeAllInputs();
211
- this.finalized = true;
212
- }
213
- catch (e) {
214
- this.finalized = false;
227
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
228
+ transaction.finalizeInput(i, this.customFinalizerP2SH);
215
229
  }
230
+ this.finalized = true;
216
231
  }
217
232
  internalPubKeyToXOnly() {
218
233
  return toXOnly(Buffer.from(this.signer.publicKey));
@@ -332,7 +347,6 @@ export class TweakedTransaction extends Logger {
332
347
  }
333
348
  if (i === 0 && this.nonWitnessUtxo) {
334
349
  input.nonWitnessUtxo = this.nonWitnessUtxo;
335
- this.log(`Using non-witness utxo for input ${i}`);
336
350
  }
337
351
  if (utxo.scriptPubKey.address &&
338
352
  AddressVerificator.isValidP2TRAddress(utxo.scriptPubKey.address, this.network)) {
@@ -341,4 +355,113 @@ export class TweakedTransaction extends Logger {
341
355
  }
342
356
  return input;
343
357
  }
358
+ async signInputsWalletBased(transaction) {
359
+ const signer = this.signer;
360
+ await signer.multiSignPsbt([transaction]);
361
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
362
+ transaction.finalizeInput(i, this.customFinalizerP2SH);
363
+ }
364
+ this.finalized = true;
365
+ }
366
+ async attemptSignTaproot(transaction, input, i, signer, publicKey) {
367
+ const isScriptSpend = this.isTaprootScriptSpend(input, publicKey);
368
+ if (isScriptSpend) {
369
+ await this.signTaprootInput(signer, transaction, i);
370
+ }
371
+ else {
372
+ let tweakedSigner;
373
+ if (signer !== this.signer) {
374
+ tweakedSigner = this.getTweakedSigner(true, signer);
375
+ }
376
+ else {
377
+ if (!this.tweakedSigner)
378
+ this.tweakSigner();
379
+ tweakedSigner = this.tweakedSigner;
380
+ }
381
+ if (tweakedSigner) {
382
+ await this.signTaprootInput(tweakedSigner, transaction, i);
383
+ }
384
+ else {
385
+ this.error(`Failed to obtain tweaked signer for input ${i}.`);
386
+ }
387
+ }
388
+ }
389
+ isTaprootScriptSpend(input, publicKey) {
390
+ if (input.tapLeafScript && input.tapLeafScript.length > 0) {
391
+ for (const tapLeafScript of input.tapLeafScript) {
392
+ if (this.pubkeyInScript(publicKey, tapLeafScript.script)) {
393
+ return true;
394
+ }
395
+ }
396
+ }
397
+ return false;
398
+ }
399
+ isTaprootInput(input) {
400
+ if (input.tapInternalKey || input.tapKeySig || input.tapScriptSig || input.tapLeafScript) {
401
+ return true;
402
+ }
403
+ if (input.witnessUtxo) {
404
+ const script = input.witnessUtxo.script;
405
+ return script.length === 34 && script[0] === opcodes.OP_1 && script[1] === 0x20;
406
+ }
407
+ return false;
408
+ }
409
+ canSignNonTaprootInput(input, publicKey) {
410
+ const script = this.getInputRelevantScript(input);
411
+ if (script) {
412
+ return this.pubkeyInScript(publicKey, script);
413
+ }
414
+ return false;
415
+ }
416
+ getInputRelevantScript(input) {
417
+ if (input.redeemScript) {
418
+ return input.redeemScript;
419
+ }
420
+ if (input.witnessScript) {
421
+ return input.witnessScript;
422
+ }
423
+ if (input.witnessUtxo) {
424
+ return input.witnessUtxo.script;
425
+ }
426
+ if (input.nonWitnessUtxo) {
427
+ return null;
428
+ }
429
+ return null;
430
+ }
431
+ pubkeyInScript(pubkey, script) {
432
+ return this.pubkeyPositionInScript(pubkey, script) !== -1;
433
+ }
434
+ pubkeyPositionInScript(pubkey, script) {
435
+ const pubkeyHash = bitCrypto.hash160(pubkey);
436
+ const pubkeyXOnly = toXOnly(pubkey);
437
+ const decompiled = bscript.decompile(script);
438
+ if (decompiled === null)
439
+ throw new Error('Unknown script error');
440
+ return decompiled.findIndex((element) => {
441
+ if (typeof element === 'number')
442
+ return false;
443
+ return (element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly));
444
+ });
445
+ }
446
+ async signTaprootInput(signer, transaction, i, tapLeafHash) {
447
+ if ('signTaprootInput' in signer) {
448
+ try {
449
+ await signer.signTaprootInput(transaction, i, tapLeafHash);
450
+ }
451
+ catch {
452
+ throw new Error('Failed to sign Taproot input with provided signer.');
453
+ }
454
+ }
455
+ else {
456
+ transaction.signTaprootInput(i, signer);
457
+ }
458
+ }
459
+ async signNonTaprootInput(signer, transaction, i) {
460
+ if ('signInput' in signer) {
461
+ await signer.signInput(transaction, i);
462
+ }
463
+ else {
464
+ transaction.signInput(i, signer);
465
+ }
466
+ }
344
467
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@btc-vision/transaction",
3
3
  "type": "module",
4
- "version": "1.1.1",
4
+ "version": "1.1.3",
5
5
  "author": "BlobMaster41",
6
6
  "description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
7
7
  "engines": {
@@ -63,6 +63,9 @@
63
63
  "browserBuild": "webpack --mode production",
64
64
  "docs": "typedoc --out docs --exclude 'src/tests/*.ts' --tsconfig tsconfig.json --readme README.md --name OPNet --plugin typedoc-material-theme --themeColor '#cb9820' --exclude src/tests/test.ts --exclude src/index.ts src"
65
65
  },
66
+ "peerDependencies": {
67
+ "@btc-vision/bitcoin": "^6.3.0"
68
+ },
66
69
  "devDependencies": {
67
70
  "@babel/core": "^7.26.0",
68
71
  "@babel/plugin-proposal-class-properties": "^7.18.6",
package/src/_version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '1.1.1';
1
+ export const version = '1.1.3';
@@ -63,8 +63,6 @@ export class BinaryWriter {
63
63
 
64
64
  const bytesToHex = BufferHelper.valueToUint8Array(bigIntValue);
65
65
  if (bytesToHex.byteLength !== 32) {
66
- console.log('Invalid u256 value:', bytesToHex);
67
-
68
66
  throw new Error(`Invalid u256 value: ${bigIntValue}`);
69
67
  }
70
68
 
@@ -1,9 +1,19 @@
1
- import { Network, networks, Psbt, TapScriptSig } from '@btc-vision/bitcoin';
1
+ import {
2
+ crypto as bitCrypto,
3
+ Network,
4
+ networks,
5
+ opcodes,
6
+ Psbt,
7
+ PsbtInput,
8
+ script as bitScript,
9
+ TapScriptSig,
10
+ } from '@btc-vision/bitcoin';
2
11
  import { ECPairInterface } from 'ecpair';
3
12
  import { EcKeyPair } from '../../../keypair/EcKeyPair.js';
4
13
  import { CustomKeypair } from '../BrowserSignerBase.js';
5
14
  import { PsbtSignatureOptions, Unisat, UnisatNetwork } from '../types/Unisat.js';
6
15
  import { PartialSig } from 'bip174/src/lib/interfaces.js';
16
+ import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
7
17
 
8
18
  declare global {
9
19
  interface Window {
@@ -122,15 +132,15 @@ export class UnisatSigner extends CustomKeypair {
122
132
  return this.publicKey;
123
133
  }
124
134
 
125
- public sign(hash: Buffer, lowR?: boolean): Buffer {
135
+ public sign(_hash: Buffer, _lowR?: boolean): Buffer {
126
136
  throw new Error('Not implemented: sign');
127
137
  }
128
138
 
129
- public signSchnorr(hash: Buffer): Buffer {
139
+ public signSchnorr(_hash: Buffer): Buffer {
130
140
  throw new Error('Not implemented: signSchnorr');
131
141
  }
132
142
 
133
- public verify(hash: Buffer, signature: Buffer): boolean {
143
+ public verify(_hash: Buffer, _signature: Buffer): boolean {
134
144
  throw new Error('Not implemented: verify');
135
145
  }
136
146
 
@@ -176,6 +186,80 @@ export class UnisatSigner extends CustomKeypair {
176
186
  this.combine(transaction, firstSignature, i);
177
187
  }
178
188
 
189
+ public async multiSignPsbt(transactions: Psbt[]): Promise<void> {
190
+ const toSignPsbts: string[] = [];
191
+ const options: PsbtSignatureOptions[] = [];
192
+
193
+ for (const psbt of transactions) {
194
+ const hex = psbt.toHex();
195
+ toSignPsbts.push(hex);
196
+
197
+ const toSignInputs = psbt.data.inputs
198
+ .map((input, i) => {
199
+ let needsToSign = false;
200
+ let viaTaproot = false;
201
+
202
+ if (isTaprootInput(input)) {
203
+ if (input.tapLeafScript && input.tapLeafScript.length > 0) {
204
+ for (const tapLeafScript of input.tapLeafScript) {
205
+ if (pubkeyInScript(this.publicKey, tapLeafScript.script)) {
206
+ needsToSign = true;
207
+ viaTaproot = false; // for opnet, we use original keys.
208
+ break;
209
+ }
210
+ }
211
+ }
212
+
213
+ if (!needsToSign && input.tapInternalKey) {
214
+ const tapInternalKey = input.tapInternalKey;
215
+ const xOnlyPubKey = toXOnly(this.publicKey);
216
+
217
+ if (tapInternalKey.equals(xOnlyPubKey)) {
218
+ needsToSign = true;
219
+ viaTaproot = true;
220
+ }
221
+ }
222
+ } else {
223
+ // Non-Taproot input
224
+ const script = getInputRelevantScript(input);
225
+
226
+ if (script && pubkeyInScript(this.publicKey, script)) {
227
+ needsToSign = true;
228
+ viaTaproot = false;
229
+ }
230
+ }
231
+
232
+ if (needsToSign) {
233
+ return {
234
+ index: i,
235
+ publicKey: this.publicKey.toString('hex'),
236
+ disableTweakSigner: !viaTaproot,
237
+ };
238
+ } else {
239
+ return null;
240
+ }
241
+ })
242
+ .filter((v) => v !== null);
243
+
244
+ options.push({
245
+ autoFinalized: false,
246
+ toSignInputs: toSignInputs,
247
+ });
248
+ }
249
+
250
+ const signed = await this.unisat.signPsbt(toSignPsbts[0], options[0]);
251
+ const signedPsbts = Psbt.fromHex(signed); //signed.map((hex) => Psbt.fromHex(hex));
252
+
253
+ /*for (let i = 0; i < signedPsbts.length; i++) {
254
+ const psbtOriginal = transactions[i];
255
+ const psbtSigned = signedPsbts[i];
256
+
257
+ psbtOriginal.combine(psbtSigned);
258
+ }*/
259
+
260
+ transactions[0].combine(signedPsbts);
261
+ }
262
+
179
263
  private hasAlreadySignedTapScriptSig(input: TapScriptSig[]): boolean {
180
264
  for (let i = 0; i < input.length; i++) {
181
265
  const item = input[i];
@@ -257,30 +341,6 @@ export class UnisatSigner extends CustomKeypair {
257
341
  return Psbt.fromHex(signed);
258
342
  }
259
343
 
260
- private async signTweaked(
261
- transaction: Psbt,
262
- i: number,
263
- sighashTypes: number[],
264
- disableTweakSigner: boolean = false,
265
- ): Promise<Psbt> {
266
- const opts: PsbtSignatureOptions = {
267
- autoFinalized: false,
268
- toSignInputs: [
269
- {
270
- index: i,
271
- publicKey: this.publicKey.toString('hex'),
272
- sighashTypes,
273
- disableTweakSigner: disableTweakSigner,
274
- },
275
- ],
276
- };
277
-
278
- const psbt = transaction.toHex();
279
- const signed = await this.unisat.signPsbt(psbt, opts);
280
-
281
- return Psbt.fromHex(signed);
282
- }
283
-
284
344
  private getNonDuplicateScriptSig(
285
345
  scriptSig1: TapScriptSig[],
286
346
  scriptSig2: TapScriptSig[],
@@ -296,3 +356,54 @@ export class UnisatSigner extends CustomKeypair {
296
356
  return nonDuplicate;
297
357
  }
298
358
  }
359
+
360
+ // Helper functions
361
+ function isTaprootInput(input: PsbtInput): boolean {
362
+ if (input.tapInternalKey || input.tapKeySig || input.tapScriptSig || input.tapLeafScript) {
363
+ return true;
364
+ }
365
+
366
+ if (input.witnessUtxo) {
367
+ const script = input.witnessUtxo.script;
368
+ return script.length === 34 && script[0] === opcodes.OP_1 && script[1] === 0x20;
369
+ }
370
+
371
+ return false;
372
+ }
373
+
374
+ function getInputRelevantScript(input: PsbtInput): Buffer | null {
375
+ if (input.redeemScript) {
376
+ return input.redeemScript;
377
+ }
378
+ if (input.witnessScript) {
379
+ return input.witnessScript;
380
+ }
381
+ if (input.witnessUtxo) {
382
+ return input.witnessUtxo.script;
383
+ }
384
+ if (input.nonWitnessUtxo) {
385
+ // Additional logic can be added here if needed
386
+ return null;
387
+ }
388
+ return null;
389
+ }
390
+
391
+ function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
392
+ return pubkeyPositionInScript(pubkey, script) !== -1;
393
+ }
394
+
395
+ function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number {
396
+ const pubkeyHash = bitCrypto.hash160(pubkey);
397
+ const pubkeyXOnly = toXOnly(pubkey);
398
+
399
+ const decompiled = bitScript.decompile(script);
400
+ if (decompiled === null) throw new Error('Unknown script error');
401
+
402
+ return decompiled.findIndex((element) => {
403
+ if (typeof element === 'number') return false;
404
+ return (
405
+ Buffer.isBuffer(element) &&
406
+ (element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly))
407
+ );
408
+ });
409
+ }
@@ -83,7 +83,7 @@ export interface Unisat {
83
83
 
84
84
  signPsbt(psbtHex: string, psbtOptions: PsbtSignatureOptions): Promise<string>;
85
85
 
86
- signPsbts(psbtHex: string[], psbtOptions: PsbtSignatureOptions): Promise<string[]>;
86
+ signPsbts(psbtHex: string[], psbtOptions: PsbtSignatureOptions[]): Promise<string[]>;
87
87
 
88
88
  pushPsbt(psbtHex: string): Promise<string>;
89
89
 
@@ -12,6 +12,7 @@ import { Compressor } from '../../bytecode/Compressor.js';
12
12
  import { SharedInteractionTransaction } from './SharedInteractionTransaction.js';
13
13
  import { ECPairInterface } from 'ecpair';
14
14
  import { Address } from '../../keypair/Address.js';
15
+ import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
15
16
 
16
17
  export class DeploymentTransaction extends TransactionBuilder<TransactionType.DEPLOYMENT> {
17
18
  public static readonly MAXIMUM_CONTRACT_SIZE = 128 * 1024;
@@ -203,6 +204,25 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
203
204
  await this.addRefundOutput(amountSpent);
204
205
  }
205
206
 
207
+ protected override async signInputsWalletBased(transaction: Psbt): Promise<void> {
208
+ const signer: UnisatSigner = this.signer as UnisatSigner;
209
+
210
+ // first, we sign the first input with the script signer.
211
+ await this.signInput(transaction, transaction.data.inputs[0], 0, this.contractSigner);
212
+
213
+ // then, we sign all the remaining inputs with the wallet signer.
214
+ await signer.multiSignPsbt([transaction]);
215
+
216
+ // Then, we finalize every input.
217
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
218
+ if (i === 0) {
219
+ transaction.finalizeInput(i, this.customFinalizer);
220
+ } else {
221
+ transaction.finalizeInput(i);
222
+ }
223
+ }
224
+ }
225
+
206
226
  /**
207
227
  * Sign the inputs
208
228
  * @param {Psbt} transaction The transaction to sign
@@ -215,6 +235,11 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
215
235
  return;
216
236
  }
217
237
 
238
+ if ('multiSignPsbt' in this.signer) {
239
+ await this.signInputsWalletBased(transaction);
240
+ return;
241
+ }
242
+
218
243
  for (let i = 0; i < transaction.data.inputs.length; i++) {
219
244
  if (i === 0) {
220
245
  // multi sig input
@@ -9,6 +9,7 @@ import { Compressor } from '../../bytecode/Compressor.js';
9
9
  import { EcKeyPair } from '../../keypair/EcKeyPair.js';
10
10
  import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
11
11
  import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
12
+ import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
12
13
 
13
14
  /**
14
15
  * Shared interaction transaction
@@ -186,27 +187,10 @@ export abstract class SharedInteractionTransaction<
186
187
  return;
187
188
  }
188
189
 
189
- for (let i = 0; i < transaction.data.inputs.length; i++) {
190
- if (i === 0) {
191
- await this.signInput(transaction, transaction.data.inputs[i], i, this.scriptSigner);
192
- await this.signInput(
193
- transaction,
194
- transaction.data.inputs[i],
195
- i,
196
- this.getSignerKey(),
197
- );
198
-
199
- transaction.finalizeInput(i, this.customFinalizer);
200
- } else {
201
- await this.signInput(
202
- transaction,
203
- transaction.data.inputs[i],
204
- i,
205
- this.getSignerKey(),
206
- );
207
-
208
- transaction.finalizeInput(i);
209
- }
190
+ if ('multiSignPsbt' in this.signer) {
191
+ await this.signInputsWalletBased(transaction);
192
+ } else {
193
+ await this.signInputsNonWalletBased(transaction);
210
194
  }
211
195
  }
212
196
 
@@ -308,6 +292,52 @@ export abstract class SharedInteractionTransaction<
308
292
  };
309
293
  };
310
294
 
295
+ // custom for interactions
296
+ protected override async signInputsWalletBased(transaction: Psbt): Promise<void> {
297
+ const signer: UnisatSigner = this.signer as UnisatSigner;
298
+
299
+ // first, we sign the first input with the script signer.
300
+ await this.signInput(transaction, transaction.data.inputs[0], 0, this.scriptSigner);
301
+
302
+ // then, we sign all the remaining inputs with the wallet signer.
303
+ await signer.multiSignPsbt([transaction]);
304
+
305
+ // Then, we finalize every input.
306
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
307
+ if (i === 0) {
308
+ transaction.finalizeInput(i, this.customFinalizer);
309
+ } else {
310
+ transaction.finalizeInput(i);
311
+ }
312
+ }
313
+ }
314
+
315
+ private async signInputsNonWalletBased(transaction: Psbt): Promise<void> {
316
+ for (let i = 0; i < transaction.data.inputs.length; i++) {
317
+ if (i === 0) {
318
+ await this.signInput(transaction, transaction.data.inputs[i], i, this.scriptSigner);
319
+
320
+ await this.signInput(
321
+ transaction,
322
+ transaction.data.inputs[i],
323
+ i,
324
+ this.getSignerKey(),
325
+ );
326
+
327
+ transaction.finalizeInput(i, this.customFinalizer);
328
+ } else {
329
+ await this.signInput(
330
+ transaction,
331
+ transaction.data.inputs[i],
332
+ i,
333
+ this.getSignerKey(),
334
+ );
335
+
336
+ transaction.finalizeInput(i);
337
+ }
338
+ }
339
+ }
340
+
311
341
  /**
312
342
  * Get the public keys
313
343
  * @private
@@ -22,6 +22,7 @@ import { UTXO } from '../../utxo/interfaces/IUTXO.js';
22
22
  import { ECPairInterface } from 'ecpair';
23
23
  import { AddressVerificator } from '../../keypair/AddressVerificator.js';
24
24
  import { TweakedTransaction } from '../shared/TweakedTransaction.js';
25
+ import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
25
26
 
26
27
  initEccLib(ecc);
27
28
 
@@ -91,7 +92,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
91
92
  /**
92
93
  * @description The signer of the transaction
93
94
  */
94
- protected readonly signer: Signer | ECPairInterface;
95
+ protected readonly signer: Signer | ECPairInterface | UnisatSigner;
95
96
 
96
97
  /**
97
98
  * @description The network where the transaction will be broadcasted