@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 +2 -2
- package/src/insight-api-client/index.js +13 -3
- package/src/tx-log/bitcoin-monitor-scanner.js +27 -3
- package/src/tx-log/bitcoin-monitor.js +1 -0
- package/src/tx-log/ordinals-indexer-utils.js +53 -0
- package/src/tx-sign/common.js +11 -15
- package/src/tx-sign/create-sign-with-wallet.js +1 -0
- package/src/tx-sign/default-create-tx.js +1 -2
- package/src/tx-sign/default-sign-hardware.js +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.7.
|
|
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": "
|
|
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 {
|
|
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
|
|
195
|
-
|
|
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())
|
|
@@ -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
|
+
)
|
package/src/tx-sign/common.js
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
export function extractTransaction({ psbt,
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|