@exodus/bitcoin-api 2.27.0 → 2.29.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,34 @@
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
+ ## [2.29.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.28.0...@exodus/bitcoin-api@2.29.0) (2024-11-10)
7
+
8
+
9
+ ### Features
10
+
11
+ * filter immature coinbase utxos in getUsableUtxos ([#4507](https://github.com/ExodusMovement/assets/issues/4507)) ([3774f66](https://github.com/ExodusMovement/assets/commit/3774f66ee733ca5a3f07a7556c349d08c2d217e9))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * fetch and require raw txs for segwit inputs ([#4399](https://github.com/ExodusMovement/assets/issues/4399)) ([f5822cc](https://github.com/ExodusMovement/assets/commit/f5822cc60d6e542f9aea88c051e1a2dbe56da13d))
17
+
18
+
19
+
20
+ ## [2.28.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.27.0...@exodus/bitcoin-api@2.28.0) (2024-10-31)
21
+
22
+
23
+ ### Features
24
+
25
+ * update bip322-js to 2.0.0 ([#4414](https://github.com/ExodusMovement/assets/issues/4414)) ([da2a7dd](https://github.com/ExodusMovement/assets/commit/da2a7dd217a6664f02fa722968bf6ff16f53ea39))
26
+
27
+
28
+ ### Bug Fixes
29
+
30
+ * stringify sent amount NU's ([#4433](https://github.com/ExodusMovement/assets/issues/4433)) ([f2886a8](https://github.com/ExodusMovement/assets/commit/f2886a85060f5e347df9bb2bf892bb52257ece6b)), closes [#4437](https://github.com/ExodusMovement/assets/issues/4437)
31
+
32
+
33
+
6
34
  ## [2.27.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.26.1...@exodus/bitcoin-api@2.27.0) (2024-10-17)
7
35
 
8
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "2.27.0",
3
+ "version": "2.29.0",
4
4
  "description": "Exodus bitcoin-api",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -23,7 +23,7 @@
23
23
  "@exodus/asset-lib": "^5.0.0",
24
24
  "@exodus/basic-utils": "^3.0.1",
25
25
  "@exodus/bip32": "^3.3.0",
26
- "@exodus/bip322-js": "^1.1.0",
26
+ "@exodus/bip322-js": "^2.0.0",
27
27
  "@exodus/bip44-constants": "^195.0.0",
28
28
  "@exodus/bitcoin-lib": "^2.4.2",
29
29
  "@exodus/bitcoinjs": "^1.1.0",
@@ -56,5 +56,5 @@
56
56
  "type": "git",
57
57
  "url": "git+https://github.com/ExodusMovement/assets.git"
58
58
  },
59
- "gitHead": "3218754fdfad087b6d789eb81f7fe9f21b636fef"
59
+ "gitHead": "500277d2c77cd37f7d7a93eceec09e5b8c49f253"
60
60
  }
@@ -58,3 +58,11 @@ export function parseCurrency(val, currency) {
58
58
  if (typeof val === 'string') return currency.parse(val)
59
59
  return currency.parse(val.value + ' ' + val.unit)
60
60
  }
61
+
62
+ export function serializeCurrency(val, currency) {
63
+ if (val === undefined) {
64
+ return val
65
+ }
66
+
67
+ return parseCurrency(val, currency).toDefaultString({ unit: true })
68
+ }
@@ -418,6 +418,7 @@ export class BitcoinMonitorScanner {
418
418
  feePerKB: txItem.fees ? (txItem.fees / txItem.vsize) * 1000 * 1e8 : null,
419
419
  rbfEnabled: txItem.rbf,
420
420
  blocksSeen: 0,
421
+ ...(txItem.vin.length === 0 ? { isCoinbase: true } : undefined),
421
422
  },
422
423
  currencies: { [assetName]: currency },
423
424
  }
@@ -505,7 +506,10 @@ export class BitcoinMonitorScanner {
505
506
  if (isSent && !txLogItem.to) {
506
507
  const val = currency.defaultUnit(vout.value)
507
508
  const sentDisplayAddress = asset.address.displayAddress?.(sentAddress) || sentAddress
508
- txLogItem.data.sent.push({ address: sentDisplayAddress, amount: val })
509
+ txLogItem.data.sent.push({
510
+ address: sentDisplayAddress,
511
+ amount: val.toDefaultString({ unit: true }),
512
+ })
509
513
  }
510
514
 
511
515
  return
@@ -541,6 +545,7 @@ export class BitcoinMonitorScanner {
541
545
  script: vout.scriptPubKey.hex,
542
546
  value: val,
543
547
  rbfEnabled: txItem.rbf,
548
+ ...(txItem.vin.length === 0 ? { isCoinbase: true } : undefined),
544
549
  }
545
550
 
546
551
  if (this.#ordinalsEnabled) {
@@ -5,7 +5,7 @@ import { retry } from '@exodus/simple-retry'
5
5
  import lodash from 'lodash'
6
6
  import assert from 'minimalistic-assert'
7
7
 
8
- import { parseCurrency } from '../fee/fee-utils.js'
8
+ import { parseCurrency, serializeCurrency } from '../fee/fee-utils.js'
9
9
  import { selectUtxos } from '../fee/utxo-selector.js'
10
10
  import { findUnconfirmedSentRbfTxs } from '../tx-utils.js'
11
11
  import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data.js'
@@ -71,7 +71,14 @@ export async function getNonWitnessTxs(asset, utxos, insightClient) {
71
71
  utxos
72
72
  .getAddressesForTxId(txId)
73
73
  .toAddressStrings()
74
- .some((a) => asset.address.isP2PKH(a) || asset.address.isP2SH(a))
74
+ .some(
75
+ (a) =>
76
+ asset.address.isP2PKH(a) ||
77
+ asset.address.isP2SH(a) ||
78
+ asset.address.isP2SH2?.(a) ||
79
+ asset.address.isP2WPKH?.(a) ||
80
+ asset.address.isP2WSH?.(a)
81
+ )
75
82
  )
76
83
 
77
84
  if (nonWitnessTxIds.length > 0) {
@@ -376,7 +383,7 @@ export const getPrepareSendTransaction =
376
383
  replaceTx = replaceTx.clone()
377
384
  replaceTx = replaceTx.update({ data: { ...replaceTx.data } })
378
385
  replaceTx.data.sent = replaceTx.data.sent.map((to) => {
379
- return { ...to, amount: parseCurrency(to.amount, asset.currency) }
386
+ return { ...to, amount: serializeCurrency(to.amount, asset.currency) }
380
387
  })
381
388
  selectedUtxos = selectedUtxos.union(
382
389
  // how to avoid replace tx inputs when inputs are ordinals? !!!!
@@ -389,7 +396,9 @@ export const getPrepareSendTransaction =
389
396
  // Inputs and Outputs
390
397
  const inputs = shuffle(createInputs(assetName, selectedUtxos.toArray(), rbfEnabled))
391
398
  let outputs = replaceTx
392
- ? replaceTx.data.sent.map(({ address, amount }) => createOutput(assetName, address, amount))
399
+ ? replaceTx.data.sent.map(({ address, amount }) =>
400
+ createOutput(assetName, address, parseCurrency(amount, currency))
401
+ )
393
402
  : []
394
403
 
395
404
  // Send output
@@ -408,7 +417,10 @@ export const getPrepareSendTransaction =
408
417
  }
409
418
 
410
419
  const totalAmount = replaceTx
411
- ? replaceTx.data.sent.reduce((total, { amount }) => total.add(amount), sendAmount)
420
+ ? replaceTx.data.sent.reduce(
421
+ (total, { amount }) => total.add(parseCurrency(amount, asset.currency)),
422
+ sendAmount
423
+ )
412
424
  : sendAmount
413
425
 
414
426
  const change = selectedUtxos.value
@@ -655,8 +667,11 @@ export const createAndBroadcastTXFactory =
655
667
  ? replaceTx.data.sent
656
668
  : []
657
669
  : replaceTx
658
- ? [...replaceTx.data.sent, { address: displayReceiveAddress, amount }]
659
- : [{ address: displayReceiveAddress, amount }]
670
+ ? [
671
+ ...replaceTx.data.sent,
672
+ { address: displayReceiveAddress, amount: serializeCurrency(amount, asset.currency) },
673
+ ]
674
+ : [{ address: displayReceiveAddress, amount: serializeCurrency(amount, asset.currency) }]
660
675
 
661
676
  const calculateCoinAmount = () => {
662
677
  if (selfSend) {
@@ -85,17 +85,24 @@ function createPsbtFromTxData({
85
85
  const isTaprootAddress = purpose === 86
86
86
 
87
87
  const txIn = { hash: txId, index: vout, sequence }
88
- if (isSegwitAddress || isTaprootAddress) {
89
- if (isTaprootAddress && tapLeafScript) {
90
- txIn.tapLeafScript = tapLeafScript
91
- }
92
88
 
93
- // witness outputs only require the value and the script, not the full transaction
89
+ if (isTaprootAddress && tapLeafScript) {
90
+ txIn.tapLeafScript = tapLeafScript
91
+ }
92
+
93
+ if (isSegwitAddress || isTaprootAddress) {
94
+ // taproot outputs only require the value and the script, not the full transaction
94
95
  txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
95
- } else {
96
- const rawTx = (rawTxs || []).find((t) => t.txId === txId)
97
- // non-witness outptus require the full transaction
98
- assert(!!rawTx, 'Non-witness outputs require the full previous transaction.')
96
+ }
97
+
98
+ const rawTx = (rawTxs || []).find((t) => t.txId === txId)
99
+
100
+ // Non-taproot outputs require the full transaction
101
+ assert(
102
+ isTaprootAddress || !!rawTx?.rawData,
103
+ `Non-taproot outputs require the full previous transaction.`
104
+ )
105
+ if (!isTaprootAddress) {
99
106
  const rawTxBuffer = Buffer.from(rawTx.rawData, 'hex')
100
107
  if (canParseTx(Transaction, rawTxBuffer)) {
101
108
  txIn.nonWitnessUtxo = rawTxBuffer
@@ -233,11 +233,25 @@ function filterDustUtxos({ utxos, feeData }) {
233
233
  return utxos
234
234
  }
235
235
 
236
- export function getUsableUtxos({ asset, utxos, feeData, txSet, unconfirmedTxAncestor }) {
236
+ const COINBASE_MATURITY_HEIGHT = 100
237
+
238
+ export function getUsableUtxos({
239
+ asset,
240
+ utxos: someCoinbaseUtxos,
241
+ feeData,
242
+ txSet,
243
+ unconfirmedTxAncestor,
244
+ }) {
237
245
  assert(asset, 'asset is required')
238
- assert(utxos, 'utxos is required')
246
+ assert(someCoinbaseUtxos, 'utxos is required')
239
247
  assert(feeData, 'feeData is required')
240
248
  assert(txSet, 'txSet is required')
249
+
250
+ // Filter out immature coinbase outputs. They are not spendable.
251
+ const utxos = someCoinbaseUtxos.filter(
252
+ (utxo) => !utxo.isCoinbase || utxo.confirmations >= COINBASE_MATURITY_HEIGHT
253
+ )
254
+
241
255
  if (!['bitcoin', 'bitcointestnet', 'bitcoinregtest'].includes(asset.name))
242
256
  return filterDustUtxos({ utxos, feeData })
243
257