@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "2.9.0",
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": "^3.7.2",
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
- "jest-when": "^3.5.1"
57
+ "bigi": "^1.4.2",
58
+ "jest-when": "^3.5.1",
59
+ "safe-buffer": "^5.2.1"
44
60
  },
45
- "gitHead": "ed03529f7a8bb4b39bfd173387f9cdd5d7207925"
61
+ "gitHead": "b282e5087dda77c80fc63947ccc0020e6aa7db93"
46
62
  }
@@ -15,6 +15,7 @@ export function createAccountState({ asset, ordinalsEnabled = false, brc20Enable
15
15
  defaults.ordinalsUtxos = empty
16
16
  defaults.knownBalanceUtxoIds = []
17
17
  defaults.mustAvoidUtxoIds = []
18
+ defaults.additionalInscriptions = []
18
19
  }
19
20
 
20
21
  if (brc20Enabled) {
@@ -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
- asset: this.asset,
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
- asset: this.asset,
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 { getUtxos } from './utxos-utils'
1
+ import assert from 'minimalistic-assert'
2
2
 
3
- export async function resolveUnconfirmedAncestorData({
4
- asset,
5
- walletAccount,
6
- accountState,
7
- insightClient,
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] = { assetName: asset.name, walletAccount, size, fees }
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({ assetName, feeRate, txId, unconfirmedTxAncestor }) {
47
- const data = getUnconfirmedAncestorData({ assetName, txId, unconfirmedTxAncestor })
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
@@ -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: allUtxos.filter(
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: allUtxos.filter((utxo) =>
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
- const { fastestFee } = feeData
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,