@exodus/bitcoin-api 2.6.2 → 2.6.4
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 +4 -4
- package/src/address-utils.js +2 -2
- package/src/bitcoinjs-lib/ecc/common.js +52 -16
- package/src/bitcoinjs-lib/ecc/desktop.js +45 -14
- package/src/bitcoinjs-lib/ecc/mobile.js +60 -18
- package/src/bitcoinjs-lib/script-classify/index.js +3 -3
- package/src/btc-like-keys.js +1 -1
- package/src/fee/fee-estimator.js +1 -7
- package/src/fee/fee-utils.js +7 -9
- package/src/insight-api-client/index.js +4 -5
- package/src/insight-api-client/util.js +1 -1
- package/src/tx-log/bitcoin-monitor-scanner.js +12 -15
- package/src/tx-sign/default-create-tx.js +4 -3
- package/src/tx-sign/taproot.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.4",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -22,7 +22,7 @@
|
|
|
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/models": "^
|
|
25
|
+
"@exodus/models": "^11.0.0",
|
|
26
26
|
"@exodus/secp256k1": "4.0.2-exodus.0",
|
|
27
27
|
"@exodus/simple-retry": "0.0.6",
|
|
28
28
|
"@exodus/timer": "^1.0.0",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"url-join": "4.0.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@exodus/bitcoin-meta": "^1.0.
|
|
41
|
+
"@exodus/bitcoin-meta": "^1.0.2",
|
|
42
42
|
"@scure/base": "^1.1.3",
|
|
43
43
|
"@scure/btc-signer": "^1.1.0",
|
|
44
44
|
"jest-when": "^3.5.1"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "33c832c0c550fa4a885aee9b34f7649bfb5fa464"
|
|
47
47
|
}
|
package/src/address-utils.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import assert from 'minimalistic-assert'
|
|
2
2
|
|
|
3
|
-
export function isReceiveAddress(address)
|
|
3
|
+
export function isReceiveAddress(address) {
|
|
4
4
|
return parsePath(address)[0] === 0
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export function isChangeAddress(address)
|
|
7
|
+
export function isChangeAddress(address) {
|
|
8
8
|
return parsePath(address)[0] === 1
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -5,24 +5,48 @@ import { toXOnly } from '../ecc-utils'
|
|
|
5
5
|
* Common ecc functions between mobile and desktop. Once mobile accepts @noble/secp256k1, we can unify both
|
|
6
6
|
*/
|
|
7
7
|
export const common = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param msg32 {Uint8Array} the message
|
|
11
|
+
* @param seckey {Uint8Array} the private key
|
|
12
|
+
* @param data {Uint8Array} the data to sign
|
|
13
|
+
*/
|
|
14
|
+
sign: (msg32, seckey, data) => secp256k1.ecdsaSign(msg32, seckey, { data }).signature,
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @param msg32 {Uint8Array} the message to verify
|
|
19
|
+
* @param publicKey {Uint8Array} the public key
|
|
20
|
+
* @param signature {Uint8Array} the signature
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
23
|
+
verify: (msg32, publicKey, signature) => secp256k1.ecdsaVerify(signature, msg32, publicKey),
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @param seckey {Uint8Array}
|
|
28
|
+
* @param tweak {Uint8Array}
|
|
29
|
+
*/
|
|
30
|
+
privateAdd: (seckey, tweak) =>
|
|
16
31
|
// cloning input. secp256k1 modifies it and it cannot be reused/cached
|
|
17
|
-
secp256k1.privateKeyTweakAdd(Buffer.from(
|
|
32
|
+
secp256k1.privateKeyTweakAdd(Buffer.from(seckey), tweak),
|
|
18
33
|
|
|
19
|
-
|
|
34
|
+
/**
|
|
35
|
+
* @param seckey {Uint8Array} the public key
|
|
36
|
+
*/
|
|
37
|
+
privateNegate: (seckey) =>
|
|
20
38
|
// cloning input. secp256k1 modifies it and it cannot be reused/cached
|
|
21
|
-
secp256k1.privateKeyNegate(Buffer.from(
|
|
39
|
+
secp256k1.privateKeyNegate(Buffer.from(seckey)),
|
|
22
40
|
|
|
23
|
-
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param publicKey {Uint8Array}
|
|
44
|
+
* @param tweak {Uint8Array}
|
|
45
|
+
* @returns {{parity: (number), xOnlyPubkey}|null}
|
|
46
|
+
*/
|
|
47
|
+
xOnlyPointAddTweak: (publicKey, tweak) => {
|
|
24
48
|
try {
|
|
25
|
-
const t = secp256k1.publicKeyTweakAdd(toPubKey(
|
|
49
|
+
const t = secp256k1.publicKeyTweakAdd(toPubKey(publicKey), tweak)
|
|
26
50
|
return {
|
|
27
51
|
parity: t[0] === 0x02 ? 0 : 1,
|
|
28
52
|
xOnlyPubkey: toXOnly(t),
|
|
@@ -32,12 +56,24 @@ export const common = {
|
|
|
32
56
|
}
|
|
33
57
|
},
|
|
34
58
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
/**
|
|
60
|
+
* @param seckey {Uint8Array}
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
isPrivate: (seckey) => secp256k1.privateKeyVerify(seckey),
|
|
64
|
+
/**
|
|
65
|
+
* @param seckey {Uint8Array}
|
|
66
|
+
* @param compressed {boolean}
|
|
67
|
+
*/
|
|
68
|
+
pointFromScalar: (seckey, compressed) => secp256k1.publicKeyCreate(seckey, compressed),
|
|
38
69
|
}
|
|
39
70
|
|
|
40
|
-
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param xOnly {Uint8Array}
|
|
74
|
+
* @returns {Uint8Array}
|
|
75
|
+
*/
|
|
76
|
+
export const toPubKey = (xOnly) => {
|
|
41
77
|
const p = new Uint8Array(33)
|
|
42
78
|
p.set([0x02])
|
|
43
79
|
p.set(xOnly, 1)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { initEccLib } from 'bitcoinjs-lib'
|
|
2
2
|
import { Point, schnorr, sign } from '@noble/secp256k1'
|
|
3
3
|
import { common, toPubKey } from './common'
|
|
4
4
|
|
|
@@ -6,20 +6,39 @@ import { common, toPubKey } from './common'
|
|
|
6
6
|
* Wrapper around `secp256k1` in order to follow the bitcoinjs-lib `TinySecp256k1Interface`
|
|
7
7
|
* Schnorr signatures are offered by @noble/secp256k1
|
|
8
8
|
*/
|
|
9
|
-
export const desktopEcc
|
|
9
|
+
export const desktopEcc = {
|
|
10
10
|
...common,
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @param message {Uint8Array}
|
|
14
|
+
* @param privateKey {Uint8Array}
|
|
15
|
+
* @param extraEntropy {Uint8Array}
|
|
16
|
+
* @returns {Promise<Uint8Array>}
|
|
17
|
+
*/
|
|
18
|
+
signAsync: async (message, privateKey, extraEntropy) =>
|
|
19
|
+
sign(message, privateKey, {
|
|
14
20
|
extraEntropy,
|
|
15
21
|
canonical: true,
|
|
16
22
|
der: false,
|
|
17
23
|
}),
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
/**
|
|
26
|
+
* @param message {Uint8Array}
|
|
27
|
+
* @param privateKey {Uint8Array}
|
|
28
|
+
* @param extraEntropy {Uint8Array}
|
|
29
|
+
* @returns {Promise<Uint8Array>}
|
|
30
|
+
*/
|
|
31
|
+
signSchnorrAsync: async (message, privateKey, extraEntropy) =>
|
|
32
|
+
schnorr.sign(message, privateKey, extraEntropy),
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param message {Uint8Array}
|
|
36
|
+
* @param publicKey {Uint8Array}
|
|
37
|
+
* @param signature {Uint8Array}
|
|
38
|
+
* @returns {Promise<boolean>}
|
|
39
|
+
*/
|
|
40
|
+
verifySchnorrAsync: async (message, publicKey, signature) =>
|
|
41
|
+
schnorr.verify(signature, message, publicKey),
|
|
23
42
|
|
|
24
43
|
// The underlying library does not expose sync functions for Schnorr sign and verify.
|
|
25
44
|
// These function are explicitly defined here as `null` for documentation purposes.
|
|
@@ -27,26 +46,38 @@ export const desktopEcc: TinySecp256k1Interface = {
|
|
|
27
46
|
signSchnorr: null,
|
|
28
47
|
verifySchnorr: null,
|
|
29
48
|
|
|
30
|
-
|
|
49
|
+
/**
|
|
50
|
+
* @param publicKey {Uint8Array}
|
|
51
|
+
* @returns {boolean}
|
|
52
|
+
*/
|
|
53
|
+
isPoint: (publicKey) => {
|
|
31
54
|
try {
|
|
32
|
-
Point.fromHex(
|
|
55
|
+
Point.fromHex(publicKey).assertValidity()
|
|
33
56
|
return true
|
|
34
57
|
} catch {
|
|
35
58
|
return false
|
|
36
59
|
}
|
|
37
60
|
},
|
|
38
61
|
|
|
39
|
-
|
|
62
|
+
/**
|
|
63
|
+
* @param publicKey {Uint8Array}
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
isXOnlyPoint: (publicKey) => {
|
|
40
67
|
try {
|
|
41
|
-
Point.fromHex(toPubKey(
|
|
68
|
+
Point.fromHex(toPubKey(publicKey)).assertValidity()
|
|
42
69
|
return true
|
|
43
70
|
} catch {
|
|
44
71
|
return false
|
|
45
72
|
}
|
|
46
73
|
},
|
|
47
74
|
|
|
48
|
-
|
|
49
|
-
|
|
75
|
+
/**
|
|
76
|
+
* @param publicKey {Uint8Array}
|
|
77
|
+
* @param compressed {boolean}
|
|
78
|
+
* @returns {Uint8Array}
|
|
79
|
+
*/
|
|
80
|
+
pointCompress: (publicKey, compressed) => Point.fromHex(publicKey).toRawBytes(compressed),
|
|
50
81
|
}
|
|
51
82
|
|
|
52
83
|
initEccLib(desktopEcc)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { initEccLib } from 'bitcoinjs-lib'
|
|
2
2
|
import secp256k1 from '@exodus/secp256k1'
|
|
3
3
|
// TODO: temp import until '@noble/secp256k1' can be used
|
|
4
4
|
import { isPoint } from 'tiny-secp256k1'
|
|
@@ -11,50 +11,92 @@ import schnorr from '@exodus/bip-schnorr'
|
|
|
11
11
|
*
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
export const mobileEcc
|
|
14
|
+
export const mobileEcc = {
|
|
15
15
|
...common,
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param message {Uint8Array}
|
|
20
|
+
* @param seckey {Uint8Array}
|
|
21
|
+
* @param extraEntropy {Uint8Array}
|
|
22
|
+
* @returns {Promise<(function(*): Uint8Array)|*>}
|
|
23
|
+
*/
|
|
24
|
+
signAsync: async (message, seckey, extraEntropy) =>
|
|
25
|
+
secp256k1.ecdsaSign(message, seckey, { data: extraEntropy }).signature,
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @param message {Uint8Array}
|
|
30
|
+
* @param privateKey {Uint8Array}
|
|
31
|
+
* @param extraEntropy {Uint8Array}
|
|
32
|
+
* @returns {Buffer}
|
|
33
|
+
*/
|
|
34
|
+
signSchnorr: (message, privateKey, extraEntropy) =>
|
|
35
|
+
schnorr.sign(privateKey.toString('hex'), message, extraEntropy),
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
/**
|
|
38
|
+
* @param message {Uint8Array}
|
|
39
|
+
* @param privateKey {Uint8Array}
|
|
40
|
+
* @param extraEntropy {Uint8Array}
|
|
41
|
+
* @returns {Promise<Buffer>}
|
|
42
|
+
*/
|
|
43
|
+
signSchnorrAsync: async (message, privateKey, extraEntropy) =>
|
|
44
|
+
mobileEcc.signSchnorr(message, privateKey, extraEntropy),
|
|
25
45
|
|
|
26
|
-
|
|
46
|
+
/**
|
|
47
|
+
* @param publicKey {Uint8Array}
|
|
48
|
+
* @param message {Uint8Array}
|
|
49
|
+
* @param signature {Uint8Array}
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
verifySchnorr: (publicKey, message, signature) => {
|
|
27
53
|
try {
|
|
28
|
-
schnorr.verify(
|
|
54
|
+
schnorr.verify(message, publicKey, signature)
|
|
29
55
|
return true
|
|
30
56
|
} catch (e) {
|
|
31
57
|
return false
|
|
32
58
|
}
|
|
33
59
|
},
|
|
34
60
|
|
|
35
|
-
|
|
36
|
-
|
|
61
|
+
/**
|
|
62
|
+
* @param publicKey {Uint8Array}
|
|
63
|
+
* @param message {Uint8Array}
|
|
64
|
+
* @param signature {Uint8Array}
|
|
65
|
+
* @returns {Promise<boolean>}
|
|
66
|
+
*/
|
|
67
|
+
verifySchnorrAsync: async (publicKey, message, signature) =>
|
|
68
|
+
mobileEcc.verifySchnorr(publicKey, message, signature),
|
|
37
69
|
|
|
38
|
-
|
|
70
|
+
/**
|
|
71
|
+
* @param publicKey {Uint8Array}
|
|
72
|
+
* @returns {boolean}
|
|
73
|
+
*/
|
|
74
|
+
isPoint: (publicKey) => {
|
|
39
75
|
try {
|
|
40
76
|
// temp solution secp256k1 does not actually verify the value range, only the data length
|
|
41
|
-
return isPoint(Buffer.from(
|
|
77
|
+
return isPoint(Buffer.from(publicKey))
|
|
42
78
|
} catch (err) {
|
|
43
79
|
return false
|
|
44
80
|
}
|
|
45
81
|
},
|
|
46
82
|
|
|
47
|
-
|
|
83
|
+
/**
|
|
84
|
+
* @param publicKey {Uint8Array}
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
87
|
+
isXOnlyPoint: (publicKey) => {
|
|
48
88
|
try {
|
|
49
89
|
// temp solution secp256k1 does not actually verify the value range, only the data length
|
|
50
|
-
return isPoint(Buffer.from(toPubKey(
|
|
90
|
+
return isPoint(Buffer.from(toPubKey(publicKey)))
|
|
51
91
|
} catch (err) {
|
|
52
92
|
return false
|
|
53
93
|
}
|
|
54
94
|
},
|
|
55
95
|
|
|
56
|
-
|
|
57
|
-
|
|
96
|
+
/**
|
|
97
|
+
* @param publicKey {Uint8Array}
|
|
98
|
+
*/
|
|
99
|
+
pointCompress: (publicKey, compressed) => secp256k1.publicKeyConvert(publicKey, compressed),
|
|
58
100
|
}
|
|
59
101
|
|
|
60
102
|
initEccLib(mobileEcc)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { payments } from 'bitcoinjs-lib'
|
|
2
2
|
|
|
3
|
-
function isPaymentFactory(payment
|
|
4
|
-
return (script
|
|
3
|
+
function isPaymentFactory(payment) {
|
|
4
|
+
return (script, eccLib) => {
|
|
5
5
|
try {
|
|
6
6
|
payment({ output: script }, { eccLib })
|
|
7
7
|
return true
|
|
@@ -30,7 +30,7 @@ const types = {
|
|
|
30
30
|
NONSTANDARD: 'nonstandard',
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const outputFactory = () => (script
|
|
33
|
+
const outputFactory = () => (script) => {
|
|
34
34
|
if (isP2WPKH(script)) return types.P2WPKH
|
|
35
35
|
if (isP2TR(script)) return types.P2TR
|
|
36
36
|
if (isP2PKH(script)) return types.P2PKH
|
package/src/btc-like-keys.js
CHANGED
|
@@ -89,7 +89,7 @@ export const createBtcLikeKeys = ({
|
|
|
89
89
|
const encodePublicTaproot =
|
|
90
90
|
encodePublicTaprootCustom ||
|
|
91
91
|
(useBip86
|
|
92
|
-
? (publicKey
|
|
92
|
+
? (publicKey) => {
|
|
93
93
|
const network = coinInfo.toBitcoinJS()
|
|
94
94
|
return bitcoinjsLib.payments.p2tr({ internalPubkey: toXOnly(publicKey), network }).address
|
|
95
95
|
}
|
package/src/fee/fee-estimator.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import assert from 'minimalistic-assert'
|
|
3
2
|
import * as varuint from 'varuint-bitcoin'
|
|
4
3
|
import { UtxoCollection } from '@exodus/models'
|
|
@@ -59,12 +58,7 @@ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
|
|
|
59
58
|
|
|
60
59
|
const scriptClassifier = scriptClassifierFactory({ addressApi })
|
|
61
60
|
|
|
62
|
-
return (
|
|
63
|
-
asset: Object,
|
|
64
|
-
inputs: Array | UtxoCollection,
|
|
65
|
-
outputs: Array,
|
|
66
|
-
{ compressed = true } = {}
|
|
67
|
-
) => {
|
|
61
|
+
return (asset, inputs, outputs, { compressed = true } = {}) => {
|
|
68
62
|
if (inputs instanceof UtxoCollection) {
|
|
69
63
|
inputs = Array.from(inputs).map((utxo) => utxo.script || null)
|
|
70
64
|
}
|
package/src/fee/fee-utils.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import NumberUnit, { isNumberUnit, UnitType } from '@exodus/currency'
|
|
1
|
+
import { isNumberUnit, UnitType } from '@exodus/currency'
|
|
4
2
|
import { resolveExtraFeeOfTx } from '../unconfirmed-ancestor-data'
|
|
5
3
|
import { UtxoCollection } from '@exodus/models'
|
|
6
4
|
import assert from 'minimalistic-assert'
|
|
7
5
|
|
|
8
|
-
export const isHex = (s
|
|
6
|
+
export const isHex = (s) => typeof s === 'string' && /[0-9a-f]*/.test(s.toLowerCase())
|
|
9
7
|
|
|
10
8
|
export function getExtraFee({ asset, inputs, feePerKB }) {
|
|
11
9
|
let extraFee = 0
|
|
@@ -27,19 +25,19 @@ export function getExtraFee({ asset, inputs, feePerKB }) {
|
|
|
27
25
|
return extraFee
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
export default function createDefaultFeeEstimator(getSize
|
|
28
|
+
export default function createDefaultFeeEstimator(getSize) {
|
|
31
29
|
return (asset, feePerKB, options) => {
|
|
32
|
-
return ({ inputs = options.inputs, outputs = options.outputs } = {})
|
|
30
|
+
return ({ inputs = options.inputs, outputs = options.outputs } = {}) => {
|
|
33
31
|
const extraFee = getExtraFee({ asset, inputs, feePerKB })
|
|
34
32
|
// Yes, it's suppose to be '1000' and not '1024'
|
|
35
33
|
// https://bitcoin.stackexchange.com/questions/24000/a-fee-is-added-per-kilobyte-of-data-that-means-1000-bytes-or-1024
|
|
36
|
-
const size
|
|
37
|
-
const feeRaw
|
|
34
|
+
const size = getSize(asset, inputs, outputs, options)
|
|
35
|
+
const feeRaw = Math.ceil((feePerKB.toBaseNumber() * size) / 1000)
|
|
38
36
|
return asset.currency.baseUnit(feeRaw + extraFee)
|
|
39
37
|
}
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
|
-
export function parseCurrency(val
|
|
40
|
+
export function parseCurrency(val, currency) {
|
|
43
41
|
assert(currency instanceof UnitType, 'Currency must be supples as a UnitType')
|
|
44
42
|
|
|
45
43
|
if (isNumberUnit(val)) return val // TODO: consider checking if the unitType.equals(currency) (if currency is object)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* @flow */
|
|
2
1
|
/* global fetch */
|
|
3
2
|
import urlJoin from 'url-join'
|
|
4
3
|
import qs from 'querystring'
|
|
@@ -102,7 +101,7 @@ export default class InsightAPIClient {
|
|
|
102
101
|
}))
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
async fetchTx(txId
|
|
104
|
+
async fetchTx(txId) {
|
|
106
105
|
const encodedTxId = encodeURIComponent(txId)
|
|
107
106
|
const url = urlJoin(this._baseURL, `/tx/${encodedTxId}`)
|
|
108
107
|
const response = await fetch(url)
|
|
@@ -114,14 +113,14 @@ export default class InsightAPIClient {
|
|
|
114
113
|
return response.json()
|
|
115
114
|
}
|
|
116
115
|
|
|
117
|
-
async fetchRawTx(txId
|
|
116
|
+
async fetchRawTx(txId) {
|
|
118
117
|
const encodedTxId = encodeURIComponent(txId)
|
|
119
118
|
const url = urlJoin(this._baseURL, `/rawtx/${encodedTxId}`)
|
|
120
119
|
const { rawtx } = await fetchJson(url)
|
|
121
120
|
return rawtx
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
async fetchTxData(addrs
|
|
123
|
+
async fetchTxData(addrs, options = {}) {
|
|
125
124
|
if (!Array.isArray(addrs) || addrs.length === 0) return { items: [], totalItems: 0 }
|
|
126
125
|
|
|
127
126
|
options = { noScriptSig: 1, noAsm: 1, noSpent: 0, from: 0, to: 10, ...options }
|
|
@@ -157,7 +156,7 @@ export default class InsightAPIClient {
|
|
|
157
156
|
return txs
|
|
158
157
|
}
|
|
159
158
|
|
|
160
|
-
async fetchUnconfirmedAncestorData(txId
|
|
159
|
+
async fetchUnconfirmedAncestorData(txId) {
|
|
161
160
|
const encodedTxId = encodeURIComponent(txId)
|
|
162
161
|
const url = urlJoin(this._baseURL, `/unconfirmed_ancestor/${encodedTxId}`)
|
|
163
162
|
return fetchJson(url)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* @flow */
|
|
2
1
|
import { orderTxs } from '../insight-api-client/util'
|
|
3
2
|
import { Address, UtxoCollection } from '@exodus/models'
|
|
4
3
|
import { isEqual, compact, uniq } from 'lodash'
|
|
@@ -20,7 +19,8 @@ export class BitcoinMonitorScanner {
|
|
|
20
19
|
#yieldToUI
|
|
21
20
|
#ordinalsEnabled
|
|
22
21
|
#ordinalChainIndex
|
|
23
|
-
|
|
22
|
+
#gapLimit
|
|
23
|
+
#refreshGapLimit
|
|
24
24
|
constructor({
|
|
25
25
|
asset,
|
|
26
26
|
assetClientInterface,
|
|
@@ -30,6 +30,8 @@ export class BitcoinMonitorScanner {
|
|
|
30
30
|
txFetchLimitResolver = ({ refresh }) => (refresh ? 50 : 10),
|
|
31
31
|
ordinalsEnabled,
|
|
32
32
|
ordinalChainIndex,
|
|
33
|
+
gapLimit = 10,
|
|
34
|
+
refreshGapLimit = 10,
|
|
33
35
|
}) {
|
|
34
36
|
assert(asset, 'asset is required!')
|
|
35
37
|
assert(assetClientInterface, 'assetClientInterface is required!')
|
|
@@ -37,6 +39,8 @@ export class BitcoinMonitorScanner {
|
|
|
37
39
|
assert(typeof yieldToUI === 'function', 'yieldToUI must be a function')
|
|
38
40
|
assert(typeof txFetchLimitResolver === 'function', 'txFetchLimitResolver must be a function')
|
|
39
41
|
assert(typeof shouldExcludeVoutUtxo === 'function', 'shouldExcludeVoutUtxo must be a function')
|
|
42
|
+
assert(typeof gapLimit === 'number', 'gapLimit must be a number')
|
|
43
|
+
assert(typeof refreshGapLimit === 'number', 'refreshGapLimit must be a number')
|
|
40
44
|
|
|
41
45
|
this.#asset = asset
|
|
42
46
|
this.#insightClient = insightClient
|
|
@@ -45,6 +49,8 @@ export class BitcoinMonitorScanner {
|
|
|
45
49
|
this.#shouldExcludeVoutUtxo = shouldExcludeVoutUtxo
|
|
46
50
|
this.#ordinalsEnabled = ordinalsEnabled
|
|
47
51
|
this.#ordinalChainIndex = ordinalChainIndex
|
|
52
|
+
this.#gapLimit = gapLimit
|
|
53
|
+
this.#refreshGapLimit = refreshGapLimit
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
async rescanBlockchainInsight({ walletAccount, refresh }) {
|
|
@@ -188,18 +194,13 @@ export class BitcoinMonitorScanner {
|
|
|
188
194
|
return txArrays.reduce((total, some) => total.concat(some), [])
|
|
189
195
|
}
|
|
190
196
|
|
|
191
|
-
const receiveGapLimit = 10
|
|
192
|
-
const restoreGapLimit = 10
|
|
193
|
-
const changeGapLimit = 10
|
|
194
|
-
const gapLimits = [receiveGapLimit, changeGapLimit]
|
|
195
197
|
const gapSearchParameters = newChains.map(({ purpose, chain }) => {
|
|
196
198
|
return {
|
|
197
199
|
purpose,
|
|
198
200
|
chain,
|
|
199
201
|
startAddressIndexes: chain.map(() => 0),
|
|
200
202
|
endAddressIndexes: chain.map(
|
|
201
|
-
(addressIndex
|
|
202
|
-
refresh ? restoreGapLimit : addressIndex + (gapLimits[chainIndex] || receiveGapLimit) // right of the || should never happen
|
|
203
|
+
(addressIndex) => (refresh ? this.#refreshGapLimit : addressIndex + this.#gapLimit) // right of the || should never happen
|
|
203
204
|
),
|
|
204
205
|
}
|
|
205
206
|
})
|
|
@@ -303,13 +304,9 @@ export class BitcoinMonitorScanner {
|
|
|
303
304
|
|
|
304
305
|
gapSearchParameters.forEach((indexData) => {
|
|
305
306
|
indexData.startAddressIndexes = [...indexData.endAddressIndexes]
|
|
306
|
-
indexData.endAddressIndexes = indexData.chain.map((addressIndex
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
(refresh && addressIndex === indexData.endAddressIndexes[chainIndex]
|
|
310
|
-
? restoreGapLimit
|
|
311
|
-
: gapLimits[chainIndex] || receiveGapLimit)
|
|
312
|
-
)
|
|
307
|
+
indexData.endAddressIndexes = indexData.chain.map((addressIndex) => {
|
|
308
|
+
const resolvedGapLimit = refresh ? this.#refreshGapLimit : this.#gapLimit
|
|
309
|
+
return addressIndex + resolvedGapLimit
|
|
313
310
|
})
|
|
314
311
|
})
|
|
315
312
|
|
|
@@ -94,7 +94,7 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
|
|
|
94
94
|
assert(keys, 'keys is required')
|
|
95
95
|
assert(coinInfo, 'coinInfo is required')
|
|
96
96
|
|
|
97
|
-
return async ({ unsignedTx, hdkeys, privateKeysAddressMap })
|
|
97
|
+
return async ({ unsignedTx, hdkeys, privateKeysAddressMap }) => {
|
|
98
98
|
assert(unsignedTx, 'unsignedTx is required')
|
|
99
99
|
assert(hdkeys || privateKeysAddressMap, 'hdkeys or privateKeysAddressMap is required')
|
|
100
100
|
const { addressPathsMap } = unsignedTx.txMeta
|
|
@@ -143,10 +143,11 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
|
|
|
143
143
|
// dApps request to sign only specific transaction inputs.
|
|
144
144
|
if (!inputInfo) continue
|
|
145
145
|
const { address, sigHash } = inputInfo
|
|
146
|
-
const sigHashTypes = sigHash ? [sigHash] : undefined
|
|
146
|
+
const sigHashTypes = sigHash !== undefined ? [sigHash || Transaction.SIGHASH_ALL] : undefined
|
|
147
147
|
const { key, purpose, publicKey } = getKeyAndPurpose(address)
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
// We shouldn't modify dApp PSBTs.
|
|
150
|
+
if (purpose === 49 && !isPsbtBufferPassed) {
|
|
150
151
|
// If spending from a P2SH address, we assume the address is P2SH wrapping
|
|
151
152
|
// P2WPKH. Exodus doesn't use P2SH addresses so we should only ever be
|
|
152
153
|
// signing a P2SH input if we are importing a private key
|
package/src/tx-sign/taproot.js
CHANGED
|
@@ -9,7 +9,7 @@ const ecc = eccFactory()
|
|
|
9
9
|
export function tweakSigner({ signer, ECPair, tweakHash, network }) {
|
|
10
10
|
assert(signer, 'signer is required')
|
|
11
11
|
assert(ECPair, 'ECPair is required')
|
|
12
|
-
let privateKey
|
|
12
|
+
let privateKey = signer.privateKey
|
|
13
13
|
if (!privateKey) {
|
|
14
14
|
throw new Error('Private key is required for tweaking signer!')
|
|
15
15
|
}
|
|
@@ -30,7 +30,7 @@ export function tweakSigner({ signer, ECPair, tweakHash, network }) {
|
|
|
30
30
|
})
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function tapTweakHash(pubKey
|
|
33
|
+
function tapTweakHash(pubKey, h) {
|
|
34
34
|
return crypto.taggedHash('TapTweak', Buffer.concat(h ? [pubKey, h] : [pubKey]))
|
|
35
35
|
}
|
|
36
36
|
|