@exodus/bitcoin-api 2.7.0 → 2.7.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.7.0",
3
+ "version": "2.7.2",
4
4
  "description": "Exodus bitcoin-api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -43,5 +43,5 @@
43
43
  "@scure/btc-signer": "^1.1.0",
44
44
  "jest-when": "^3.5.1"
45
45
  },
46
- "gitHead": "5c306496cb4086ff823640e8eadb0d9750ac3683"
46
+ "gitHead": "fc4a81292bf22690343a09522c159773a1606b7f"
47
47
  }
@@ -2,6 +2,7 @@
2
2
  import urlJoin from 'url-join'
3
3
  import qs from 'querystring'
4
4
  import delay from 'delay'
5
+ import { isEmpty } from 'lodash'
5
6
 
6
7
  const getTextFromResponse = async (response) => {
7
8
  try {
@@ -105,13 +106,22 @@ export default class InsightAPIClient {
105
106
  const url = urlJoin(this._baseURL, `/tx/${encodedTxId}`)
106
107
  const response = await fetch(url)
107
108
 
108
- // change in https://github.com/jprichardson/exodus-rn/pull/4336 may break Insight compatibility
109
- // we're probably past the point of just spinning up an Insight server and have it plug n' play for Magnifier
110
-
111
109
  if (response.status === 404) return null
112
110
  return response.json()
113
111
  }
114
112
 
113
+ async fetchTxObject(txId) {
114
+ const url = urlJoin(this._baseURL, `/fulltx?${new URLSearchParams({ hash: txId })}`)
115
+ const response = await fetch(url)
116
+
117
+ if (response.status === 404) return null
118
+ const object = await response.json()
119
+ if (isEmpty(object)) {
120
+ return null
121
+ }
122
+ return object
123
+ }
124
+
115
125
  async fetchRawTx(txId) {
116
126
  const encodedTxId = encodeURIComponent(txId)
117
127
  const url = urlJoin(this._baseURL, `/rawtx/${encodedTxId}`)
@@ -1,11 +1,12 @@
1
1
  import { orderTxs } from '../insight-api-client/util'
2
2
  import { Address, UtxoCollection } from '@exodus/models'
3
- import { isEqual, compact, uniq } from 'lodash'
3
+ import { compact, isEqual, uniq } from 'lodash'
4
4
  import ms from 'ms'
5
5
  import assert from 'minimalistic-assert'
6
6
  import { isChangeAddress, isReceiveAddress } from '../address-utils'
7
7
  import { getOrdinalsUtxos, getUtxos, partitionUtxos } from '../utxos-utils'
8
8
  import { getOrdinalAddress } from '../ordinals-utils'
9
+ import { indexOrdinalUnconfirmedTx } from './ordinals-indexer-utils'
9
10
 
10
11
  // Time to check whether to drop a sent tx
11
12
  const SENT_TIME_TO_DROP = ms('2m')
@@ -191,8 +192,30 @@ export class BitcoinMonitorScanner {
191
192
  promises.push(promise)
192
193
  }
193
194
 
194
- const txArrays = await Promise.all(promises)
195
- return txArrays.reduce((total, some) => total.concat(some), [])
195
+ const insightTxs = (await Promise.all(promises)).reduce(
196
+ (total, some) => total.concat(some),
197
+ []
198
+ )
199
+ if (!this.#ordinalsEnabled) {
200
+ return insightTxs
201
+ }
202
+ return Promise.all(
203
+ insightTxs.map((tx) => {
204
+ try {
205
+ return indexOrdinalUnconfirmedTx({
206
+ tx,
207
+ currency: this.#asset.currency,
208
+ insightClient,
209
+ })
210
+ } catch (e) {
211
+ console.warn(
212
+ `Could not index ${asset.name} ordinal tx ${tx.txid} for wallet account ${walletAccount}. message: ${e.message}`,
213
+ e
214
+ )
215
+ return tx
216
+ }
217
+ })
218
+ )
196
219
  }
197
220
 
198
221
  const gapSearchParameters = newChains.map(({ purpose, chain }) => {
@@ -244,6 +267,7 @@ export class BitcoinMonitorScanner {
244
267
  const receiveAddress = await this.#assetClientInterface.getReceiveAddressObject({
245
268
  assetName,
246
269
  walletAccount,
270
+ useCache: true,
247
271
  })
248
272
  if (!addresses.includes(receiveAddress.toString())) {
249
273
  addresses.push(receiveAddress.toString())
@@ -86,6 +86,7 @@ export class Monitor extends BaseMonitor {
86
86
  multiAddressMode: true,
87
87
  assetName: this.asset.name,
88
88
  walletAccount,
89
+ useCache: true,
89
90
  }),
90
91
  ])
91
92
  )
@@ -0,0 +1,53 @@
1
+ import { memoizeLruCache } from '@exodus/asset-lib'
2
+ import { cloneDeep } from 'lodash'
3
+
4
+ export const indexOutputs = ({ tx, currency }) => {
5
+ const inscriptions = []
6
+
7
+ let inputOffset = 0
8
+ for (let i = 0; i < tx.vin.length; i++) {
9
+ const vin = tx.vin[i]
10
+ const value = currency.defaultUnit(vin.value).toBaseNumber()
11
+ inscriptions.push(
12
+ ...(vin.inscriptions || []).map((i) => ({ ...i, offset: i.offset + inputOffset }))
13
+ )
14
+ inputOffset = value
15
+ }
16
+
17
+ let outputOffset = 0
18
+ for (let i = 0; i < tx.vout.length; i++) {
19
+ const vout = tx.vout[i]
20
+ const value = currency.defaultUnit(vout.value).toBaseNumber()
21
+ vout.inscriptions = inscriptions
22
+ .map((i) => ({ ...i, offset: i.offset - outputOffset }))
23
+ .filter((i) => i.offset >= 0 && i.offset < value)
24
+ outputOffset = value
25
+ }
26
+ tx.inscriptionsMemoryIndexed = true // avoids btc being spent even when the mempool index was done in memory
27
+ return tx
28
+ }
29
+
30
+ export const indexOrdinalUnconfirmedTx = memoizeLruCache(
31
+ async ({ insightClient, currency, tx }) => {
32
+ if (tx.inscriptionsIndexed || tx.inscriptionsMemoryIndexed) {
33
+ return tx
34
+ }
35
+ const copyTx = cloneDeep(tx)
36
+ await Promise.all(
37
+ copyTx.vin.map(async (vin) => {
38
+ const outputTx = await indexOrdinalUnconfirmedTx({
39
+ insightClient,
40
+ currency,
41
+ tx: await insightClient.fetchTxObject(vin.txid),
42
+ })
43
+ if (!outputTx.inscriptionsIndexed && !outputTx.inscriptionsMemoryIndexed) {
44
+ throw new Error(`Cannot index ${tx.txid}. Input tx ${outputTx.txid} is not indexed. `)
45
+ }
46
+ vin.inscriptions = outputTx.vout[vin.vout].inscriptions
47
+ })
48
+ )
49
+ return indexOutputs({ tx: copyTx, currency })
50
+ },
51
+ ({ tx }) => tx.txid,
52
+ 100
53
+ )
@@ -1,20 +1,16 @@
1
- export function extractTransaction({ psbt, skipFinalize }) {
2
- // If a dapp authored the TX, it expects a serialized PSBT response.
3
- // Note: we wouldn't be able to finalise inputs in some cases that's why we serialize before finalizing inputs.
4
-
5
- if (skipFinalize) {
6
- const rawPSBT = psbt.toBuffer()
7
-
8
- return { plainTx: { rawPSBT } }
9
- } else {
1
+ export function extractTransaction({ psbt, extract }) {
2
+ if (extract) {
10
3
  // Serialize tx
11
- psbt.finalizeAllInputs()
12
- const tx = psbt.extractTransaction()
13
- const rawTx = tx.toBuffer()
14
- const txId = tx.getId()
15
-
16
4
  // tx needs to be serializable for desktop RPC send => sign communication
17
- return { rawTx, txId, tx: serializeTx({ tx }) }
5
+ const extractedTx = psbt.extractTransaction()
6
+ return {
7
+ rawTx: extractedTx.toBuffer(),
8
+ txId: extractedTx.getId(),
9
+ tx: serializeTx({ tx: extractedTx }),
10
+ }
11
+ } else {
12
+ // Web3 transactions can't be expected to be extractable.
13
+ return { plainTx: { rawPSBT: psbt.toBuffer() } }
18
14
  }
19
15
  }
20
16
 
@@ -65,6 +65,7 @@ export function createSignWithWallet({
65
65
  // desktop / BE / mobile with bip-schnorr signing
66
66
  const signingKey = isTaprootAddress ? tweakSigner({ signer: key, ECPair, network }) : key
67
67
  await psbt.signInputAsync(index, toAsyncSigner({ keyPair: signingKey }), sigHashTypes)
68
+ psbt.finalizeInput(index)
68
69
  }
69
70
  }
70
71
  }
@@ -37,7 +37,6 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
37
37
 
38
38
  await signWithWallet(psbt, inputsToSign)
39
39
 
40
- const skipFinalize = !!unsignedTx.txData.psbtBuffer
41
- return extractTransaction({ psbt, skipFinalize })
40
+ return extractTransaction({ psbt, extract: !unsignedTx.txData.psbtBuffer })
42
41
  }
43
42
  }
@@ -39,8 +39,7 @@ export const signHardwareFactory = ({ assetName, resolvePurpose, keys, coinInfo
39
39
  accountIndex,
40
40
  })
41
41
 
42
- const skipFinalize = !!unsignedTx.txData.psbtBuffer
43
- return extractTransaction({ psbt, skipFinalize })
42
+ return extractTransaction({ psbt, extract: !unsignedTx.txData.psbtBuffer })
44
43
  }
45
44
  }
46
45
 
@@ -88,6 +87,8 @@ export function applySignatures(psbt, signatures, inputsToSign) {
88
87
  ],
89
88
  })
90
89
  }
90
+
91
+ psbt.finalizeInput(inputIndex)
91
92
  } else {
92
93
  throw new Error(
93
94
  `expected to sign for inputIndex ${inputIndex} but no signature was produced`