@exodus/bitcoin-api 2.9.2 → 2.9.3
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/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 +3 -1
- package/src/fee/fee-estimator.js +5 -1
- package/src/fee/fee-utils.js +6 -5
- package/src/fee/get-fee-resolver.js +2 -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 +14 -12
- 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/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 +12 -6
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",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"jest-when": "^3.5.1",
|
|
59
59
|
"safe-buffer": "^5.2.1"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "fe5b49b00fff26b6090cad149ca522db8aa971f8"
|
|
62
62
|
}
|
|
@@ -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
|
}
|
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!!!')
|
|
@@ -115,7 +116,7 @@ export class GetFeeResolver {
|
|
|
115
116
|
feeRate: feePerKB,
|
|
116
117
|
receiveAddress,
|
|
117
118
|
inscriptionIds,
|
|
118
|
-
isSendAll
|
|
119
|
+
isSendAll,
|
|
119
120
|
getFeeEstimator: this.#getFeeEstimator,
|
|
120
121
|
allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
|
|
121
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
|
}
|
|
@@ -7,8 +7,8 @@ import { fetch } from '@exodus/fetch'
|
|
|
7
7
|
const getTextFromResponse = async (response) => {
|
|
8
8
|
try {
|
|
9
9
|
const responseBody = await response.text()
|
|
10
|
-
return responseBody.
|
|
11
|
-
} catch
|
|
10
|
+
return responseBody.slice(0, 100)
|
|
11
|
+
} catch {
|
|
12
12
|
return ''
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -17,8 +17,9 @@ const fetchJson = async (url, fetchOptions) => {
|
|
|
17
17
|
const response = await fetch(url, fetchOptions)
|
|
18
18
|
if (!response.ok)
|
|
19
19
|
throw new Error(
|
|
20
|
-
`${url} returned ${response.status}: ${
|
|
21
|
-
'Unknown Status Text'
|
|
20
|
+
`${url} returned ${response.status}: ${
|
|
21
|
+
response.statusText || 'Unknown Status Text'
|
|
22
|
+
}. Body: ${await getTextFromResponse(response)}`
|
|
22
23
|
)
|
|
23
24
|
return response.json()
|
|
24
25
|
}
|
|
@@ -30,8 +31,7 @@ async function fetchJsonRetry(url, fetchOptions) {
|
|
|
30
31
|
let i = 0
|
|
31
32
|
while (true) {
|
|
32
33
|
try {
|
|
33
|
-
|
|
34
|
-
return data
|
|
34
|
+
return await fetchJson(url, fetchOptions)
|
|
35
35
|
} catch (e) {
|
|
36
36
|
if (i < waitTimes.length) {
|
|
37
37
|
await delay(waitTimes[i])
|
|
@@ -54,7 +54,7 @@ export default class InsightAPIClient {
|
|
|
54
54
|
|
|
55
55
|
async isNetworkConnected() {
|
|
56
56
|
const url = urlJoin(this._baseURL, '/peer')
|
|
57
|
-
const peerStatus = await fetchJson(url, { timeout:
|
|
57
|
+
const peerStatus = await fetchJson(url, { timeout: 10_000 })
|
|
58
58
|
return !!peerStatus.connected
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -66,15 +66,15 @@ export default class InsightAPIClient {
|
|
|
66
66
|
|
|
67
67
|
async fetchBlockHeight() {
|
|
68
68
|
const url = urlJoin(this._baseURL, '/status')
|
|
69
|
-
const status = await fetchJson(url, { timeout:
|
|
69
|
+
const status = await fetchJson(url, { timeout: 10_000 })
|
|
70
70
|
return status.info.blocks
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
async fetchAddress(address, opts
|
|
73
|
+
async fetchAddress(address, opts) {
|
|
74
74
|
const encodedAddress = encodeURIComponent(address)
|
|
75
75
|
const url = urlJoin(
|
|
76
76
|
this._baseURL,
|
|
77
|
-
opts
|
|
77
|
+
opts?.includeTxs ? `/addr/${encodedAddress}` : `/addr/${encodedAddress}?noTxList=1`
|
|
78
78
|
)
|
|
79
79
|
return fetchJson(url)
|
|
80
80
|
}
|
|
@@ -85,6 +85,7 @@ export default class InsightAPIClient {
|
|
|
85
85
|
if (assetNames) {
|
|
86
86
|
query.set('assetNames', assetNames.join(','))
|
|
87
87
|
}
|
|
88
|
+
|
|
88
89
|
const encodedAddresses = encodeURIComponent(addresses)
|
|
89
90
|
const url = urlJoin(this._baseURL, `/addrs/${encodedAddresses}/utxo?${query}`)
|
|
90
91
|
const utxos = await fetchJson(url)
|
|
@@ -119,6 +120,7 @@ export default class InsightAPIClient {
|
|
|
119
120
|
if (isEmpty(object)) {
|
|
120
121
|
return null
|
|
121
122
|
}
|
|
123
|
+
|
|
122
124
|
return object
|
|
123
125
|
}
|
|
124
126
|
|
|
@@ -133,7 +135,7 @@ export default class InsightAPIClient {
|
|
|
133
135
|
if (!Array.isArray(addrs) || addrs.length === 0) return { items: [], totalItems: 0 }
|
|
134
136
|
|
|
135
137
|
options = { noScriptSig: 1, noAsm: 1, noSpent: 0, from: 0, to: 10, ...options }
|
|
136
|
-
const url = `${urlJoin(this._baseURL,
|
|
138
|
+
const url = `${urlJoin(this._baseURL, '/addrs/txs')}?${new URLSearchParams(options)}`
|
|
137
139
|
|
|
138
140
|
const fetchOptions = {
|
|
139
141
|
method: 'post',
|
|
@@ -142,7 +144,7 @@ export default class InsightAPIClient {
|
|
|
142
144
|
'Content-Type': 'application/json',
|
|
143
145
|
},
|
|
144
146
|
body: JSON.stringify({ addrs: addrs.join(',') }),
|
|
145
|
-
timeout:
|
|
147
|
+
timeout: 10_000,
|
|
146
148
|
}
|
|
147
149
|
|
|
148
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
|
|
package/src/move-funds.js
CHANGED
|
@@ -8,7 +8,7 @@ const isValidPrivateKey = (privateKey) => {
|
|
|
8
8
|
try {
|
|
9
9
|
wif.decode(privateKey)
|
|
10
10
|
return true
|
|
11
|
-
} catch
|
|
11
|
+
} catch {
|
|
12
12
|
return false
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -21,6 +21,7 @@ const wifToPublicKey = ({ coinInfo, privateKeyWIF }) => {
|
|
|
21
21
|
const { privateKey, compressed } = wif.decode(privateKeyWIF, versions.private)
|
|
22
22
|
return { compressed, publicKey: secp256k1.publicKeyCreate(privateKey, compressed) }
|
|
23
23
|
}
|
|
24
|
+
|
|
24
25
|
export const getAddressesFromPrivateKeyFactory = ({ purposes, keys, coinInfo }) => {
|
|
25
26
|
assert(purposes, 'purposes is required')
|
|
26
27
|
assert(keys, 'keys is required')
|
|
@@ -30,6 +31,7 @@ export const getAddressesFromPrivateKeyFactory = ({ purposes, keys, coinInfo })
|
|
|
30
31
|
if (!isValidPrivateKey(privateKey)) {
|
|
31
32
|
return { invalid: true }
|
|
32
33
|
}
|
|
34
|
+
|
|
33
35
|
const { publicKey, compressed } = wifToPublicKey({ coinInfo, privateKeyWIF: privateKey })
|
|
34
36
|
const addresses = purposes.map((purpose) =>
|
|
35
37
|
keys.encodePublic(purpose === 49 ? secp256k1.publicKeyConvert(publicKey, true) : publicKey, {
|
|
@@ -41,12 +43,14 @@ export const getAddressesFromPrivateKeyFactory = ({ purposes, keys, coinInfo })
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export const renderAddresses = (addresses) => {
|
|
44
|
-
if (
|
|
46
|
+
if (addresses.length === 0) {
|
|
45
47
|
return ''
|
|
46
48
|
}
|
|
49
|
+
|
|
47
50
|
if (addresses.length === 1) {
|
|
48
51
|
return `${addresses[0]}`
|
|
49
52
|
}
|
|
53
|
+
|
|
50
54
|
return `${addresses.slice(0, -1).join(', ')}, or ${addresses[addresses.length - 1]}`
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -82,29 +86,24 @@ export const moveFundsFactory = ({
|
|
|
82
86
|
assert(MoveFundsError, 'MoveFundsError is required') // should we move MoveFundsError to asset libs?
|
|
83
87
|
|
|
84
88
|
const formatProps = {
|
|
85
|
-
asset,
|
|
86
|
-
input,
|
|
89
|
+
assetName: asset.name,
|
|
87
90
|
}
|
|
88
91
|
// WIF format private key
|
|
89
92
|
const privateKey = input
|
|
90
93
|
|
|
91
|
-
const { addresses, compressed, invalid } = getAddressesFromPrivateKey({
|
|
92
|
-
privateKey,
|
|
93
|
-
formatProps,
|
|
94
|
-
MoveFundsError,
|
|
95
|
-
})
|
|
94
|
+
const { addresses, compressed, invalid } = getAddressesFromPrivateKey({ privateKey })
|
|
96
95
|
|
|
97
96
|
if (invalid) {
|
|
98
97
|
throw new MoveFundsError('private-key-invalid', formatProps)
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
const recieveAddressesObjects = await assetClientInterface.getReceiveAddresses({
|
|
101
|
+
walletAccount,
|
|
102
|
+
assetName,
|
|
103
|
+
multiAddressMode: true,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const receiveAddresses = recieveAddressesObjects.map(
|
|
108
107
|
(receiveAddress) =>
|
|
109
108
|
address.toLegacyAddress?.(receiveAddress.toString()) || receiveAddress.toString()
|
|
110
109
|
)
|
|
@@ -127,6 +126,7 @@ export const moveFundsFactory = ({
|
|
|
127
126
|
return { fromAddress: currentAddress, utxos }
|
|
128
127
|
}
|
|
129
128
|
}
|
|
129
|
+
|
|
130
130
|
throw new MoveFundsError('balance-zero', {
|
|
131
131
|
...formatProps,
|
|
132
132
|
fromAddress: renderAddresses(addresses),
|
|
@@ -139,7 +139,7 @@ export const moveFundsFactory = ({
|
|
|
139
139
|
const feeData = await assetClientInterface.getFeeConfig({ assetName })
|
|
140
140
|
const { fee, sizeKB } = getFee({ asset, feeData, utxos, compressed })
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
const amount = utxos.value.sub(fee)
|
|
143
143
|
if (amount.isNegative) {
|
|
144
144
|
throw new MoveFundsError('balance-negative', formatProps)
|
|
145
145
|
}
|
package/src/ordinals-utils.js
CHANGED
|
@@ -10,7 +10,7 @@ export async function getOrdinalAddress({
|
|
|
10
10
|
assert(assetClientInterface, 'assetClientInterface is required')
|
|
11
11
|
assert(walletAccount, 'walletAccount is required')
|
|
12
12
|
if (ordinalChainIndex === undefined) {
|
|
13
|
-
return
|
|
13
|
+
return
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const purposes = await assetClientInterface.getSupportedPurposes({
|
|
@@ -21,7 +21,7 @@ export async function getOrdinalAddress({
|
|
|
21
21
|
const purpose = 86
|
|
22
22
|
|
|
23
23
|
if (!purposes.includes(purpose)) {
|
|
24
|
-
return
|
|
24
|
+
return
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
return assetClientInterface.getAddress({
|