@exodus/bitcoin-api 2.18.3 → 2.19.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,24 @@
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.19.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.18.4...@exodus/bitcoin-api@2.19.0) (2024-07-05)
7
+
8
+
9
+ ### Features
10
+
11
+ * **BTC:** add taprootInputWitnessSize for tx size estimation ([#2737](https://github.com/ExodusMovement/assets/issues/2737)) ([13f727a](https://github.com/ExodusMovement/assets/commit/13f727a836f9fc620628abb6a94a49c1a6774ca5))
12
+
13
+
14
+
15
+ ## [2.18.4](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.18.3...@exodus/bitcoin-api@2.18.4) (2024-06-20)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * bitcoinjs-lib bump, ecc cleanup ([#2634](https://github.com/ExodusMovement/assets/issues/2634)) ([96d47a5](https://github.com/ExodusMovement/assets/commit/96d47a508657389ba766bfd44feef7365eb0de9b))
21
+
22
+
23
+
6
24
  ## [2.18.3](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.18.2...@exodus/bitcoin-api@2.18.3) (2024-06-20)
7
25
 
8
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "2.18.3",
3
+ "version": "2.19.0",
4
4
  "description": "Exodus bitcoin-api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -24,8 +24,7 @@
24
24
  "@exodus/bip322-js": "^1.1.0-exodus.4",
25
25
  "@exodus/bip44-constants": "^195.0.0",
26
26
  "@exodus/bitcoin-lib": "^2.4.1",
27
- "@exodus/bitcoinerlab-secp256k1": "^1.0.5-exodus.1",
28
- "@exodus/bitcoinjs-lib": "^6.1.5-exodus.1",
27
+ "@exodus/bitcoinjs-lib": "^6.1.5-exodus.2",
29
28
  "@exodus/currency": "^2.3.2",
30
29
  "@exodus/fetch": "^1.3.0",
31
30
  "@exodus/key-identifier": "^1.1.1",
@@ -67,5 +66,5 @@
67
66
  "type": "git",
68
67
  "url": "git+https://github.com/ExodusMovement/assets.git"
69
68
  },
70
- "gitHead": "e6202a0f144b4f90ef5e438da03c10acd2395698"
69
+ "gitHead": "9c3024f69fbda1069a437fb19c43e6f341185c6f"
71
70
  }
@@ -1,9 +1,9 @@
1
1
  import ECPairFactory from 'ecpair'
2
- import { eccFactory } from './ecc'
2
+ import { ecc } from './ecc'
3
3
 
4
4
  let ECPair
5
5
 
6
6
  export const getECPair = () => {
7
- ECPair = ECPair || ECPairFactory(eccFactory())
7
+ ECPair = ECPair || ECPairFactory(ecc)
8
8
  return ECPair
9
9
  }
@@ -1,9 +1,4 @@
1
- import { initEccLib } from '@exodus/bitcoinjs-lib'
2
-
3
- import bitcoinerlab from '@exodus/bitcoinerlab-secp256k1'
4
-
5
- export const ecc = bitcoinerlab
6
-
7
- initEccLib(ecc)
1
+ import { eccLib } from '@exodus/bitcoinjs-lib'
8
2
 
3
+ export const ecc = eccLib
9
4
  export const eccFactory = () => ecc
@@ -7,7 +7,7 @@ import * as defaultBitcoinjsLib from '@exodus/bitcoinjs-lib'
7
7
  import * as secp256k1 from 'secp256k1'
8
8
  import { hash160 } from './hash-utils'
9
9
  import { toXOnly } from './bitcoinjs-lib/ecc-utils'
10
- import { eccFactory } from './bitcoinjs-lib/ecc'
10
+ import { ecc } from './bitcoinjs-lib/ecc'
11
11
 
12
12
  export const publicKeyToHashFactory = (p2pkh) => (publicKey) => {
13
13
  const payload = Buffer.concat([Buffer.from([p2pkh]), hash160(publicKey)])
@@ -23,7 +23,6 @@ export const createBtcLikeKeys = ({
23
23
  }) => {
24
24
  assert(coinInfo, 'coinInfo is required')
25
25
  assert(versions, 'versions is required')
26
- const ecc = eccFactory()
27
26
  const {
28
27
  encodePrivate: encodePrivateCustom,
29
28
  encodePublic: encodePublicCustom,
@@ -18,6 +18,7 @@ const _canBumpTx = ({
18
18
  getFeeEstimator,
19
19
  allowUnconfirmedRbfEnabledUtxos,
20
20
  utxosDescendingOrder,
21
+ taprootInputWitnessSize,
21
22
  }) => {
22
23
  assert(asset, 'asset must be provided')
23
24
  assert(tx, 'tx must be provided')
@@ -93,6 +94,7 @@ const _canBumpTx = ({
93
94
  allowUnconfirmedRbfEnabledUtxos,
94
95
  unconfirmedTxAncestor,
95
96
  utxosDescendingOrder,
97
+ taprootInputWitnessSize,
96
98
  })
97
99
  if (replaceTx) return { bumpType: BumpType.RBF, bumpFee: fee.sub(replaceTx.feeAmount) }
98
100
  }
@@ -107,6 +109,7 @@ const _canBumpTx = ({
107
109
  allowUnconfirmedRbfEnabledUtxos,
108
110
  unconfirmedTxAncestor,
109
111
  utxosDescendingOrder,
112
+ taprootInputWitnessSize,
110
113
  })
111
114
 
112
115
  return fee ? { bumpType: BumpType.CPFP, bumpFee: fee } : { errorMessage: 'insufficient funds' }
@@ -48,6 +48,11 @@ const scriptPubKeyLengths = {
48
48
  [P2TR]: 34,
49
49
  }
50
50
 
51
+ // Only the 64 byte Schnorr signature is present for Taproot Key-Path spend
52
+ const signatureLength = 64
53
+ const taprootInputKeyPathWitnessSize =
54
+ varuint.encodingLength(1) + varuint.encodingLength(signatureLength) + signatureLength
55
+
51
56
  // bitcoin and bitcoin-like:
52
57
  // 10 = version: 4, locktime: 4, inputs and outputs count: 1
53
58
  // 148 = txId: 32, vout: 4, count: 1, script: 107 (max), sequence: 4
@@ -58,7 +63,12 @@ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
58
63
 
59
64
  const scriptClassifier = scriptClassifierFactory({ addressApi })
60
65
 
61
- return (asset, inputs, outputs, { compressed = true } = {}) => {
66
+ return (
67
+ asset,
68
+ inputs,
69
+ outputs,
70
+ { compressed = true, taprootInputWitnessSize = taprootInputKeyPathWitnessSize } = {}
71
+ ) => {
62
72
  if (inputs instanceof UtxoCollection) {
63
73
  inputs = [...inputs].map((utxo) => utxo.script || null)
64
74
  }
@@ -136,14 +146,7 @@ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
136
146
  }
137
147
 
138
148
  if ([P2TR].includes(utxoScriptType)) {
139
- // Only the 64 byte Schnorr signature is present for Taproot Key-Path spend
140
- const signatureLength = 64
141
- return (
142
- t +
143
- varuint.encodingLength(1) +
144
- varuint.encodingLength(signatureLength) +
145
- signatureLength
146
- )
149
+ return t + taprootInputWitnessSize
147
150
  }
148
151
 
149
152
  // Non-witness inputs get a placeholder zero byte
@@ -36,11 +36,12 @@ export default function createDefaultFeeEstimator(getSize) {
36
36
  inputs = options.inputs,
37
37
  outputs = options.outputs,
38
38
  unconfirmedTxAncestor = options.unconfirmedTxAncestor,
39
+ taprootInputWitnessSize,
39
40
  } = {}) => {
40
41
  const extraFee = getExtraFee({ asset, inputs, feePerKB, unconfirmedTxAncestor })
41
42
  // Yes, it's suppose to be '1000' and not '1024'
42
43
  // https://bitcoin.stackexchange.com/questions/24000/a-fee-is-added-per-kilobyte-of-data-that-means-1000-bytes-or-1024
43
- const size = getSize(asset, inputs, outputs, options)
44
+ const size = getSize(asset, inputs, outputs, { ...options, taprootInputWitnessSize })
44
45
  const feeRaw = Math.ceil((feePerKB.toBaseNumber() * size) / 1000)
45
46
  return asset.currency.baseUnit(feeRaw + extraFee)
46
47
  }
@@ -35,6 +35,7 @@ export class GetFeeResolver {
35
35
  nft, // sending one nft
36
36
  brc20, // sending multiple inscriptions ids (as in sending multiple transfer ordinals)
37
37
  receiveAddress,
38
+ taprootInputWitnessSize,
38
39
  }) => {
39
40
  if (nft) {
40
41
  assert(!amount, 'amount must not be provided when nft is provided!!!')
@@ -60,11 +61,21 @@ export class GetFeeResolver {
60
61
  customFee,
61
62
  isSendAll,
62
63
  inscriptionIds,
64
+ taprootInputWitnessSize,
63
65
  })
64
66
  return { fee: resolvedFee, extraFee }
65
67
  }
66
68
 
67
- getAvailableBalance = ({ asset, accountState, txSet, feeData, amount, customFee, isSendAll }) => {
69
+ getAvailableBalance = ({
70
+ asset,
71
+ accountState,
72
+ txSet,
73
+ feeData,
74
+ amount,
75
+ customFee,
76
+ isSendAll,
77
+ taprootInputWitnessSize,
78
+ }) => {
68
79
  return this.#getUtxosData({
69
80
  asset,
70
81
  accountState,
@@ -73,16 +84,18 @@ export class GetFeeResolver {
73
84
  customFee,
74
85
  isSendAll,
75
86
  amount,
87
+ taprootInputWitnessSize,
76
88
  }).availableBalance
77
89
  }
78
90
 
79
- getSpendableBalance = ({ asset, accountState, txSet, feeData }) => {
91
+ getSpendableBalance = ({ asset, accountState, txSet, feeData, taprootInputWitnessSize }) => {
80
92
  return this.#getUtxosData({
81
93
  asset,
82
94
  accountState,
83
95
  txSet,
84
96
  feeData,
85
97
  isSendAll: true,
98
+ taprootInputWitnessSize,
86
99
  }).spendableBalance
87
100
  }
88
101
 
@@ -96,6 +109,7 @@ export class GetFeeResolver {
96
109
  customFee,
97
110
  isSendAll,
98
111
  inscriptionIds,
112
+ taprootInputWitnessSize,
99
113
  }) => {
100
114
  assert(asset, 'asset must be provided')
101
115
  assert(feeData, 'feeData must be provided')
@@ -136,6 +150,7 @@ export class GetFeeResolver {
136
150
  allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
137
151
  unconfirmedTxAncestor,
138
152
  utxosDescendingOrder: this.#utxosDescendingOrder,
153
+ taprootInputWitnessSize,
139
154
  })
140
155
  }
141
156
 
@@ -35,6 +35,7 @@ export const selectUtxos = ({
35
35
  unconfirmedTxAncestor,
36
36
  inscriptionIds, // for each inscription transfer, we need to calculate one more input and one more output
37
37
  transferOrdinalsUtxos, // to calculate the size of the input
38
+ taprootInputWitnessSize,
38
39
  }) => {
39
40
  const resolvedReceiveAddresses = getBestReceiveAddresses({
40
41
  asset,
@@ -100,9 +101,13 @@ export const selectUtxos = ({
100
101
 
101
102
  if (isSendAll) {
102
103
  additionalUtxos = UtxoCollection.fromArray(confirmedUtxosArray, { currency })
103
- fee = replaceFeeEstimator({ inputs: inputs.union(additionalUtxos), outputs })
104
+ fee = replaceFeeEstimator({
105
+ inputs: inputs.union(additionalUtxos),
106
+ outputs,
107
+ taprootInputWitnessSize,
108
+ })
104
109
  } else {
105
- fee = replaceFeeEstimator({ inputs, outputs })
110
+ fee = replaceFeeEstimator({ inputs, outputs, taprootInputWitnessSize })
106
111
  additionalUtxos = UtxoCollection.createEmpty({ currency })
107
112
  while (replaceTxAmount.add(additionalUtxos.value).lt(amount.add(fee))) {
108
113
  if (confirmedUtxosArray.length === 0) {
@@ -116,6 +121,7 @@ export const selectUtxos = ({
116
121
  fee = replaceFeeEstimator({
117
122
  inputs: inputs.union(additionalUtxos),
118
123
  outputs: noChangeOutputs,
124
+ taprootInputWitnessSize,
119
125
  })
120
126
  }
121
127
 
@@ -127,6 +133,7 @@ export const selectUtxos = ({
127
133
  fee = replaceFeeEstimator({
128
134
  inputs: inputs.union(additionalUtxos),
129
135
  outputs,
136
+ taprootInputWitnessSize,
130
137
  })
131
138
  }
132
139
  }
@@ -136,6 +143,7 @@ export const selectUtxos = ({
136
143
  const chainFee = feeEstimator({
137
144
  inputs: changeUtxos.union(additionalUtxos),
138
145
  outputs: chainOutputs,
146
+ taprootInputWitnessSize,
139
147
  })
140
148
  // If batching is same or more expensive than chaining, don't replace
141
149
  // Unless we are accelerating a changeless tx, then we must replace because it can't be chained
@@ -173,7 +181,11 @@ export const selectUtxos = ({
173
181
 
174
182
  if (isSendAll) {
175
183
  const selectedUtxos = UtxoCollection.fromArray(utxosArray, { currency })
176
- const fee = feeEstimator({ inputs: selectedUtxos, outputs: receiveAddresses })
184
+ const fee = feeEstimator({
185
+ inputs: selectedUtxos,
186
+ outputs: receiveAddresses,
187
+ taprootInputWitnessSize,
188
+ })
177
189
  if (selectedUtxos.value.lt(amount.add(fee))) {
178
190
  return { fee }
179
191
  }
@@ -208,19 +220,23 @@ export const selectUtxos = ({
208
220
  ? [changeAddressType]
209
221
  : [...receiveAddresses, changeAddressType]
210
222
 
211
- let fee = feeEstimator({ inputs: selectedUtxos, outputs })
223
+ let fee = feeEstimator({ inputs: selectedUtxos, outputs, taprootInputWitnessSize })
212
224
 
213
225
  while (selectedUtxos.value.lt(amount.add(fee))) {
214
226
  // We ran out of UTXOs, give up now
215
227
  if (remainingUtxosArray.length === 0) {
216
228
  // Try fee with no change
217
- fee = feeEstimator({ inputs: selectedUtxos, outputs: receiveAddresses })
229
+ fee = feeEstimator({
230
+ inputs: selectedUtxos,
231
+ outputs: receiveAddresses,
232
+ taprootInputWitnessSize,
233
+ })
218
234
  break
219
235
  }
220
236
 
221
237
  // Add a new UTXO and recalculate the fee
222
238
  selectedUtxos = selectedUtxos.addUtxo(remainingUtxosArray.shift())
223
- fee = feeEstimator({ inputs: selectedUtxos, outputs })
239
+ fee = feeEstimator({ inputs: selectedUtxos, outputs, taprootInputWitnessSize })
224
240
  }
225
241
 
226
242
  if (selectedUtxos.value.lt(amount.add(fee))) {
@@ -246,6 +262,7 @@ export const getUtxosData = ({
246
262
  transferOrdinalsUtxos,
247
263
  unconfirmedTxAncestor,
248
264
  utxosDescendingOrder,
265
+ taprootInputWitnessSize,
249
266
  }) => {
250
267
  const { selectedUtxos, replaceTx, fee } = selectUtxos({
251
268
  asset,
@@ -263,6 +280,7 @@ export const getUtxosData = ({
263
280
  inscriptionIds,
264
281
  transferOrdinalsUtxos,
265
282
  utxosDescendingOrder,
283
+ taprootInputWitnessSize,
266
284
  })
267
285
 
268
286
  const resolvedFee = replaceTx ? fee.sub(replaceTx.feeAmount) : fee
@@ -1,6 +1,6 @@
1
1
  import * as defaultBitcoinjsLib from '@exodus/bitcoinjs-lib'
2
2
  import { toXOnly } from './bitcoinjs-lib/ecc-utils'
3
- import { eccFactory } from './bitcoinjs-lib/ecc'
3
+ import { ecc as defaultEcc } from './bitcoinjs-lib/ecc'
4
4
 
5
5
  // Key to use when key path spending is disabled https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs
6
6
  const DUMMY_TAPROOT_PUBKEY = Buffer.from(
@@ -18,7 +18,7 @@ export const createEncodeMultisigContract =
18
18
  ({
19
19
  bitcoinjsLib = defaultBitcoinjsLib,
20
20
  network = bitcoinjsLib.Network.bitcoin,
21
- ecc = eccFactory(),
21
+ ecc = defaultEcc,
22
22
  }) =>
23
23
  (publicKeys, { threshold = publicKeys.length, version = 0 } = Object.create(null)) => {
24
24
  if (
@@ -236,6 +236,7 @@ export const getPrepareSendTransaction =
236
236
  isExchange,
237
237
  isBip70,
238
238
  isRbfAllowed,
239
+ taprootInputWitnessSize,
239
240
  } = options
240
241
 
241
242
  const asset = maybeToken.baseAsset
@@ -352,6 +353,7 @@ export const getPrepareSendTransaction =
352
353
  inscriptionIds,
353
354
  transferOrdinalsUtxos,
354
355
  utxosDescendingOrder,
356
+ taprootInputWitnessSize,
355
357
  })
356
358
 
357
359
  if (!selectedUtxos && !replaceTx) throw new Error('Not enough funds.')
@@ -474,7 +476,15 @@ export const createAndBroadcastTXFactory =
474
476
  }) =>
475
477
  async ({ asset: maybeToken, walletAccount, address, amount: tokenAmount, options }) => {
476
478
  // Prepare transaction
477
- const { bumpTxId, nft, isExchange, isBip70, isRbfAllowed = true, feeOpts } = options
479
+ const {
480
+ bumpTxId,
481
+ nft,
482
+ isExchange,
483
+ isBip70,
484
+ isRbfAllowed = true,
485
+ feeOpts,
486
+ taprootInputWitnessSize,
487
+ } = options
478
488
 
479
489
  const asset = maybeToken.baseAsset
480
490
  const assetName = asset.name
@@ -498,6 +508,7 @@ export const createAndBroadcastTXFactory =
498
508
  utxosDescendingOrder,
499
509
  rbfEnabled,
500
510
  assetClientInterface,
511
+ taprootInputWitnessSize,
501
512
  })({ asset: maybeToken, walletAccount, address, amount: tokenAmount, options })
502
513
  const {
503
514
  amount,
@@ -2,10 +2,9 @@ import { crypto } from '@exodus/bitcoinjs-lib'
2
2
  import assert from 'minimalistic-assert'
3
3
 
4
4
  import { getSchnorrEntropy } from './default-entropy'
5
- import { eccFactory } from '../bitcoinjs-lib/ecc'
5
+ import { ecc } from '../bitcoinjs-lib/ecc'
6
6
  import { getECPair } from '../bitcoinjs-lib'
7
7
 
8
- const ecc = eccFactory()
9
8
  const ECPair = getECPair()
10
9
 
11
10
  function tweakSigner({ signer, tweakHash, network }) {