@exodus/bitcoin-api 4.1.4 → 4.1.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,22 @@
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.1.6](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.1.5...@exodus/bitcoin-api@4.1.6) (2025-10-16)
7
+
8
+ **Note:** Version bump only for package @exodus/bitcoin-api
9
+
10
+
11
+
12
+
13
+
14
+ ## [4.1.5](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.1.4...@exodus/bitcoin-api@4.1.5) (2025-10-16)
15
+
16
+ **Note:** Version bump only for package @exodus/bitcoin-api
17
+
18
+
19
+
20
+
21
+
6
22
  ## [4.1.4](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.1.3...@exodus/bitcoin-api@4.1.4) (2025-10-15)
7
23
 
8
24
  **Note:** Version bump only for package @exodus/bitcoin-api
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "4.1.4",
3
+ "version": "4.1.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",
@@ -60,5 +60,5 @@
60
60
  "type": "git",
61
61
  "url": "git+https://github.com/ExodusMovement/assets.git"
62
62
  },
63
- "gitHead": "0efd644cc413c308688b43c8563b172b3a80e3fa"
63
+ "gitHead": "f193d5effadc719b3fb4ba7d32fc46c0979f258a"
64
64
  }
@@ -10,6 +10,255 @@ import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data.js'
10
10
  import { getUsableUtxos, getUtxos } from '../utxos-utils.js'
11
11
  import { createInputs, createOutput, getBlockHeight, getNonWitnessTxs } from './tx-create-utils.js'
12
12
 
13
+ // Helper to shuffle arrays for randomized input/output ordering
14
+ const shuffle = (list) => {
15
+ return lodash.shuffle(list)
16
+ }
17
+
18
+ function validateTransactionParams({ assetClientInterface, asset, toAddress, bumpTxId }) {
19
+ assert(assetClientInterface, `assetClientInterface must be supplied in sendTx for ${asset.name}`)
20
+ assert(
21
+ toAddress || bumpTxId,
22
+ 'should not be called without either a receiving toAddress or to bump a tx'
23
+ )
24
+ }
25
+
26
+ async function prepareTransactionContext({
27
+ asset,
28
+ assetName,
29
+ walletAccount,
30
+ multipleAddressesEnabled,
31
+ assetClientInterface,
32
+ accountState,
33
+ insightClient,
34
+ feeData,
35
+ feePerKB,
36
+ isExchange,
37
+ isRbfAllowed,
38
+ }) {
39
+ const updatedFeeData = { ...feeData, feePerKB: feePerKB ?? feeData.feePerKB }
40
+
41
+ const blockHeight = await getBlockHeight({ assetName, insightClient })
42
+
43
+ const rbfEnabled = updatedFeeData.rbfEnabled && !isExchange && isRbfAllowed
44
+
45
+ const changeAddress = multipleAddressesEnabled
46
+ ? await assetClientInterface.getNextChangeAddress({ assetName, walletAccount })
47
+ : await assetClientInterface.getReceiveAddressObject({ assetName, walletAccount })
48
+
49
+ const txSet = await assetClientInterface.getTxLog({ assetName, walletAccount })
50
+
51
+ const unconfirmedTxAncestor = getUnconfirmedTxAncestorMap({ accountState })
52
+
53
+ const usableUtxos = getUsableUtxos({
54
+ asset,
55
+ utxos: getUtxos({ accountState, asset }),
56
+ feeData: updatedFeeData,
57
+ txSet,
58
+ unconfirmedTxAncestor,
59
+ })
60
+
61
+ return {
62
+ updatedFeeData,
63
+ blockHeight,
64
+ rbfEnabled,
65
+ changeAddress,
66
+ txSet,
67
+ unconfirmedTxAncestor,
68
+ usableUtxos,
69
+ }
70
+ }
71
+
72
+ // Process and normalize addresses
73
+ function processAddress({ asset, toAddress }) {
74
+ let processedAddress = toAddress
75
+
76
+ if (asset.address.toLegacyAddress) {
77
+ processedAddress = asset.address.toLegacyAddress(toAddress)
78
+ }
79
+
80
+ if (asset.name === 'digibyte' && asset.address.isP2SH2(processedAddress)) {
81
+ processedAddress = asset.address.P2SH2ToP2SH(processedAddress)
82
+ }
83
+
84
+ const useCashAddress = asset.address.isCashAddress?.(toAddress)
85
+
86
+ return { processedAddress, useCashAddress }
87
+ }
88
+
89
+ // Determine strategy for transaction bumping (RBF vs CPFP)
90
+ function determineBumpStrategy({ bumpTxId, replaceableTxs, usableUtxos }) {
91
+ if (!bumpTxId) {
92
+ return { replaceableTxs, utxosToBump: undefined }
93
+ }
94
+
95
+ // Check if we can use RBF (Replace-By-Fee)
96
+ const bumpTx = replaceableTxs.find(({ txId }) => txId === bumpTxId)
97
+
98
+ if (bumpTx) {
99
+ // Use RBF: replace the transaction directly
100
+ return { replaceableTxs: [bumpTx], utxosToBump: undefined }
101
+ }
102
+
103
+ // Otherwise try CPFP (Child-Pays-For-Parent) by spending the transaction's outputs
104
+ const utxosToBump = usableUtxos.getTxIdUtxos(bumpTxId)
105
+ if (utxosToBump.size === 0) {
106
+ throw new Error(`Cannot bump transaction ${bumpTxId}`)
107
+ }
108
+
109
+ return { replaceableTxs: [], utxosToBump }
110
+ }
111
+
112
+ // Prepare parameters for UTXO selection
113
+ function prepareUtxoSelectionParams({
114
+ bumpTxId,
115
+ asset,
116
+ amount,
117
+ replaceableTxs,
118
+ processedAddress,
119
+ changeAddressType,
120
+ updatedFeeData,
121
+ rbfEnabled,
122
+ feePerKB,
123
+ customFee,
124
+ isSendAll,
125
+ }) {
126
+ const sendAmount = bumpTxId ? asset.currency.ZERO : amount
127
+
128
+ const receiveAddress = bumpTxId
129
+ ? replaceableTxs.length > 0
130
+ ? null
131
+ : changeAddressType
132
+ : processedAddress
133
+
134
+ const feeRate = customFee || updatedFeeData.feePerKB
135
+ const resolvedIsSendAll = !rbfEnabled && feePerKB ? false : isSendAll
136
+
137
+ return {
138
+ sendAmount,
139
+ receiveAddress,
140
+ feeRate,
141
+ resolvedIsSendAll,
142
+ }
143
+ }
144
+
145
+ // Process RBF replacement transaction
146
+ function processReplacementTransaction({ replaceTx, asset }) {
147
+ if (!replaceTx) return { replaceTx: undefined, replaceTxInputUtxos: undefined }
148
+
149
+ const clonedTx = replaceTx.clone()
150
+ const updatedTx = clonedTx.update({ data: { ...clonedTx.data } })
151
+
152
+ updatedTx.data.sent = updatedTx.data.sent.map((to) => ({
153
+ ...to,
154
+ amount: serializeCurrency(to.amount, asset.currency),
155
+ }))
156
+
157
+ const replaceTxInputUtxos = UtxoCollection.fromJSON(updatedTx.data.inputs, {
158
+ currency: asset.currency,
159
+ })
160
+
161
+ return { replaceTx: updatedTx, replaceTxInputUtxos }
162
+ }
163
+
164
+ // Create transaction outputs
165
+ function createTransactionOutputs({
166
+ replaceTx,
167
+ processedAddress,
168
+ sendAmount,
169
+ asset,
170
+ selectedUtxos,
171
+ fee,
172
+ changeAddress,
173
+ }) {
174
+ const assetName = asset.name
175
+ let outputs = []
176
+ let sendOutput
177
+
178
+ // Add existing outputs from replacement transaction
179
+ if (replaceTx) {
180
+ outputs = replaceTx.data.sent.map(({ address, amount }) =>
181
+ createOutput(assetName, address, parseCurrency(amount, asset.currency))
182
+ )
183
+ }
184
+
185
+ // Add send output if we have a destination address
186
+ if (processedAddress) {
187
+ sendOutput = createOutput(assetName, processedAddress, sendAmount)
188
+ outputs.push(sendOutput)
189
+ }
190
+
191
+ // Calculate total amount
192
+ const totalAmount = replaceTx
193
+ ? replaceTx.data.sent.reduce(
194
+ (total, { amount }) => total.add(parseCurrency(amount, asset.currency)),
195
+ sendAmount
196
+ )
197
+ : sendAmount
198
+
199
+ // Handle change output
200
+ const { changeOutput, adjustedFee, changeAddressKeypath, ourAddress } = createChangeOutput({
201
+ selectedUtxos,
202
+ totalAmount,
203
+ fee,
204
+ replaceTx,
205
+ changeAddress,
206
+ asset,
207
+ })
208
+
209
+ if (changeOutput) {
210
+ outputs.push(changeOutput)
211
+ }
212
+
213
+ return {
214
+ outputs: replaceTx ? outputs : shuffle(outputs),
215
+ sendOutput,
216
+ changeOutput,
217
+ totalAmount,
218
+ adjustedFee,
219
+ changeAddressKeypath,
220
+ ourAddress,
221
+ }
222
+ }
223
+
224
+ // Create change output if needed
225
+ function createChangeOutput({ selectedUtxos, totalAmount, fee, replaceTx, changeAddress, asset }) {
226
+ const change = selectedUtxos.value.sub(totalAmount).sub(fee)
227
+ const dust = getChangeDustValue(asset)
228
+
229
+ // Process change address
230
+ let ourAddress = replaceTx?.data?.changeAddress || changeAddress
231
+ if (asset.address.toLegacyAddress) {
232
+ const legacyAddress = asset.address.toLegacyAddress(ourAddress.address)
233
+ ourAddress = Address.create(legacyAddress, ourAddress.meta)
234
+ }
235
+
236
+ // Create change output if above dust threshold
237
+ if (change.gte(dust)) {
238
+ const changeOutput = createOutput(
239
+ asset.name,
240
+ ourAddress.address ?? ourAddress.toString(),
241
+ change
242
+ )
243
+
244
+ // Return the keypath for hardware wallet change detection
245
+ return {
246
+ changeOutput,
247
+ adjustedFee: fee,
248
+ changeAddressKeypath: ourAddress.meta.path,
249
+ ourAddress,
250
+ }
251
+ }
252
+
253
+ // Add dust to fee if not enough for change output
254
+ return {
255
+ changeOutput: undefined,
256
+ adjustedFee: fee.add(change),
257
+ changeAddressKeypath: undefined,
258
+ ourAddress,
259
+ }
260
+ }
261
+
13
262
  async function createUnsignedTx({
14
263
  inputs,
15
264
  outputs,
@@ -51,8 +300,6 @@ const transferHandler = {
51
300
  walletAccount,
52
301
  toAddress,
53
302
  amount,
54
- blockHeight: providedBlockHeight,
55
- rbfEnabled: providedRbfEnabled,
56
303
  multipleAddressesEnabled,
57
304
  feePerKB,
58
305
  customFee,
@@ -70,99 +317,72 @@ const transferHandler = {
70
317
  changeAddressType,
71
318
  }) => {
72
319
  const assetName = asset.name
73
- const updatedFeeData = { ...feeData, feePerKB: feePerKB ?? feeData.feePerKB }
74
320
  const insightClient = asset.baseAsset.insightClient
75
321
 
76
- const blockHeight = providedBlockHeight || (await getBlockHeight({ assetName, insightClient }))
77
-
78
- const rbfEnabled =
79
- providedRbfEnabled || (updatedFeeData.rbfEnabled && !isExchange && isRbfAllowed)
80
-
81
- const shuffle = (list) => {
82
- // Using full lodash.shuffle notation so it can be mocked with spyOn in tests
83
- return lodash.shuffle(list)
84
- }
322
+ validateTransactionParams({ assetClientInterface, asset, toAddress, bumpTxId })
85
323
 
86
- assert(
324
+ const context = await prepareTransactionContext({
325
+ asset,
326
+ assetName,
327
+ walletAccount,
328
+ multipleAddressesEnabled,
87
329
  assetClientInterface,
88
- `assetClientInterface must be supplied in sendTx for ${asset.name}`
89
- )
90
- assert(
91
- toAddress || bumpTxId,
92
- 'should not be called without either a receiving toAddress or to bump a tx'
93
- )
330
+ accountState,
331
+ insightClient,
332
+ feeData,
333
+ feePerKB,
334
+ isExchange,
335
+ isRbfAllowed,
336
+ })
94
337
 
95
- const useCashAddress = asset.address.isCashAddress?.(toAddress)
338
+ const { processedAddress, useCashAddress } = processAddress({ asset, toAddress })
96
339
 
97
- const changeAddress = multipleAddressesEnabled
98
- ? await assetClientInterface.getNextChangeAddress({ assetName, walletAccount })
99
- : await assetClientInterface.getReceiveAddressObject({ assetName, walletAccount })
340
+ // Get replaceable transactions
341
+ let replaceableTxs = findUnconfirmedSentRbfTxs(context.txSet)
100
342
 
101
- const txSet = await assetClientInterface.getTxLog({ assetName, walletAccount })
343
+ // Determine bumping strategy (RBF or CPFP)
344
+ const { replaceableTxs: updatedReplaceableTxs, utxosToBump } = determineBumpStrategy({
345
+ bumpTxId,
346
+ replaceableTxs,
347
+ usableUtxos: context.usableUtxos,
348
+ })
349
+ replaceableTxs = updatedReplaceableTxs
102
350
 
103
- const unconfirmedTxAncestor = getUnconfirmedTxAncestorMap({ accountState })
104
- const usableUtxos = getUsableUtxos({
351
+ const utxoParams = prepareUtxoSelectionParams({
352
+ bumpTxId,
105
353
  asset,
106
- utxos: getUtxos({ accountState, asset }),
107
- feeData: updatedFeeData,
108
- txSet,
109
- unconfirmedTxAncestor,
354
+ amount,
355
+ replaceableTxs,
356
+ processedAddress,
357
+ changeAddressType,
358
+ updatedFeeData: context.updatedFeeData,
359
+ rbfEnabled: context.rbfEnabled,
360
+ feePerKB,
361
+ customFee,
362
+ isSendAll,
110
363
  })
111
364
 
112
- let replaceableTxs = findUnconfirmedSentRbfTxs(txSet)
113
-
114
- let processedAddress = toAddress
115
- if (asset.address.toLegacyAddress) {
116
- processedAddress = asset.address.toLegacyAddress(toAddress)
117
- }
118
-
119
- if (assetName === 'digibyte' && asset.address.isP2SH2(processedAddress)) {
120
- processedAddress = asset.address.P2SH2ToP2SH(processedAddress)
121
- }
122
-
123
- let utxosToBump
124
- if (bumpTxId) {
125
- const bumpTx = replaceableTxs.find(({ txId }) => txId === bumpTxId)
126
- if (bumpTx) {
127
- replaceableTxs = [bumpTx]
128
- } else {
129
- utxosToBump = usableUtxos.getTxIdUtxos(bumpTxId)
130
- if (utxosToBump.size === 0) {
131
- throw new Error(`Cannot bump transaction ${bumpTxId}`)
132
- }
133
-
134
- replaceableTxs = []
135
- }
136
- }
137
-
138
- const sendAmount = bumpTxId ? asset.currency.ZERO : amount
139
- const receiveAddress = bumpTxId
140
- ? replaceableTxs.length > 0
141
- ? null
142
- : changeAddressType
143
- : processedAddress
144
- const feeRate = updatedFeeData.feePerKB
145
- const resolvedIsSendAll = !rbfEnabled && feePerKB ? false : isSendAll
146
-
147
365
  let { selectedUtxos, fee, replaceTx } = selectUtxos({
148
366
  asset,
149
- usableUtxos,
367
+ usableUtxos: context.usableUtxos,
150
368
  replaceableTxs,
151
- amount: sendAmount,
152
- feeRate: customFee || feeRate,
153
- receiveAddress,
154
- isSendAll: resolvedIsSendAll,
369
+ amount: utxoParams.sendAmount,
370
+ feeRate: utxoParams.feeRate,
371
+ receiveAddress: utxoParams.receiveAddress,
372
+ isSendAll: utxoParams.resolvedIsSendAll,
155
373
  getFeeEstimator: (asset, { feePerKB, ...options }) =>
156
374
  getFeeEstimator(asset, feePerKB, options),
157
375
  mustSpendUtxos: utxosToBump,
158
376
  allowUnconfirmedRbfEnabledUtxos,
159
- unconfirmedTxAncestor,
377
+ unconfirmedTxAncestor: context.unconfirmedTxAncestor,
160
378
  utxosDescendingOrder,
161
379
  taprootInputWitnessSize,
162
380
  changeAddressType,
163
381
  })
164
382
 
165
- if (!selectedUtxos && !replaceTx) throw new Error('Not enough funds.')
383
+ if (!selectedUtxos && !replaceTx) {
384
+ throw new Error('Not enough funds.')
385
+ }
166
386
 
167
387
  // When bumping a tx, we can either replace the tx with RBF or spend its selected change.
168
388
  // If there is no selected UTXO or the tx to replace is not the tx we want to bump,
@@ -173,70 +393,52 @@ const transferHandler = {
173
393
  throw new Error(`Unable to bump ${bumpTxId}`)
174
394
  }
175
395
 
176
- if (replaceTx) {
177
- replaceTx = replaceTx.clone()
178
- replaceTx = replaceTx.update({ data: { ...replaceTx.data } })
179
- replaceTx.data.sent = replaceTx.data.sent.map((to) => {
180
- return { ...to, amount: serializeCurrency(to.amount, asset.currency) }
181
- })
182
- selectedUtxos = selectedUtxos.union(
183
- UtxoCollection.fromJSON(replaceTx.data.inputs, { currency: asset.currency })
184
- )
396
+ const { replaceTx: processedReplaceTx, replaceTxInputUtxos } = processReplacementTransaction({
397
+ replaceTx,
398
+ asset,
399
+ })
400
+
401
+ if (replaceTxInputUtxos) {
402
+ selectedUtxos = selectedUtxos.union(replaceTxInputUtxos)
185
403
  }
186
404
 
187
405
  const addressPathsMap = selectedUtxos.getAddressPathsMap()
188
406
 
189
- // Inputs and Outputs
190
- const inputs = shuffle(createInputs(assetName, selectedUtxos.toArray(), rbfEnabled))
191
- let outputs = replaceTx
192
- ? replaceTx.data.sent.map(({ address, amount }) =>
193
- createOutput(assetName, address, parseCurrency(amount, asset.currency))
194
- )
195
- : []
196
-
197
- // Send output
198
- let sendOutput
199
- if (processedAddress) {
200
- sendOutput = createOutput(assetName, processedAddress, sendAmount)
201
- outputs.push(sendOutput)
202
- }
407
+ // Create inputs
408
+ const inputs = shuffle(createInputs(assetName, selectedUtxos.toArray(), context.rbfEnabled))
203
409
 
204
- const totalAmount = replaceTx
205
- ? replaceTx.data.sent.reduce(
206
- (total, { amount }) => total.add(parseCurrency(amount, asset.currency)),
207
- sendAmount
208
- )
209
- : sendAmount
210
-
211
- const change = selectedUtxos.value.sub(totalAmount).sub(fee)
212
- const dust = getChangeDustValue(asset)
213
- let ourAddress = replaceTx?.data?.changeAddress || changeAddress
214
- if (asset.address.toLegacyAddress) {
215
- const legacyAddress = asset.address.toLegacyAddress(ourAddress.address)
216
- ourAddress = Address.create(legacyAddress, ourAddress.meta)
217
- }
410
+ // Create outputs
411
+ const {
412
+ outputs,
413
+ sendOutput,
414
+ changeOutput,
415
+ totalAmount,
416
+ adjustedFee,
417
+ changeAddressKeypath,
418
+ ourAddress,
419
+ } = createTransactionOutputs({
420
+ replaceTx: processedReplaceTx,
421
+ processedAddress,
422
+ sendAmount: utxoParams.sendAmount,
423
+ asset,
424
+ selectedUtxos,
425
+ fee,
426
+ changeAddress: context.changeAddress,
427
+ })
218
428
 
219
- // Change Output
220
- let changeOutput
221
- if (change.gte(dust)) {
222
- changeOutput = createOutput(assetName, ourAddress.address ?? ourAddress.toString(), change)
223
- // Add the keypath of change address to support Trezor detect the change output.
224
- // Output is change and does not need approval from user which shows the strange address that user never seen.
225
- addressPathsMap[changeAddress] = ourAddress.meta.path
226
- outputs.push(changeOutput)
227
- } else {
228
- // If we don't have enough for a change output, then all remaining dust is just added to fee
229
- fee = fee.add(change)
429
+ // Add the keypath of change address to support Trezor detect the change output.
430
+ // Output is change and does not need approval from user which shows the strange address that user never seen.
431
+ if (changeAddressKeypath) {
432
+ addressPathsMap[context.changeAddress] = changeAddressKeypath
230
433
  }
231
434
 
232
- outputs = replaceTx ? outputs : shuffle(outputs)
233
-
435
+ // Create unsigned transaction
234
436
  const unsignedTx = await createUnsignedTx({
235
437
  inputs,
236
438
  outputs,
237
439
  useCashAddress,
238
440
  addressPathsMap,
239
- blockHeight,
441
+ blockHeight: context.blockHeight,
240
442
  asset,
241
443
  selectedUtxos,
242
444
  insightClient,
@@ -244,20 +446,22 @@ const transferHandler = {
244
446
 
245
447
  return {
246
448
  unsignedTx,
247
- fee,
449
+ fee: adjustedFee,
248
450
  metadata: {
249
451
  amount,
250
- change,
452
+ change: selectedUtxos.value.sub(totalAmount).sub(adjustedFee),
251
453
  totalAmount,
252
454
  address: processedAddress,
253
455
  ourAddress,
254
- receiveAddress,
255
- sendAmount,
256
- usableUtxos,
456
+ receiveAddress: utxoParams.receiveAddress,
457
+ sendAmount: utxoParams.sendAmount,
458
+ usableUtxos: context.usableUtxos,
257
459
  selectedUtxos,
258
- replaceTx,
460
+ replaceTx: processedReplaceTx,
259
461
  sendOutput,
260
462
  changeOutput,
463
+ blockHeight: context.blockHeight,
464
+ rbfEnabled: context.rbfEnabled,
261
465
  },
262
466
  }
263
467
  },
@@ -277,8 +481,6 @@ export const createTxFactory =
277
481
  type,
278
482
  toAddress,
279
483
  amount,
280
- blockHeight,
281
- rbfEnabled,
282
484
  multipleAddressesEnabled,
283
485
  feePerKB,
284
486
  customFee,
@@ -302,8 +504,6 @@ export const createTxFactory =
302
504
  walletAccount,
303
505
  toAddress,
304
506
  amount,
305
- blockHeight,
306
- rbfEnabled,
307
507
  multipleAddressesEnabled,
308
508
  feePerKB,
309
509
  customFee,
@@ -2,7 +2,6 @@ import * as defaultBitcoinjsLib from '@exodus/bitcoinjs'
2
2
  import assert from 'minimalistic-assert'
3
3
 
4
4
  import { createTxFactory } from '../tx-create/create-tx.js'
5
- import { getBlockHeight } from '../tx-create/tx-create-utils.js'
6
5
  import { broadcastTransaction } from './broadcast-tx.js'
7
6
  import { updateAccountState, updateTransactionLog } from './update-state.js'
8
7
 
@@ -104,11 +103,9 @@ const getPrepareSendTransaction = async ({
104
103
  amount,
105
104
  asset,
106
105
  assetClientInterface,
107
- blockHeight,
108
106
  changeAddressType,
109
107
  getFeeEstimator,
110
108
  options,
111
- rbfEnabled,
112
109
  utxosDescendingOrder,
113
110
  walletAccount,
114
111
  }) => {
@@ -120,15 +117,17 @@ const getPrepareSendTransaction = async ({
120
117
  changeAddressType,
121
118
  })
122
119
 
120
+ // Set default values for options
121
+ const { isRbfAllowed = true, ...restOptions } = options || Object.create(null)
122
+
123
123
  return createTx({
124
124
  asset,
125
125
  walletAccount,
126
126
  type: 'transfer',
127
127
  toAddress: address,
128
128
  amount,
129
- blockHeight,
130
- rbfEnabled,
131
- ...options,
129
+ isRbfAllowed,
130
+ ...restOptions,
132
131
  })
133
132
  }
134
133
 
@@ -145,17 +144,10 @@ export const createAndBroadcastTXFactory =
145
144
  }) =>
146
145
  async ({ asset, walletAccount, address, amount, options }) => {
147
146
  // Prepare transaction
148
- const { bumpTxId, isExchange, isRbfAllowed = true } = options
147
+ const { bumpTxId } = options
149
148
 
150
149
  const assetName = asset.name
151
- const feeData = await assetClientInterface.getFeeConfig({ assetName })
152
150
  const accountState = await assetClientInterface.getAccountState({ assetName, walletAccount })
153
- const insightClient = asset.baseAsset.insightClient
154
-
155
- const rbfEnabled = feeData.rbfEnabled && !isExchange && isRbfAllowed
156
-
157
- // blockHeight
158
- const blockHeight = await getBlockHeight({ assetName, insightClient })
159
151
 
160
152
  const transactionDescriptor = await getPrepareSendTransaction({
161
153
  address,
@@ -163,17 +155,23 @@ export const createAndBroadcastTXFactory =
163
155
  amount,
164
156
  asset,
165
157
  assetClientInterface,
166
- blockHeight,
167
158
  changeAddressType,
168
159
  getFeeEstimator,
169
160
  options,
170
- rbfEnabled,
171
161
  utxosDescendingOrder,
172
162
  walletAccount,
173
163
  })
174
164
 
175
165
  const { unsignedTx, fee, metadata } = transactionDescriptor
176
- const { sendAmount, usableUtxos, replaceTx, sendOutput, changeOutput } = metadata
166
+ const {
167
+ sendAmount,
168
+ usableUtxos,
169
+ replaceTx,
170
+ sendOutput,
171
+ changeOutput,
172
+ blockHeight,
173
+ rbfEnabled,
174
+ } = metadata
177
175
 
178
176
  const outputs = unsignedTx.txData.outputs
179
177