@exodus/bitcoin-api 2.9.0 → 2.9.2
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 +23 -7
- package/src/account-state.js +1 -0
- package/src/fee/can-bump-tx.js +1 -1
- package/src/fee/get-fee-resolver.js +1 -0
- package/src/insight-api-client/index.js +2 -1
- package/src/tx-log/bitcoin-monitor-scanner.js +2 -0
- package/src/tx-log/bitcoin-monitor.js +4 -10
- package/src/tx-utils.js +1 -8
- package/src/unconfirmed-ancestor-data.js +12 -30
- package/src/utxos-utils.js +47 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.2",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -13,34 +13,50 @@
|
|
|
13
13
|
"access": "restricted"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "jest",
|
|
17
|
-
"lint": "eslint ./src",
|
|
16
|
+
"test": "run -T jest",
|
|
17
|
+
"lint": "run -T eslint ./src",
|
|
18
18
|
"lint:fix": "yarn lint --fix"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@exodus/asset-lib": "^
|
|
21
|
+
"@exodus/asset-lib": "^4.0.0",
|
|
22
22
|
"@exodus/basic-utils": "^2.1.0",
|
|
23
23
|
"@exodus/bip-schnorr": "0.6.6-fork-1",
|
|
24
24
|
"@exodus/bip44-constants": "^195.0.0",
|
|
25
|
+
"@exodus/bitcoin-lib": "2.3.0",
|
|
25
26
|
"@exodus/bitcoinjs-lib": "^6.1.5-exodus.0",
|
|
27
|
+
"@exodus/currency": "^2.3.2",
|
|
28
|
+
"@exodus/fetch": "^1.3.0",
|
|
26
29
|
"@exodus/models": "^11.0.0",
|
|
27
30
|
"@exodus/secp256k1": "4.0.2-exodus.0",
|
|
28
31
|
"@exodus/simple-retry": "0.0.6",
|
|
29
32
|
"@exodus/timer": "^1.0.0",
|
|
30
33
|
"@noble/secp256k1": "~1.7.1",
|
|
31
34
|
"bech32": "^1.1.3",
|
|
35
|
+
"bip32-path": "^0.4.2",
|
|
36
|
+
"bn.js": "4.12.0",
|
|
37
|
+
"bs58check": "^2.1.2",
|
|
32
38
|
"coininfo": "5.1.0",
|
|
39
|
+
"create-hash": "^1.2.0",
|
|
33
40
|
"delay": "4.0.1",
|
|
34
41
|
"ecpair": "2.0.1",
|
|
42
|
+
"lodash": "^4.17.21",
|
|
43
|
+
"minimalistic-assert": "^1.0.1",
|
|
44
|
+
"ms": "^2.1.1",
|
|
45
|
+
"secp256k1": "^3.0.1",
|
|
35
46
|
"socket.io-client": "2.1.1",
|
|
36
47
|
"tiny-secp256k1": "1.1.3",
|
|
37
|
-
"url-join": "4.0.0"
|
|
48
|
+
"url-join": "4.0.0",
|
|
49
|
+
"varuint-bitcoin": "^1.1.0",
|
|
50
|
+
"wif": "^2.0.6"
|
|
38
51
|
},
|
|
39
52
|
"devDependencies": {
|
|
53
|
+
"@exodus/assets-testing": "^1.0.0",
|
|
40
54
|
"@exodus/bitcoin-meta": "^1.0.2",
|
|
41
55
|
"@scure/base": "^1.1.3",
|
|
42
56
|
"@scure/btc-signer": "^1.1.0",
|
|
43
|
-
"
|
|
57
|
+
"bigi": "^1.4.2",
|
|
58
|
+
"jest-when": "^3.5.1",
|
|
59
|
+
"safe-buffer": "^5.2.1"
|
|
44
60
|
},
|
|
45
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "b282e5087dda77c80fc63947ccc0020e6aa7db93"
|
|
46
62
|
}
|
package/src/account-state.js
CHANGED
package/src/fee/can-bump-tx.js
CHANGED
|
@@ -104,7 +104,7 @@ const _canBumpTx = ({
|
|
|
104
104
|
unconfirmedTxAncestor,
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
-
return fee ? { bumpType: BumpType.CPFP } : { errorMessage: 'insufficient funds' }
|
|
107
|
+
return fee ? { bumpType: BumpType.CPFP, bumpFee: fee } : { errorMessage: 'insufficient funds' }
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
const validateIsNumber = (number, name) => {
|
|
@@ -90,6 +90,7 @@ export class GetFeeResolver {
|
|
|
90
90
|
}) => {
|
|
91
91
|
assert(asset, 'asset must be provided')
|
|
92
92
|
assert(feeData, 'feeData must be provided')
|
|
93
|
+
assert(customFee || feeData.feePerKB, 'feePerKB must be provided')
|
|
93
94
|
assert(accountState, 'accountState must be provided')
|
|
94
95
|
assert(txSet, 'txSet must be provided')
|
|
95
96
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
/* global fetch */
|
|
2
1
|
import urlJoin from 'url-join'
|
|
3
2
|
import delay from 'delay'
|
|
4
3
|
import { isEmpty } from 'lodash'
|
|
5
4
|
|
|
5
|
+
import { fetch } from '@exodus/fetch'
|
|
6
|
+
|
|
6
7
|
const getTextFromResponse = async (response) => {
|
|
7
8
|
try {
|
|
8
9
|
const responseBody = await response.text()
|
|
@@ -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 {
|
|
@@ -168,13 +168,10 @@ export class Monitor extends BaseMonitor {
|
|
|
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)) {
|
|
172
|
+
if (utxos && ['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) {
|
|
174
173
|
const unconfirmedTxAncestor = await resolveUnconfirmedAncestorData({
|
|
175
|
-
|
|
176
|
-
accountState,
|
|
177
|
-
walletAccount,
|
|
174
|
+
utxos,
|
|
178
175
|
insightClient: this.#insightClient,
|
|
179
176
|
})
|
|
180
177
|
newData.mem = { unconfirmedTxAncestor }
|
|
@@ -269,12 +266,9 @@ export class Monitor extends BaseMonitor {
|
|
|
269
266
|
}
|
|
270
267
|
|
|
271
268
|
// Move to after tick
|
|
272
|
-
if (['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) {
|
|
273
|
-
const accountState = await aci.getAccountState({ assetName, walletAccount })
|
|
269
|
+
if (utxos && ['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) {
|
|
274
270
|
const unconfirmedTxAncestor = await resolveUnconfirmedAncestorData({
|
|
275
|
-
|
|
276
|
-
accountState,
|
|
277
|
-
walletAccount,
|
|
271
|
+
utxos,
|
|
278
272
|
insightClient: this.#insightClient,
|
|
279
273
|
})
|
|
280
274
|
newData.mem = { unconfirmedTxAncestor }
|
package/src/tx-utils.js
CHANGED
|
@@ -11,13 +11,7 @@ export const findUnconfirmedSentRbfTxs = (txSet) =>
|
|
|
11
11
|
tx.data.inputs
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
export const findLargeUnconfirmedTxs = ({
|
|
15
|
-
assetName,
|
|
16
|
-
txSet,
|
|
17
|
-
feeRate,
|
|
18
|
-
maxFee,
|
|
19
|
-
unconfirmedTxAncestor,
|
|
20
|
-
}) =>
|
|
14
|
+
export const findLargeUnconfirmedTxs = ({ txSet, feeRate, maxFee, unconfirmedTxAncestor }) =>
|
|
21
15
|
!txSet
|
|
22
16
|
? new Set()
|
|
23
17
|
: new Set(
|
|
@@ -25,7 +19,6 @@ export const findLargeUnconfirmedTxs = ({
|
|
|
25
19
|
.filter((tx) => {
|
|
26
20
|
if (!tx.pending) return false
|
|
27
21
|
const extraFee = resolveExtraFeeOfTx({
|
|
28
|
-
assetName,
|
|
29
22
|
feeRate,
|
|
30
23
|
txId: tx.txId,
|
|
31
24
|
unconfirmedTxAncestor,
|
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
2
|
|
|
3
|
-
export async function resolveUnconfirmedAncestorData({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const dataMap = getUnconfirmedTxAncestorMap({ accountState })
|
|
10
|
-
Object.entries(dataMap).forEach(([txId, stored]) => {
|
|
11
|
-
if (stored.walletAccount === walletAccount && stored.assetName === asset.name)
|
|
12
|
-
delete dataMap[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
|
|
13
9
|
})
|
|
14
|
-
|
|
15
|
-
const utxos = getUtxos({ accountState, asset })
|
|
16
|
-
.toArray()
|
|
17
|
-
.filter((data) => {
|
|
18
|
-
return !data.confirmations || data.confirmations <= 0
|
|
19
|
-
})
|
|
20
|
-
const txIds = new Set(utxos.map(({ txId }) => txId))
|
|
10
|
+
const txIds = new Set(unconfirmedUtxos.map(({ txId }) => txId))
|
|
21
11
|
for (const txId of txIds) {
|
|
22
12
|
try {
|
|
23
13
|
const { size, fees } = await insightClient.fetchUnconfirmedAncestorData(txId)
|
|
24
14
|
if (size !== 0) {
|
|
25
|
-
dataMap[txId] = {
|
|
15
|
+
dataMap[txId] = { size, fees }
|
|
26
16
|
}
|
|
27
17
|
} catch (e) {
|
|
28
18
|
console.warn(e)
|
|
@@ -32,19 +22,11 @@ export async function resolveUnconfirmedAncestorData({
|
|
|
32
22
|
}
|
|
33
23
|
|
|
34
24
|
export function getUnconfirmedTxAncestorMap({ accountState }) {
|
|
35
|
-
return accountState?.mem?.unconfirmedTxAncestor ||
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getUnconfirmedAncestorData({ txId, assetName, unconfirmedTxAncestor = {} }) {
|
|
39
|
-
const stored = unconfirmedTxAncestor[txId]
|
|
40
|
-
if (stored?.assetName === assetName) {
|
|
41
|
-
return { size: stored.size, fees: stored.fees }
|
|
42
|
-
}
|
|
43
|
-
return undefined
|
|
25
|
+
return accountState?.mem?.unconfirmedTxAncestor || Object.create(null)
|
|
44
26
|
}
|
|
45
27
|
|
|
46
|
-
export function resolveExtraFeeOfTx({
|
|
47
|
-
const data =
|
|
28
|
+
export function resolveExtraFeeOfTx({ feeRate, txId, unconfirmedTxAncestor }) {
|
|
29
|
+
const data = unconfirmedTxAncestor?.[txId]
|
|
48
30
|
if (!data) return 0
|
|
49
31
|
const { fees, size } = data
|
|
50
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,
|
|
@@ -185,13 +222,17 @@ export function getUsableUtxos({ asset, utxos, feeData, txSet, unconfirmedTxAnce
|
|
|
185
222
|
assert(txSet, 'txSet is required')
|
|
186
223
|
if (!['bitcoin', 'bitcointestnet', 'bitcoinregtest'].includes(asset.name))
|
|
187
224
|
return filterDustUtxos({ utxos, feeData })
|
|
188
|
-
|
|
225
|
+
|
|
226
|
+
assert(feeData.fastestFee, 'feeData.fastestFee is required')
|
|
227
|
+
assert(
|
|
228
|
+
typeof feeData.maxExtraCpfpFee === 'number' && !Number.isNaN(feeData.maxExtraCpfpFee),
|
|
229
|
+
' feeData.maxExtraCpfpFee must be a number'
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
const { fastestFee, maxExtraCpfpFee: maxFee } = feeData
|
|
189
233
|
const feeRate = fastestFee.toBaseNumber()
|
|
190
|
-
const maxFee = feeData.maxExtraCpfpFee
|
|
191
|
-
assert(typeof maxFee === 'number' && !Number.isNaN(maxFee), 'maxFee must be a number')
|
|
192
234
|
|
|
193
235
|
const largeUnconfirmedTxs = findLargeUnconfirmedTxs({
|
|
194
|
-
assetName: asset.name,
|
|
195
236
|
txSet,
|
|
196
237
|
feeRate,
|
|
197
238
|
maxFee,
|