@exodus/bitcoin-api 2.8.1 → 2.9.1
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 +2 -2
- package/src/account-state.js +4 -0
- package/src/fee/can-bump-tx.js +5 -1
- package/src/fee/fee-utils.js +14 -5
- package/src/fee/get-fee-resolver.js +6 -1
- package/src/fee/utxo-selector.js +17 -4
- package/src/tx-log/bitcoin-monitor-scanner.js +2 -0
- package/src/tx-log/bitcoin-monitor.js +32 -25
- package/src/tx-send/index.js +5 -1
- package/src/tx-utils.js +6 -2
- package/src/unconfirmed-ancestor-data.js +15 -29
- package/src/utxos-utils.js +41 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"@scure/btc-signer": "^1.1.0",
|
|
43
43
|
"jest-when": "^3.5.1"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "1f96673b2977e792373f9f3c4eb98c778817b2b6"
|
|
46
46
|
}
|
package/src/account-state.js
CHANGED
|
@@ -6,12 +6,16 @@ export function createAccountState({ asset, ordinalsEnabled = false, brc20Enable
|
|
|
6
6
|
})
|
|
7
7
|
const defaults = {
|
|
8
8
|
utxos: empty,
|
|
9
|
+
mem: {
|
|
10
|
+
unconfirmedTxAncestor: {},
|
|
11
|
+
},
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
if (ordinalsEnabled) {
|
|
12
15
|
defaults.ordinalsUtxos = empty
|
|
13
16
|
defaults.knownBalanceUtxoIds = []
|
|
14
17
|
defaults.mustAvoidUtxoIds = []
|
|
18
|
+
defaults.additionalInscriptions = []
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
if (brc20Enabled) {
|
package/src/fee/can-bump-tx.js
CHANGED
|
@@ -4,6 +4,7 @@ import { findUnconfirmedSentRbfTxs } from '../tx-utils'
|
|
|
4
4
|
import { getUsableUtxos, getUtxos } from '../utxos-utils'
|
|
5
5
|
|
|
6
6
|
import { BumpType } from '@exodus/bitcoin-lib'
|
|
7
|
+
import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data'
|
|
7
8
|
|
|
8
9
|
export const ASSET_NAMES = ['bitcoin', 'bitcoinregtest', 'bitcointestnet']
|
|
9
10
|
|
|
@@ -51,13 +52,14 @@ const _canBumpTx = ({
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
const utxos = getUtxos({ accountState, asset })
|
|
54
|
-
|
|
55
|
+
const unconfirmedTxAncestor = getUnconfirmedTxAncestorMap({ accountState })
|
|
55
56
|
const usableUtxos = getUsableUtxos({
|
|
56
57
|
asset,
|
|
57
58
|
utxos,
|
|
58
59
|
feeData,
|
|
59
60
|
txSet,
|
|
60
61
|
allowUnconfirmedRbfEnabledUtxos,
|
|
62
|
+
unconfirmedTxAncestor,
|
|
61
63
|
})
|
|
62
64
|
if (usableUtxos.value.isZero) return { errorMessage: 'insufficient funds' }
|
|
63
65
|
|
|
@@ -86,6 +88,7 @@ const _canBumpTx = ({
|
|
|
86
88
|
receiveAddress: null,
|
|
87
89
|
getFeeEstimator,
|
|
88
90
|
allowUnconfirmedRbfEnabledUtxos,
|
|
91
|
+
unconfirmedTxAncestor,
|
|
89
92
|
})
|
|
90
93
|
if (replaceTx) return { bumpType: BumpType.RBF, bumpFee: fee.sub(replaceTx.feeAmount) }
|
|
91
94
|
}
|
|
@@ -98,6 +101,7 @@ const _canBumpTx = ({
|
|
|
98
101
|
getFeeEstimator,
|
|
99
102
|
mustSpendUtxos: changeUtxos,
|
|
100
103
|
allowUnconfirmedRbfEnabledUtxos,
|
|
104
|
+
unconfirmedTxAncestor,
|
|
101
105
|
})
|
|
102
106
|
|
|
103
107
|
return fee ? { bumpType: BumpType.CPFP } : { errorMessage: 'insufficient funds' }
|
package/src/fee/fee-utils.js
CHANGED
|
@@ -5,7 +5,7 @@ import assert from 'minimalistic-assert'
|
|
|
5
5
|
|
|
6
6
|
export const isHex = (s) => typeof s === 'string' && /[0-9a-f]*/.test(s.toLowerCase())
|
|
7
7
|
|
|
8
|
-
export function getExtraFee({ asset, inputs, feePerKB }) {
|
|
8
|
+
export function getExtraFee({ asset, inputs, feePerKB, unconfirmedTxAncestor }) {
|
|
9
9
|
let extraFee = 0
|
|
10
10
|
// Add extra fee to unconfirmed bitcoin utxos to allow transaction to CPFP ancestors
|
|
11
11
|
if (
|
|
@@ -18,7 +18,12 @@ export function getExtraFee({ asset, inputs, feePerKB }) {
|
|
|
18
18
|
)
|
|
19
19
|
const txIds = new Set(utxos.map(({ txId }) => txId))
|
|
20
20
|
for (const txId of txIds) {
|
|
21
|
-
extraFee += resolveExtraFeeOfTx({
|
|
21
|
+
extraFee += resolveExtraFeeOfTx({
|
|
22
|
+
assetName: asset.name,
|
|
23
|
+
feeRate,
|
|
24
|
+
txId,
|
|
25
|
+
unconfirmedTxAncestor,
|
|
26
|
+
})
|
|
22
27
|
}
|
|
23
28
|
extraFee = Math.ceil(extraFee)
|
|
24
29
|
}
|
|
@@ -26,9 +31,13 @@ export function getExtraFee({ asset, inputs, feePerKB }) {
|
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
export default function createDefaultFeeEstimator(getSize) {
|
|
29
|
-
return (asset, feePerKB, options) => {
|
|
30
|
-
return ({
|
|
31
|
-
|
|
34
|
+
return (asset, feePerKB, options = {}) => {
|
|
35
|
+
return ({
|
|
36
|
+
inputs = options.inputs,
|
|
37
|
+
outputs = options.outputs,
|
|
38
|
+
unconfirmedTxAncestor = options.unconfirmedTxAncestor,
|
|
39
|
+
} = {}) => {
|
|
40
|
+
const extraFee = getExtraFee({ asset, inputs, feePerKB, unconfirmedTxAncestor })
|
|
32
41
|
// Yes, it's suppose to be '1000' and not '1024'
|
|
33
42
|
// https://bitcoin.stackexchange.com/questions/24000/a-fee-is-added-per-kilobyte-of-data-that-means-1000-bytes-or-1024
|
|
34
43
|
const size = getSize(asset, inputs, outputs, options)
|
|
@@ -3,6 +3,7 @@ import { getUtxosData } from './utxo-selector'
|
|
|
3
3
|
import { findUnconfirmedSentRbfTxs } from '../tx-utils'
|
|
4
4
|
import { getUsableUtxos, getUtxos } from '../utxos-utils'
|
|
5
5
|
import { canBumpTx } from './can-bump-tx'
|
|
6
|
+
import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data'
|
|
6
7
|
|
|
7
8
|
export class GetFeeResolver {
|
|
8
9
|
#getFeeEstimator
|
|
@@ -10,7 +11,8 @@ export class GetFeeResolver {
|
|
|
10
11
|
|
|
11
12
|
constructor({ getFeeEstimator, allowUnconfirmedRbfEnabledUtxos }) {
|
|
12
13
|
assert(getFeeEstimator, 'getFeeEstimator must be provided')
|
|
13
|
-
this.#getFeeEstimator = (asset, { feePerKB }) =>
|
|
14
|
+
this.#getFeeEstimator = (asset, { feePerKB, ...options }) =>
|
|
15
|
+
getFeeEstimator(asset, feePerKB, options)
|
|
14
16
|
this.#allowUnconfirmedRbfEnabledUtxos = allowUnconfirmedRbfEnabledUtxos
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -92,12 +94,14 @@ export class GetFeeResolver {
|
|
|
92
94
|
assert(txSet, 'txSet must be provided')
|
|
93
95
|
|
|
94
96
|
const utxos = getUtxos({ accountState, asset })
|
|
97
|
+
const unconfirmedTxAncestor = getUnconfirmedTxAncestorMap({ accountState })
|
|
95
98
|
|
|
96
99
|
const usableUtxos = getUsableUtxos({
|
|
97
100
|
asset,
|
|
98
101
|
utxos,
|
|
99
102
|
feeData,
|
|
100
103
|
txSet,
|
|
104
|
+
unconfirmedTxAncestor,
|
|
101
105
|
})
|
|
102
106
|
const replaceableTxs = findUnconfirmedSentRbfTxs(txSet)
|
|
103
107
|
|
|
@@ -113,6 +117,7 @@ export class GetFeeResolver {
|
|
|
113
117
|
isSendAll: isSendAll,
|
|
114
118
|
getFeeEstimator: this.#getFeeEstimator,
|
|
115
119
|
allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
|
|
120
|
+
unconfirmedTxAncestor,
|
|
116
121
|
})
|
|
117
122
|
}
|
|
118
123
|
|
package/src/fee/utxo-selector.js
CHANGED
|
@@ -29,6 +29,7 @@ export const selectUtxos = ({
|
|
|
29
29
|
disableReplacement = false,
|
|
30
30
|
mustSpendUtxos,
|
|
31
31
|
allowUnconfirmedRbfEnabledUtxos,
|
|
32
|
+
unconfirmedTxAncestor,
|
|
32
33
|
inscriptionIds, // for each inscription transfer, we need to calculate one more input and one more output
|
|
33
34
|
}) => {
|
|
34
35
|
const resolvedReceiveAddresses = getBestReceiveAddresses({
|
|
@@ -52,7 +53,7 @@ export const selectUtxos = ({
|
|
|
52
53
|
? 'P2WPKH'
|
|
53
54
|
: 'P2PKH'
|
|
54
55
|
|
|
55
|
-
const feeEstimator = getFeeEstimator(asset, { feePerKB: feeRate })
|
|
56
|
+
const feeEstimator = getFeeEstimator(asset, { feePerKB: feeRate, unconfirmedTxAncestor })
|
|
56
57
|
const { currency } = asset
|
|
57
58
|
if (!amount) amount = currency.ZERO
|
|
58
59
|
|
|
@@ -78,7 +79,7 @@ export const selectUtxos = ({
|
|
|
78
79
|
feePerKB = feeRate
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
const replaceFeeEstimator = getFeeEstimator(asset, { feePerKB })
|
|
82
|
+
const replaceFeeEstimator = getFeeEstimator(asset, { feePerKB, unconfirmedTxAncestor })
|
|
82
83
|
// how to avoid replace tx inputs when inputs are ordinals? !!!!
|
|
83
84
|
const inputs = UtxoCollection.fromJSON(tx.data.inputs, { currency })
|
|
84
85
|
const outputs = isSendAll
|
|
@@ -118,7 +119,10 @@ export const selectUtxos = ({
|
|
|
118
119
|
|
|
119
120
|
// Add a new UTXO and recalculate the fee
|
|
120
121
|
additionalUtxos = additionalUtxos.addUtxo(confirmedUtxosArray.shift())
|
|
121
|
-
fee = replaceFeeEstimator({
|
|
122
|
+
fee = replaceFeeEstimator({
|
|
123
|
+
inputs: inputs.union(additionalUtxos),
|
|
124
|
+
outputs,
|
|
125
|
+
})
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
if (isSendAll || replaceTxAmount.add(additionalUtxos.value).gte(amount.add(fee))) {
|
|
@@ -224,6 +228,7 @@ export const getUtxosData = ({
|
|
|
224
228
|
mustSpendUtxos,
|
|
225
229
|
allowUnconfirmedRbfEnabledUtxos,
|
|
226
230
|
inscriptionIds,
|
|
231
|
+
unconfirmedTxAncestor,
|
|
227
232
|
}) => {
|
|
228
233
|
const { selectedUtxos, replaceTx, fee } = selectUtxos({
|
|
229
234
|
asset,
|
|
@@ -237,6 +242,7 @@ export const getUtxosData = ({
|
|
|
237
242
|
disableReplacement,
|
|
238
243
|
mustSpendUtxos,
|
|
239
244
|
allowUnconfirmedRbfEnabledUtxos,
|
|
245
|
+
unconfirmedTxAncestor,
|
|
240
246
|
inscriptionIds,
|
|
241
247
|
})
|
|
242
248
|
|
|
@@ -252,7 +258,14 @@ export const getUtxosData = ({
|
|
|
252
258
|
const spendableBalance = spendableUtxos.value
|
|
253
259
|
|
|
254
260
|
const extraFee = selectedUtxos
|
|
255
|
-
? asset.currency.baseUnit(
|
|
261
|
+
? asset.currency.baseUnit(
|
|
262
|
+
getExtraFee({
|
|
263
|
+
asset,
|
|
264
|
+
inputs: selectedUtxos,
|
|
265
|
+
feePerKB: feeRate,
|
|
266
|
+
unconfirmedTxAncestor,
|
|
267
|
+
})
|
|
268
|
+
)
|
|
256
269
|
: asset.currency.ZERO
|
|
257
270
|
|
|
258
271
|
const availableBalance = spendableBalance.sub(resolvedFee).clampLowerZero()
|
|
@@ -603,6 +603,7 @@ export class BitcoinMonitorScanner {
|
|
|
603
603
|
ordinalAddress,
|
|
604
604
|
knownBalanceUtxoIds: latestAccountState.knownBalanceUtxoIds,
|
|
605
605
|
mustAvoidUtxoIds: latestAccountState.mustAvoidUtxoIds,
|
|
606
|
+
additionalInscriptions: latestAccountState.additionalInscriptions,
|
|
606
607
|
})
|
|
607
608
|
: {}
|
|
608
609
|
|
|
@@ -674,6 +675,7 @@ export class BitcoinMonitorScanner {
|
|
|
674
675
|
ordinalAddress: ordinalAddress,
|
|
675
676
|
knownBalanceUtxoIds: latestAccountState.knownBalanceUtxoIds,
|
|
676
677
|
mustAvoidUtxoIds: latestAccountState.mustAvoidUtxoIds,
|
|
678
|
+
additionalInscriptions: latestAccountState.additionalInscriptions,
|
|
677
679
|
})
|
|
678
680
|
|
|
679
681
|
return {
|
|
@@ -3,7 +3,7 @@ import { isEmpty, isEqual, pickBy } from 'lodash'
|
|
|
3
3
|
|
|
4
4
|
import { BaseMonitor } from '@exodus/asset-lib'
|
|
5
5
|
import InsightWSClient from '../insight-api-client/ws'
|
|
6
|
-
import {
|
|
6
|
+
import { resolveUnconfirmedAncestorData } from '../unconfirmed-ancestor-data'
|
|
7
7
|
import { BitcoinMonitorScanner } from './bitcoin-monitor-scanner'
|
|
8
8
|
import { normalizeInsightConfig, toWSUrl } from '../insight-api-client/util'
|
|
9
9
|
import ms from 'ms'
|
|
@@ -153,32 +153,35 @@ export class Monitor extends BaseMonitor {
|
|
|
153
153
|
walletAccount,
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
+
const newData = {}
|
|
156
157
|
if (utxos || ordinalsUtxos) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
newData: pickBy(
|
|
158
|
+
Object.assign(
|
|
159
|
+
newData,
|
|
160
|
+
pickBy(
|
|
161
161
|
{
|
|
162
162
|
utxos,
|
|
163
163
|
ordinalsUtxos,
|
|
164
164
|
},
|
|
165
165
|
Boolean
|
|
166
|
-
)
|
|
167
|
-
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
if (txsToUpdate.length) {
|
|
171
|
-
const accountState = await aci.getAccountState({ assetName, walletAccount })
|
|
172
171
|
await this.updateTxLog({ assetName, walletAccount, logItems: txsToUpdate })
|
|
173
|
-
if (['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
walletAccount,
|
|
177
|
-
accountState,
|
|
172
|
+
if (utxos && ['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) {
|
|
173
|
+
const unconfirmedTxAncestor = await resolveUnconfirmedAncestorData({
|
|
174
|
+
utxos,
|
|
178
175
|
insightClient: this.#insightClient,
|
|
179
176
|
})
|
|
177
|
+
newData.mem = { unconfirmedTxAncestor }
|
|
180
178
|
}
|
|
181
179
|
}
|
|
180
|
+
await aci.updateAccountState({
|
|
181
|
+
assetName,
|
|
182
|
+
walletAccount,
|
|
183
|
+
newData,
|
|
184
|
+
})
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
|
|
@@ -228,20 +231,19 @@ export class Monitor extends BaseMonitor {
|
|
|
228
231
|
walletAccount,
|
|
229
232
|
refresh,
|
|
230
233
|
})
|
|
231
|
-
const accountState = await aci.getAccountState({ assetName, walletAccount })
|
|
232
234
|
|
|
235
|
+
const newData = {}
|
|
233
236
|
if (utxos || ordinalsUtxos)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
newData: pickBy(
|
|
237
|
+
Object.assign(
|
|
238
|
+
newData,
|
|
239
|
+
pickBy(
|
|
238
240
|
{
|
|
239
241
|
utxos,
|
|
240
242
|
ordinalsUtxos,
|
|
241
243
|
},
|
|
242
244
|
Boolean
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
+
)
|
|
246
|
+
)
|
|
245
247
|
|
|
246
248
|
if (!isEmpty(changedUnusedAddressIndexes)) {
|
|
247
249
|
// Only for mobile atm, browser and hydra calculates from the latest txLogs
|
|
@@ -264,14 +266,19 @@ export class Monitor extends BaseMonitor {
|
|
|
264
266
|
}
|
|
265
267
|
|
|
266
268
|
// Move to after tick
|
|
267
|
-
if (['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
walletAccount,
|
|
271
|
-
accountState,
|
|
269
|
+
if (utxos && ['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) {
|
|
270
|
+
const unconfirmedTxAncestor = await resolveUnconfirmedAncestorData({
|
|
271
|
+
utxos,
|
|
272
272
|
insightClient: this.#insightClient,
|
|
273
273
|
})
|
|
274
|
+
newData.mem = { unconfirmedTxAncestor }
|
|
274
275
|
}
|
|
276
|
+
|
|
277
|
+
await aci.updateAccountState({
|
|
278
|
+
assetName,
|
|
279
|
+
walletAccount,
|
|
280
|
+
newData,
|
|
281
|
+
})
|
|
275
282
|
}
|
|
276
283
|
|
|
277
284
|
#logWsStatus = (message, ...args) => {
|
package/src/tx-send/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from '../utxos-utils'
|
|
23
23
|
|
|
24
24
|
import * as defaultBitcoinjsLib from '@exodus/bitcoinjs-lib'
|
|
25
|
+
import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data'
|
|
25
26
|
|
|
26
27
|
const ASSETS_SUPPORTED_BIP_174 = [
|
|
27
28
|
'bitcoin',
|
|
@@ -209,11 +210,13 @@ export const createAndBroadcastTXFactory = ({
|
|
|
209
210
|
const insightClient = asset.baseAsset.insightClient
|
|
210
211
|
const currency = asset.currency
|
|
211
212
|
const feeData = await assetClientInterface.getFeeConfig({ assetName })
|
|
213
|
+
const unconfirmedTxAncestor = getUnconfirmedTxAncestorMap({ accountState })
|
|
212
214
|
const usableUtxos = getUsableUtxos({
|
|
213
215
|
asset,
|
|
214
216
|
utxos: getUtxos({ accountState, asset }),
|
|
215
217
|
feeData,
|
|
216
218
|
txSet,
|
|
219
|
+
unconfirmedTxAncestor,
|
|
217
220
|
})
|
|
218
221
|
|
|
219
222
|
let replaceableTxs = findUnconfirmedSentRbfTxs(txSet)
|
|
@@ -257,9 +260,10 @@ export const createAndBroadcastTXFactory = ({
|
|
|
257
260
|
feeRate: customFee || feeRate,
|
|
258
261
|
receiveAddress: receiveAddress,
|
|
259
262
|
isSendAll: resolvedIsSendAll,
|
|
260
|
-
getFeeEstimator: (asset, { feePerKB }) => getFeeEstimator(asset, feePerKB),
|
|
263
|
+
getFeeEstimator: (asset, { feePerKB, ...options }) => getFeeEstimator(asset, feePerKB, options),
|
|
261
264
|
mustSpendUtxos: utxosToBump,
|
|
262
265
|
allowUnconfirmedRbfEnabledUtxos,
|
|
266
|
+
unconfirmedTxAncestor,
|
|
263
267
|
inscriptionIds,
|
|
264
268
|
})
|
|
265
269
|
|
package/src/tx-utils.js
CHANGED
|
@@ -11,14 +11,18 @@ export const findUnconfirmedSentRbfTxs = (txSet) =>
|
|
|
11
11
|
tx.data.inputs
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
export const findLargeUnconfirmedTxs = ({
|
|
14
|
+
export const findLargeUnconfirmedTxs = ({ txSet, feeRate, maxFee, unconfirmedTxAncestor }) =>
|
|
15
15
|
!txSet
|
|
16
16
|
? new Set()
|
|
17
17
|
: new Set(
|
|
18
18
|
Array.from(txSet)
|
|
19
19
|
.filter((tx) => {
|
|
20
20
|
if (!tx.pending) return false
|
|
21
|
-
const extraFee = resolveExtraFeeOfTx({
|
|
21
|
+
const extraFee = resolveExtraFeeOfTx({
|
|
22
|
+
feeRate,
|
|
23
|
+
txId: tx.txId,
|
|
24
|
+
unconfirmedTxAncestor,
|
|
25
|
+
})
|
|
22
26
|
return extraFee && extraFee > maxFee
|
|
23
27
|
})
|
|
24
28
|
.map((tx) => tx.txId)
|
|
@@ -1,46 +1,32 @@
|
|
|
1
|
-
import
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
})
|
|
11
|
-
for (const [txId, stored] of dataMap) {
|
|
12
|
-
if (stored.walletAccount === walletAccount && stored.assetName === asset.name)
|
|
13
|
-
dataMap.delete(txId)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const utxos = getUtxos({ accountState, asset })
|
|
17
|
-
.toArray()
|
|
18
|
-
.filter((data) => {
|
|
19
|
-
return !data.confirmations || data.confirmations <= 0
|
|
20
|
-
})
|
|
21
|
-
const txIds = new Set(utxos.map(({ txId }) => txId))
|
|
3
|
+
export async function resolveUnconfirmedAncestorData({ utxos, insightClient }) {
|
|
4
|
+
assert(utxos, 'utxos is required')
|
|
5
|
+
assert(insightClient, 'insightClient is required')
|
|
6
|
+
const dataMap = Object.create(null)
|
|
7
|
+
const unconfirmedUtxos = utxos.toArray().filter((data) => {
|
|
8
|
+
return !data.confirmations || data.confirmations <= 0
|
|
9
|
+
})
|
|
10
|
+
const txIds = new Set(unconfirmedUtxos.map(({ txId }) => txId))
|
|
22
11
|
for (const txId of txIds) {
|
|
23
12
|
try {
|
|
24
13
|
const { size, fees } = await insightClient.fetchUnconfirmedAncestorData(txId)
|
|
25
14
|
if (size !== 0) {
|
|
26
|
-
dataMap
|
|
15
|
+
dataMap[txId] = { size, fees }
|
|
27
16
|
}
|
|
28
17
|
} catch (e) {
|
|
29
18
|
console.warn(e)
|
|
30
19
|
}
|
|
31
20
|
}
|
|
21
|
+
return dataMap
|
|
32
22
|
}
|
|
33
23
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
if (stored?.assetName === assetName) {
|
|
37
|
-
return { size: stored.size, fees: stored.fees }
|
|
38
|
-
}
|
|
39
|
-
return undefined
|
|
24
|
+
export function getUnconfirmedTxAncestorMap({ accountState }) {
|
|
25
|
+
return accountState?.mem?.unconfirmedTxAncestor || Object.create(null)
|
|
40
26
|
}
|
|
41
27
|
|
|
42
|
-
export function resolveExtraFeeOfTx({
|
|
43
|
-
const data =
|
|
28
|
+
export function resolveExtraFeeOfTx({ feeRate, txId, unconfirmedTxAncestor }) {
|
|
29
|
+
const data = unconfirmedTxAncestor?.[txId]
|
|
44
30
|
if (!data) return 0
|
|
45
31
|
const { fees, size } = data
|
|
46
32
|
// Get the difference in fee rate between ancestors and current estimate
|
package/src/utxos-utils.js
CHANGED
|
@@ -122,17 +122,54 @@ function isOrdinalUtxo({
|
|
|
122
122
|
return hasOrdinals
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
export function mergeAdditionalInscriptions({ allUtxos, additionalInscriptions }) {
|
|
126
|
+
return UtxoCollection.fromArray(
|
|
127
|
+
allUtxos.toArray().map((utxo) => {
|
|
128
|
+
const inscriptions = additionalInscriptions
|
|
129
|
+
.filter((additionalInscription) => {
|
|
130
|
+
const forUtxo =
|
|
131
|
+
additionalInscription.vout === utxo.vout && additionalInscription.txId === utxo.txId
|
|
132
|
+
if (forUtxo) {
|
|
133
|
+
// avoid duplicated
|
|
134
|
+
return !utxo.inscriptions?.find(
|
|
135
|
+
(existingInscription) =>
|
|
136
|
+
existingInscription.inscriptionId === additionalInscription.inscriptionId
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
return forUtxo
|
|
140
|
+
})
|
|
141
|
+
.map((additionalInscription) => ({
|
|
142
|
+
inscriptionId: additionalInscription.inscriptionId,
|
|
143
|
+
offset: additionalInscription.offset || 0,
|
|
144
|
+
}))
|
|
145
|
+
if (inscriptions.length) {
|
|
146
|
+
utxo.inscriptions = [...(utxo.inscriptions || []), ...inscriptions]
|
|
147
|
+
}
|
|
148
|
+
return utxo
|
|
149
|
+
}),
|
|
150
|
+
{
|
|
151
|
+
currency: allUtxos.currency,
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
125
156
|
export function partitionUtxos({
|
|
126
157
|
allUtxos,
|
|
127
158
|
ordinalsEnabled,
|
|
128
159
|
knownBalanceUtxoIds,
|
|
129
160
|
ordinalAddress,
|
|
130
161
|
mustAvoidUtxoIds,
|
|
162
|
+
additionalInscriptions,
|
|
131
163
|
}) {
|
|
132
164
|
assert(allUtxos, 'allUtxos is required')
|
|
133
165
|
if (ordinalsEnabled) assert(ordinalAddress, 'ordinalAddress is required')
|
|
166
|
+
|
|
167
|
+
const expandedAllUtxos = ordinalsEnabled
|
|
168
|
+
? mergeAdditionalInscriptions({ allUtxos, additionalInscriptions })
|
|
169
|
+
: allUtxos
|
|
170
|
+
|
|
134
171
|
return {
|
|
135
|
-
utxos:
|
|
172
|
+
utxos: expandedAllUtxos.filter(
|
|
136
173
|
(utxo) =>
|
|
137
174
|
!isOrdinalUtxo({
|
|
138
175
|
utxo,
|
|
@@ -142,7 +179,7 @@ export function partitionUtxos({
|
|
|
142
179
|
mustAvoidUtxoIds,
|
|
143
180
|
})
|
|
144
181
|
),
|
|
145
|
-
ordinalsUtxos:
|
|
182
|
+
ordinalsUtxos: expandedAllUtxos.filter((utxo) =>
|
|
146
183
|
isOrdinalUtxo({
|
|
147
184
|
utxo,
|
|
148
185
|
ordinalsEnabled,
|
|
@@ -178,7 +215,7 @@ function filterDustUtxos({ utxos, feeData }) {
|
|
|
178
215
|
return utxos
|
|
179
216
|
}
|
|
180
217
|
|
|
181
|
-
export function getUsableUtxos({ asset, utxos, feeData, txSet }) {
|
|
218
|
+
export function getUsableUtxos({ asset, utxos, feeData, txSet, unconfirmedTxAncestor }) {
|
|
182
219
|
assert(asset, 'asset is required')
|
|
183
220
|
assert(utxos, 'utxos is required')
|
|
184
221
|
assert(feeData, 'feeData is required')
|
|
@@ -191,10 +228,10 @@ export function getUsableUtxos({ asset, utxos, feeData, txSet }) {
|
|
|
191
228
|
assert(typeof maxFee === 'number' && !Number.isNaN(maxFee), 'maxFee must be a number')
|
|
192
229
|
|
|
193
230
|
const largeUnconfirmedTxs = findLargeUnconfirmedTxs({
|
|
194
|
-
assetName: asset.name,
|
|
195
231
|
txSet,
|
|
196
232
|
feeRate,
|
|
197
233
|
maxFee,
|
|
234
|
+
unconfirmedTxAncestor,
|
|
198
235
|
})
|
|
199
236
|
const confirmedAndSmallUtxos =
|
|
200
237
|
largeUnconfirmedTxs.size === 0
|