@exodus/bitcoin-api 1.0.3 → 1.0.5
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 +6 -4
- package/src/btc-like-address.js +1 -1
- package/src/constants/bip44.js +3 -27
- package/src/fee/fee-estimator.js +104 -102
- package/src/fee/script-classifier.js +53 -0
- package/src/insight-api-client/index.js +28 -29
- package/src/tx-sign/default-create-tx.js +20 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -19,13 +19,15 @@
|
|
|
19
19
|
"lint:fix": "yarn lint --fix"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@exodus/asset-lib": "^3.7.
|
|
22
|
+
"@exodus/asset-lib": "^3.7.2",
|
|
23
|
+
"@exodus/bip44-constants": "^195.0.0",
|
|
23
24
|
"@exodus/bitcoinjs-lib": "6.0.2-beta.4",
|
|
24
25
|
"@exodus/keychain": "^3.0.0",
|
|
25
26
|
"@exodus/models": "^8.10.4",
|
|
26
27
|
"@exodus/secp256k1": "4.0.2-exodus.0",
|
|
27
28
|
"@exodus/simple-retry": "0.0.6",
|
|
28
|
-
"
|
|
29
|
+
"@exodus/timer": "^1.0.0",
|
|
30
|
+
"bech32": "^1.1.3",
|
|
29
31
|
"coininfo": "5.1.0",
|
|
30
32
|
"delay": "4.0.1",
|
|
31
33
|
"ecpair": "2.0.1",
|
|
@@ -38,5 +40,5 @@
|
|
|
38
40
|
"@exodus/bip-schnorr": "0.6.6-fork-1",
|
|
39
41
|
"@noble/secp256k1": "~1.5.3"
|
|
40
42
|
},
|
|
41
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "0be2bccacf297e2b4fcb65cf758f415f1b2df50e"
|
|
42
44
|
}
|
package/src/btc-like-address.js
CHANGED
|
@@ -21,7 +21,7 @@ export const createBtcLikeAddress = ({
|
|
|
21
21
|
? undefined
|
|
22
22
|
: (string) => {
|
|
23
23
|
const payload = bs58check.decodeUnsafe(string)
|
|
24
|
-
return payload && payload.length === 21 && payload[0] === version
|
|
24
|
+
return !!payload && payload.length === 21 && payload[0] === version
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const bech32ValidateFactory = (version, length) =>
|
package/src/constants/bip44.js
CHANGED
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// Translate from
|
|
6
|
-
// array of [bip44, symbol, coin]
|
|
7
|
-
// to
|
|
8
|
-
// map of symbol ---> bip44
|
|
9
|
-
//
|
|
10
|
-
// Careful: for duplicate symbols only the first definition gets stored.
|
|
11
|
-
// Duplicate symbols: LTBC, DST, SAFE, BCO, RYO, XRD
|
|
12
|
-
// Run tests to find all differences between existing and new constants.
|
|
13
|
-
|
|
14
|
-
const _bip44Constants = {}
|
|
15
|
-
for (let [c, t] of bip44Constants) {
|
|
16
|
-
if (!(t in _bip44Constants)) {
|
|
17
|
-
_bip44Constants[t] = c
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// force NANO to 'Bitcoin Nano'
|
|
22
|
-
_bip44Constants['NANO'] = 0x80000100
|
|
23
|
-
_bip44Constants['HBAR'] = _bip44Constants['XHB']
|
|
24
|
-
_bip44Constants['FLR'] = 0x8000022a
|
|
25
|
-
_bip44Constants['EGLD'] = 0x800001fc
|
|
26
|
-
|
|
27
|
-
export default _bip44Constants
|
|
1
|
+
// back compatibility. Use @exodus/bip44-constants/by-ticker instead
|
|
2
|
+
import bip44Constants from '@exodus/bip44-constants/by-ticker'
|
|
3
|
+
export default bip44Constants
|
package/src/fee/fee-estimator.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import assert from 'minimalistic-assert'
|
|
3
3
|
import * as varuint from 'varuint-bitcoin'
|
|
4
4
|
import { UtxoCollection } from '@exodus/models'
|
|
5
|
-
|
|
6
5
|
import { scriptClassify } from '../bitcoinjs-lib'
|
|
7
6
|
|
|
7
|
+
import { scriptClassifierFactory } from './script-classifier'
|
|
8
|
+
|
|
8
9
|
import createDefaultFeeEstimator, { isHex } from './fee-utils'
|
|
9
10
|
|
|
10
11
|
const { P2PKH, P2SH, P2WPKH, P2WSH, P2TR } = scriptClassify.types
|
|
@@ -52,116 +53,117 @@ const scriptPubKeyLengths = {
|
|
|
52
53
|
// 10 = version: 4, locktime: 4, inputs and outputs count: 1
|
|
53
54
|
// 148 = txId: 32, vout: 4, count: 1, script: 107 (max), sequence: 4
|
|
54
55
|
// 34 = value: 8, count: 1, scriptPubKey: 25 (P2PKH) and 23 (P2SH)
|
|
55
|
-
export const getSizeFactory = ({ ecc, defaultOutputType, addressApi }) =>
|
|
56
|
-
asset: Object,
|
|
57
|
-
inputs: Array | UtxoCollection,
|
|
58
|
-
outputs: Array,
|
|
59
|
-
{ compressed = true } = {}
|
|
60
|
-
) => {
|
|
56
|
+
export const getSizeFactory = ({ ecc, defaultOutputType, addressApi }) => {
|
|
61
57
|
assert(ecc, 'ecc is required')
|
|
62
58
|
assert(defaultOutputType, 'defaultOutputType is required')
|
|
63
59
|
assert(addressApi, 'addressApi is required')
|
|
64
|
-
const assetName = asset.name
|
|
65
|
-
if (inputs instanceof UtxoCollection) {
|
|
66
|
-
inputs = Array.from(inputs).map((utxo) => utxo.script || null)
|
|
67
|
-
}
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
outputs.reduce((t, output) => {
|
|
98
|
-
// if (output === null) output = get(asset, 'address.versions.bech32') ? 'P2WSH' : 'P2PKH'
|
|
99
|
-
|
|
100
|
-
if (output === null) output = defaultOutputType
|
|
101
|
-
|
|
102
|
-
let scriptType = scriptClassify.types[output]
|
|
103
|
-
const supportedTypes = supportedOutputTypes[assetName] || supportedOutputTypes.default
|
|
104
|
-
|
|
105
|
-
if (!supportedTypes.includes(scriptType)) {
|
|
106
|
-
if (addressApi.isP2PKH(output)) scriptType = P2PKH
|
|
107
|
-
else if (addressApi.isP2SH(output)) scriptType = P2SH
|
|
108
|
-
else if (addressApi.isP2WPKH && addressApi.isP2WPKH(output)) scriptType = P2WPKH
|
|
109
|
-
else if (addressApi.isP2TR && addressApi.isP2TR(output)) scriptType = P2TR
|
|
110
|
-
else if (addressApi.isP2WSH && addressApi.isP2WSH(output)) scriptType = P2WSH
|
|
111
|
-
else {
|
|
112
|
-
scriptType = classifyOutput(addressApi.toScriptPubKey(output))
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
assert(
|
|
116
|
-
supportedTypes.includes(scriptType),
|
|
117
|
-
`Only ${supportedTypes.join(', ')} outputs supported right now`
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
const scriptPubKeyLength = scriptPubKeyLengths[scriptType]
|
|
121
|
-
return t + 8 + varuint.encodingLength(scriptPubKeyLength) + scriptPubKeyLength
|
|
122
|
-
}, 0)
|
|
123
|
-
|
|
124
|
-
const witnessSize =
|
|
125
|
-
1 + // marker
|
|
126
|
-
1 + // flag
|
|
127
|
-
// witnesses
|
|
128
|
-
inputs.reduce((t, script) => {
|
|
129
|
-
if (!script) return t + 1
|
|
130
|
-
const utxoScriptType = classifyOutput(Buffer.from(script, 'hex'))
|
|
131
|
-
if ([P2SH, P2WPKH].includes(utxoScriptType)) {
|
|
132
|
-
const pubKeyLength = 33
|
|
133
|
-
const signatureLength = 73 // maximum possible length
|
|
134
|
-
// Need to encode the witness item count, which is 2 for for P2WPKH as a var_int
|
|
135
|
-
return (
|
|
136
|
-
t +
|
|
137
|
-
varuint.encodingLength(2) +
|
|
138
|
-
varuint.encodingLength(pubKeyLength) +
|
|
139
|
-
pubKeyLength +
|
|
140
|
-
varuint.encodingLength(signatureLength) +
|
|
141
|
-
signatureLength
|
|
61
|
+
const scriptClassifier = scriptClassifierFactory({ ecc, addressApi })
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
asset: Object,
|
|
65
|
+
inputs: Array | UtxoCollection,
|
|
66
|
+
outputs: Array,
|
|
67
|
+
{ compressed = true } = {}
|
|
68
|
+
) => {
|
|
69
|
+
if (inputs instanceof UtxoCollection) {
|
|
70
|
+
inputs = Array.from(inputs).map((utxo) => utxo.script || null)
|
|
71
|
+
}
|
|
72
|
+
const assetName = asset.name
|
|
73
|
+
// other bitcoin-like assets
|
|
74
|
+
const baseSize =
|
|
75
|
+
4 + // n_version
|
|
76
|
+
4 + // n_locktime
|
|
77
|
+
varuint.encodingLength(inputs.length) + // inputs_len
|
|
78
|
+
// input[]
|
|
79
|
+
inputs.reduce((t, script) => {
|
|
80
|
+
if (script === null) script = '76a914000000000000000000000000000000000000000088ac' // P2PKH
|
|
81
|
+
assert(isHex(script), 'script must be hex string')
|
|
82
|
+
|
|
83
|
+
const scriptType = scriptClassifier.classifyScriptHex({ assetName, script })
|
|
84
|
+
|
|
85
|
+
const supportedTypes = supportedInputTypes[assetName] || supportedInputTypes.default
|
|
86
|
+
assert(
|
|
87
|
+
supportedTypes.includes(scriptType),
|
|
88
|
+
`Only ${supportedTypes.join(', ')} inputs supported right now`
|
|
142
89
|
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
90
|
+
|
|
91
|
+
const scriptSigLengths = compressed
|
|
92
|
+
? scriptSigCompressedLengths
|
|
93
|
+
: scriptSigUncompressedLengths
|
|
94
|
+
const scriptSigLength = scriptSigLengths[scriptType]
|
|
95
|
+
return t + 32 + 4 + varuint.encodingLength(scriptSigLength) + scriptSigLength + 4
|
|
96
|
+
}, 0) +
|
|
97
|
+
varuint.encodingLength(outputs.length) + // outputs_len
|
|
98
|
+
// output[]
|
|
99
|
+
outputs.reduce((t, output) => {
|
|
100
|
+
// if (output === null) output = get(asset, 'address.versions.bech32') ? 'P2WSH' : 'P2PKH'
|
|
101
|
+
|
|
102
|
+
if (output === null) output = defaultOutputType
|
|
103
|
+
|
|
104
|
+
let scriptType = scriptClassify.types[output]
|
|
105
|
+
const supportedTypes = supportedOutputTypes[assetName] || supportedOutputTypes.default
|
|
106
|
+
|
|
107
|
+
if (!supportedTypes.includes(scriptType)) {
|
|
108
|
+
scriptType = scriptClassifier.classifyAddress({
|
|
109
|
+
assetName,
|
|
110
|
+
address: output,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
assert(
|
|
114
|
+
supportedTypes.includes(scriptType),
|
|
115
|
+
`Only ${supportedTypes.join(', ')} outputs supported right now`
|
|
149
116
|
)
|
|
150
|
-
}
|
|
151
117
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
118
|
+
const scriptPubKeyLength = scriptPubKeyLengths[scriptType]
|
|
119
|
+
return t + 8 + varuint.encodingLength(scriptPubKeyLength) + scriptPubKeyLength
|
|
120
|
+
}, 0)
|
|
121
|
+
|
|
122
|
+
const witnessSize =
|
|
123
|
+
1 + // marker
|
|
124
|
+
1 + // flag
|
|
125
|
+
// witnesses
|
|
126
|
+
inputs.reduce((t, script) => {
|
|
127
|
+
if (!script) return t + 1
|
|
128
|
+
const utxoScriptType = scriptClassifier.classifyScriptHex({ assetName, script })
|
|
129
|
+
if ([P2SH, P2WPKH].includes(utxoScriptType)) {
|
|
130
|
+
const pubKeyLength = 33
|
|
131
|
+
const signatureLength = 73 // maximum possible length
|
|
132
|
+
// Need to encode the witness item count, which is 2 for for P2WPKH as a var_int
|
|
133
|
+
return (
|
|
134
|
+
t +
|
|
135
|
+
varuint.encodingLength(2) +
|
|
136
|
+
varuint.encodingLength(pubKeyLength) +
|
|
137
|
+
pubKeyLength +
|
|
138
|
+
varuint.encodingLength(signatureLength) +
|
|
139
|
+
signatureLength
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
if ([P2TR].includes(utxoScriptType)) {
|
|
143
|
+
// Only the 64 byte Schnorr signature is present for Taproot Key-Path spend
|
|
144
|
+
const signatureLength = 64
|
|
145
|
+
return (
|
|
146
|
+
t +
|
|
147
|
+
varuint.encodingLength(1) +
|
|
148
|
+
varuint.encodingLength(signatureLength) +
|
|
149
|
+
signatureLength
|
|
150
|
+
)
|
|
151
|
+
}
|
|
155
152
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (witnessSize === 1 + 1 + inputs.length) return baseSize
|
|
153
|
+
// Non-witness inputs get a placeholder zero byte
|
|
154
|
+
return t + 1
|
|
155
|
+
}, 0)
|
|
160
156
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
// If witness is all placeholder bytes, that means we have no witness inputs
|
|
158
|
+
// In that case, we're going to use the legacy serialization format, so just
|
|
159
|
+
// return baseSize
|
|
160
|
+
if (witnessSize === 1 + 1 + inputs.length) return baseSize
|
|
161
|
+
|
|
162
|
+
const totalSize = baseSize + witnessSize
|
|
163
|
+
const weight = baseSize * 3 + totalSize
|
|
164
|
+
// Return vbytes
|
|
165
|
+
return Math.ceil(weight / 4)
|
|
166
|
+
}
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
const getFeeEstimatorFactory = ({ ecc, defaultOutputType, addressApi }) => {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { scriptClassify } from '../bitcoinjs-lib'
|
|
2
|
+
import createHash from 'create-hash'
|
|
3
|
+
|
|
4
|
+
import { memoizeLruCache } from '@exodus/asset-lib'
|
|
5
|
+
import assert from 'minimalistic-assert'
|
|
6
|
+
|
|
7
|
+
const { P2PKH, P2SH, P2WPKH, P2WSH, P2TR } = scriptClassify.types
|
|
8
|
+
|
|
9
|
+
const cacheSize = 1000
|
|
10
|
+
const maxSize = 30
|
|
11
|
+
const hashStringIfTooBig = (str) =>
|
|
12
|
+
str.length > maxSize
|
|
13
|
+
? createHash('sha256')
|
|
14
|
+
.update(str)
|
|
15
|
+
.digest('hex')
|
|
16
|
+
.slice(0, maxSize)
|
|
17
|
+
: str
|
|
18
|
+
|
|
19
|
+
export const scriptClassifierFactory = ({ addressApi, ecc }) => {
|
|
20
|
+
assert(ecc, 'ecc is required')
|
|
21
|
+
assert(addressApi, 'addressApi is required')
|
|
22
|
+
|
|
23
|
+
const classifyOutput = scriptClassify.outputFactory({ ecc })
|
|
24
|
+
|
|
25
|
+
const classifyScriptHex = memoizeLruCache(
|
|
26
|
+
({ assetName, script }) => {
|
|
27
|
+
assert(assetName, 'assetName is required')
|
|
28
|
+
assert(script, 'script is required')
|
|
29
|
+
return classifyOutput(Buffer.from(script, 'hex'))
|
|
30
|
+
},
|
|
31
|
+
({ assetName, script }) => `${assetName}_${hashStringIfTooBig(script)}`, // hashing the script in case the script is really long
|
|
32
|
+
cacheSize
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const classifyAddress = memoizeLruCache(
|
|
36
|
+
({ assetName, address }) => {
|
|
37
|
+
assert(assetName, 'assetName is required')
|
|
38
|
+
assert(assetName, address, 'address is required')
|
|
39
|
+
if (addressApi.isP2PKH(address)) return P2PKH
|
|
40
|
+
else if (addressApi.isP2SH(address)) return P2SH
|
|
41
|
+
else if (addressApi.isP2WPKH && addressApi.isP2WPKH(address)) return P2WPKH
|
|
42
|
+
else if (addressApi.isP2TR && addressApi.isP2TR(address)) return P2TR
|
|
43
|
+
else if (addressApi.isP2WSH && addressApi.isP2WSH(address)) return P2WSH
|
|
44
|
+
return classifyScriptHex({
|
|
45
|
+
classifyOutput,
|
|
46
|
+
script: addressApi.toScriptPubKey(address).toString('hex'),
|
|
47
|
+
})
|
|
48
|
+
},
|
|
49
|
+
({ assetName, address }) => `${assetName}_${address}`,
|
|
50
|
+
cacheSize
|
|
51
|
+
)
|
|
52
|
+
return { classifyScriptHex, classifyAddress }
|
|
53
|
+
}
|
|
@@ -4,6 +4,25 @@ import urlJoin from 'url-join'
|
|
|
4
4
|
import qs from 'querystring'
|
|
5
5
|
import delay from 'delay'
|
|
6
6
|
|
|
7
|
+
const getTextFromResponse = async (response) => {
|
|
8
|
+
try {
|
|
9
|
+
const responseBody = await response.text()
|
|
10
|
+
return responseBody.substring(0, 100)
|
|
11
|
+
} catch (e) {
|
|
12
|
+
return ''
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const fetchJson = async (url, fetchOptions) => {
|
|
17
|
+
const response = await fetch(url, fetchOptions)
|
|
18
|
+
if (!response.ok)
|
|
19
|
+
throw new Error(
|
|
20
|
+
`${url} returned ${response.status}: ${response.statusText ||
|
|
21
|
+
'Unknown Status Text'}. Body: ${await getTextFromResponse(response)}`
|
|
22
|
+
)
|
|
23
|
+
return response.json()
|
|
24
|
+
}
|
|
25
|
+
|
|
7
26
|
// TODO: use p-retry
|
|
8
27
|
async function fetchJsonRetry(url, fetchOptions) {
|
|
9
28
|
const waitTimes = [5, 10, 20, 30].map((t) => t * 1000)
|
|
@@ -22,12 +41,6 @@ async function fetchJsonRetry(url, fetchOptions) {
|
|
|
22
41
|
}
|
|
23
42
|
}
|
|
24
43
|
}
|
|
25
|
-
|
|
26
|
-
async function fetchJson(url, fetchOptions) {
|
|
27
|
-
const response = await fetch(url, fetchOptions)
|
|
28
|
-
if (!response.ok) throw new Error(`${url} returned ${response.status}: ${response.statusText}`)
|
|
29
|
-
return response.json()
|
|
30
|
-
}
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
export default class InsightAPIClient {
|
|
@@ -41,9 +54,7 @@ export default class InsightAPIClient {
|
|
|
41
54
|
|
|
42
55
|
async isNetworkConnected() {
|
|
43
56
|
const url = urlJoin(this._baseURL, '/peer')
|
|
44
|
-
const
|
|
45
|
-
const peerStatus = await resp.json()
|
|
46
|
-
|
|
57
|
+
const peerStatus = await fetchJson(url, { timeout: 10000 })
|
|
47
58
|
return !!peerStatus.connected
|
|
48
59
|
}
|
|
49
60
|
|
|
@@ -56,9 +67,7 @@ export default class InsightAPIClient {
|
|
|
56
67
|
|
|
57
68
|
async fetchBlockHeight() {
|
|
58
69
|
const url = urlJoin(this._baseURL, '/status')
|
|
59
|
-
const
|
|
60
|
-
const status = await resp.json()
|
|
61
|
-
|
|
70
|
+
const status = await fetchJson(url, { timeout: 10000 })
|
|
62
71
|
return status.info.blocks
|
|
63
72
|
}
|
|
64
73
|
|
|
@@ -68,9 +77,7 @@ export default class InsightAPIClient {
|
|
|
68
77
|
this._baseURL,
|
|
69
78
|
opts.includeTxs ? `/addr/${encodedAddress}` : `/addr/${encodedAddress}?noTxList=1`
|
|
70
79
|
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return response.json()
|
|
80
|
+
return fetchJson(url)
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
async fetchUTXOs(addresses, { assetNames = [] } = {}) {
|
|
@@ -81,8 +88,7 @@ export default class InsightAPIClient {
|
|
|
81
88
|
}
|
|
82
89
|
const encodedAddresses = encodeURIComponent(addresses)
|
|
83
90
|
const url = urlJoin(this._baseURL, `/addrs/${encodedAddresses}/utxo?${query}`)
|
|
84
|
-
const
|
|
85
|
-
const utxos = await response.json()
|
|
91
|
+
const utxos = await fetchJson(url)
|
|
86
92
|
|
|
87
93
|
return utxos.map((utxo) => ({
|
|
88
94
|
address: utxo.address,
|
|
@@ -111,8 +117,7 @@ export default class InsightAPIClient {
|
|
|
111
117
|
async fetchRawTx(txId: string) {
|
|
112
118
|
const encodedTxId = encodeURIComponent(txId)
|
|
113
119
|
const url = urlJoin(this._baseURL, `/rawtx/${encodedTxId}`)
|
|
114
|
-
const
|
|
115
|
-
const { rawtx } = await response.json()
|
|
120
|
+
const { rawtx } = await fetchJson(url)
|
|
116
121
|
return rawtx
|
|
117
122
|
}
|
|
118
123
|
|
|
@@ -155,14 +160,12 @@ export default class InsightAPIClient {
|
|
|
155
160
|
async fetchUnconfirmedAncestorData(txId: string) {
|
|
156
161
|
const encodedTxId = encodeURIComponent(txId)
|
|
157
162
|
const url = urlJoin(this._baseURL, `/unconfirmed_ancestor/${encodedTxId}`)
|
|
158
|
-
|
|
159
|
-
return response.json()
|
|
163
|
+
return fetchJson(url)
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
async fetchFeeRate() {
|
|
163
167
|
const url = urlJoin(this._baseURL, '/v2/fees')
|
|
164
|
-
|
|
165
|
-
return response.json()
|
|
168
|
+
return fetchJson(url)
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
async broadcastTx(rawTx) {
|
|
@@ -200,16 +203,12 @@ export default class InsightAPIClient {
|
|
|
200
203
|
async getClaimable(address) {
|
|
201
204
|
const encodedAddress = encodeURIComponent(address)
|
|
202
205
|
const url = urlJoin(this._baseURL, `/addr/${encodedAddress}/claimable`)
|
|
203
|
-
|
|
204
|
-
if (!response.ok) throw new Error(`${url} returned ${response.status}: ${response.statusText}`)
|
|
205
|
-
return response.json()
|
|
206
|
+
return fetchJson(url)
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
async getUnclaimed(address) {
|
|
209
210
|
const encodedAddress = encodeURIComponent(address)
|
|
210
211
|
const url = urlJoin(this._baseURL, `/addr/${encodedAddress}/unclaimed`)
|
|
211
|
-
|
|
212
|
-
if (!response.ok) throw new Error(`${url} returned ${response.status}: ${response.statusText}`)
|
|
213
|
-
return response.json()
|
|
212
|
+
return fetchJson(url)
|
|
214
213
|
}
|
|
215
214
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'minimalistic-assert'
|
|
2
2
|
import lodash from 'lodash'
|
|
3
3
|
import ECPairFactory from 'ecpair'
|
|
4
|
-
import { Psbt } from '@exodus/bitcoinjs-lib'
|
|
4
|
+
import { Psbt, Transaction } from '@exodus/bitcoinjs-lib'
|
|
5
5
|
|
|
6
6
|
import { toAsyncSigner, tweakSigner } from './taproot'
|
|
7
7
|
|
|
@@ -12,6 +12,15 @@ const _MAXIMUM_FEE_RATES = {
|
|
|
12
12
|
ravencoin: 1000000,
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const canParseTx = (rawTxBuffer) => {
|
|
16
|
+
try {
|
|
17
|
+
Transaction.fromBuffer(rawTxBuffer)
|
|
18
|
+
return true
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, network, ecc }) => {
|
|
16
25
|
assert(assetName, 'assetName is required')
|
|
17
26
|
assert(resolvePurpose, 'resolvePurpose is required')
|
|
@@ -62,10 +71,18 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
|
|
|
62
71
|
// witness outputs only require the value and the script, not the full transaction
|
|
63
72
|
txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
|
|
64
73
|
} else {
|
|
65
|
-
// non-witness outptus require the full transaction
|
|
66
74
|
const rawTx = (rawTxs || []).find((t) => t.txId === txId)
|
|
75
|
+
// non-witness outptus require the full transaction
|
|
67
76
|
assert(!!rawTx, 'Non-witness outputs require the full previous transaction.')
|
|
68
|
-
|
|
77
|
+
const rawTxBuffer = Buffer.from(rawTx.rawData, 'hex')
|
|
78
|
+
if (canParseTx(rawTxBuffer)) {
|
|
79
|
+
txIn.nonWitnessUtxo = rawTxBuffer
|
|
80
|
+
} else {
|
|
81
|
+
// temp fix for https://exodusio.slack.com/archives/CP202D90Q/p1671014704829939 until bitcoinjs could parse a mweb tx without failing
|
|
82
|
+
console.warn(`Setting psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true for asset ${assetName}`)
|
|
83
|
+
psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true
|
|
84
|
+
txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
|
|
85
|
+
}
|
|
69
86
|
}
|
|
70
87
|
psbt.addInput(txIn)
|
|
71
88
|
}
|