@exodus/bitcoin-api 4.1.2 → 4.1.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/CHANGELOG.md +20 -0
- package/package.json +2 -2
- package/src/fee/get-fee-resolver.js +1 -25
- package/src/fee/utxo-selector.js +4 -24
- package/src/tx-create/create-tx.js +23 -67
- package/src/tx-send/broadcast-tx.js +48 -0
- package/src/tx-send/index.js +36 -180
- package/src/tx-send/update-state.js +188 -0
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.1.4](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.1.3...@exodus/bitcoin-api@4.1.4) (2025-10-15)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @exodus/bitcoin-api
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [4.1.3](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.1.1...@exodus/bitcoin-api@4.1.3) (2025-10-14)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
* fix: remove isBip70 from bitcoin libs (#6660)
|
|
21
|
+
|
|
22
|
+
* fix: remove unused bitcoin.api.prepareSendTx (#6662)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
## [4.1.2](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.1.1...@exodus/bitcoin-api@4.1.2) (2025-10-09)
|
|
7
27
|
|
|
8
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.4",
|
|
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": "
|
|
63
|
+
"gitHead": "0efd644cc413c308688b43c8563b172b3a80e3fa"
|
|
64
64
|
}
|
|
@@ -2,13 +2,7 @@ import assert from 'minimalistic-assert'
|
|
|
2
2
|
|
|
3
3
|
import { findUnconfirmedSentRbfTxs } from '../tx-utils.js'
|
|
4
4
|
import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data.js'
|
|
5
|
-
import {
|
|
6
|
-
getInscriptionIds,
|
|
7
|
-
getOrdinalsUtxos,
|
|
8
|
-
getTransferOrdinalsUtxos,
|
|
9
|
-
getUsableUtxos,
|
|
10
|
-
getUtxos,
|
|
11
|
-
} from '../utxos-utils.js'
|
|
5
|
+
import { getUsableUtxos, getUtxos } from '../utxos-utils.js'
|
|
12
6
|
import { canBumpTx } from './can-bump-tx.js'
|
|
13
7
|
import { getUtxosData } from './utxo-selector.js'
|
|
14
8
|
|
|
@@ -40,17 +34,9 @@ export class GetFeeResolver {
|
|
|
40
34
|
amount,
|
|
41
35
|
customFee,
|
|
42
36
|
isSendAll,
|
|
43
|
-
nft, // sending one nft
|
|
44
37
|
receiveAddress,
|
|
45
38
|
taprootInputWitnessSize,
|
|
46
39
|
}) => {
|
|
47
|
-
if (nft) {
|
|
48
|
-
assert(!amount, 'amount must not be provided when nft is provided!!!')
|
|
49
|
-
assert(!isSendAll, 'isSendAll must not be provided when nft is provided!!!')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const inscriptionIds = getInscriptionIds({ nft })
|
|
53
|
-
|
|
54
40
|
const { fee, unspendableFee, extraFeeData } = this.#getUtxosData({
|
|
55
41
|
asset,
|
|
56
42
|
accountState,
|
|
@@ -60,7 +46,6 @@ export class GetFeeResolver {
|
|
|
60
46
|
amount,
|
|
61
47
|
customFee,
|
|
62
48
|
isSendAll,
|
|
63
|
-
inscriptionIds,
|
|
64
49
|
taprootInputWitnessSize,
|
|
65
50
|
})
|
|
66
51
|
return { fee, unspendableFee, extraFeeData }
|
|
@@ -75,7 +60,6 @@ export class GetFeeResolver {
|
|
|
75
60
|
amount,
|
|
76
61
|
customFee,
|
|
77
62
|
isSendAll,
|
|
78
|
-
inscriptionIds,
|
|
79
63
|
taprootInputWitnessSize,
|
|
80
64
|
}) => {
|
|
81
65
|
assert(asset, 'asset must be provided')
|
|
@@ -87,12 +71,6 @@ export class GetFeeResolver {
|
|
|
87
71
|
const utxos = getUtxos({ accountState, asset })
|
|
88
72
|
const unconfirmedTxAncestor = getUnconfirmedTxAncestorMap({ accountState })
|
|
89
73
|
|
|
90
|
-
const ordinalsUtxos = getOrdinalsUtxos({ accountState, asset })
|
|
91
|
-
|
|
92
|
-
const transferOrdinalsUtxos = inscriptionIds
|
|
93
|
-
? getTransferOrdinalsUtxos({ inscriptionIds, ordinalsUtxos })
|
|
94
|
-
: undefined
|
|
95
|
-
|
|
96
74
|
const usableUtxos = getUsableUtxos({
|
|
97
75
|
asset,
|
|
98
76
|
utxos,
|
|
@@ -110,8 +88,6 @@ export class GetFeeResolver {
|
|
|
110
88
|
amount,
|
|
111
89
|
feeRate: feePerKB,
|
|
112
90
|
receiveAddress,
|
|
113
|
-
transferOrdinalsUtxos,
|
|
114
|
-
inscriptionIds,
|
|
115
91
|
isSendAll,
|
|
116
92
|
getFeeEstimator: this.#getFeeEstimator,
|
|
117
93
|
allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
|
package/src/fee/utxo-selector.js
CHANGED
|
@@ -11,11 +11,7 @@ const { sortBy } = lodash
|
|
|
11
11
|
|
|
12
12
|
const MIN_RELAY_FEE = 1000
|
|
13
13
|
|
|
14
|
-
const getBestReceiveAddresses = ({ asset, receiveAddress
|
|
15
|
-
if (inscriptionIds) {
|
|
16
|
-
return receiveAddress || 'P2TR'
|
|
17
|
-
}
|
|
18
|
-
|
|
14
|
+
const getBestReceiveAddresses = ({ asset, receiveAddress }) => {
|
|
19
15
|
if (receiveAddress === null) {
|
|
20
16
|
return null
|
|
21
17
|
}
|
|
@@ -38,20 +34,15 @@ export const selectUtxos = ({
|
|
|
38
34
|
mustSpendUtxos,
|
|
39
35
|
allowUnconfirmedRbfEnabledUtxos,
|
|
40
36
|
unconfirmedTxAncestor,
|
|
41
|
-
inscriptionIds, // for each inscription transfer, we need to calculate one more input and one more output
|
|
42
|
-
transferOrdinalsUtxos, // to calculate the size of the input
|
|
43
37
|
taprootInputWitnessSize,
|
|
44
38
|
changeAddressType = 'P2PKH',
|
|
45
39
|
}) => {
|
|
46
40
|
const resolvedReceiveAddresses = getBestReceiveAddresses({
|
|
47
41
|
asset,
|
|
48
42
|
receiveAddress,
|
|
49
|
-
inscriptionIds,
|
|
50
43
|
})
|
|
51
44
|
|
|
52
|
-
if (
|
|
53
|
-
receiveAddresses.push(...inscriptionIds.map(() => resolvedReceiveAddresses))
|
|
54
|
-
} else if (receiveAddresses.length === 0) {
|
|
45
|
+
if (receiveAddresses.length === 0) {
|
|
55
46
|
receiveAddresses.push(resolvedReceiveAddresses)
|
|
56
47
|
}
|
|
57
48
|
|
|
@@ -66,7 +57,6 @@ export const selectUtxos = ({
|
|
|
66
57
|
// We can only replace for a sendAll if only 1 replaceable tx and no unconfirmed utxos
|
|
67
58
|
const confirmedUtxosArray = getConfirmedUtxos({ asset, utxos: usableUtxos }).toArray()
|
|
68
59
|
const canReplace =
|
|
69
|
-
!inscriptionIds &&
|
|
70
60
|
!mustSpendUtxos &&
|
|
71
61
|
!disableReplacement &&
|
|
72
62
|
replaceableTxs &&
|
|
@@ -86,7 +76,6 @@ export const selectUtxos = ({
|
|
|
86
76
|
}
|
|
87
77
|
|
|
88
78
|
const replaceFeeEstimator = getFeeEstimator(asset, { feePerKB, unconfirmedTxAncestor })
|
|
89
|
-
// how to avoid replace tx inputs when inputs are ordinals? !!!!
|
|
90
79
|
const inputs = UtxoCollection.fromJSON(tx.data.inputs, { currency })
|
|
91
80
|
const outputs = isSendAll
|
|
92
81
|
? tx.data.sent.map(({ address }) => address)
|
|
@@ -211,15 +200,10 @@ export const selectUtxos = ({
|
|
|
211
200
|
selectedUtxosValue = selectedUtxosValue.add(newUtxo.value)
|
|
212
201
|
}
|
|
213
202
|
|
|
214
|
-
let selectedUtxos =
|
|
215
|
-
UtxoCollection.fromArray(selectedUtxosArray, { currency })
|
|
216
|
-
) // extremelly important, orden must be kept!!! ordinals utxos go first!!!
|
|
203
|
+
let selectedUtxos = UtxoCollection.fromArray(selectedUtxosArray, { currency })
|
|
217
204
|
|
|
218
205
|
// start figuring out fees
|
|
219
|
-
const outputs =
|
|
220
|
-
amount.isZero && !inscriptionIds
|
|
221
|
-
? [changeAddressType]
|
|
222
|
-
: [...receiveAddresses, changeAddressType]
|
|
206
|
+
const outputs = amount.isZero ? [changeAddressType] : [...receiveAddresses, changeAddressType]
|
|
223
207
|
|
|
224
208
|
let fee = feeEstimator({ inputs: selectedUtxos, outputs, taprootInputWitnessSize })
|
|
225
209
|
|
|
@@ -259,8 +243,6 @@ export const getUtxosData = ({
|
|
|
259
243
|
disableReplacement,
|
|
260
244
|
mustSpendUtxos,
|
|
261
245
|
allowUnconfirmedRbfEnabledUtxos,
|
|
262
|
-
inscriptionIds,
|
|
263
|
-
transferOrdinalsUtxos,
|
|
264
246
|
unconfirmedTxAncestor,
|
|
265
247
|
utxosDescendingOrder,
|
|
266
248
|
taprootInputWitnessSize,
|
|
@@ -283,8 +265,6 @@ export const getUtxosData = ({
|
|
|
283
265
|
mustSpendUtxos,
|
|
284
266
|
allowUnconfirmedRbfEnabledUtxos,
|
|
285
267
|
unconfirmedTxAncestor,
|
|
286
|
-
inscriptionIds,
|
|
287
|
-
transferOrdinalsUtxos,
|
|
288
268
|
utxosDescendingOrder,
|
|
289
269
|
taprootInputWitnessSize,
|
|
290
270
|
changeAddressType,
|
|
@@ -7,13 +7,7 @@ import { parseCurrency, serializeCurrency } from '../fee/fee-utils.js'
|
|
|
7
7
|
import { selectUtxos } from '../fee/utxo-selector.js'
|
|
8
8
|
import { findUnconfirmedSentRbfTxs } from '../tx-utils.js'
|
|
9
9
|
import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data.js'
|
|
10
|
-
import {
|
|
11
|
-
getInscriptionIds,
|
|
12
|
-
getOrdinalsUtxos,
|
|
13
|
-
getTransferOrdinalsUtxos,
|
|
14
|
-
getUsableUtxos,
|
|
15
|
-
getUtxos,
|
|
16
|
-
} from '../utxos-utils.js'
|
|
10
|
+
import { getUsableUtxos, getUtxos } from '../utxos-utils.js'
|
|
17
11
|
import { createInputs, createOutput, getBlockHeight, getNonWitnessTxs } from './tx-create-utils.js'
|
|
18
12
|
|
|
19
13
|
async function createUnsignedTx({
|
|
@@ -64,13 +58,11 @@ const transferHandler = {
|
|
|
64
58
|
customFee,
|
|
65
59
|
isSendAll,
|
|
66
60
|
bumpTxId,
|
|
67
|
-
nft,
|
|
68
61
|
isExchange,
|
|
69
62
|
isRbfAllowed,
|
|
70
63
|
taprootInputWitnessSize,
|
|
71
64
|
accountState,
|
|
72
65
|
feeData,
|
|
73
|
-
ordinalsEnabled,
|
|
74
66
|
getFeeEstimator,
|
|
75
67
|
allowUnconfirmedRbfEnabledUtxos,
|
|
76
68
|
utxosDescendingOrder,
|
|
@@ -84,18 +76,11 @@ const transferHandler = {
|
|
|
84
76
|
const blockHeight = providedBlockHeight || (await getBlockHeight({ assetName, insightClient }))
|
|
85
77
|
|
|
86
78
|
const rbfEnabled =
|
|
87
|
-
providedRbfEnabled || (updatedFeeData.rbfEnabled && !isExchange && isRbfAllowed
|
|
88
|
-
|
|
89
|
-
const inscriptionIds = getInscriptionIds({ nft })
|
|
90
|
-
|
|
91
|
-
assert(
|
|
92
|
-
ordinalsEnabled || !inscriptionIds,
|
|
93
|
-
'inscriptions cannot be sent when ordinalsEnabled=false '
|
|
94
|
-
)
|
|
79
|
+
providedRbfEnabled || (updatedFeeData.rbfEnabled && !isExchange && isRbfAllowed)
|
|
95
80
|
|
|
96
81
|
const shuffle = (list) => {
|
|
97
82
|
// Using full lodash.shuffle notation so it can be mocked with spyOn in tests
|
|
98
|
-
return
|
|
83
|
+
return lodash.shuffle(list)
|
|
99
84
|
}
|
|
100
85
|
|
|
101
86
|
assert(
|
|
@@ -107,11 +92,6 @@ const transferHandler = {
|
|
|
107
92
|
'should not be called without either a receiving toAddress or to bump a tx'
|
|
108
93
|
)
|
|
109
94
|
|
|
110
|
-
if (inscriptionIds) {
|
|
111
|
-
assert(!bumpTxId, 'only inscriptionIds or bumpTxId must be provided')
|
|
112
|
-
assert(toAddress, 'toAddress must be provided when sending ordinals')
|
|
113
|
-
}
|
|
114
|
-
|
|
115
95
|
const useCashAddress = asset.address.isCashAddress?.(toAddress)
|
|
116
96
|
|
|
117
97
|
const changeAddress = multipleAddressesEnabled
|
|
@@ -120,11 +100,6 @@ const transferHandler = {
|
|
|
120
100
|
|
|
121
101
|
const txSet = await assetClientInterface.getTxLog({ assetName, walletAccount })
|
|
122
102
|
|
|
123
|
-
const currentOrdinalsUtxos = getOrdinalsUtxos({ accountState, asset })
|
|
124
|
-
const transferOrdinalsUtxos = inscriptionIds
|
|
125
|
-
? getTransferOrdinalsUtxos({ inscriptionIds, ordinalsUtxos: currentOrdinalsUtxos })
|
|
126
|
-
: undefined
|
|
127
|
-
|
|
128
103
|
const unconfirmedTxAncestor = getUnconfirmedTxAncestorMap({ accountState })
|
|
129
104
|
const usableUtxos = getUsableUtxos({
|
|
130
105
|
asset,
|
|
@@ -160,14 +135,14 @@ const transferHandler = {
|
|
|
160
135
|
}
|
|
161
136
|
}
|
|
162
137
|
|
|
163
|
-
const sendAmount = bumpTxId
|
|
138
|
+
const sendAmount = bumpTxId ? asset.currency.ZERO : amount
|
|
164
139
|
const receiveAddress = bumpTxId
|
|
165
140
|
? replaceableTxs.length > 0
|
|
166
141
|
? null
|
|
167
142
|
: changeAddressType
|
|
168
143
|
: processedAddress
|
|
169
144
|
const feeRate = updatedFeeData.feePerKB
|
|
170
|
-
const resolvedIsSendAll =
|
|
145
|
+
const resolvedIsSendAll = !rbfEnabled && feePerKB ? false : isSendAll
|
|
171
146
|
|
|
172
147
|
let { selectedUtxos, fee, replaceTx } = selectUtxos({
|
|
173
148
|
asset,
|
|
@@ -182,8 +157,6 @@ const transferHandler = {
|
|
|
182
157
|
mustSpendUtxos: utxosToBump,
|
|
183
158
|
allowUnconfirmedRbfEnabledUtxos,
|
|
184
159
|
unconfirmedTxAncestor,
|
|
185
|
-
inscriptionIds,
|
|
186
|
-
transferOrdinalsUtxos,
|
|
187
160
|
utxosDescendingOrder,
|
|
188
161
|
taprootInputWitnessSize,
|
|
189
162
|
changeAddressType,
|
|
@@ -207,7 +180,6 @@ const transferHandler = {
|
|
|
207
180
|
return { ...to, amount: serializeCurrency(to.amount, asset.currency) }
|
|
208
181
|
})
|
|
209
182
|
selectedUtxos = selectedUtxos.union(
|
|
210
|
-
// how to avoid replace tx inputs when inputs are ordinals? !!!!
|
|
211
183
|
UtxoCollection.fromJSON(replaceTx.data.inputs, { currency: asset.currency })
|
|
212
184
|
)
|
|
213
185
|
}
|
|
@@ -225,16 +197,8 @@ const transferHandler = {
|
|
|
225
197
|
// Send output
|
|
226
198
|
let sendOutput
|
|
227
199
|
if (processedAddress) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
...transferOrdinalsUtxos
|
|
231
|
-
.toArray()
|
|
232
|
-
.map((ordinalUtxo) => createOutput(assetName, processedAddress, ordinalUtxo.value))
|
|
233
|
-
)
|
|
234
|
-
} else {
|
|
235
|
-
sendOutput = createOutput(assetName, processedAddress, sendAmount)
|
|
236
|
-
outputs.push(sendOutput)
|
|
237
|
-
}
|
|
200
|
+
sendOutput = createOutput(assetName, processedAddress, sendAmount)
|
|
201
|
+
outputs.push(sendOutput)
|
|
238
202
|
}
|
|
239
203
|
|
|
240
204
|
const totalAmount = replaceTx
|
|
@@ -244,10 +208,7 @@ const transferHandler = {
|
|
|
244
208
|
)
|
|
245
209
|
: sendAmount
|
|
246
210
|
|
|
247
|
-
const change = selectedUtxos.value
|
|
248
|
-
.sub(totalAmount)
|
|
249
|
-
.sub(transferOrdinalsUtxos?.value || asset.currency.ZERO)
|
|
250
|
-
.sub(fee)
|
|
211
|
+
const change = selectedUtxos.value.sub(totalAmount).sub(fee)
|
|
251
212
|
const dust = getChangeDustValue(asset)
|
|
252
213
|
let ourAddress = replaceTx?.data?.changeAddress || changeAddress
|
|
253
214
|
if (asset.address.toLegacyAddress) {
|
|
@@ -282,30 +243,28 @@ const transferHandler = {
|
|
|
282
243
|
})
|
|
283
244
|
|
|
284
245
|
return {
|
|
285
|
-
amount,
|
|
286
|
-
change,
|
|
287
|
-
totalAmount,
|
|
288
|
-
currentOrdinalsUtxos,
|
|
289
|
-
inscriptionIds,
|
|
290
|
-
address: processedAddress,
|
|
291
|
-
ourAddress,
|
|
292
|
-
receiveAddress,
|
|
293
|
-
sendAmount,
|
|
294
|
-
fee,
|
|
295
|
-
usableUtxos,
|
|
296
|
-
selectedUtxos,
|
|
297
|
-
transferOrdinalsUtxos,
|
|
298
|
-
replaceTx,
|
|
299
|
-
sendOutput,
|
|
300
|
-
changeOutput,
|
|
301
246
|
unsignedTx,
|
|
247
|
+
fee,
|
|
248
|
+
metadata: {
|
|
249
|
+
amount,
|
|
250
|
+
change,
|
|
251
|
+
totalAmount,
|
|
252
|
+
address: processedAddress,
|
|
253
|
+
ourAddress,
|
|
254
|
+
receiveAddress,
|
|
255
|
+
sendAmount,
|
|
256
|
+
usableUtxos,
|
|
257
|
+
selectedUtxos,
|
|
258
|
+
replaceTx,
|
|
259
|
+
sendOutput,
|
|
260
|
+
changeOutput,
|
|
261
|
+
},
|
|
302
262
|
}
|
|
303
263
|
},
|
|
304
264
|
}
|
|
305
265
|
|
|
306
266
|
export const createTxFactory =
|
|
307
267
|
({
|
|
308
|
-
ordinalsEnabled,
|
|
309
268
|
getFeeEstimator,
|
|
310
269
|
allowUnconfirmedRbfEnabledUtxos,
|
|
311
270
|
utxosDescendingOrder,
|
|
@@ -325,7 +284,6 @@ export const createTxFactory =
|
|
|
325
284
|
customFee,
|
|
326
285
|
isSendAll,
|
|
327
286
|
bumpTxId,
|
|
328
|
-
nft,
|
|
329
287
|
isExchange,
|
|
330
288
|
isRbfAllowed,
|
|
331
289
|
taprootInputWitnessSize,
|
|
@@ -351,13 +309,11 @@ export const createTxFactory =
|
|
|
351
309
|
customFee,
|
|
352
310
|
isSendAll,
|
|
353
311
|
bumpTxId,
|
|
354
|
-
nft,
|
|
355
312
|
isExchange,
|
|
356
313
|
isRbfAllowed,
|
|
357
314
|
taprootInputWitnessSize,
|
|
358
315
|
accountState,
|
|
359
316
|
feeData,
|
|
360
|
-
ordinalsEnabled,
|
|
361
317
|
getFeeEstimator,
|
|
362
318
|
allowUnconfirmedRbfEnabledUtxos,
|
|
363
319
|
utxosDescendingOrder,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { retry } from '@exodus/simple-retry'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Broadcast a signed transaction to the Bitcoin network with retry logic
|
|
5
|
+
* @param {Object} params
|
|
6
|
+
* @param {Object} params.asset - The asset object
|
|
7
|
+
* @param {Buffer} params.rawTx - The raw signed transaction
|
|
8
|
+
* @returns {Promise<void>}
|
|
9
|
+
* @throws {Error} With finalError=true for non-retryable errors
|
|
10
|
+
*/
|
|
11
|
+
export async function broadcastTransaction({ asset, rawTx }) {
|
|
12
|
+
const broadcastTxWithRetry = retry(
|
|
13
|
+
async (rawTxHex) => {
|
|
14
|
+
try {
|
|
15
|
+
return await asset.api.broadcastTx(rawTxHex)
|
|
16
|
+
} catch (e) {
|
|
17
|
+
// Mark certain errors as final (non-retryable)
|
|
18
|
+
if (
|
|
19
|
+
/missing inputs/i.test(e.message) ||
|
|
20
|
+
/absurdly-high-fee/.test(e.message) ||
|
|
21
|
+
/too-long-mempool-chain/.test(e.message) ||
|
|
22
|
+
/txn-mempool-conflict/.test(e.message) ||
|
|
23
|
+
/tx-size/.test(e.message) ||
|
|
24
|
+
/txn-already-in-mempool/.test(e.message)
|
|
25
|
+
) {
|
|
26
|
+
e.finalError = true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
throw e
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{ delayTimesMs: ['10s'] }
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const rawTxHex = rawTx.toString('hex')
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await broadcastTxWithRetry(rawTxHex)
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err.message.includes('txn-already-in-mempool')) {
|
|
41
|
+
// Not an error, transaction is already broadcast
|
|
42
|
+
console.log('Transaction is already in the mempool.')
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throw err
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/tx-send/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as defaultBitcoinjsLib from '@exodus/bitcoinjs'
|
|
2
|
-
import { Address } from '@exodus/models'
|
|
3
|
-
import { retry } from '@exodus/simple-retry'
|
|
4
2
|
import assert from 'minimalistic-assert'
|
|
5
3
|
|
|
6
|
-
import { serializeCurrency } from '../fee/fee-utils.js'
|
|
7
4
|
import { createTxFactory } from '../tx-create/create-tx.js'
|
|
8
5
|
import { getBlockHeight } from '../tx-create/tx-create-utils.js'
|
|
6
|
+
import { broadcastTransaction } from './broadcast-tx.js'
|
|
7
|
+
import { updateAccountState, updateTransactionLog } from './update-state.js'
|
|
9
8
|
|
|
10
9
|
const getSize = (tx) => {
|
|
11
10
|
if (typeof tx.size === 'number') return tx.size
|
|
@@ -109,13 +108,11 @@ const getPrepareSendTransaction = async ({
|
|
|
109
108
|
changeAddressType,
|
|
110
109
|
getFeeEstimator,
|
|
111
110
|
options,
|
|
112
|
-
ordinalsEnabled,
|
|
113
111
|
rbfEnabled,
|
|
114
112
|
utxosDescendingOrder,
|
|
115
113
|
walletAccount,
|
|
116
114
|
}) => {
|
|
117
115
|
const createTx = createTxFactory({
|
|
118
|
-
ordinalsEnabled,
|
|
119
116
|
getFeeEstimator,
|
|
120
117
|
allowUnconfirmedRbfEnabledUtxos,
|
|
121
118
|
utxosDescendingOrder,
|
|
@@ -142,21 +139,20 @@ export const createAndBroadcastTXFactory =
|
|
|
142
139
|
getFeeEstimator,
|
|
143
140
|
getSizeAndChangeScript = getSizeAndChangeScriptFactory(), // for decred customizations
|
|
144
141
|
allowUnconfirmedRbfEnabledUtxos,
|
|
145
|
-
ordinalsEnabled = false,
|
|
146
142
|
utxosDescendingOrder,
|
|
147
143
|
assetClientInterface,
|
|
148
144
|
changeAddressType,
|
|
149
145
|
}) =>
|
|
150
146
|
async ({ asset, walletAccount, address, amount, options }) => {
|
|
151
147
|
// Prepare transaction
|
|
152
|
-
const { bumpTxId,
|
|
148
|
+
const { bumpTxId, isExchange, isRbfAllowed = true } = options
|
|
153
149
|
|
|
154
150
|
const assetName = asset.name
|
|
155
151
|
const feeData = await assetClientInterface.getFeeConfig({ assetName })
|
|
156
152
|
const accountState = await assetClientInterface.getAccountState({ assetName, walletAccount })
|
|
157
153
|
const insightClient = asset.baseAsset.insightClient
|
|
158
154
|
|
|
159
|
-
const rbfEnabled = feeData.rbfEnabled && !isExchange && isRbfAllowed
|
|
155
|
+
const rbfEnabled = feeData.rbfEnabled && !isExchange && isRbfAllowed
|
|
160
156
|
|
|
161
157
|
// blockHeight
|
|
162
158
|
const blockHeight = await getBlockHeight({ assetName, insightClient })
|
|
@@ -171,31 +167,17 @@ export const createAndBroadcastTXFactory =
|
|
|
171
167
|
changeAddressType,
|
|
172
168
|
getFeeEstimator,
|
|
173
169
|
options,
|
|
174
|
-
ordinalsEnabled,
|
|
175
170
|
rbfEnabled,
|
|
176
171
|
utxosDescendingOrder,
|
|
177
172
|
walletAccount,
|
|
178
173
|
})
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
inscriptionIds,
|
|
184
|
-
ourAddress,
|
|
185
|
-
receiveAddress,
|
|
186
|
-
sendAmount,
|
|
187
|
-
fee,
|
|
188
|
-
usableUtxos,
|
|
189
|
-
selectedUtxos,
|
|
190
|
-
transferOrdinalsUtxos,
|
|
191
|
-
replaceTx,
|
|
192
|
-
sendOutput,
|
|
193
|
-
changeOutput,
|
|
194
|
-
unsignedTx,
|
|
195
|
-
} = transactionDescriptor
|
|
174
|
+
|
|
175
|
+
const { unsignedTx, fee, metadata } = transactionDescriptor
|
|
176
|
+
const { sendAmount, usableUtxos, replaceTx, sendOutput, changeOutput } = metadata
|
|
177
|
+
|
|
196
178
|
const outputs = unsignedTx.txData.outputs
|
|
197
179
|
|
|
198
|
-
address =
|
|
180
|
+
address = metadata.address
|
|
199
181
|
|
|
200
182
|
// Sign transaction
|
|
201
183
|
const { rawTx, txId, tx } = await signTransaction({
|
|
@@ -206,44 +188,18 @@ export const createAndBroadcastTXFactory =
|
|
|
206
188
|
})
|
|
207
189
|
|
|
208
190
|
// Broadcast transaction
|
|
209
|
-
const broadcastTxWithRetry = retry(
|
|
210
|
-
async (rawTx) => {
|
|
211
|
-
try {
|
|
212
|
-
return await asset.api.broadcastTx(rawTx)
|
|
213
|
-
} catch (e) {
|
|
214
|
-
if (
|
|
215
|
-
/missing inputs/i.test(e.message) ||
|
|
216
|
-
/absurdly-high-fee/.test(e.message) ||
|
|
217
|
-
/too-long-mempool-chain/.test(e.message) ||
|
|
218
|
-
/txn-mempool-conflict/.test(e.message) ||
|
|
219
|
-
/tx-size/.test(e.message) ||
|
|
220
|
-
/txn-already-in-mempool/.test(e.message)
|
|
221
|
-
) {
|
|
222
|
-
e.finalError = true
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
throw e
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
{ delayTimesMs: ['10s'] }
|
|
229
|
-
)
|
|
230
|
-
|
|
231
191
|
try {
|
|
232
|
-
await
|
|
192
|
+
await broadcastTransaction({ asset, rawTx })
|
|
233
193
|
} catch (err) {
|
|
234
|
-
if (err.message
|
|
235
|
-
// It's not an error, we must ignore it.
|
|
236
|
-
console.log('Transaction is already in the mempool.')
|
|
237
|
-
} else if (/insight broadcast http error.*missing inputs/i.test(err.message)) {
|
|
194
|
+
if (/insight broadcast http error.*missing inputs/i.test(err.message)) {
|
|
238
195
|
err.txInfo = JSON.stringify({
|
|
239
196
|
amount: sendAmount.toDefaultString({ unit: true }),
|
|
240
|
-
fee: ((fee && fee.toDefaultString({ unit: true })) || 0).toString(),
|
|
197
|
+
fee: ((fee && fee.toDefaultString({ unit: true })) || 0).toString(),
|
|
241
198
|
allUtxos: usableUtxos.toJSON(),
|
|
242
199
|
})
|
|
243
|
-
throw err
|
|
244
|
-
} else {
|
|
245
|
-
throw err
|
|
246
200
|
}
|
|
201
|
+
|
|
202
|
+
throw err
|
|
247
203
|
}
|
|
248
204
|
|
|
249
205
|
function findUtxoIndex(output) {
|
|
@@ -263,135 +219,35 @@ export const createAndBroadcastTXFactory =
|
|
|
263
219
|
const changeUtxoIndex = findUtxoIndex(changeOutput)
|
|
264
220
|
const sendUtxoIndex = findUtxoIndex(sendOutput)
|
|
265
221
|
|
|
266
|
-
const {
|
|
267
|
-
|
|
268
|
-
// for ordinals, used to allow users spending change utxos even when unconfirmed and ordinals are unknown
|
|
269
|
-
const knownBalanceUtxoIds = accountState.knownBalanceUtxoIds || []
|
|
270
|
-
let remainingUtxos = usableUtxos.difference(selectedUtxos)
|
|
271
|
-
if (changeUtxoIndex !== -1) {
|
|
272
|
-
const address = Address.create(ourAddress.address, ourAddress.meta)
|
|
273
|
-
const changeUtxo = {
|
|
274
|
-
txId,
|
|
275
|
-
address,
|
|
276
|
-
vout: changeUtxoIndex,
|
|
277
|
-
script,
|
|
278
|
-
value: change,
|
|
279
|
-
confirmations: 0,
|
|
280
|
-
rbfEnabled,
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
knownBalanceUtxoIds.push(`${changeUtxo.txId}:${changeUtxo.vout}`.toLowerCase())
|
|
284
|
-
remainingUtxos = remainingUtxos.addUtxo(changeUtxo)
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (replaceTx) {
|
|
288
|
-
remainingUtxos = remainingUtxos.difference(remainingUtxos.getTxIdUtxos(replaceTx.txId))
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const remainingOrdinalsUtxos = transferOrdinalsUtxos
|
|
292
|
-
? currentOrdinalsUtxos.difference(transferOrdinalsUtxos)
|
|
293
|
-
: currentOrdinalsUtxos
|
|
294
|
-
|
|
295
|
-
await assetClientInterface.updateAccountState({
|
|
296
|
-
assetName,
|
|
297
|
-
walletAccount,
|
|
298
|
-
newData: {
|
|
299
|
-
utxos: remainingUtxos,
|
|
300
|
-
ordinalsUtxos: remainingOrdinalsUtxos,
|
|
301
|
-
knownBalanceUtxoIds,
|
|
302
|
-
},
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
const config = await assetClientInterface.getAssetConfig?.({
|
|
222
|
+
const { size } = await updateAccountState({
|
|
223
|
+
assetClientInterface,
|
|
306
224
|
assetName,
|
|
307
225
|
walletAccount,
|
|
226
|
+
accountState,
|
|
227
|
+
txId,
|
|
228
|
+
metadata,
|
|
229
|
+
tx,
|
|
230
|
+
rawTx,
|
|
231
|
+
changeUtxoIndex,
|
|
232
|
+
getSizeAndChangeScript,
|
|
233
|
+
rbfEnabled,
|
|
308
234
|
})
|
|
309
|
-
const walletAddressObjects = await assetClientInterface.getReceiveAddresses({
|
|
310
|
-
walletAccount,
|
|
311
|
-
assetName,
|
|
312
|
-
multiAddressMode: config?.multiAddressMode ?? true,
|
|
313
|
-
})
|
|
314
|
-
// There are two cases of bumping, replacing or chaining a self-send.
|
|
315
|
-
// If we have a bumpTxId, but we aren't replacing, then it is a self-send.
|
|
316
|
-
const selfSend = bumpTxId
|
|
317
|
-
? !replaceTx
|
|
318
|
-
: walletAddressObjects.some((receiveAddress) => String(receiveAddress) === String(address))
|
|
319
|
-
|
|
320
|
-
const displayReceiveAddress = asset.address.displayAddress?.(receiveAddress) || receiveAddress
|
|
321
|
-
|
|
322
|
-
const receivers = bumpTxId
|
|
323
|
-
? replaceTx
|
|
324
|
-
? replaceTx.data.sent
|
|
325
|
-
: []
|
|
326
|
-
: replaceTx
|
|
327
|
-
? [
|
|
328
|
-
...replaceTx.data.sent,
|
|
329
|
-
{ address: displayReceiveAddress, amount: serializeCurrency(amount, asset.currency) },
|
|
330
|
-
]
|
|
331
|
-
: [{ address: displayReceiveAddress, amount: serializeCurrency(amount, asset.currency) }]
|
|
332
|
-
|
|
333
|
-
const calculateCoinAmount = () => {
|
|
334
|
-
if (selfSend) {
|
|
335
|
-
return asset.currency.ZERO
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (nft) {
|
|
339
|
-
return transferOrdinalsUtxos.value.abs().negate()
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return totalAmount.abs().negate()
|
|
343
|
-
}
|
|
344
235
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
assetName: asset.name,
|
|
236
|
+
await updateTransactionLog({
|
|
237
|
+
asset,
|
|
238
|
+
assetClientInterface,
|
|
349
239
|
walletAccount,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
data: {
|
|
360
|
-
sent: selfSend ? [] : receivers,
|
|
361
|
-
rbfEnabled,
|
|
362
|
-
feePerKB: size ? fee.div(size / 1000).toBaseNumber() : undefined,
|
|
363
|
-
changeAddress: changeOutput ? ourAddress : undefined,
|
|
364
|
-
blockHeight,
|
|
365
|
-
blocksSeen: 0,
|
|
366
|
-
inputs: selectedUtxos.toJSON(),
|
|
367
|
-
replacedTxId: replaceTx ? replaceTx.txId : undefined,
|
|
368
|
-
nftId: nft ? `${assetName}:${nft.tokenId}` : undefined, // it allows BE to load the nft info while the nft is in transit
|
|
369
|
-
inscriptionsIndexed: ordinalsEnabled ? true : undefined,
|
|
370
|
-
sentInscriptions: inscriptionIds
|
|
371
|
-
? inscriptionIds.map((inscriptionId) => {
|
|
372
|
-
return {
|
|
373
|
-
inscriptionId,
|
|
374
|
-
offset: 0,
|
|
375
|
-
value: 0,
|
|
376
|
-
}
|
|
377
|
-
})
|
|
378
|
-
: undefined,
|
|
379
|
-
},
|
|
380
|
-
},
|
|
381
|
-
],
|
|
240
|
+
txId,
|
|
241
|
+
fee,
|
|
242
|
+
metadata,
|
|
243
|
+
address,
|
|
244
|
+
amount,
|
|
245
|
+
bumpTxId,
|
|
246
|
+
size,
|
|
247
|
+
blockHeight,
|
|
248
|
+
rbfEnabled,
|
|
382
249
|
})
|
|
383
250
|
|
|
384
|
-
// If we are replacing the tx, add the replacedBy info to the previous tx to update UI
|
|
385
|
-
// Also, clone the personal note and attach it to the new tx so it is not lost
|
|
386
|
-
if (replaceTx) {
|
|
387
|
-
replaceTx.data.replacedBy = txId
|
|
388
|
-
await assetClientInterface.updateTxLogAndNotify({
|
|
389
|
-
assetName,
|
|
390
|
-
walletAccount,
|
|
391
|
-
txs: [replaceTx],
|
|
392
|
-
})
|
|
393
|
-
}
|
|
394
|
-
|
|
395
251
|
return {
|
|
396
252
|
txId,
|
|
397
253
|
sendUtxoIndex,
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Address } from '@exodus/models'
|
|
2
|
+
|
|
3
|
+
import { serializeCurrency } from '../fee/fee-utils.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Update account state after transaction is broadcast
|
|
7
|
+
* @param {Object} params
|
|
8
|
+
* @param {Object} params.assetClientInterface - Asset client interface
|
|
9
|
+
* @param {string} params.assetName - Name of the asset
|
|
10
|
+
* @param {Object} params.walletAccount - Wallet account
|
|
11
|
+
* @param {Object} params.accountState - Current account state
|
|
12
|
+
* @param {string} params.txId - Transaction ID
|
|
13
|
+
* @param {Object} params.metadata - Transaction metadata
|
|
14
|
+
* @param {Object} params.tx - Signed transaction object
|
|
15
|
+
* @param {Buffer} params.rawTx - Raw transaction
|
|
16
|
+
* @param {number} params.changeUtxoIndex - Index of change output
|
|
17
|
+
* @param {Object} params.changeOutput - Change output details
|
|
18
|
+
* @param {Object} params.getSizeAndChangeScript - Function to get size and script
|
|
19
|
+
* @param {boolean} params.rbfEnabled - Whether RBF is enabled
|
|
20
|
+
*/
|
|
21
|
+
export async function updateAccountState({
|
|
22
|
+
assetClientInterface,
|
|
23
|
+
assetName,
|
|
24
|
+
walletAccount,
|
|
25
|
+
accountState,
|
|
26
|
+
txId,
|
|
27
|
+
metadata,
|
|
28
|
+
tx,
|
|
29
|
+
rawTx,
|
|
30
|
+
changeUtxoIndex,
|
|
31
|
+
getSizeAndChangeScript,
|
|
32
|
+
rbfEnabled,
|
|
33
|
+
}) {
|
|
34
|
+
const { usableUtxos, selectedUtxos, replaceTx, change, ourAddress } = metadata
|
|
35
|
+
|
|
36
|
+
// Get change script and size
|
|
37
|
+
const { script, size } = getSizeAndChangeScript({
|
|
38
|
+
assetName,
|
|
39
|
+
tx,
|
|
40
|
+
rawTx,
|
|
41
|
+
changeUtxoIndex,
|
|
42
|
+
txId,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Update remaining UTXOs
|
|
46
|
+
const knownBalanceUtxoIds = accountState.knownBalanceUtxoIds || []
|
|
47
|
+
let remainingUtxos = usableUtxos.difference(selectedUtxos)
|
|
48
|
+
|
|
49
|
+
// Add change UTXO if present
|
|
50
|
+
if (changeUtxoIndex !== -1 && ourAddress) {
|
|
51
|
+
const address = Address.create(ourAddress.address || ourAddress, ourAddress.meta || {})
|
|
52
|
+
const changeUtxo = {
|
|
53
|
+
txId,
|
|
54
|
+
address,
|
|
55
|
+
vout: changeUtxoIndex,
|
|
56
|
+
script,
|
|
57
|
+
value: change,
|
|
58
|
+
confirmations: 0,
|
|
59
|
+
rbfEnabled,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
knownBalanceUtxoIds.push(`${changeUtxo.txId}:${changeUtxo.vout}`.toLowerCase())
|
|
63
|
+
remainingUtxos = remainingUtxos.addUtxo(changeUtxo)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Remove replaced transaction UTXOs if present
|
|
67
|
+
if (replaceTx) {
|
|
68
|
+
remainingUtxos = remainingUtxos.difference(remainingUtxos.getTxIdUtxos(replaceTx.txId))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Update account state
|
|
72
|
+
await assetClientInterface.updateAccountState({
|
|
73
|
+
assetName,
|
|
74
|
+
walletAccount,
|
|
75
|
+
newData: {
|
|
76
|
+
utxos: remainingUtxos,
|
|
77
|
+
knownBalanceUtxoIds,
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return { size }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Update transaction log with new transaction
|
|
86
|
+
* @param {Object} params
|
|
87
|
+
* @param {Object} params.asset - Asset object
|
|
88
|
+
* @param {Object} params.assetClientInterface - Asset client interface
|
|
89
|
+
* @param {Object} params.walletAccount - Wallet account
|
|
90
|
+
* @param {string} params.txId - Transaction ID
|
|
91
|
+
* @param {Object} params.fee - Transaction fee
|
|
92
|
+
* @param {Object} params.metadata - Transaction metadata
|
|
93
|
+
* @param {string} params.address - Recipient address
|
|
94
|
+
* @param {Object} params.amount - Transaction amount (for regular sends)
|
|
95
|
+
* @param {string} params.bumpTxId - ID of transaction being bumped (if applicable)
|
|
96
|
+
* @param {number} params.size - Transaction size
|
|
97
|
+
* @param {number} params.blockHeight - Block height
|
|
98
|
+
* @param {boolean} params.rbfEnabled - Whether RBF is enabled
|
|
99
|
+
*/
|
|
100
|
+
export async function updateTransactionLog({
|
|
101
|
+
asset,
|
|
102
|
+
assetClientInterface,
|
|
103
|
+
walletAccount,
|
|
104
|
+
txId,
|
|
105
|
+
fee,
|
|
106
|
+
metadata,
|
|
107
|
+
address,
|
|
108
|
+
amount,
|
|
109
|
+
bumpTxId,
|
|
110
|
+
size,
|
|
111
|
+
blockHeight,
|
|
112
|
+
rbfEnabled,
|
|
113
|
+
}) {
|
|
114
|
+
const { totalAmount, selectedUtxos, replaceTx, changeOutput, ourAddress } = metadata
|
|
115
|
+
const assetName = asset.name
|
|
116
|
+
|
|
117
|
+
// Check if this is a self-send
|
|
118
|
+
const config = await assetClientInterface.getAssetConfig?.({
|
|
119
|
+
assetName,
|
|
120
|
+
walletAccount,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const walletAddressObjects = await assetClientInterface.getReceiveAddresses({
|
|
124
|
+
walletAccount,
|
|
125
|
+
assetName,
|
|
126
|
+
multiAddressMode: config?.multiAddressMode ?? true,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// There are two cases of bumping, replacing or chaining a self-send.
|
|
130
|
+
// If we have a bumpTxId, but we aren't replacing, then it is a self-send.
|
|
131
|
+
const selfSend = bumpTxId
|
|
132
|
+
? !replaceTx
|
|
133
|
+
: walletAddressObjects.some((receiveAddress) => String(receiveAddress) === String(address))
|
|
134
|
+
|
|
135
|
+
const displayReceiveAddress = asset.address.displayAddress?.(address) || address
|
|
136
|
+
|
|
137
|
+
// Build receivers list
|
|
138
|
+
const receivers = bumpTxId
|
|
139
|
+
? replaceTx
|
|
140
|
+
? replaceTx.data.sent
|
|
141
|
+
: []
|
|
142
|
+
: replaceTx
|
|
143
|
+
? [
|
|
144
|
+
...replaceTx.data.sent,
|
|
145
|
+
{ address: displayReceiveAddress, amount: serializeCurrency(amount, asset.currency) },
|
|
146
|
+
]
|
|
147
|
+
: [{ address: displayReceiveAddress, amount: serializeCurrency(amount, asset.currency) }]
|
|
148
|
+
|
|
149
|
+
// Calculate coin amount
|
|
150
|
+
const coinAmount = selfSend ? asset.currency.ZERO : totalAmount.abs().negate()
|
|
151
|
+
|
|
152
|
+
// Update transaction log
|
|
153
|
+
await assetClientInterface.updateTxLogAndNotify({
|
|
154
|
+
assetName,
|
|
155
|
+
walletAccount,
|
|
156
|
+
txs: [
|
|
157
|
+
{
|
|
158
|
+
txId,
|
|
159
|
+
confirmations: 0,
|
|
160
|
+
coinAmount,
|
|
161
|
+
coinName: asset.name,
|
|
162
|
+
feeAmount: fee,
|
|
163
|
+
feeCoinName: assetName,
|
|
164
|
+
selfSend,
|
|
165
|
+
data: {
|
|
166
|
+
sent: selfSend ? [] : receivers,
|
|
167
|
+
rbfEnabled,
|
|
168
|
+
feePerKB: size ? fee.div(size / 1000).toBaseNumber() : undefined,
|
|
169
|
+
changeAddress: changeOutput ? ourAddress : undefined,
|
|
170
|
+
blockHeight,
|
|
171
|
+
blocksSeen: 0,
|
|
172
|
+
inputs: selectedUtxos.toJSON(),
|
|
173
|
+
replacedTxId: replaceTx ? replaceTx.txId : undefined,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// If replacing a transaction, update the old one
|
|
180
|
+
if (replaceTx) {
|
|
181
|
+
replaceTx.data.replacedBy = txId
|
|
182
|
+
await assetClientInterface.updateTxLogAndNotify({
|
|
183
|
+
assetName,
|
|
184
|
+
walletAccount,
|
|
185
|
+
txs: [replaceTx],
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|