@exodus/bitcoin-api 4.4.0 → 4.5.0

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [4.5.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.2.1...@exodus/bitcoin-api@4.5.0) (2025-11-12)
7
+
8
+
9
+ ### Features
10
+
11
+
12
+ * feat: add PSBT builder infrastructure (#6822)
13
+
14
+ * feat: add PSBT parser functionality (#6823)
15
+
16
+ * feat: integrate PSBT support and legacy chain index (#6819)
17
+
18
+
19
+
6
20
  ## [4.4.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.2.1...@exodus/bitcoin-api@4.4.0) (2025-11-12)
7
21
 
8
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "4.4.0",
3
+ "version": "4.5.0",
4
4
  "description": "Bitcoin transaction and fee monitors, RPC with the blockchain node, other networking code.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -60,5 +60,5 @@
60
60
  "type": "git",
61
61
  "url": "git+https://github.com/ExodusMovement/assets.git"
62
62
  },
63
- "gitHead": "5bc77cf38ec6fbd32d9e286d5bda25ea7366854d"
63
+ "gitHead": "482aa1dda8d9273c4f1a477ffcef2310f1df9884"
64
64
  }
@@ -70,14 +70,7 @@ function writeGlobalMetadata(psbt, metadata) {
70
70
  }
71
71
  }
72
72
 
73
- function createPsbtInput({
74
- input,
75
- asset,
76
- addressPathsMap,
77
- purposeXPubs,
78
- nonWitnessTxs,
79
- allowedPurposes,
80
- }) {
73
+ function createPsbtInput({ input, asset, addressPathsMap, purposeXPubs, nonWitnessTxs }) {
81
74
  const psbtInput = {
82
75
  hash: input.txId,
83
76
  index: input.vout,
@@ -85,7 +78,7 @@ function createPsbtInput({
85
78
  }
86
79
 
87
80
  const purpose = asset.address.resolvePurpose(input.address)
88
- validatePurpose(purpose, allowedPurposes, `address ${input.address}`)
81
+ validatePurpose(purpose, purposeXPubs, `address ${input.address}`)
89
82
 
90
83
  const { isSegwitAddress, isTaprootAddress, isWrappedSegwitAddress } = getAddressType(purpose)
91
84
 
@@ -141,14 +134,7 @@ function createPsbtInput({
141
134
  return { ...psbtInput, ...derivationData }
142
135
  }
143
136
 
144
- function createPsbtOutput({
145
- address,
146
- amount,
147
- asset,
148
- addressPathsMap,
149
- purposeXPubs,
150
- allowedPurposes,
151
- }) {
137
+ function createPsbtOutput({ address, amount, asset, addressPathsMap, purposeXPubs }) {
152
138
  const psbtOutput = {
153
139
  address,
154
140
  value: amount,
@@ -160,7 +146,7 @@ function createPsbtOutput({
160
146
  }
161
147
 
162
148
  const purpose = asset.address.resolvePurpose(address)
163
- validatePurpose(purpose, allowedPurposes, `output address ${address}`)
149
+ validatePurpose(purpose, purposeXPubs, `output address ${address}`)
164
150
 
165
151
  const { isTaprootAddress, isWrappedSegwitAddress } = getAddressType(purpose)
166
152
 
@@ -244,5 +230,5 @@ export async function createPsbtWithMetadata({
244
230
  writePsbtOutputField(psbt, sendOutputIndex, SubType.OutputRole, 'primary')
245
231
  }
246
232
 
247
- return psbt.toBase64()
233
+ return psbt.toBuffer()
248
234
  }
@@ -34,7 +34,7 @@ function extractInputUtxoData(psbtInput, txInput, index) {
34
34
  throw new Error(`Input ${index} has no witnessUtxo or nonWitnessUtxo`)
35
35
  }
36
36
 
37
- function parseSingleInput({ txInput, psbtInput, index, asset, purposeXPubs, allowedPurposes }) {
37
+ function parseSingleInput({ txInput, psbtInput, index, asset, purposeXPubs }) {
38
38
  const input = {
39
39
  txId: Buffer.from(txInput.hash).reverse().toString('hex'),
40
40
  vout: txInput.index,
@@ -53,7 +53,7 @@ function parseSingleInput({ txInput, psbtInput, index, asset, purposeXPubs, allo
53
53
 
54
54
  const address = asset.address.fromScriptPubKey(inputUtxoData.scriptBuffer)
55
55
  const purpose = asset.address.resolvePurpose(address)
56
- validatePurpose(purpose, allowedPurposes, `input ${index}`)
56
+ validatePurpose(purpose, purposeXPubs, `input ${index}`)
57
57
 
58
58
  const { isTaprootAddress } = getAddressType(purpose)
59
59
 
@@ -75,15 +75,7 @@ function parseSingleInput({ txInput, psbtInput, index, asset, purposeXPubs, allo
75
75
  return input
76
76
  }
77
77
 
78
- function parseSingleOutput({
79
- txOutput,
80
- psbtOutput,
81
- index,
82
- asset,
83
- purposeXPubs,
84
- psbt,
85
- allowedPurposes,
86
- }) {
78
+ function parseSingleOutput({ txOutput, psbtOutput, index, asset, purposeXPubs, psbt }) {
87
79
  const address = txOutput.address ?? asset.address.fromScriptPubKey(txOutput.script)
88
80
  const output = { amount: txOutput.value }
89
81
 
@@ -94,7 +86,7 @@ function parseSingleOutput({
94
86
 
95
87
  const purpose = asset.address.resolvePurpose(address)
96
88
  try {
97
- validatePurpose(purpose, allowedPurposes)
89
+ validatePurpose(purpose, purposeXPubs)
98
90
  } catch {
99
91
  output.address = { address }
100
92
  return output
@@ -332,13 +324,13 @@ function extractRawTransactions(parsedInputs) {
332
324
  }
333
325
 
334
326
  export async function parsePsbt({
335
- psbtBase64,
327
+ psbtBuffer,
336
328
  asset,
337
329
  assetClientInterface,
338
330
  walletAccount,
339
331
  allowedPurposes,
340
332
  }) {
341
- assert(psbtBase64, 'psbtBase64 is required')
333
+ assert(psbtBuffer, 'psbtBuffer is required')
342
334
  assert(asset, 'asset is required')
343
335
  assert(assetClientInterface, 'assetClientInterface is required')
344
336
  assert(walletAccount, 'walletAccount is required')
@@ -350,8 +342,7 @@ export async function parsePsbt({
350
342
  allowedPurposes,
351
343
  })
352
344
 
353
- // TBD: change it to fromBuffer
354
- const psbt = Psbt.fromBase64(psbtBase64, { network: asset.coinInfo.toBitcoinJS() })
345
+ const psbt = Psbt.fromBuffer(psbtBuffer, { network: asset.coinInfo.toBitcoinJS() })
355
346
 
356
347
  const inputs = []
357
348
  for (let i = 0; i < psbt.inputCount; i++) {
@@ -392,13 +383,13 @@ export async function parsePsbt({
392
383
  }
393
384
 
394
385
  export async function extractTransactionContext({
395
- psbtBase64,
386
+ psbtBuffer,
396
387
  asset,
397
388
  assetClientInterface,
398
389
  walletAccount,
399
390
  allowedPurposes,
400
391
  }) {
401
- assert(psbtBase64, 'psbtBase64 is required')
392
+ assert(psbtBuffer, 'psbtBuffer is required')
402
393
  assert(asset, 'asset is required')
403
394
  assert(assetClientInterface, 'assetClientInterface is required')
404
395
  assert(walletAccount, 'walletAccount is required')
@@ -409,7 +400,7 @@ export async function extractTransactionContext({
409
400
  fee: calculatedFee,
410
401
  globalMetadata,
411
402
  } = await parsePsbt({
412
- psbtBase64,
403
+ psbtBuffer,
413
404
  asset,
414
405
  assetClientInterface,
415
406
  walletAccount,
package/src/psbt-utils.js CHANGED
@@ -29,26 +29,31 @@ export async function getPurposeXPubs({
29
29
  const purposeXPubs = Object.create(null)
30
30
 
31
31
  for (const purpose of allowedPurposes) {
32
- const xpub = await assetClientInterface.getExtendedPublicKey({
33
- walletAccount,
34
- assetName: asset.name,
35
- purpose,
36
- })
37
- const hdkey = BIP32.fromXPub(xpub)
38
- const masterFingerprint = Buffer.alloc(4)
39
- masterFingerprint.writeUint32BE(hdkey.fingerprint)
40
- purposeXPubs[purpose] = {
41
- hdkey,
42
- masterFingerprint,
32
+ try {
33
+ const xpub = await assetClientInterface.getExtendedPublicKey({
34
+ walletAccount,
35
+ assetName: asset.name,
36
+ purpose,
37
+ })
38
+ const hdkey = BIP32.fromXPub(xpub)
39
+ const masterFingerprint = Buffer.alloc(4)
40
+ masterFingerprint.writeUint32BE(hdkey.fingerprint)
41
+ purposeXPubs[purpose] = {
42
+ hdkey,
43
+ masterFingerprint,
44
+ }
45
+ } catch {
46
+ // ignore any error that happened while we are getting the extended public key to handle cases where the extended public key is not available
47
+ // Eg. Ledger/Trezor doesn't support getting extended public keys for certain purposes
43
48
  }
44
49
  }
45
50
 
46
51
  return purposeXPubs
47
52
  }
48
53
 
49
- export function validatePurpose(purpose, allowedPurposes, context = '') {
50
- assert(allowedPurposes, 'allowedPurposes is required')
51
- if (!allowedPurposes.includes(purpose)) {
54
+ export function validatePurpose(purpose, purposeXPubs, context = '') {
55
+ assert(purposeXPubs, 'purposeXPubs is required')
56
+ if (!purposeXPubs[purpose]) {
52
57
  throw new Error(`Purpose ${purpose} not found${context ? ' for ' + context : ''}`)
53
58
  }
54
59
  }
@@ -5,6 +5,7 @@ import assert from 'minimalistic-assert'
5
5
  import { getChangeDustValue } from '../dust.js'
6
6
  import { parseCurrency, serializeCurrency } from '../fee/fee-utils.js'
7
7
  import { selectUtxos } from '../fee/utxo-selector.js'
8
+ import { createPsbtWithMetadata } from '../psbt-builder.js'
8
9
  import { findUnconfirmedSentRbfTxs } from '../tx-utils.js'
9
10
  import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data.js'
10
11
  import { getUsableUtxos, getUtxos } from '../utxos-utils.js'
@@ -267,10 +268,18 @@ async function createUnsignedTx({
267
268
  asset,
268
269
  selectedUtxos,
269
270
  insightClient,
271
+ assetClientInterface,
272
+ walletAccount,
273
+ bumpTxId,
274
+ rbfEnabled,
275
+ txType = 'transfer',
276
+ sendOutputIndex,
277
+ changeOutputIndex,
278
+ allowedPurposes,
270
279
  }) {
271
280
  const nonWitnessTxs = await getNonWitnessTxs(asset, selectedUtxos, insightClient)
272
281
 
273
- return {
282
+ const result = {
274
283
  txData: {
275
284
  inputs,
276
285
  outputs,
@@ -282,6 +291,34 @@ async function createUnsignedTx({
282
291
  rawTxs: nonWitnessTxs,
283
292
  },
284
293
  }
294
+
295
+ // Only attach PSBT metadata for Bitcoin transfer flows for now; support for other coins and
296
+ // transaction types will come later.
297
+ const isBitcoin = ['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(asset.name)
298
+ if (isBitcoin) {
299
+ const psbtBuffer = await createPsbtWithMetadata({
300
+ inputs,
301
+ outputs,
302
+ asset,
303
+ assetClientInterface,
304
+ walletAccount,
305
+ nonWitnessTxs,
306
+ addressPathsMap,
307
+ allowedPurposes,
308
+ metadata: {
309
+ rbfEnabled,
310
+ txType,
311
+ sendOutputIndexes: [sendOutputIndex],
312
+ changeOutputIndex,
313
+ bumpTxId,
314
+ blockHeight,
315
+ },
316
+ })
317
+
318
+ result.txData.psbtBuffer = psbtBuffer
319
+ }
320
+
321
+ return result
285
322
  }
286
323
 
287
324
  const getTxHandler = (type) => {
@@ -309,6 +346,7 @@ const transferHandler = {
309
346
  utxosDescendingOrder,
310
347
  assetClientInterface,
311
348
  changeAddressType,
349
+ allowedPurposes,
312
350
  }) => {
313
351
  const assetName = asset.name
314
352
  const insightClient = asset.baseAsset.insightClient
@@ -437,12 +475,20 @@ const transferHandler = {
437
475
  asset,
438
476
  selectedUtxos,
439
477
  insightClient,
478
+ assetClientInterface,
479
+ walletAccount,
480
+ bumpTxId,
481
+ rbfEnabled: context.rbfEnabled,
482
+ txType: 'transfer',
483
+ sendOutputIndex: sendOutput ? outputs.indexOf(sendOutput) : undefined,
484
+ changeOutputIndex: changeOutput ? outputs.indexOf(changeOutput) : undefined,
485
+ allowedPurposes,
440
486
  })
441
487
 
442
488
  return {
443
489
  unsignedTx,
444
- fee: adjustedFee,
445
490
  metadata: {
491
+ fee: adjustedFee,
446
492
  amount,
447
493
  change: selectedUtxos.value.sub(totalAmount).sub(adjustedFee),
448
494
  totalAmount,
@@ -469,6 +515,7 @@ export const createTxFactory =
469
515
  utxosDescendingOrder,
470
516
  assetClientInterface,
471
517
  changeAddressType,
518
+ allowedPurposes,
472
519
  }) =>
473
520
  async ({
474
521
  asset,
@@ -514,5 +561,6 @@ export const createTxFactory =
514
561
  utxosDescendingOrder,
515
562
  assetClientInterface,
516
563
  changeAddressType,
564
+ allowedPurposes,
517
565
  })
518
566
  }
@@ -1,6 +1,7 @@
1
1
  import * as defaultBitcoinjsLib from '@exodus/bitcoinjs'
2
2
  import assert from 'minimalistic-assert'
3
3
 
4
+ import { extractTransactionContext } from '../psbt-parser.js'
4
5
  import { createTxFactory } from '../tx-create/create-tx.js'
5
6
  import { broadcastTransaction } from './broadcast-tx.js'
6
7
  import { updateAccountState, updateTransactionLog } from './update-state.js'
@@ -108,6 +109,7 @@ const getPrepareSendTransaction = async ({
108
109
  options,
109
110
  utxosDescendingOrder,
110
111
  walletAccount,
112
+ allowedPurposes,
111
113
  }) => {
112
114
  const createTx = createTxFactory({
113
115
  getFeeEstimator,
@@ -115,6 +117,7 @@ const getPrepareSendTransaction = async ({
115
117
  utxosDescendingOrder,
116
118
  assetClientInterface,
117
119
  changeAddressType,
120
+ allowedPurposes,
118
121
  })
119
122
 
120
123
  // Set default values for options
@@ -131,6 +134,59 @@ const getPrepareSendTransaction = async ({
131
134
  })
132
135
  }
133
136
 
137
+ function toTransactionDescriptor(txContext, psbtBuffer) {
138
+ const {
139
+ inputs,
140
+ outputs,
141
+ addressPathsMap,
142
+ blockHeight,
143
+ rawTxs,
144
+ fee,
145
+ totalSendAmount,
146
+ changeAmount,
147
+ totalAmount,
148
+ primaryAddresses,
149
+ ourAddress,
150
+ usableUtxos,
151
+ selectedUtxos,
152
+ replaceTx,
153
+ sendOutputs,
154
+ changeOutput,
155
+ rbfEnabled,
156
+ } = txContext
157
+
158
+ return {
159
+ unsignedTx: {
160
+ txData: {
161
+ inputs,
162
+ outputs,
163
+ psbtBuffer,
164
+ },
165
+ txMeta: {
166
+ addressPathsMap,
167
+ blockHeight,
168
+ rawTxs,
169
+ },
170
+ },
171
+ metadata: {
172
+ fee,
173
+ amount: totalSendAmount.isZero ? undefined : totalSendAmount,
174
+ sendAmount: totalSendAmount,
175
+ change: changeAmount,
176
+ totalAmount,
177
+ address: primaryAddresses[0]?.address,
178
+ ourAddress,
179
+ usableUtxos,
180
+ selectedUtxos,
181
+ replaceTx,
182
+ sendOutput: sendOutputs[0],
183
+ changeOutput,
184
+ rbfEnabled,
185
+ blockHeight,
186
+ },
187
+ }
188
+ }
189
+
134
190
  // not ported from Exodus; but this demos signing / broadcasting
135
191
  // NOTE: this will be ripped out in the coming weeks
136
192
  export const createAndBroadcastTXFactory =
@@ -141,6 +197,7 @@ export const createAndBroadcastTXFactory =
141
197
  utxosDescendingOrder,
142
198
  assetClientInterface,
143
199
  changeAddressType,
200
+ allowedPurposes,
144
201
  }) =>
145
202
  async ({ asset, walletAccount, address, amount, options }) => {
146
203
  // Prepare transaction
@@ -149,7 +206,7 @@ export const createAndBroadcastTXFactory =
149
206
  const assetName = asset.name
150
207
  const accountState = await assetClientInterface.getAccountState({ assetName, walletAccount })
151
208
 
152
- const transactionDescriptor = await getPrepareSendTransaction({
209
+ let transactionDescriptor = await getPrepareSendTransaction({
153
210
  address,
154
211
  allowUnconfirmedRbfEnabledUtxos,
155
212
  amount,
@@ -160,22 +217,31 @@ export const createAndBroadcastTXFactory =
160
217
  options,
161
218
  utxosDescendingOrder,
162
219
  walletAccount,
220
+ allowedPurposes,
163
221
  })
164
222
 
165
- const { unsignedTx, fee, metadata } = transactionDescriptor
166
- const {
167
- sendAmount,
168
- usableUtxos,
169
- replaceTx,
170
- sendOutput,
171
- changeOutput,
172
- blockHeight,
173
- rbfEnabled,
174
- } = metadata
175
-
176
- const outputs = unsignedTx.txData.outputs
223
+ // If we already created a PSBT for Bitcoin, hydrate the full transaction
224
+ // context from it before signing.
225
+ let unsignedTx, metadata
226
+ if (transactionDescriptor.unsignedTx?.txData?.psbtBuffer) {
227
+ const psbtBuffer = transactionDescriptor.unsignedTx.txData.psbtBuffer
228
+ const txContext = await extractTransactionContext({
229
+ psbtBuffer,
230
+ asset,
231
+ assetClientInterface,
232
+ walletAccount,
233
+ allowedPurposes,
234
+ })
235
+ transactionDescriptor = toTransactionDescriptor(txContext, psbtBuffer)
236
+ unsignedTx = transactionDescriptor.unsignedTx
237
+ metadata = transactionDescriptor.metadata
238
+ } else {
239
+ // Legacy/non-PSBT flows stick with the original descriptor shape.
240
+ unsignedTx = transactionDescriptor.unsignedTx
241
+ metadata = transactionDescriptor.metadata
242
+ }
177
243
 
178
- address = metadata.address
244
+ const { sendAmount, usableUtxos, replaceTx, sendOutput, changeOutput } = metadata
179
245
 
180
246
  // Sign transaction
181
247
  const { rawTx, txId, tx } = await signTransaction({
@@ -192,7 +258,7 @@ export const createAndBroadcastTXFactory =
192
258
  if (/insight broadcast http error.*missing inputs/i.test(err.message)) {
193
259
  err.txInfo = JSON.stringify({
194
260
  amount: sendAmount.toDefaultString({ unit: true }),
195
- fee: ((fee && fee.toDefaultString({ unit: true })) || 0).toString(),
261
+ fee: ((metadata.fee && metadata.fee.toDefaultString({ unit: true })) || 0).toString(),
196
262
  allUtxos: usableUtxos.toJSON(),
197
263
  })
198
264
  }
@@ -203,7 +269,7 @@ export const createAndBroadcastTXFactory =
203
269
  function findUtxoIndex(output) {
204
270
  let utxoIndex = -1
205
271
  if (output) {
206
- for (const [i, [address, amount]] of outputs.entries()) {
272
+ for (const [i, [address, amount]] of unsignedTx.txData.outputs.entries()) {
207
273
  if (output[0] === address && output[1] === amount) {
208
274
  utxoIndex = i
209
275
  break
@@ -228,7 +294,6 @@ export const createAndBroadcastTXFactory =
228
294
  rawTx,
229
295
  changeUtxoIndex,
230
296
  getSizeAndChangeScript,
231
- rbfEnabled,
232
297
  })
233
298
 
234
299
  await updateTransactionLog({
@@ -236,14 +301,9 @@ export const createAndBroadcastTXFactory =
236
301
  assetClientInterface,
237
302
  walletAccount,
238
303
  txId,
239
- fee,
240
304
  metadata,
241
- address,
242
- amount,
243
305
  bumpTxId,
244
306
  size,
245
- blockHeight,
246
- rbfEnabled,
247
307
  })
248
308
 
249
309
  return {
@@ -16,7 +16,6 @@ import { serializeCurrency } from '../fee/fee-utils.js'
16
16
  * @param {number} params.changeUtxoIndex - Index of change output
17
17
  * @param {Object} params.changeOutput - Change output details
18
18
  * @param {Object} params.getSizeAndChangeScript - Function to get size and script
19
- * @param {boolean} params.rbfEnabled - Whether RBF is enabled
20
19
  */
21
20
  export async function updateAccountState({
22
21
  assetClientInterface,
@@ -29,9 +28,8 @@ export async function updateAccountState({
29
28
  rawTx,
30
29
  changeUtxoIndex,
31
30
  getSizeAndChangeScript,
32
- rbfEnabled,
33
31
  }) {
34
- const { usableUtxos, selectedUtxos, replaceTx, change, ourAddress } = metadata
32
+ const { usableUtxos, selectedUtxos, replaceTx, change, ourAddress, rbfEnabled } = metadata
35
33
 
36
34
  // Get change script and size
37
35
  const { script, size } = getSizeAndChangeScript({
@@ -88,30 +86,31 @@ export async function updateAccountState({
88
86
  * @param {Object} params.assetClientInterface - Asset client interface
89
87
  * @param {Object} params.walletAccount - Wallet account
90
88
  * @param {string} params.txId - Transaction ID
91
- * @param {Object} params.fee - Transaction fee
92
89
  * @param {Object} params.metadata - Transaction metadata
93
- * @param {string} params.address - Recipient address
94
- * @param {Object} params.amount - Transaction amount (for regular sends)
95
90
  * @param {string} params.bumpTxId - ID of transaction being bumped (if applicable)
96
91
  * @param {number} params.size - Transaction size
97
- * @param {number} params.blockHeight - Block height
98
- * @param {boolean} params.rbfEnabled - Whether RBF is enabled
99
92
  */
100
93
  export async function updateTransactionLog({
101
94
  asset,
102
95
  assetClientInterface,
103
96
  walletAccount,
104
97
  txId,
105
- fee,
106
98
  metadata,
107
- address,
108
- amount,
109
99
  bumpTxId,
110
100
  size,
111
- blockHeight,
112
- rbfEnabled,
113
101
  }) {
114
- const { totalAmount, selectedUtxos, replaceTx, changeOutput, ourAddress } = metadata
102
+ const {
103
+ totalAmount,
104
+ selectedUtxos,
105
+ replaceTx,
106
+ changeOutput,
107
+ ourAddress,
108
+ fee,
109
+ blockHeight,
110
+ rbfEnabled,
111
+ address,
112
+ amount,
113
+ } = metadata
115
114
  const assetName = asset.name
116
115
 
117
116
  // Check if this is a self-send
@@ -1,6 +1,7 @@
1
1
  import { bip371, payments, Transaction } from '@exodus/bitcoinjs'
2
2
  import { publicKeyToX } from '@exodus/crypto/secp256k1'
3
3
 
4
+ import { withUnsafeNonSegwit } from '../psbt-utils.js'
4
5
  import { createGetKeyWithMetadata } from './create-get-key-and-purpose.js'
5
6
  import { toAsyncBufferSigner, toAsyncSigner } from './taproot.js'
6
7
 
@@ -82,9 +83,12 @@ export function createSignWithWallet({
82
83
  : toAsyncSigner({ privateKey, publicKey, isTaprootKeySpend })
83
84
 
84
85
  // desktop / BE / mobile with bip-schnorr signing
85
- signingPromises.push(psbt.signInputAsync(index, asyncSigner, allowedSigHashTypes))
86
+ signingPromises.push(() => psbt.signInputAsync(index, asyncSigner, allowedSigHashTypes))
86
87
  }
87
88
 
88
- await Promise.all(signingPromises)
89
+ await withUnsafeNonSegwit({
90
+ psbt,
91
+ fn: () => Promise.all(signingPromises.map((sign) => sign())),
92
+ })
89
93
  }
90
94
  }
@@ -57,7 +57,11 @@ export const signTxFactory = ({
57
57
  },
58
58
  })
59
59
 
60
- const skipFinalize = !!unsignedTx.txData.psbtBuffer || unsignedTx.txMeta.returnPsbt
60
+ const isExternalPsbt =
61
+ unsignedTx.txData.psbtBuffer &&
62
+ unsignedTx.txMeta.addressPathsMap &&
63
+ unsignedTx.txMeta.inputsToSign
64
+ const skipFinalize = isExternalPsbt || unsignedTx.txMeta.returnPsbt
61
65
  await signWithWallet(psbt, inputsToSign, skipFinalize)
62
66
  return extractTransaction({ psbt, skipFinalize })
63
67
  }
@@ -27,17 +27,8 @@ export function createPrepareForSigning({
27
27
  return ({ unsignedTx }) => {
28
28
  const networkInfo = coinInfo.toBitcoinJS()
29
29
 
30
- const isPsbtBufferPassed =
31
- unsignedTx.txData.psbtBuffer &&
32
- unsignedTx.txMeta.addressPathsMap &&
33
- unsignedTx.txMeta.inputsToSign
34
- if (isPsbtBufferPassed) {
35
- // PSBT created externally (Web3, etc..)
36
- return createPsbtFromBuffer({
37
- Psbt,
38
- psbtBuffer: unsignedTx.txData.psbtBuffer,
39
- networkInfo,
40
- })
30
+ if (unsignedTx.txData.psbtBuffer) {
31
+ return createPsbtFromBuffer({ Psbt, psbtBuffer: unsignedTx.txData.psbtBuffer, networkInfo })
41
32
  }
42
33
 
43
34
  // Create PSBT based on internal Exodus data structure
@@ -40,7 +40,11 @@ export const signHardwareFactory = ({ assetName, resolvePurpose, keys, coinInfo
40
40
  multisigData,
41
41
  })
42
42
 
43
- const skipFinalize = !!unsignedTx.txData.psbtBuffer || unsignedTx.txMeta.returnPsbt
43
+ const isExternalPsbt =
44
+ unsignedTx.txData.psbtBuffer &&
45
+ unsignedTx.txMeta.addressPathsMap &&
46
+ unsignedTx.txMeta.inputsToSign
47
+ const skipFinalize = isExternalPsbt || unsignedTx.txMeta.returnPsbt
44
48
  return extractTransaction({ psbt, skipFinalize })
45
49
  }
46
50
  }