@exodus/bitcoin-api 2.9.1 → 2.9.3-hotfix
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 -8
- package/src/bitcoinjs-lib/ecc/common.js +1 -1
- package/src/bitcoinjs-lib/ecc/mobile.js +3 -3
- package/src/bitcoinjs-lib/script-classify/index.js +1 -1
- package/src/btc-like-address.js +3 -4
- package/src/btc-like-keys.js +4 -0
- package/src/constants/bip44.js +2 -2
- package/src/fee/can-bump-tx.js +4 -2
- package/src/fee/fee-estimator.js +5 -1
- package/src/fee/fee-utils.js +6 -5
- package/src/fee/get-fee-resolver.js +3 -1
- package/src/fee/script-classifier.js +5 -10
- package/src/fee/utxo-selector.js +10 -3
- package/src/hash-utils.js +4 -9
- package/src/insight-api-client/index.js +16 -13
- package/src/insight-api-client/util.js +16 -15
- package/src/insight-api-client/ws.js +1 -1
- package/src/move-funds.js +17 -17
- package/src/ordinals-utils.js +2 -2
- package/src/parse-unsigned-tx.js +80 -81
- package/src/tx-log/bitcoin-monitor-scanner.js +50 -38
- package/src/tx-log/bitcoin-monitor.js +14 -13
- package/src/tx-log/ordinals-indexer-utils.js +6 -0
- package/src/tx-send/dogecoin.js +2 -2
- package/src/tx-send/index.js +426 -418
- package/src/tx-sign/common.js +9 -9
- package/src/tx-sign/create-get-key-and-purpose.js +2 -2
- package/src/tx-sign/create-sign-with-wallet.js +3 -3
- package/src/tx-sign/default-entropy.js +1 -3
- package/src/tx-sign/default-prepare-for-signing.js +16 -18
- package/src/tx-sign/default-sign-hardware.js +2 -1
- package/src/tx-sign/taproot.js +4 -0
- package/src/tx-utils.js +5 -5
- package/src/unconfirmed-ancestor-data.js +1 -0
- package/src/utxos-utils.js +20 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.3-hotfix",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -13,34 +13,49 @@
|
|
|
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
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
"bigi": "^1.4.2",
|
|
58
|
+
"jest-when": "^3.5.1",
|
|
59
|
+
"safe-buffer": "^5.2.1"
|
|
60
|
+
}
|
|
46
61
|
}
|
|
@@ -53,7 +53,7 @@ export const mobileEcc = {
|
|
|
53
53
|
try {
|
|
54
54
|
schnorr.verify(message, publicKey, signature)
|
|
55
55
|
return true
|
|
56
|
-
} catch
|
|
56
|
+
} catch {
|
|
57
57
|
return false
|
|
58
58
|
}
|
|
59
59
|
},
|
|
@@ -75,7 +75,7 @@ export const mobileEcc = {
|
|
|
75
75
|
try {
|
|
76
76
|
// temp solution secp256k1 does not actually verify the value range, only the data length
|
|
77
77
|
return isPoint(Buffer.from(publicKey))
|
|
78
|
-
} catch
|
|
78
|
+
} catch {
|
|
79
79
|
return false
|
|
80
80
|
}
|
|
81
81
|
},
|
|
@@ -88,7 +88,7 @@ export const mobileEcc = {
|
|
|
88
88
|
try {
|
|
89
89
|
// temp solution secp256k1 does not actually verify the value range, only the data length
|
|
90
90
|
return isPoint(Buffer.from(toPubKey(publicKey)))
|
|
91
|
-
} catch
|
|
91
|
+
} catch {
|
|
92
92
|
return false
|
|
93
93
|
}
|
|
94
94
|
},
|
package/src/btc-like-address.js
CHANGED
|
@@ -34,9 +34,8 @@ export const createBtcLikeAddress = ({
|
|
|
34
34
|
// NOTE: We only support version zero witness programs
|
|
35
35
|
if (decoded.words[0] !== 0) return false
|
|
36
36
|
// Ensure correct length
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} catch (e) {
|
|
37
|
+
return bech32.fromWords(decoded.words.slice(1)).length === length
|
|
38
|
+
} catch {
|
|
40
39
|
return false
|
|
41
40
|
}
|
|
42
41
|
}
|
|
@@ -55,7 +54,7 @@ export const createBtcLikeAddress = ({
|
|
|
55
54
|
const network = coinInfo.toBitcoinJS()
|
|
56
55
|
bitcoinjsLibFork.payments.p2tr({ address: addr, network })
|
|
57
56
|
return true
|
|
58
|
-
} catch
|
|
57
|
+
} catch {
|
|
59
58
|
return false
|
|
60
59
|
}
|
|
61
60
|
})) ||
|
package/src/btc-like-keys.js
CHANGED
|
@@ -103,18 +103,22 @@ export const createBtcLikeKeys = ({
|
|
|
103
103
|
assert(encodePublicPurpose44, errorMessage)
|
|
104
104
|
return encodePublicPurpose44(publicKey)
|
|
105
105
|
}
|
|
106
|
+
|
|
106
107
|
if (purpose === 49) {
|
|
107
108
|
assert(encodeNestedP2WPKH, errorMessage)
|
|
108
109
|
return encodeNestedP2WPKH(publicKey)
|
|
109
110
|
}
|
|
111
|
+
|
|
110
112
|
if (purpose === 84) {
|
|
111
113
|
assert(encodePublicBech32, errorMessage)
|
|
112
114
|
return encodePublicBech32(publicKey)
|
|
113
115
|
}
|
|
116
|
+
|
|
114
117
|
if (purpose === 86) {
|
|
115
118
|
assert(encodePublicTaproot, errorMessage)
|
|
116
119
|
return encodePublicTaproot(publicKey)
|
|
117
120
|
}
|
|
121
|
+
|
|
118
122
|
throw new Error(errorMessage)
|
|
119
123
|
}
|
|
120
124
|
|
package/src/constants/bip44.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// back compatibility. Use @exodus/bip44-constants/by-ticker instead
|
|
2
|
-
|
|
3
|
-
export default
|
|
2
|
+
|
|
3
|
+
export { default } from '@exodus/bip44-constants/by-ticker'
|
package/src/fee/can-bump-tx.js
CHANGED
|
@@ -42,7 +42,9 @@ const _canBumpTx = ({
|
|
|
42
42
|
const feePerKB = tx.data.feePerKB ? asset.currency.baseUnit(tx.data.feePerKB) : undefined
|
|
43
43
|
if (!feePerKB) {
|
|
44
44
|
return { errorMessage: 'fee rate is high enough' }
|
|
45
|
-
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (feeData.nextBlockMinimumFee) {
|
|
46
48
|
if (feePerKB.gt(feeData.nextBlockMinimumFee.mul(1e3))) {
|
|
47
49
|
return { errorMessage: 'fee rate is high enough' }
|
|
48
50
|
}
|
|
@@ -104,7 +106,7 @@ const _canBumpTx = ({
|
|
|
104
106
|
unconfirmedTxAncestor,
|
|
105
107
|
})
|
|
106
108
|
|
|
107
|
-
return fee ? { bumpType: BumpType.CPFP } : { errorMessage: 'insufficient funds' }
|
|
109
|
+
return fee ? { bumpType: BumpType.CPFP, bumpFee: fee } : { errorMessage: 'insufficient funds' }
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
const validateIsNumber = (number, name) => {
|
package/src/fee/fee-estimator.js
CHANGED
|
@@ -60,8 +60,9 @@ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
|
|
|
60
60
|
|
|
61
61
|
return (asset, inputs, outputs, { compressed = true } = {}) => {
|
|
62
62
|
if (inputs instanceof UtxoCollection) {
|
|
63
|
-
inputs =
|
|
63
|
+
inputs = [...inputs].map((utxo) => utxo.script || null)
|
|
64
64
|
}
|
|
65
|
+
|
|
65
66
|
const assetName = asset.name
|
|
66
67
|
// other bitcoin-like assets
|
|
67
68
|
const baseSize =
|
|
@@ -103,6 +104,7 @@ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
|
|
|
103
104
|
address: output,
|
|
104
105
|
})
|
|
105
106
|
}
|
|
107
|
+
|
|
106
108
|
assert(
|
|
107
109
|
supportedTypes.includes(scriptType),
|
|
108
110
|
`Only ${supportedTypes.join(', ')} outputs supported right now`
|
|
@@ -132,6 +134,7 @@ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
|
|
|
132
134
|
signatureLength
|
|
133
135
|
)
|
|
134
136
|
}
|
|
137
|
+
|
|
135
138
|
if ([P2TR].includes(utxoScriptType)) {
|
|
136
139
|
// Only the 64 byte Schnorr signature is present for Taproot Key-Path spend
|
|
137
140
|
const signatureLength = 64
|
|
@@ -163,4 +166,5 @@ const getFeeEstimatorFactory = ({ defaultOutputType, addressApi }) => {
|
|
|
163
166
|
const getSize = getSizeFactory({ defaultOutputType, addressApi })
|
|
164
167
|
return createDefaultFeeEstimator(getSize)
|
|
165
168
|
}
|
|
169
|
+
|
|
166
170
|
export default getFeeEstimatorFactory
|
package/src/fee/fee-utils.js
CHANGED
|
@@ -3,7 +3,7 @@ import { resolveExtraFeeOfTx } from '../unconfirmed-ancestor-data'
|
|
|
3
3
|
import { UtxoCollection } from '@exodus/models'
|
|
4
4
|
import assert from 'minimalistic-assert'
|
|
5
5
|
|
|
6
|
-
export const isHex = (s) => typeof s === 'string' && /[
|
|
6
|
+
export const isHex = (s) => typeof s === 'string' && /[\da-f]*/.test(s.toLowerCase())
|
|
7
7
|
|
|
8
8
|
export function getExtraFee({ asset, inputs, feePerKB, unconfirmedTxAncestor }) {
|
|
9
9
|
let extraFee = 0
|
|
@@ -13,9 +13,7 @@ export function getExtraFee({ asset, inputs, feePerKB, unconfirmedTxAncestor })
|
|
|
13
13
|
(inputs instanceof UtxoCollection || !Array.isArray(inputs))
|
|
14
14
|
) {
|
|
15
15
|
const feeRate = feePerKB.toBaseNumber() / 1000
|
|
16
|
-
const utxos =
|
|
17
|
-
({ confirmations }) => !confirmations || confirmations <= 0
|
|
18
|
-
)
|
|
16
|
+
const utxos = [...inputs].filter(({ confirmations }) => !confirmations || confirmations <= 0)
|
|
19
17
|
const txIds = new Set(utxos.map(({ txId }) => txId))
|
|
20
18
|
for (const txId of txIds) {
|
|
21
19
|
extraFee += resolveExtraFeeOfTx({
|
|
@@ -25,8 +23,10 @@ export function getExtraFee({ asset, inputs, feePerKB, unconfirmedTxAncestor })
|
|
|
25
23
|
unconfirmedTxAncestor,
|
|
26
24
|
})
|
|
27
25
|
}
|
|
26
|
+
|
|
28
27
|
extraFee = Math.ceil(extraFee)
|
|
29
28
|
}
|
|
29
|
+
|
|
30
30
|
return extraFee
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -46,11 +46,12 @@ export default function createDefaultFeeEstimator(getSize) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
+
|
|
49
50
|
export function parseCurrency(val, currency) {
|
|
50
51
|
assert(currency instanceof UnitType, 'Currency must be supples as a UnitType')
|
|
51
52
|
|
|
52
53
|
if (isNumberUnit(val)) return val // TODO: consider checking if the unitType.equals(currency) (if currency is object)
|
|
53
54
|
|
|
54
55
|
if (typeof val === 'string') return currency.parse(val)
|
|
55
|
-
|
|
56
|
+
return currency.parse(val.value + ' ' + val.unit)
|
|
56
57
|
}
|
|
@@ -33,6 +33,7 @@ export class GetFeeResolver {
|
|
|
33
33
|
assert(!isSendAll, 'isSendAll must not be provided when nft is provided!!!')
|
|
34
34
|
assert(!brc20, 'brc20 must not be provided when nft is provided!!!')
|
|
35
35
|
}
|
|
36
|
+
|
|
36
37
|
if (brc20) {
|
|
37
38
|
// assert(!amount, 'amount must not be provided when brc20 is provided!!!')
|
|
38
39
|
assert(!isSendAll, 'isSendAll must not be provided when brc20 is provided!!!')
|
|
@@ -90,6 +91,7 @@ export class GetFeeResolver {
|
|
|
90
91
|
}) => {
|
|
91
92
|
assert(asset, 'asset must be provided')
|
|
92
93
|
assert(feeData, 'feeData must be provided')
|
|
94
|
+
assert(customFee || feeData.feePerKB, 'feePerKB must be provided')
|
|
93
95
|
assert(accountState, 'accountState must be provided')
|
|
94
96
|
assert(txSet, 'txSet must be provided')
|
|
95
97
|
|
|
@@ -114,7 +116,7 @@ export class GetFeeResolver {
|
|
|
114
116
|
feeRate: feePerKB,
|
|
115
117
|
receiveAddress,
|
|
116
118
|
inscriptionIds,
|
|
117
|
-
isSendAll
|
|
119
|
+
isSendAll,
|
|
118
120
|
getFeeEstimator: this.#getFeeEstimator,
|
|
119
121
|
allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
|
|
120
122
|
unconfirmedTxAncestor,
|
|
@@ -9,12 +9,7 @@ const { P2PKH, P2SH, P2WPKH, P2WSH, P2TR } = scriptClassify.types
|
|
|
9
9
|
const cacheSize = 1000
|
|
10
10
|
const maxSize = 30
|
|
11
11
|
const hashStringIfTooBig = (str) =>
|
|
12
|
-
str.length > maxSize
|
|
13
|
-
? createHash('sha256')
|
|
14
|
-
.update(str)
|
|
15
|
-
.digest('hex')
|
|
16
|
-
.slice(0, maxSize)
|
|
17
|
-
: str
|
|
12
|
+
str.length > maxSize ? createHash('sha256').update(str).digest('hex').slice(0, maxSize) : str
|
|
18
13
|
|
|
19
14
|
export const scriptClassifierFactory = ({ addressApi }) => {
|
|
20
15
|
assert(addressApi, 'addressApi is required')
|
|
@@ -36,10 +31,10 @@ export const scriptClassifierFactory = ({ addressApi }) => {
|
|
|
36
31
|
assert(assetName, 'assetName is required')
|
|
37
32
|
assert(assetName, address, 'address is required')
|
|
38
33
|
if (addressApi.isP2PKH(address)) return P2PKH
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
if (addressApi.isP2SH(address)) return P2SH
|
|
35
|
+
if (addressApi.isP2WPKH && addressApi.isP2WPKH(address)) return P2WPKH
|
|
36
|
+
if (addressApi.isP2TR && addressApi.isP2TR(address)) return P2TR
|
|
37
|
+
if (addressApi.isP2WSH && addressApi.isP2WSH(address)) return P2WSH
|
|
43
38
|
return classifyScriptHex({
|
|
44
39
|
assetName,
|
|
45
40
|
classifyOutput,
|
package/src/fee/utxo-selector.js
CHANGED
|
@@ -10,6 +10,7 @@ const getBestReceiveAddresses = ({ asset, receiveAddress, inscriptionIds }) => {
|
|
|
10
10
|
if (inscriptionIds) {
|
|
11
11
|
return receiveAddress || 'P2TR'
|
|
12
12
|
}
|
|
13
|
+
|
|
13
14
|
if (receiveAddress === null) {
|
|
14
15
|
return null
|
|
15
16
|
}
|
|
@@ -68,7 +69,7 @@ export const selectUtxos = ({
|
|
|
68
69
|
(replaceableTxs.length === 1 && confirmedUtxosArray.length === usableUtxos.size - 1))
|
|
69
70
|
|
|
70
71
|
if (canReplace) {
|
|
71
|
-
for (
|
|
72
|
+
for (const tx of replaceableTxs) {
|
|
72
73
|
const changeUtxos = usableUtxos.getTxIdUtxos(tx.txId)
|
|
73
74
|
// Don't replace a tx that has already been spent
|
|
74
75
|
if (tx.data.changeAddress && changeUtxos.size === 0) continue
|
|
@@ -88,7 +89,7 @@ export const selectUtxos = ({
|
|
|
88
89
|
...tx.data.sent.map(({ address }) => address),
|
|
89
90
|
tx.data.changeAddress?.address || changeAddressType,
|
|
90
91
|
]
|
|
91
|
-
if (receiveAddresses.
|
|
92
|
+
if (receiveAddresses.some(Boolean)) outputs.push(...receiveAddresses)
|
|
92
93
|
|
|
93
94
|
let fee
|
|
94
95
|
let additionalUtxos
|
|
@@ -114,6 +115,7 @@ export const selectUtxos = ({
|
|
|
114
115
|
outputs: noChangeOutputs,
|
|
115
116
|
})
|
|
116
117
|
}
|
|
118
|
+
|
|
117
119
|
break
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -125,6 +127,7 @@ export const selectUtxos = ({
|
|
|
125
127
|
})
|
|
126
128
|
}
|
|
127
129
|
}
|
|
130
|
+
|
|
128
131
|
if (isSendAll || replaceTxAmount.add(additionalUtxos.value).gte(amount.add(fee))) {
|
|
129
132
|
const chainOutputs = isSendAll ? receiveAddresses : [...receiveAddresses, changeAddressType]
|
|
130
133
|
const chainFee = feeEstimator({
|
|
@@ -136,6 +139,7 @@ export const selectUtxos = ({
|
|
|
136
139
|
if ((!amount.isZero || tx.data.changeAddress) && fee.sub(tx.feeAmount).gte(chainFee)) {
|
|
137
140
|
continue
|
|
138
141
|
}
|
|
142
|
+
|
|
139
143
|
return { selectedUtxos: additionalUtxos, fee, replaceTx: tx }
|
|
140
144
|
}
|
|
141
145
|
}
|
|
@@ -146,7 +150,7 @@ export const selectUtxos = ({
|
|
|
146
150
|
// We can still spend our rbf utxos, but put them last
|
|
147
151
|
let ourRbfUtxos = UtxoCollection.createEmpty({ currency })
|
|
148
152
|
if (replaceableTxs) {
|
|
149
|
-
for (
|
|
153
|
+
for (const tx of replaceableTxs) {
|
|
150
154
|
if (!tx.data.changeAddress) continue
|
|
151
155
|
const changeUtxos = usableUtxos.getTxIdUtxos(tx.txId)
|
|
152
156
|
ourRbfUtxos = ourRbfUtxos.union(changeUtxos)
|
|
@@ -167,6 +171,7 @@ export const selectUtxos = ({
|
|
|
167
171
|
if (selectedUtxos.value.lt(amount.add(fee))) {
|
|
168
172
|
return { fee }
|
|
169
173
|
}
|
|
174
|
+
|
|
170
175
|
return { selectedUtxos, fee }
|
|
171
176
|
}
|
|
172
177
|
|
|
@@ -209,9 +214,11 @@ export const selectUtxos = ({
|
|
|
209
214
|
selectedUtxos = selectedUtxos.addUtxo(remainingUtxosArray.shift())
|
|
210
215
|
fee = feeEstimator({ inputs: selectedUtxos, outputs })
|
|
211
216
|
}
|
|
217
|
+
|
|
212
218
|
if (selectedUtxos.value.lt(amount.add(fee))) {
|
|
213
219
|
return { fee }
|
|
214
220
|
}
|
|
221
|
+
|
|
215
222
|
return { selectedUtxos, fee }
|
|
216
223
|
}
|
|
217
224
|
|
package/src/hash-utils.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import createHash from 'create-hash'
|
|
2
|
+
|
|
2
3
|
export function hash160(buffer) {
|
|
3
4
|
const sha256Hash = sha256(buffer)
|
|
4
5
|
try {
|
|
5
|
-
return createHash('ripemd160')
|
|
6
|
-
.update(sha256Hash)
|
|
7
|
-
.digest()
|
|
6
|
+
return createHash('ripemd160').update(sha256Hash).digest()
|
|
8
7
|
} catch {
|
|
9
|
-
return createHash('rmd160')
|
|
10
|
-
.update(sha256Hash)
|
|
11
|
-
.digest()
|
|
8
|
+
return createHash('rmd160').update(sha256Hash).digest()
|
|
12
9
|
}
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
export function sha256(buffer) {
|
|
16
|
-
return createHash('sha256')
|
|
17
|
-
.update(buffer)
|
|
18
|
-
.digest()
|
|
13
|
+
return createHash('sha256').update(buffer).digest()
|
|
19
14
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
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()
|
|
9
|
-
return responseBody.
|
|
10
|
-
} catch
|
|
10
|
+
return responseBody.slice(0, 100)
|
|
11
|
+
} catch {
|
|
11
12
|
return ''
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -16,8 +17,9 @@ const fetchJson = async (url, fetchOptions) => {
|
|
|
16
17
|
const response = await fetch(url, fetchOptions)
|
|
17
18
|
if (!response.ok)
|
|
18
19
|
throw new Error(
|
|
19
|
-
`${url} returned ${response.status}: ${
|
|
20
|
-
'Unknown Status Text'
|
|
20
|
+
`${url} returned ${response.status}: ${
|
|
21
|
+
response.statusText || 'Unknown Status Text'
|
|
22
|
+
}. Body: ${await getTextFromResponse(response)}`
|
|
21
23
|
)
|
|
22
24
|
return response.json()
|
|
23
25
|
}
|
|
@@ -29,8 +31,7 @@ async function fetchJsonRetry(url, fetchOptions) {
|
|
|
29
31
|
let i = 0
|
|
30
32
|
while (true) {
|
|
31
33
|
try {
|
|
32
|
-
|
|
33
|
-
return data
|
|
34
|
+
return await fetchJson(url, fetchOptions)
|
|
34
35
|
} catch (e) {
|
|
35
36
|
if (i < waitTimes.length) {
|
|
36
37
|
await delay(waitTimes[i])
|
|
@@ -53,7 +54,7 @@ export default class InsightAPIClient {
|
|
|
53
54
|
|
|
54
55
|
async isNetworkConnected() {
|
|
55
56
|
const url = urlJoin(this._baseURL, '/peer')
|
|
56
|
-
const peerStatus = await fetchJson(url, { timeout:
|
|
57
|
+
const peerStatus = await fetchJson(url, { timeout: 10_000 })
|
|
57
58
|
return !!peerStatus.connected
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -65,15 +66,15 @@ export default class InsightAPIClient {
|
|
|
65
66
|
|
|
66
67
|
async fetchBlockHeight() {
|
|
67
68
|
const url = urlJoin(this._baseURL, '/status')
|
|
68
|
-
const status = await fetchJson(url, { timeout:
|
|
69
|
+
const status = await fetchJson(url, { timeout: 10_000 })
|
|
69
70
|
return status.info.blocks
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
async fetchAddress(address, opts
|
|
73
|
+
async fetchAddress(address, opts) {
|
|
73
74
|
const encodedAddress = encodeURIComponent(address)
|
|
74
75
|
const url = urlJoin(
|
|
75
76
|
this._baseURL,
|
|
76
|
-
opts
|
|
77
|
+
opts?.includeTxs ? `/addr/${encodedAddress}` : `/addr/${encodedAddress}?noTxList=1`
|
|
77
78
|
)
|
|
78
79
|
return fetchJson(url)
|
|
79
80
|
}
|
|
@@ -84,6 +85,7 @@ export default class InsightAPIClient {
|
|
|
84
85
|
if (assetNames) {
|
|
85
86
|
query.set('assetNames', assetNames.join(','))
|
|
86
87
|
}
|
|
88
|
+
|
|
87
89
|
const encodedAddresses = encodeURIComponent(addresses)
|
|
88
90
|
const url = urlJoin(this._baseURL, `/addrs/${encodedAddresses}/utxo?${query}`)
|
|
89
91
|
const utxos = await fetchJson(url)
|
|
@@ -118,6 +120,7 @@ export default class InsightAPIClient {
|
|
|
118
120
|
if (isEmpty(object)) {
|
|
119
121
|
return null
|
|
120
122
|
}
|
|
123
|
+
|
|
121
124
|
return object
|
|
122
125
|
}
|
|
123
126
|
|
|
@@ -132,7 +135,7 @@ export default class InsightAPIClient {
|
|
|
132
135
|
if (!Array.isArray(addrs) || addrs.length === 0) return { items: [], totalItems: 0 }
|
|
133
136
|
|
|
134
137
|
options = { noScriptSig: 1, noAsm: 1, noSpent: 0, from: 0, to: 10, ...options }
|
|
135
|
-
const url = `${urlJoin(this._baseURL,
|
|
138
|
+
const url = `${urlJoin(this._baseURL, '/addrs/txs')}?${new URLSearchParams(options)}`
|
|
136
139
|
|
|
137
140
|
const fetchOptions = {
|
|
138
141
|
method: 'post',
|
|
@@ -141,7 +144,7 @@ export default class InsightAPIClient {
|
|
|
141
144
|
'Content-Type': 'application/json',
|
|
142
145
|
},
|
|
143
146
|
body: JSON.stringify({ addrs: addrs.join(',') }),
|
|
144
|
-
timeout:
|
|
147
|
+
timeout: 10_000,
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
return fetchJsonRetry(url, fetchOptions)
|
|
@@ -31,41 +31,41 @@ export function orderTxs(txs) {
|
|
|
31
31
|
|
|
32
32
|
// sort unique time keys
|
|
33
33
|
const txTimes = Object.keys(txTimesMap)
|
|
34
|
-
txTimes.sort((a, b) =>
|
|
34
|
+
txTimes.sort((a, b) => Math.trunc(a) - Math.trunc(b))
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
const rettxs = []
|
|
37
37
|
|
|
38
38
|
txTimes.forEach((txTime) => {
|
|
39
|
-
|
|
39
|
+
const txsAtTime = txTimesMap[txTime] // array of txs
|
|
40
40
|
// should always be an array, but just incase
|
|
41
41
|
if (!Array.isArray(txsAtTime)) return
|
|
42
42
|
if (txsAtTime.length === 1) return rettxs.push(txsAtTime[0])
|
|
43
43
|
|
|
44
44
|
// existing order { txid: orderNum }
|
|
45
|
-
|
|
46
|
-
for (
|
|
47
|
-
txOrderCol.push({ txid:
|
|
45
|
+
const txOrderCol = []
|
|
46
|
+
for (const [i, element] of txsAtTime.entries()) {
|
|
47
|
+
txOrderCol.push({ txid: element.txid, order: i })
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// determine new orders, checks each vin and looks for max order,
|
|
51
51
|
// want current tx greater than any vin
|
|
52
|
-
const txsAtTimeClone = txsAtTime
|
|
52
|
+
const txsAtTimeClone = [...txsAtTime]
|
|
53
53
|
while (txsAtTime.length > 0) {
|
|
54
|
-
|
|
54
|
+
const tx = txsAtTime.shift()
|
|
55
55
|
let maxOrder = 0
|
|
56
56
|
tx.vin.forEach((vin) => {
|
|
57
|
-
|
|
57
|
+
const txOrderMatch = txOrderCol.find((opair) => opair.txid === vin.txid)
|
|
58
58
|
if (txOrderMatch && txOrderMatch.order > maxOrder) maxOrder = txOrderMatch.order
|
|
59
59
|
})
|
|
60
60
|
txOrderCol.find((opair) => opair.txid === tx.txid).order = maxOrder + 1
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
const baseTime = Math.trunc(txTime)
|
|
64
64
|
const newWorldOrder = sortBy(txOrderCol, ['order'])
|
|
65
65
|
newWorldOrder.forEach((opair, i) => {
|
|
66
|
-
|
|
66
|
+
const txid = opair.txid
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
const tx = txsAtTimeClone.find((tx) => tx.txid === txid)
|
|
69
69
|
assert(tx, 'InsightAPIClient.orderTxs() tx undefined.')
|
|
70
70
|
|
|
71
71
|
// we must do this to preserve order outside of this algorithm, difference is negligible
|
|
@@ -89,16 +89,17 @@ export function toWSUrl(apiUrl) {
|
|
|
89
89
|
if (!apiUrl) {
|
|
90
90
|
return apiUrl
|
|
91
91
|
}
|
|
92
|
+
|
|
92
93
|
try {
|
|
93
94
|
// Note, we are not using URL because URL is different between mobile, desktop and BE
|
|
94
95
|
// Using hydra's networking modules is an overkill for this function.
|
|
95
96
|
const firstSplit = apiUrl.split('://')
|
|
96
97
|
if (firstSplit.length > 1) {
|
|
97
98
|
return `${firstSplit[0]}://${firstSplit[1].split('/')[0]}`
|
|
98
|
-
} else {
|
|
99
|
-
return apiUrl
|
|
100
99
|
}
|
|
101
|
-
|
|
100
|
+
|
|
101
|
+
return apiUrl
|
|
102
|
+
} catch {
|
|
102
103
|
return apiUrl
|
|
103
104
|
}
|
|
104
105
|
}
|
|
@@ -11,7 +11,7 @@ export default class InsightWSClient extends EventEmitter {
|
|
|
11
11
|
|
|
12
12
|
connect(addresses, opts) {
|
|
13
13
|
const options = Object.assign(
|
|
14
|
-
{ transports: ['websocket'], reconnectionDelayMax:
|
|
14
|
+
{ transports: ['websocket'], reconnectionDelayMax: 30_000, reconnectionDelay: 10_000 },
|
|
15
15
|
opts
|
|
16
16
|
)
|
|
17
17
|
|