@exodus/bitcoin-api 4.15.4 → 4.15.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
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.15.6](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.15.5...@exodus/bitcoin-api@4.15.6) (2026-06-03)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: remove stale stored UTXOs during refresh (#8184)
13
+
14
+
15
+
16
+ ## [4.15.5](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.15.4...@exodus/bitcoin-api@4.15.5) (2026-06-02)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+
22
+ * fix: restrict unsafe non-segwit signing to internal LTC PSBTs (#8172)
23
+
24
+
25
+
6
26
  ## [4.15.4](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.15.3...@exodus/bitcoin-api@4.15.4) (2026-05-28)
7
27
 
8
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "4.15.4",
3
+ "version": "4.15.6",
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",
@@ -63,5 +63,5 @@
63
63
  "type": "git",
64
64
  "url": "git+https://github.com/ExodusMovement/assets.git"
65
65
  },
66
- "gitHead": "a2a294de9468382e1c3bbdebf6db6eea2bea4b1c"
66
+ "gitHead": "c19821f0073cd61c5e2a6b07c962165e8ee77416"
67
67
  }
package/src/move-funds.js CHANGED
@@ -162,6 +162,7 @@ export const moveFundsFactory = ({
162
162
  },
163
163
  txMeta: {
164
164
  addressPathsMap: utxos.getAddressPathsMap(),
165
+ psbtOrigin: 'internal',
165
166
  },
166
167
  }
167
168
  const nonWitnessTxs = await getNonWitnessTxs(
package/src/psbt-utils.js CHANGED
@@ -118,10 +118,9 @@ export const createPsbtToUnsignedTx =
118
118
  }
119
119
 
120
120
  /**
121
- * Temporarily turns on __UNSAFE_SIGN_NONSEGWIT so we can sign or validate PSBTs
122
- * whose legacy inputs are missing nonWitnessUtxo.
121
+ * Temporarily sets __UNSAFE_SIGN_NONSEGWIT for explicitly allowed internal fallbacks.
123
122
  */
124
- export async function withUnsafeNonSegwit({ psbt, fn, unsafe = true }) {
123
+ export async function withUnsafeNonSegwit({ psbt, fn, unsafe = false }) {
125
124
  const cache = psbt.__CACHE
126
125
  const prevValue = cache.__UNSAFE_SIGN_NONSEGWIT
127
126
  cache.__UNSAFE_SIGN_NONSEGWIT = unsafe
@@ -339,6 +339,7 @@ async function createUnsignedTx({
339
339
  Transaction,
340
340
  })
341
341
 
342
+ result.txMeta.psbtOrigin = 'internal'
342
343
  result.txData.psbtBuffer = psbt.toBuffer()
343
344
  }
344
345
 
@@ -258,6 +258,14 @@ export class BitcoinMonitorScanner {
258
258
  // newChains moves receive to 7. The second loop then scans receive 8..12
259
259
  // and change 7..7. This keeps expanding until the fetched address batch has no txs.
260
260
  let allTxs = []
261
+ const scannedAddressSet = new Set()
262
+ const fetchStoredUtxoAddressTxs = async ({ storedUtxoAddresses }) => {
263
+ const storedOnlyAddresses = [...storedUtxoAddresses].filter(
264
+ (storedUtxoAddress) => !scannedAddressSet.has(storedUtxoAddress)
265
+ )
266
+ return fetchAllTxs(storedOnlyAddresses)
267
+ }
268
+
261
269
  for (let fetchCount = 0; ; fetchCount++) {
262
270
  const chainObjects = gapSearchParameters.flatMap(
263
271
  ({ purpose, chain, startAddressIndexes, endAddressIndexes }) => {
@@ -289,6 +297,7 @@ export class BitcoinMonitorScanner {
289
297
  }
290
298
 
291
299
  const addresses = await aggregateAddresses(chainObjects)
300
+ addresses.forEach((address) => scannedAddressSet.add(address))
292
301
 
293
302
  if (fetchCount === 0) {
294
303
  // in compatibility mode, the receive address could be different
@@ -299,6 +308,7 @@ export class BitcoinMonitorScanner {
299
308
  })
300
309
  if (!addresses.includes(receiveAddress.toString())) {
301
310
  addresses.push(receiveAddress.toString())
311
+ scannedAddressSet.add(receiveAddress.toString())
302
312
  addrMap[receiveAddress.toString()] = receiveAddress
303
313
  }
304
314
  }
@@ -370,6 +380,16 @@ export class BitcoinMonitorScanner {
370
380
  }
371
381
 
372
382
  allTxs = orderTxs(allTxs)
383
+ let storedUtxoAddressTxs = []
384
+ if (refresh) {
385
+ const storedUtxoAddresses = new Set(
386
+ storedUtxos
387
+ .toArray()
388
+ .map((utxo) => utxo.address?.toString())
389
+ .filter(Boolean)
390
+ )
391
+ storedUtxoAddressTxs = await fetchStoredUtxoAddressTxs({ storedUtxoAddresses })
392
+ }
373
393
 
374
394
  // post process TX data
375
395
  // NOTE: this can be optimized
@@ -379,6 +399,14 @@ export class BitcoinMonitorScanner {
379
399
  const newTxs = []
380
400
  const existingTxs = []
381
401
 
402
+ storedUtxoAddressTxs.forEach((txItem) => {
403
+ txItem.vin.forEach((vin) => {
404
+ if (Object.keys(vin).length === 0) return
405
+
406
+ vinTxids[`${vin.txid}-${vin.vout}`] = true
407
+ })
408
+ })
409
+
382
410
  allTxs.forEach((txItem) => {
383
411
  const isCoinbase = txItem.vin.length === 0
384
412
 
@@ -14,6 +14,7 @@ export function createSignWithWallet({
14
14
  coinInfo,
15
15
  getKeyIdentifier,
16
16
  getPrivateKeyFromMap,
17
+ allowUnsafeNonSegwit = false,
17
18
  }) {
18
19
  const getKeyWithMetadata = createGetKeyWithMetadata({
19
20
  signer,
@@ -90,6 +91,7 @@ export function createSignWithWallet({
90
91
  await withUnsafeNonSegwit({
91
92
  psbt,
92
93
  fn: () => Promise.all(signingPromises.map((sign) => sign())),
94
+ unsafe: allowUnsafeNonSegwit,
93
95
  })
94
96
  }
95
97
  }
@@ -12,6 +12,7 @@ export const signTxFactory = ({
12
12
  network,
13
13
  getKeyIdentifier,
14
14
  getPrivateKeyFromMap,
15
+ allowUnsafeNonSegwitSigning = false,
15
16
  Psbt = DefaultPsbt,
16
17
  Transaction = DefaultTransaction,
17
18
  }) => {
@@ -39,6 +40,8 @@ export const signTxFactory = ({
39
40
  const psbt = prepareForSigning({ unsignedTx })
40
41
 
41
42
  const inputsToSign = unsignedTx.txMeta.inputsToSign || unsignedTx.txData.inputs
43
+ const allowUnsafeNonSegwit =
44
+ allowUnsafeNonSegwitSigning && unsignedTx.txMeta.psbtOrigin === 'internal'
42
45
  const signWithWallet = createSignWithWallet({
43
46
  signer,
44
47
  hdkeys,
@@ -48,6 +51,7 @@ export const signTxFactory = ({
48
51
  addressPathsMap,
49
52
  coinInfo,
50
53
  network,
54
+ allowUnsafeNonSegwit,
51
55
  getKeyIdentifier: (args) => {
52
56
  assert(
53
57
  !('accountIndex' in args) || args.accountIndex === accountIndex,
@@ -130,9 +130,7 @@ function createPsbtFromTxData({
130
130
  if (canParseTx(Transaction, rawTxBuffer)) {
131
131
  txIn.nonWitnessUtxo = rawTxBuffer
132
132
  } else {
133
- // temp fix for https://exodusio.slack.com/archives/CP202D90Q/p1671014704829939 until bitcoinjs could parse a mweb tx without failing
134
- console.warn(`Setting psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true for asset ${assetName}`)
135
- psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true
133
+ console.warn(`Falling back to witnessUtxo for unparseable previous tx on ${assetName}`)
136
134
  txIn.witnessUtxo = {
137
135
  value: normalizeWitnessUtxoValue(value),
138
136
  script: Buffer.from(script, 'hex'),