@exodus/bitcoin-api 1.0.2 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Exodus bitcoin-api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -25,6 +25,7 @@
25
25
  "@exodus/models": "^8.10.4",
26
26
  "@exodus/secp256k1": "4.0.2-exodus.0",
27
27
  "@exodus/simple-retry": "0.0.6",
28
+ "bech32": "^1.1.3",
28
29
  "bip44-constants": "55.0.0",
29
30
  "coininfo": "5.1.0",
30
31
  "delay": "4.0.1",
@@ -38,5 +39,5 @@
38
39
  "@exodus/bip-schnorr": "0.6.6-fork-1",
39
40
  "@noble/secp256k1": "~1.5.3"
40
41
  },
41
- "gitHead": "6bbd1f431656185196b2dbe74a9347a29e6bb3b1"
42
+ "gitHead": "00377ab9ad8a29114f5064efcb0197d4257056d1"
42
43
  }
@@ -1,9 +1,3 @@
1
- export const eccFactory = (useDesktopEcc, useSchnorrEcc) => {
2
- if (useDesktopEcc) {
3
- return require('./desktop').desktopEcc
4
- }
5
- if (useSchnorrEcc) {
6
- return require('./mobile-schnorr').mobileSchnorrEcc
7
- }
8
- return require('./mobile').mobileEcc
9
- }
1
+ import { desktopEcc } from './desktop'
2
+
3
+ export const eccFactory = () => desktopEcc
@@ -1,7 +1,4 @@
1
- import assert from 'minimalistic-assert'
2
-
3
- export const eccFactory = (useDesktopEcc, useSchnorrEcc) => {
4
- assert(!useDesktopEcc, 'useDesktopEcc must be false on mobile!!')
1
+ export const eccFactory = (useSchnorrEcc) => {
5
2
  if (useSchnorrEcc) {
6
3
  return require('./mobile-schnorr').mobileSchnorrEcc
7
4
  }
@@ -1,7 +1,7 @@
1
1
  import { selectUtxos } from './utxo-selector'
2
2
  import assert from 'minimalistic-assert'
3
3
  import { findUnconfirmedSentRbfTxs } from '../tx-utils'
4
- import { getSpendableUtxos, getUtxos } from '../utxos-utils'
4
+ import { getUsableUtxos, getUtxos } from '../utxos-utils'
5
5
 
6
6
  import { BumpType } from '@exodus/bitcoin-lib/lib/selectors/get-can-bump-tx-factory'
7
7
 
@@ -16,6 +16,7 @@ const _canBumpTx = ({
16
16
  feeData,
17
17
  getFeeEstimator,
18
18
  taprootEnabled,
19
+ allowUnconfirmedRbfEnabledUtxos,
19
20
  }) => {
20
21
  assert(asset, 'asset must be provided')
21
22
  assert(tx, 'tx must be provided')
@@ -52,19 +53,20 @@ const _canBumpTx = ({
52
53
 
53
54
  const utxos = getUtxos({ accountState, asset })
54
55
 
55
- const spendableUtxos = getSpendableUtxos({
56
+ const usableUtxos = getUsableUtxos({
56
57
  asset,
57
58
  utxos,
58
59
  feeData,
59
60
  txSet,
60
61
  taprootEnabled,
62
+ allowUnconfirmedRbfEnabledUtxos,
61
63
  })
62
- if (!spendableUtxos) return { errorMessage: 'insufficient funds' }
64
+ if (usableUtxos.value.isZero) return { errorMessage: 'insufficient funds' }
63
65
 
64
66
  const { txId } = tx
65
67
  const replaceableTxs = findUnconfirmedSentRbfTxs(txSet)
66
68
  const bumpTx = replaceableTxs.find((tx) => tx.txId === txId)
67
- const changeUtxos = spendableUtxos.getTxIdUtxos(txId)
69
+ const changeUtxos = usableUtxos.getTxIdUtxos(txId)
68
70
 
69
71
  // Can't bump a non-rbf tx with no change
70
72
  if (!bumpTx && changeUtxos.size === 0) return { errorMessage: 'no change' }
@@ -80,22 +82,24 @@ const _canBumpTx = ({
80
82
  if (bumpTx) {
81
83
  const { replaceTx } = selectUtxos({
82
84
  asset,
83
- spendableUtxos,
85
+ usableUtxos,
84
86
  replaceableTxs: [bumpTx],
85
87
  feeRate,
86
88
  receiveAddress: null,
87
89
  getFeeEstimator,
90
+ allowUnconfirmedRbfEnabledUtxos,
88
91
  })
89
92
  if (replaceTx) return { bumpType: BumpType.RBF }
90
93
  }
91
94
 
92
95
  const { fee } = selectUtxos({
93
96
  asset,
94
- spendableUtxos,
97
+ usableUtxos,
95
98
  feeRate,
96
99
  receiveAddress: 'P2WPKH',
97
100
  getFeeEstimator,
98
101
  mustSpendUtxos: changeUtxos,
102
+ allowUnconfirmedRbfEnabledUtxos,
99
103
  })
100
104
 
101
105
  return fee ? { bumpType: BumpType.CPFP } : { errorMessage: 'insufficient funds' }
@@ -1,17 +1,19 @@
1
1
  import assert from 'minimalistic-assert'
2
2
  import { getUtxosData } from './utxo-selector'
3
3
  import { findUnconfirmedSentRbfTxs } from '../tx-utils'
4
- import { getSpendableUtxos, getUtxos } from '../utxos-utils'
4
+ import { getSpendableUtxos, getUsableUtxos, getUtxos } from '../utxos-utils'
5
5
  import { canBumpTx } from './can-bump-tx'
6
6
 
7
7
  export class GetFeeResolver {
8
8
  #getFeeEstimator
9
9
  #taprootEnabled
10
+ #allowUnconfirmedRbfEnabledUtxos
10
11
 
11
- constructor({ getFeeEstimator, taprootEnabled }) {
12
+ constructor({ getFeeEstimator, taprootEnabled, allowUnconfirmedRbfEnabledUtxos }) {
12
13
  assert(getFeeEstimator, 'getFeeEstimator must be provided')
13
14
  this.#getFeeEstimator = (asset, { feePerKB }) => getFeeEstimator(asset, feePerKB)
14
15
  this.#taprootEnabled = taprootEnabled
16
+ this.#allowUnconfirmedRbfEnabledUtxos = allowUnconfirmedRbfEnabledUtxos
15
17
  }
16
18
 
17
19
  getFee = ({ asset, accountState, txSet, feeData, amount, customFee, isSendAll }) => {
@@ -47,6 +49,7 @@ export class GetFeeResolver {
47
49
  feeData,
48
50
  txSet,
49
51
  taprootEnabled: this.#taprootEnabled,
52
+ allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
50
53
  })
51
54
  return spendableUtxos.value
52
55
  }
@@ -59,7 +62,7 @@ export class GetFeeResolver {
59
62
 
60
63
  const utxos = getUtxos({ accountState, asset })
61
64
 
62
- const spendableUtxos = getSpendableUtxos({
65
+ const usableUtxos = getUsableUtxos({
63
66
  asset,
64
67
  utxos,
65
68
  feeData,
@@ -75,13 +78,14 @@ export class GetFeeResolver {
75
78
  const feePerKB = customFee || feeData.feePerKB
76
79
  return getUtxosData({
77
80
  asset,
78
- spendableUtxos,
81
+ usableUtxos,
79
82
  replaceableTxs,
80
83
  amount,
81
84
  feeRate: feePerKB,
82
85
  receiveAddress,
83
86
  isSendAll: isSendAll,
84
87
  getFeeEstimator: this.#getFeeEstimator,
88
+ allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
85
89
  })
86
90
  }
87
91
 
@@ -94,6 +98,7 @@ export class GetFeeResolver {
94
98
  feeData,
95
99
  getFeeEstimator: this.#getFeeEstimator,
96
100
  taprootEnabled: this.#taprootEnabled,
101
+ allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
97
102
  })
98
103
  }
99
104
  }
@@ -2,12 +2,13 @@ import { UtxoCollection } from '@exodus/models'
2
2
  import NumberUnit from '@exodus/currency'
3
3
  import assert from 'minimalistic-assert'
4
4
  import { getExtraFee } from './fee-utils'
5
+ import { getConfirmedUtxos, getConfirmedOrRfbDisabledUtxos } from '../utxos-utils'
5
6
 
6
7
  const MIN_RELAY_FEE = 1000
7
8
 
8
9
  export const selectUtxos = ({
9
10
  asset,
10
- spendableUtxos,
11
+ usableUtxos,
11
12
  replaceableTxs,
12
13
  amount,
13
14
  feeRate,
@@ -16,9 +17,10 @@ export const selectUtxos = ({
16
17
  getFeeEstimator,
17
18
  disableReplacement = false,
18
19
  mustSpendUtxos,
20
+ allowUnconfirmedRbfEnabledUtxos,
19
21
  }) => {
20
22
  assert(asset, 'asset is required')
21
- assert(spendableUtxos, 'spendableUtxos is required')
23
+ assert(usableUtxos, 'usableUtxos is required')
22
24
  assert(getFeeEstimator, 'getFeeEstimator is required')
23
25
 
24
26
  const changeAddressType = ['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(asset.name)
@@ -30,19 +32,17 @@ export const selectUtxos = ({
30
32
  if (!amount) amount = currency.ZERO
31
33
 
32
34
  // We can only replace for a sendAll if only 1 replaceable tx and no unconfirmed utxos
33
- const confirmedUtxosArray = Array.from(spendableUtxos).filter(
34
- ({ confirmations }) => confirmations > 0
35
- )
35
+ const confirmedUtxosArray = getConfirmedUtxos({ asset, utxos: usableUtxos }).toArray()
36
36
  const canReplace =
37
37
  !mustSpendUtxos &&
38
38
  !disableReplacement &&
39
39
  replaceableTxs &&
40
40
  (!isSendAll ||
41
- (replaceableTxs.length === 1 && confirmedUtxosArray.length === spendableUtxos.size - 1))
41
+ (replaceableTxs.length === 1 && confirmedUtxosArray.length === usableUtxos.size - 1))
42
42
 
43
43
  if (canReplace) {
44
44
  for (let tx of replaceableTxs) {
45
- const changeUtxos = spendableUtxos.getTxIdUtxos(tx.txId)
45
+ const changeUtxos = usableUtxos.getTxIdUtxos(tx.txId)
46
46
  // Don't replace a tx that has already been spent
47
47
  if (tx.data.changeAddress && changeUtxos.size === 0) continue
48
48
  let feePerKB
@@ -117,11 +117,17 @@ export const selectUtxos = ({
117
117
  if (replaceableTxs) {
118
118
  for (let tx of replaceableTxs) {
119
119
  if (!tx.data.changeAddress) continue
120
- const changeUtxos = spendableUtxos.getTxIdUtxos(tx.txId)
120
+ const changeUtxos = usableUtxos.getTxIdUtxos(tx.txId)
121
121
  ourRbfUtxos = ourRbfUtxos.union(changeUtxos)
122
122
  }
123
123
  }
124
124
 
125
+ const spendableUtxos = getConfirmedOrRfbDisabledUtxos({
126
+ asset,
127
+ utxos: usableUtxos,
128
+ allowUnconfirmedRbfEnabledUtxos,
129
+ })
130
+
125
131
  const utxosArray = spendableUtxos.union(ourRbfUtxos).toPriorityOrderedArray()
126
132
 
127
133
  if (isSendAll) {
@@ -177,7 +183,7 @@ export const selectUtxos = ({
177
183
 
178
184
  export const getUtxosData = ({
179
185
  asset,
180
- spendableUtxos,
186
+ usableUtxos,
181
187
  replaceableTxs,
182
188
  amount,
183
189
  feeRate,
@@ -186,10 +192,11 @@ export const getUtxosData = ({
186
192
  getFeeEstimator,
187
193
  disableReplacement,
188
194
  mustSpendUtxos,
195
+ allowUnconfirmedRbfEnabledUtxos,
189
196
  }) => {
190
197
  const { selectedUtxos, replaceTx, fee } = selectUtxos({
191
198
  asset,
192
- spendableUtxos,
199
+ usableUtxos,
193
200
  replaceableTxs,
194
201
  amount,
195
202
  feeRate,
@@ -198,11 +205,16 @@ export const getUtxosData = ({
198
205
  getFeeEstimator,
199
206
  disableReplacement,
200
207
  mustSpendUtxos,
208
+ allowUnconfirmedRbfEnabledUtxos,
201
209
  })
202
210
 
203
211
  const resolvedFee = replaceTx ? fee.sub(replaceTx.feeAmount) : fee
204
212
 
205
- const spendableBalance = spendableUtxos.value
213
+ const spendableBalance = getConfirmedOrRfbDisabledUtxos({
214
+ asset,
215
+ utxos: usableUtxos,
216
+ allowUnconfirmedRbfEnabledUtxos,
217
+ }).value
206
218
 
207
219
  const extraFee = selectedUtxos
208
220
  ? asset.currency.baseUnit(getExtraFee({ asset, inputs: selectedUtxos, feePerKB: feeRate }))
@@ -4,6 +4,25 @@ import urlJoin from 'url-join'
4
4
  import qs from 'querystring'
5
5
  import delay from 'delay'
6
6
 
7
+ const getTextFromResponse = async (response) => {
8
+ try {
9
+ const responseBody = await response.text()
10
+ return responseBody.substring(0, 100)
11
+ } catch (e) {
12
+ return ''
13
+ }
14
+ }
15
+
16
+ const fetchJson = async (url, fetchOptions) => {
17
+ const response = await fetch(url, fetchOptions)
18
+ if (!response.ok)
19
+ throw new Error(
20
+ `${url} returned ${response.status}: ${response.statusText ||
21
+ 'Unknown Status Text'}. Body: ${await getTextFromResponse(response)}`
22
+ )
23
+ return response.json()
24
+ }
25
+
7
26
  // TODO: use p-retry
8
27
  async function fetchJsonRetry(url, fetchOptions) {
9
28
  const waitTimes = [5, 10, 20, 30].map((t) => t * 1000)
@@ -22,12 +41,6 @@ async function fetchJsonRetry(url, fetchOptions) {
22
41
  }
23
42
  }
24
43
  }
25
-
26
- async function fetchJson(url, fetchOptions) {
27
- const response = await fetch(url, fetchOptions)
28
- if (!response.ok) throw new Error(`${url} returned ${response.status}: ${response.statusText}`)
29
- return response.json()
30
- }
31
44
  }
32
45
 
33
46
  export default class InsightAPIClient {
@@ -41,9 +54,7 @@ export default class InsightAPIClient {
41
54
 
42
55
  async isNetworkConnected() {
43
56
  const url = urlJoin(this._baseURL, '/peer')
44
- const resp = await fetch(url, { timeout: 10000 })
45
- const peerStatus = await resp.json()
46
-
57
+ const peerStatus = await fetchJson(url, { timeout: 10000 })
47
58
  return !!peerStatus.connected
48
59
  }
49
60
 
@@ -56,9 +67,7 @@ export default class InsightAPIClient {
56
67
 
57
68
  async fetchBlockHeight() {
58
69
  const url = urlJoin(this._baseURL, '/status')
59
- const resp = await fetch(url, { timeout: 10000 })
60
- const status = await resp.json()
61
-
70
+ const status = await fetchJson(url, { timeout: 10000 })
62
71
  return status.info.blocks
63
72
  }
64
73
 
@@ -68,9 +77,7 @@ export default class InsightAPIClient {
68
77
  this._baseURL,
69
78
  opts.includeTxs ? `/addr/${encodedAddress}` : `/addr/${encodedAddress}?noTxList=1`
70
79
  )
71
- const response = await fetch(url)
72
-
73
- return response.json()
80
+ return fetchJson(url)
74
81
  }
75
82
 
76
83
  async fetchUTXOs(addresses, { assetNames = [] } = {}) {
@@ -81,8 +88,7 @@ export default class InsightAPIClient {
81
88
  }
82
89
  const encodedAddresses = encodeURIComponent(addresses)
83
90
  const url = urlJoin(this._baseURL, `/addrs/${encodedAddresses}/utxo?${query}`)
84
- const response = await fetch(url)
85
- const utxos = await response.json()
91
+ const utxos = await fetchJson(url)
86
92
 
87
93
  return utxos.map((utxo) => ({
88
94
  address: utxo.address,
@@ -111,8 +117,7 @@ export default class InsightAPIClient {
111
117
  async fetchRawTx(txId: string) {
112
118
  const encodedTxId = encodeURIComponent(txId)
113
119
  const url = urlJoin(this._baseURL, `/rawtx/${encodedTxId}`)
114
- const response = await fetch(url)
115
- const { rawtx } = await response.json()
120
+ const { rawtx } = await fetchJson(url)
116
121
  return rawtx
117
122
  }
118
123
 
@@ -155,17 +160,15 @@ export default class InsightAPIClient {
155
160
  async fetchUnconfirmedAncestorData(txId: string) {
156
161
  const encodedTxId = encodeURIComponent(txId)
157
162
  const url = urlJoin(this._baseURL, `/unconfirmed_ancestor/${encodedTxId}`)
158
- const response = await fetch(url)
159
- return response.json()
163
+ return fetchJson(url)
160
164
  }
161
165
 
162
166
  async fetchFeeRate() {
163
167
  const url = urlJoin(this._baseURL, '/v2/fees')
164
- const response = await fetch(url)
165
- return response.json()
168
+ return fetchJson(url)
166
169
  }
167
170
 
168
- broadcastTx = async (rawTx) => {
171
+ async broadcastTx(rawTx) {
169
172
  console.log('gonna broadcastTx')
170
173
  const url = urlJoin(this._baseURL, '/tx/send')
171
174
  const fetchOptions = {
@@ -200,16 +203,12 @@ export default class InsightAPIClient {
200
203
  async getClaimable(address) {
201
204
  const encodedAddress = encodeURIComponent(address)
202
205
  const url = urlJoin(this._baseURL, `/addr/${encodedAddress}/claimable`)
203
- const response = await fetch(url)
204
- if (!response.ok) throw new Error(`${url} returned ${response.status}: ${response.statusText}`)
205
- return response.json()
206
+ return fetchJson(url)
206
207
  }
207
208
 
208
209
  async getUnclaimed(address) {
209
210
  const encodedAddress = encodeURIComponent(address)
210
211
  const url = urlJoin(this._baseURL, `/addr/${encodedAddress}/unclaimed`)
211
- const response = await fetch(url)
212
- if (!response.ok) throw new Error(`${url} returned ${response.status}: ${response.statusText}`)
213
- return response.json()
212
+ return fetchJson(url)
214
213
  }
215
214
  }
@@ -14,7 +14,7 @@ import {
14
14
  createOutput as dogecoinCreateOutput,
15
15
  } from './dogecoin'
16
16
  import { findUnconfirmedSentRbfTxs } from '../tx-utils'
17
- import { getSpendableUtxos, getUtxos } from '../utxos-utils'
17
+ import { getUsableUtxos, getUtxos } from '../utxos-utils'
18
18
 
19
19
  const ASSETS_SUPPORTED_BIP_174 = [
20
20
  'bitcoin',
@@ -82,10 +82,11 @@ export async function getNonWitnessTxs(asset, utxos, insightClient) {
82
82
  // not ported from Exodus; but this demos signing / broadcasting
83
83
  // NOTE: this will be ripped out in the coming weeks
84
84
 
85
- export const createAndBroadcastTXFactory = ({ getFeeEstimator, taprootEnabled }) => async (
86
- { asset, walletAccount, address, amount, options },
87
- { assetClientInterface }
88
- ) => {
85
+ export const createAndBroadcastTXFactory = ({
86
+ getFeeEstimator,
87
+ taprootEnabled,
88
+ allowUnconfirmedRbfEnabledUtxos,
89
+ }) => async ({ asset, walletAccount, address, amount, options }, { assetClientInterface }) => {
89
90
  const {
90
91
  multipleAddressesEnabled,
91
92
  feePerKB,
@@ -120,7 +121,7 @@ export const createAndBroadcastTXFactory = ({ getFeeEstimator, taprootEnabled })
120
121
  const insightClient = asset.baseAsset.insightClient
121
122
  const currency = asset.currency
122
123
  const feeData = await assetClientInterface.getFeeConfig({ assetName })
123
- const spendableUtxos = getSpendableUtxos({
124
+ const usableUtxos = getUsableUtxos({
124
125
  asset,
125
126
  utxos: getUtxos({ accountState, asset }),
126
127
  feeData,
@@ -145,7 +146,7 @@ export const createAndBroadcastTXFactory = ({ getFeeEstimator, taprootEnabled })
145
146
  if (bumpTxId) {
146
147
  const bumpTx = replaceableTxs.find(({ txId }) => txId === bumpTxId)
147
148
  if (!bumpTx) {
148
- utxosToBump = spendableUtxos.getTxIdUtxos(bumpTxId)
149
+ utxosToBump = usableUtxos.getTxIdUtxos(bumpTxId)
149
150
  if (utxosToBump.size === 0) {
150
151
  throw new Error(`Cannot bump transaction ${bumpTxId}`)
151
152
  }
@@ -161,7 +162,7 @@ export const createAndBroadcastTXFactory = ({ getFeeEstimator, taprootEnabled })
161
162
 
162
163
  let { selectedUtxos, fee, replaceTx } = selectUtxos({
163
164
  asset,
164
- spendableUtxos,
165
+ usableUtxos,
165
166
  replaceableTxs,
166
167
  amount: sendAmount,
167
168
  feeRate: customFee || feeRate,
@@ -169,6 +170,7 @@ export const createAndBroadcastTXFactory = ({ getFeeEstimator, taprootEnabled })
169
170
  isSendAll: !rbfEnabled && feePerKB ? false : isSendAll,
170
171
  getFeeEstimator: (asset, { feePerKB }) => getFeeEstimator(asset, feePerKB),
171
172
  mustSpendUtxos: utxosToBump,
173
+ allowUnconfirmedRbfEnabledUtxos,
172
174
  })
173
175
 
174
176
  if (!selectedUtxos && !replaceTx) throw new Error('Not enough funds.')
@@ -287,7 +289,7 @@ export const createAndBroadcastTXFactory = ({ getFeeEstimator, taprootEnabled })
287
289
  err.txInfo = JSON.stringify({
288
290
  amount: sendAmount.toDefaultString({ unit: true }),
289
291
  fee: ((fee && fee.toDefaultString({ unit: true })) || 0).toString(), // todo why does 0 not have a unit? Is default unit ok here?
290
- allUtxos: spendableUtxos.toJSON(),
292
+ allUtxos: usableUtxos.toJSON(),
291
293
  })
292
294
  throw err
293
295
  } else {
@@ -306,7 +308,7 @@ export const createAndBroadcastTXFactory = ({ getFeeEstimator, taprootEnabled })
306
308
  }
307
309
  }
308
310
 
309
- let remainingUtxos = spendableUtxos.difference(selectedUtxos)
311
+ let remainingUtxos = usableUtxos.difference(selectedUtxos)
310
312
  if (changeUtxoIndex !== -1) {
311
313
  const address = Address.create(ourAddress.address, ourAddress.meta)
312
314
  const changeUtxo = {
@@ -1,7 +1,7 @@
1
1
  import assert from 'minimalistic-assert'
2
2
  import lodash from 'lodash'
3
3
  import ECPairFactory from 'ecpair'
4
- import { Psbt } from '@exodus/bitcoinjs-lib'
4
+ import { Psbt, Transaction } from '@exodus/bitcoinjs-lib'
5
5
 
6
6
  import { toAsyncSigner, tweakSigner } from './taproot'
7
7
 
@@ -12,6 +12,15 @@ const _MAXIMUM_FEE_RATES = {
12
12
  ravencoin: 1000000,
13
13
  }
14
14
 
15
+ const canParseTx = (rawTxBuffer) => {
16
+ try {
17
+ Transaction.fromBuffer(rawTxBuffer)
18
+ return true
19
+ } catch (e) {
20
+ return false
21
+ }
22
+ }
23
+
15
24
  export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, network, ecc }) => {
16
25
  assert(assetName, 'assetName is required')
17
26
  assert(resolvePurpose, 'resolvePurpose is required')
@@ -62,10 +71,18 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
62
71
  // witness outputs only require the value and the script, not the full transaction
63
72
  txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
64
73
  } else {
65
- // non-witness outptus require the full transaction
66
74
  const rawTx = (rawTxs || []).find((t) => t.txId === txId)
75
+ // non-witness outptus require the full transaction
67
76
  assert(!!rawTx, 'Non-witness outputs require the full previous transaction.')
68
- txIn.nonWitnessUtxo = Buffer.from(rawTx.rawData, 'hex')
77
+ const rawTxBuffer = Buffer.from(rawTx.rawData, 'hex')
78
+ if (canParseTx(rawTxBuffer)) {
79
+ txIn.nonWitnessUtxo = rawTxBuffer
80
+ } else {
81
+ // temp fix for https://exodusio.slack.com/archives/CP202D90Q/p1671014704829939 until bitcoinjs could parse a mweb tx without failing
82
+ console.warn(`Setting psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true for asset ${assetName}`)
83
+ psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true
84
+ txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
85
+ }
69
86
  }
70
87
  psbt.addInput(txIn)
71
88
  }
@@ -0,0 +1,4 @@
1
+ // extension point so tests can assert signatures deterministically
2
+ export function getSchnorrEntropy() {
3
+ return undefined
4
+ }
@@ -1,5 +1,6 @@
1
1
  import { crypto } from '@exodus/bitcoinjs-lib'
2
2
  import assert from 'minimalistic-assert'
3
+ import { getSchnorrEntropy } from './default-entropy'
3
4
 
4
5
  export function tweakSigner({ signer, ECPair, ecc, tweakHash, network }) {
5
6
  assert(signer, 'signer is required')
@@ -42,7 +43,7 @@ export function toAsyncSigner({ keyPair, ecc }) {
42
43
  }
43
44
 
44
45
  keyPair.signSchnorr = async (h) => {
45
- const sig = await ecc.signSchnorrAsync(h, keyPair.privateKey)
46
+ const sig = await ecc.signSchnorrAsync(h, keyPair.privateKey, getSchnorrEntropy())
46
47
  return Buffer.from(sig)
47
48
  }
48
49
  return keyPair
@@ -12,7 +12,11 @@ export function getUtxos({ accountState, asset }) {
12
12
  )
13
13
  }
14
14
 
15
- export const getBalancesFactory = ({ taprootEnabled, feeData }) => {
15
+ export const getBalancesFactory = ({
16
+ taprootEnabled,
17
+ feeData,
18
+ allowUnconfirmedRbfEnabledUtxos,
19
+ }) => {
16
20
  assert(feeData, 'feeData is required')
17
21
  return ({ asset, accountState, txLog }) => {
18
22
  assert(asset, 'asset is required')
@@ -26,6 +30,7 @@ export const getBalancesFactory = ({ taprootEnabled, feeData }) => {
26
30
  txSet: txLog,
27
31
  feeData,
28
32
  taprootEnabled,
33
+ allowUnconfirmedRbfEnabledUtxos,
29
34
  }).value
30
35
  return { balance, spendableBalance }
31
36
  }
@@ -33,16 +38,45 @@ export const getBalancesFactory = ({ taprootEnabled, feeData }) => {
33
38
 
34
39
  const isTaprootUtxo = ({ utxo }) => String(utxo.address).length === 62
35
40
 
36
- export function getSpendableUtxos({ asset, utxos, feeData, txSet, taprootEnabled }) {
37
- if (!['bitcoin', 'bitcointestnet', 'bitcoinregtest'].includes(asset.name)) return utxos
41
+ export function getConfirmedUtxos({ asset, utxos }) {
42
+ assert(asset, 'asset is required')
43
+ assert(utxos, 'utxos is required')
44
+ const currency = asset.currency
45
+ return UtxoCollection.fromArray(
46
+ utxos.toArray().filter(({ confirmations }) => confirmations > 0),
47
+ { currency }
48
+ )
49
+ }
50
+
51
+ export function getConfirmedOrRfbDisabledUtxos({ asset, utxos, allowUnconfirmedRbfEnabledUtxos }) {
52
+ assert(asset, 'asset is required')
53
+ assert(utxos, 'utxos is required')
54
+ assert(
55
+ allowUnconfirmedRbfEnabledUtxos !== undefined,
56
+ 'allowUnconfirmedRbfEnabledUtxos is required'
57
+ )
58
+ if (allowUnconfirmedRbfEnabledUtxos) {
59
+ return utxos
60
+ }
61
+ const currency = asset.currency
62
+ return UtxoCollection.fromArray(
63
+ utxos.toArray().filter((utxo) => utxo.confirmations > 0 || !utxo.rbfEnabled),
64
+ { currency }
65
+ )
66
+ }
38
67
 
68
+ export function getUsableUtxos({ asset, utxos, feeData, txSet, taprootEnabled }) {
69
+ assert(asset, 'asset is required')
70
+ assert(utxos, 'utxos is required')
71
+ assert(feeData, 'feeData is required')
72
+ assert(txSet, 'txSet is required')
73
+ if (!['bitcoin', 'bitcointestnet', 'bitcoinregtest'].includes(asset.name)) return utxos
39
74
  if (!taprootEnabled) {
40
75
  utxos = UtxoCollection.fromArray(
41
76
  utxos.toArray().filter((utxo) => !isTaprootUtxo({ utxo })),
42
77
  { currency: asset.currency }
43
78
  )
44
79
  }
45
-
46
80
  const { fastestFee } = feeData
47
81
  const feeRate = fastestFee.toBaseNumber()
48
82
  const maxFee = feeData.maxExtraCpfpFee
@@ -61,3 +95,19 @@ export function getSpendableUtxos({ asset, utxos, feeData, txSet, taprootEnabled
61
95
  { currency: asset.currency }
62
96
  )
63
97
  }
98
+
99
+ export function getSpendableUtxos({
100
+ asset,
101
+ utxos,
102
+ feeData,
103
+ txSet,
104
+ taprootEnabled,
105
+ allowUnconfirmedRbfEnabledUtxos,
106
+ }) {
107
+ const usableUtxos = getUsableUtxos({ asset, utxos, feeData, txSet, taprootEnabled })
108
+ return getConfirmedOrRfbDisabledUtxos({
109
+ asset,
110
+ utxos: usableUtxos,
111
+ allowUnconfirmedRbfEnabledUtxos,
112
+ })
113
+ }