@exodus/bitcoin-api 2.28.0 → 2.29.1
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/CHANGELOG.md +19 -0
- package/package.json +2 -2
- package/src/fee/fee-estimator.js +50 -41
- package/src/fee/index.js +8 -1
- package/src/tx-log/bitcoin-monitor-scanner.js +2 -0
- package/src/tx-send/index.js +8 -1
- package/src/tx-sign/default-prepare-for-signing.js +16 -9
- package/src/utxos-utils.js +16 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [2.29.1](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.29.0...@exodus/bitcoin-api@2.29.1) (2024-11-13)
|
|
7
|
+
|
|
8
|
+
**Note:** Update exports in fee module of package @exodus/bitcoin-api
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [2.29.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.28.0...@exodus/bitcoin-api@2.29.0) (2024-11-10)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* filter immature coinbase utxos in getUsableUtxos ([#4507](https://github.com/ExodusMovement/assets/issues/4507)) ([3774f66](https://github.com/ExodusMovement/assets/commit/3774f66ee733ca5a3f07a7556c349d08c2d217e9))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* fetch and require raw txs for segwit inputs ([#4399](https://github.com/ExodusMovement/assets/issues/4399)) ([f5822cc](https://github.com/ExodusMovement/assets/commit/f5822cc60d6e542f9aea88c051e1a2dbe56da13d))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
## [2.28.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.27.0...@exodus/bitcoin-api@2.28.0) (2024-10-31)
|
|
7
26
|
|
|
8
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.29.1",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"type": "git",
|
|
57
57
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "5faabb2475dbf47e9877276b1ef22ad237628c40"
|
|
60
60
|
}
|
package/src/fee/fee-estimator.js
CHANGED
|
@@ -79,49 +79,17 @@ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
|
|
|
79
79
|
4 + // n_locktime
|
|
80
80
|
varuint.encodingLength(inputs.length) + // inputs_len
|
|
81
81
|
// input[]
|
|
82
|
-
inputs.reduce(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const scriptType = scriptClassifier.classifyScriptHex({ assetName, script })
|
|
87
|
-
|
|
88
|
-
const supportedTypes = supportedInputTypes[assetName] || supportedInputTypes.default
|
|
89
|
-
assert(
|
|
90
|
-
supportedTypes.includes(scriptType),
|
|
91
|
-
`Only ${supportedTypes.join(', ')} inputs supported right now`
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
const scriptSigLengths = compressed
|
|
95
|
-
? scriptSigCompressedLengths
|
|
96
|
-
: scriptSigUncompressedLengths
|
|
97
|
-
const scriptSigLength = scriptSigLengths[scriptType]
|
|
98
|
-
return t + 32 + 4 + varuint.encodingLength(scriptSigLength) + scriptSigLength + 4
|
|
99
|
-
}, 0) +
|
|
82
|
+
inputs.reduce(
|
|
83
|
+
(t, script) => t + getInputSize({ script, scriptClassifier, assetName, compressed }),
|
|
84
|
+
0
|
|
85
|
+
) +
|
|
100
86
|
varuint.encodingLength(outputs.length) + // outputs_len
|
|
101
87
|
// output[]
|
|
102
|
-
outputs.reduce(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let scriptType = scriptClassify.types[output]
|
|
108
|
-
const supportedTypes = supportedOutputTypes[assetName] || supportedOutputTypes.default
|
|
109
|
-
|
|
110
|
-
if (!supportedTypes.includes(scriptType)) {
|
|
111
|
-
scriptType = scriptClassifier.classifyAddress({
|
|
112
|
-
assetName,
|
|
113
|
-
address: output,
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
assert(
|
|
118
|
-
supportedTypes.includes(scriptType),
|
|
119
|
-
`Only ${supportedTypes.join(', ')} outputs supported right now`
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
const scriptPubKeyLength = scriptPubKeyLengths[scriptType]
|
|
123
|
-
return t + 8 + varuint.encodingLength(scriptPubKeyLength) + scriptPubKeyLength
|
|
124
|
-
}, 0)
|
|
88
|
+
outputs.reduce(
|
|
89
|
+
(t, output) =>
|
|
90
|
+
t + getOutputSize({ output, scriptClassifier, assetName, defaultOutputType }),
|
|
91
|
+
0
|
|
92
|
+
)
|
|
125
93
|
|
|
126
94
|
const witnessSize =
|
|
127
95
|
1 + // marker
|
|
@@ -169,4 +137,45 @@ const getFeeEstimatorFactory = ({ defaultOutputType, addressApi }) => {
|
|
|
169
137
|
return createDefaultFeeEstimator(getSize)
|
|
170
138
|
}
|
|
171
139
|
|
|
140
|
+
export const getInputSize = ({ script, scriptClassifier, assetName, compressed }) => {
|
|
141
|
+
if (script === null) script = '76a914000000000000000000000000000000000000000088ac' // P2PKH
|
|
142
|
+
assert(isHex(script), 'script must be hex string')
|
|
143
|
+
|
|
144
|
+
const scriptType = scriptClassifier.classifyScriptHex({ assetName, script })
|
|
145
|
+
|
|
146
|
+
const supportedTypes = supportedInputTypes[assetName] || supportedInputTypes.default
|
|
147
|
+
assert(
|
|
148
|
+
supportedTypes.includes(scriptType),
|
|
149
|
+
`Only ${supportedTypes.join(', ')} inputs supported right now`
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const scriptSigLengths = compressed ? scriptSigCompressedLengths : scriptSigUncompressedLengths
|
|
153
|
+
const scriptSigLength = scriptSigLengths[scriptType]
|
|
154
|
+
return 32 + 4 + varuint.encodingLength(scriptSigLength) + scriptSigLength + 4
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const getOutputSize = ({ output, scriptClassifier, assetName, defaultOutputType }) => {
|
|
158
|
+
// if (output === null) output = get(asset, 'address.versions.bech32') ? 'P2WSH' : 'P2PKH'
|
|
159
|
+
|
|
160
|
+
if (output === null) output = defaultOutputType
|
|
161
|
+
|
|
162
|
+
let scriptType = scriptClassify.types[output]
|
|
163
|
+
const supportedTypes = supportedOutputTypes[assetName] || supportedOutputTypes.default
|
|
164
|
+
|
|
165
|
+
if (!supportedTypes.includes(scriptType)) {
|
|
166
|
+
scriptType = scriptClassifier.classifyAddress({
|
|
167
|
+
assetName,
|
|
168
|
+
address: output,
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
assert(
|
|
173
|
+
supportedTypes.includes(scriptType),
|
|
174
|
+
`Only ${supportedTypes.join(', ')} outputs supported right now`
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const scriptPubKeyLength = scriptPubKeyLengths[scriptType]
|
|
178
|
+
return 8 + varuint.encodingLength(scriptPubKeyLength) + scriptPubKeyLength
|
|
179
|
+
}
|
|
180
|
+
|
|
172
181
|
export default getFeeEstimatorFactory
|
package/src/fee/index.js
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
export * from './get-fee-resolver.js'
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
default as getFeeEstimatorFactory,
|
|
4
|
+
getInputSize,
|
|
5
|
+
getOutputSize,
|
|
6
|
+
getSizeFactory,
|
|
7
|
+
} from './fee-estimator.js'
|
|
8
|
+
export { default as createDefaultFeeEstimator } from './fee-utils.js'
|
|
9
|
+
export { scriptClassifierFactory } from './script-classifier.js'
|
|
@@ -418,6 +418,7 @@ export class BitcoinMonitorScanner {
|
|
|
418
418
|
feePerKB: txItem.fees ? (txItem.fees / txItem.vsize) * 1000 * 1e8 : null,
|
|
419
419
|
rbfEnabled: txItem.rbf,
|
|
420
420
|
blocksSeen: 0,
|
|
421
|
+
...(txItem.vin.length === 0 ? { isCoinbase: true } : undefined),
|
|
421
422
|
},
|
|
422
423
|
currencies: { [assetName]: currency },
|
|
423
424
|
}
|
|
@@ -544,6 +545,7 @@ export class BitcoinMonitorScanner {
|
|
|
544
545
|
script: vout.scriptPubKey.hex,
|
|
545
546
|
value: val,
|
|
546
547
|
rbfEnabled: txItem.rbf,
|
|
548
|
+
...(txItem.vin.length === 0 ? { isCoinbase: true } : undefined),
|
|
547
549
|
}
|
|
548
550
|
|
|
549
551
|
if (this.#ordinalsEnabled) {
|
package/src/tx-send/index.js
CHANGED
|
@@ -71,7 +71,14 @@ export async function getNonWitnessTxs(asset, utxos, insightClient) {
|
|
|
71
71
|
utxos
|
|
72
72
|
.getAddressesForTxId(txId)
|
|
73
73
|
.toAddressStrings()
|
|
74
|
-
.some(
|
|
74
|
+
.some(
|
|
75
|
+
(a) =>
|
|
76
|
+
asset.address.isP2PKH(a) ||
|
|
77
|
+
asset.address.isP2SH(a) ||
|
|
78
|
+
asset.address.isP2SH2?.(a) ||
|
|
79
|
+
asset.address.isP2WPKH?.(a) ||
|
|
80
|
+
asset.address.isP2WSH?.(a)
|
|
81
|
+
)
|
|
75
82
|
)
|
|
76
83
|
|
|
77
84
|
if (nonWitnessTxIds.length > 0) {
|
|
@@ -85,17 +85,24 @@ function createPsbtFromTxData({
|
|
|
85
85
|
const isTaprootAddress = purpose === 86
|
|
86
86
|
|
|
87
87
|
const txIn = { hash: txId, index: vout, sequence }
|
|
88
|
-
if (isSegwitAddress || isTaprootAddress) {
|
|
89
|
-
if (isTaprootAddress && tapLeafScript) {
|
|
90
|
-
txIn.tapLeafScript = tapLeafScript
|
|
91
|
-
}
|
|
92
88
|
|
|
93
|
-
|
|
89
|
+
if (isTaprootAddress && tapLeafScript) {
|
|
90
|
+
txIn.tapLeafScript = tapLeafScript
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (isSegwitAddress || isTaprootAddress) {
|
|
94
|
+
// taproot outputs only require the value and the script, not the full transaction
|
|
94
95
|
txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const rawTx = (rawTxs || []).find((t) => t.txId === txId)
|
|
99
|
+
|
|
100
|
+
// Non-taproot outputs require the full transaction
|
|
101
|
+
assert(
|
|
102
|
+
isTaprootAddress || !!rawTx?.rawData,
|
|
103
|
+
`Non-taproot outputs require the full previous transaction.`
|
|
104
|
+
)
|
|
105
|
+
if (!isTaprootAddress) {
|
|
99
106
|
const rawTxBuffer = Buffer.from(rawTx.rawData, 'hex')
|
|
100
107
|
if (canParseTx(Transaction, rawTxBuffer)) {
|
|
101
108
|
txIn.nonWitnessUtxo = rawTxBuffer
|
package/src/utxos-utils.js
CHANGED
|
@@ -233,11 +233,25 @@ function filterDustUtxos({ utxos, feeData }) {
|
|
|
233
233
|
return utxos
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
|
|
236
|
+
const COINBASE_MATURITY_HEIGHT = 100
|
|
237
|
+
|
|
238
|
+
export function getUsableUtxos({
|
|
239
|
+
asset,
|
|
240
|
+
utxos: someCoinbaseUtxos,
|
|
241
|
+
feeData,
|
|
242
|
+
txSet,
|
|
243
|
+
unconfirmedTxAncestor,
|
|
244
|
+
}) {
|
|
237
245
|
assert(asset, 'asset is required')
|
|
238
|
-
assert(
|
|
246
|
+
assert(someCoinbaseUtxos, 'utxos is required')
|
|
239
247
|
assert(feeData, 'feeData is required')
|
|
240
248
|
assert(txSet, 'txSet is required')
|
|
249
|
+
|
|
250
|
+
// Filter out immature coinbase outputs. They are not spendable.
|
|
251
|
+
const utxos = someCoinbaseUtxos.filter(
|
|
252
|
+
(utxo) => !utxo.isCoinbase || utxo.confirmations >= COINBASE_MATURITY_HEIGHT
|
|
253
|
+
)
|
|
254
|
+
|
|
241
255
|
if (!['bitcoin', 'bitcointestnet', 'bitcoinregtest'].includes(asset.name))
|
|
242
256
|
return filterDustUtxos({ utxos, feeData })
|
|
243
257
|
|