@exodus/bitcoin-api 1.0.4 → 2.0.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "1.0.4",
3
+ "version": "2.0.0",
4
4
  "description": "Exodus bitcoin-api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -19,14 +19,15 @@
19
19
  "lint:fix": "yarn lint --fix"
20
20
  },
21
21
  "dependencies": {
22
- "@exodus/asset-lib": "^3.7.1",
23
- "@exodus/bitcoinjs-lib": "6.0.2-beta.4",
22
+ "@exodus/asset-lib": "^3.7.2",
23
+ "@exodus/bip44-constants": "^195.0.0",
24
+ "@exodus/bitcoinjs-lib": "6.0.2-beta.5",
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",
29
+ "@exodus/timer": "^1.0.0",
28
30
  "bech32": "^1.1.3",
29
- "bip44-constants": "55.0.0",
30
31
  "coininfo": "5.1.0",
31
32
  "delay": "4.0.1",
32
33
  "ecpair": "2.0.1",
@@ -39,5 +40,5 @@
39
40
  "@exodus/bip-schnorr": "0.6.6-fork-1",
40
41
  "@noble/secp256k1": "~1.5.3"
41
42
  },
42
- "gitHead": "00377ab9ad8a29114f5064efcb0197d4257056d1"
43
+ "gitHead": "53103ac1ac2c6fd96111869a13893b6c13bbcb3a"
43
44
  }
@@ -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) =>
@@ -1,27 +1,3 @@
1
- import bip44Constants from 'bip44-constants'
2
-
3
- // MOVE TO SHARED LIB!!!
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
@@ -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
- // other bitcoin-like assets
70
- const classifyOutput = scriptClassify.outputFactory({ ecc })
71
- const baseSize =
72
- 4 + // n_version
73
- 4 + // n_locktime
74
- varuint.encodingLength(inputs.length) + // inputs_len
75
- // input[]
76
- inputs.reduce((t, script) => {
77
- if (script === null) script = '76a914000000000000000000000000000000000000000088ac' // P2PKH
78
- assert(isHex(script), 'script must be hex string')
79
-
80
- const scriptBuffer = Buffer.from(script, 'hex')
81
- const scriptType = classifyOutput(scriptBuffer)
82
-
83
- const supportedTypes = supportedInputTypes[assetName] || supportedInputTypes.default
84
- assert(
85
- supportedTypes.includes(scriptType),
86
- `Only ${supportedTypes.join(', ')} inputs supported right now`
87
- )
88
-
89
- const scriptSigLengths = compressed
90
- ? scriptSigCompressedLengths
91
- : scriptSigUncompressedLengths
92
- const scriptSigLength = scriptSigLengths[scriptType]
93
- return t + 32 + 4 + varuint.encodingLength(scriptSigLength) + scriptSigLength + 4
94
- }, 0) +
95
- varuint.encodingLength(outputs.length) + // outputs_len
96
- // output[]
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
- if ([P2TR].includes(utxoScriptType)) {
145
- // Only the 64 byte Schnorr signature is present for Taproot Key-Path spend
146
- const signatureLength = 64
147
- return (
148
- t + varuint.encodingLength(1) + varuint.encodingLength(signatureLength) + signatureLength
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
- // Non-witness inputs get a placeholder zero byte
153
- return t + 1
154
- }, 0)
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
- // If witness is all placeholder bytes, that means we have no witness inputs
157
- // In that case, we're going to use the legacy serialization format, so just
158
- // return baseSize
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
- const totalSize = baseSize + witnessSize
162
- const weight = baseSize * 3 + totalSize
163
- // Return vbytes
164
- return Math.ceil(weight / 4)
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
+ }
package/src/index.js CHANGED
@@ -6,7 +6,6 @@ export * from './bitcoinjs-lib'
6
6
  export { default as InsightAPIClient } from './insight-api-client'
7
7
  export { default as InsightWSClient } from './insight-api-client/ws'
8
8
  export { default as bip44Constants } from './constants/bip44'
9
- export { default as createGetKeyIdentifier } from './key-identifier'
10
9
  export * from './tx-send'
11
10
  export * from './tx-sign'
12
11
  export * from './fee'
@@ -1,21 +0,0 @@
1
- import { buildBip32Path } from '@exodus/keychain'
2
- import assert from 'minimalistic-assert'
3
-
4
- export default ({ bip44 }) => ({ purpose, accountIndex, chainIndex, addressIndex }) => {
5
- assert(typeof bip44 === 'number', 'bip44 must be a number')
6
- // TODO, move key-utils.js hydra/keychain to asset libs.
7
- // https://github.com/ExodusMovement/assets/pull/410#discussion_r1026331735
8
- const derivationPath = buildBip32Path({
9
- asset: { bip44 }, // buildBip32Path only requires bip44. Change asset with bip44 and validate!
10
- purpose,
11
- accountIndex,
12
- chainIndex,
13
- addressIndex,
14
- })
15
-
16
- return {
17
- derivationAlgorithm: 'BIP32',
18
- derivationPath,
19
- keyType: 'secp256k1',
20
- }
21
- }