@exodus/bitcoin-api 4.8.1 → 4.8.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.
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.8.3](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.8.2...@exodus/bitcoin-api@4.8.3) (2025-12-16)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: keep pending txs until /tx check fails for utxo coins (#7009)
13
+
14
+
15
+
16
+ ## [4.8.2](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.8.1...@exodus/bitcoin-api@4.8.2) (2025-12-04)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+
22
+ * fix: utxo psbtify fixes (#7029)
23
+
24
+
25
+
6
26
  ## [4.8.1](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.8.0...@exodus/bitcoin-api@4.8.1) (2025-12-03)
7
27
 
8
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "4.8.1",
3
+ "version": "4.8.3",
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": "f4afd4b4ade5a5bb4d5f8342ba41b6852eaf3f81"
63
+ "gitHead": "84be327e5d39ecfd0e36abc038f33f7f3b9c33b8"
64
64
  }
@@ -3,6 +3,7 @@ import { Address, UtxoCollection } from '@exodus/models'
3
3
  import BipPath from 'bip32-path'
4
4
  import assert from 'minimalistic-assert'
5
5
 
6
+ import { parseCurrency, serializeCurrency } from './fee/fee-utils.js'
6
7
  import { readPsbtGlobalField, readPsbtOutputField, SubType } from './psbt-proprietary-types.js'
7
8
  import { getAddressType, getPurposeXPubs, validatePurpose } from './psbt-utils.js'
8
9
  import { createInputs, createOutput } from './tx-create/tx-create-utils.js'
@@ -278,7 +279,7 @@ function processReplacementTransaction(replaceTx, asset) {
278
279
 
279
280
  updatedTx.data.sent = updatedTx?.data?.sent.map((to) => ({
280
281
  ...to,
281
- amount: asset.currency.baseUnit(to.amount),
282
+ amount: serializeCurrency(to.amount, asset.currency),
282
283
  }))
283
284
 
284
285
  return updatedTx
@@ -295,7 +296,10 @@ function calculateAmounts({ primaryOutputs, changeOutputData, processedReplaceTx
295
296
  : asset.currency.ZERO
296
297
 
297
298
  const totalAmount = processedReplaceTx
298
- ? processedReplaceTx.data.sent.reduce((total, { amount }) => total.add(amount), totalSendAmount)
299
+ ? processedReplaceTx.data.sent.reduce(
300
+ (total, { amount }) => total.add(parseCurrency(amount, asset.currency)),
301
+ totalSendAmount
302
+ )
299
303
  : totalSendAmount
300
304
 
301
305
  return { sendAmounts, totalSendAmount, changeAmount, totalAmount }
@@ -334,6 +338,26 @@ function extractRawTransactions(parsedInputs) {
334
338
  return [...rawTxsMap].map(([txId, rawData]) => ({ txId, rawData }))
335
339
  }
336
340
 
341
+ function orderSelectedUtxos(parsedInputs, selectedUtxos) {
342
+ const selectedUtxoKey = (txId, vout) => `${txId}:${vout}`
343
+ const selectedUtxoMap = new Map(
344
+ selectedUtxos.toArray().map((utxo) => [selectedUtxoKey(utxo.txId, utxo.vout), utxo])
345
+ )
346
+
347
+ const orderedSelectedUtxos = []
348
+ for (const parsedInput of parsedInputs) {
349
+ const key = selectedUtxoKey(parsedInput.txId, parsedInput.vout)
350
+ const utxo = selectedUtxoMap.get(key)
351
+ if (utxo) {
352
+ orderedSelectedUtxos.push(utxo)
353
+ } else {
354
+ throw new Error(`orderSelectedUtxos: missing selected UTXO for ${key}`)
355
+ }
356
+ }
357
+
358
+ return orderedSelectedUtxos
359
+ }
360
+
337
361
  export async function parsePsbt({
338
362
  psbtBuffer,
339
363
  asset,
@@ -605,7 +629,11 @@ export async function extractTransactionContext({
605
629
  unconfirmedTxAncestor,
606
630
  })
607
631
 
608
- const inputs = createInputs(assetName, selectedUtxos.toArray(), globalMetadata.rbfEnabled)
632
+ const inputs = createInputs(
633
+ assetName,
634
+ orderSelectedUtxos(parsedInputs, selectedUtxos),
635
+ globalMetadata.rbfEnabled
636
+ )
609
637
  const outputs = parsedOutputs.map((output) =>
610
638
  createOutput(assetName, output.address.address, asset.currency.baseUnit(output.amount))
611
639
  )
@@ -280,7 +280,8 @@ async function createUnsignedTx({
280
280
  Transaction,
281
281
  }) {
282
282
  const nonWitnessTxs = await getNonWitnessTxs(asset, selectedUtxos, insightClient)
283
-
283
+ const sendOutputIndexes =
284
+ sendOutputIndex === undefined || sendOutputIndex === null ? [] : [sendOutputIndex]
284
285
  const result = {
285
286
  txData: {
286
287
  inputs,
@@ -291,8 +292,7 @@ async function createUnsignedTx({
291
292
  addressPathsMap,
292
293
  blockHeight,
293
294
  rawTxs: nonWitnessTxs,
294
- sendOutputIndexes:
295
- sendOutputIndex === undefined || sendOutputIndex === null ? [] : [sendOutputIndex],
295
+ sendOutputIndexes,
296
296
  changeOutputIndex,
297
297
  outputAddressPurposesMap,
298
298
  rbfEnabled,
@@ -317,7 +317,7 @@ async function createUnsignedTx({
317
317
  metadata: {
318
318
  rbfEnabled,
319
319
  txType,
320
- sendOutputIndexes: [sendOutputIndex],
320
+ sendOutputIndexes,
321
321
  changeOutputIndex,
322
322
  bumpTxId,
323
323
  blockHeight,
@@ -669,7 +669,26 @@ export class BitcoinMonitorScanner {
669
669
  // Keep new utxos when they intersect with the stored utxos.
670
670
  utxoCol = utxoCol.union(currentStoredUtxos).difference(utxosToRemoveCol)
671
671
 
672
- for (const tx of Object.values(unconfirmedTxsToCheck)) {
672
+ const pendingDropCandidates = Object.values(unconfirmedTxsToCheck)
673
+ const verificationResults = await Promise.all(
674
+ pendingDropCandidates.map(async (tx) => {
675
+ try {
676
+ const txStatus = await insightClient.fetchTx(tx.txId)
677
+ return { tx, shouldDrop: !txStatus }
678
+ } catch (err) {
679
+ console.log(
680
+ `${assetName}: failed to verify pending tx ${tx.txId} before drop check: ${
681
+ err?.message || 'unknown'
682
+ }`
683
+ )
684
+ return { tx, shouldDrop: false }
685
+ }
686
+ })
687
+ )
688
+
689
+ for (const { tx, shouldDrop } of verificationResults) {
690
+ if (!shouldDrop) continue
691
+
673
692
  existingTxs.push({ ...tx, dropped: true }) // TODO: this will decrease the chain index, it shouldn't be an issue considering the gap limit
674
693
  utxoCol = utxoCol.difference(utxoCol.getTxIdUtxos(tx.txId))
675
694
  const utxosToAdd = []
@@ -3,7 +3,7 @@
3
3
  // bitcoinjs default). If an asset is missing, the library fallback (5000 sat/vB)
4
4
  // will apply instead.
5
5
  const MAXIMUM_FEE_RATES = {
6
- dogecoin: 12_000,
6
+ dogecoin: 200_000,
7
7
  qtumignition: 25_000,
8
8
  ravencoin: 1_000_000,
9
9
  }