esplora_client/api.rs
1// Bitcoin Dev Kit
2// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
3//
4// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers
5//
6// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
7// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
9// You may not use this file except in accordance with one or both of these
10// licenses.
11
12//! Structs from the Esplora API
13//!
14//! See: <https://github.com/Blockstream/esplora/blob/master/API.md>
15
16use bitcoin::hash_types;
17use serde::Deserialize;
18use std::collections::HashMap;
19
20pub use bitcoin::consensus::{deserialize, serialize};
21use bitcoin::hash_types::TxMerkleNode;
22pub use bitcoin::hex::FromHex;
23pub use bitcoin::{
24 absolute, block, transaction, Address, Amount, Block, BlockHash, CompactTarget, FeeRate,
25 OutPoint, Script, ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid, Weight, Witness,
26 Wtxid,
27};
28
29/// Information about a previous output.
30#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
31pub struct PrevOut {
32 /// The value of the previous output, in satoshis.
33 pub value: u64,
34 /// The ScriptPubKey that the previous output is locked to, as a [`ScriptBuf`].
35 pub scriptpubkey: ScriptBuf,
36}
37
38/// Information about an input from a [`Transaction`].
39#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
40pub struct Vin {
41 /// The [`Txid`] of the previous [`Transaction`] this input spends from.
42 pub txid: Txid,
43 /// The output index of the previous output in the [`Transaction`] that created it.
44 pub vout: u32,
45 /// The previous output amount and ScriptPubKey.
46 /// `None` if this is a coinbase input.
47 pub prevout: Option<PrevOut>,
48 /// The ScriptSig authorizes spending this input.
49 pub scriptsig: ScriptBuf,
50 /// The Witness that authorizes spending this input, if this is a SegWit spend.
51 #[serde(deserialize_with = "deserialize_witness", default)]
52 pub witness: Vec<Vec<u8>>,
53 /// The sequence value for this input, used to set RBF and Locktime behavior.
54 pub sequence: u32,
55 /// Whether this is a coinbase input.
56 pub is_coinbase: bool,
57}
58
59/// Information about a [`Transaction`]s output.
60#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
61pub struct Vout {
62 /// The value of the output, in satoshis.
63 pub value: u64,
64 /// The ScriptPubKey that the output is locked to, as a [`ScriptBuf`].
65 pub scriptpubkey: ScriptBuf,
66}
67
68/// The confirmation status of a [`Transaction`].
69#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
70pub struct TxStatus {
71 /// Whether the [`Transaction`] is confirmed or not.
72 pub confirmed: bool,
73 /// The block height the [`Transaction`] was confirmed in.
74 pub block_height: Option<u32>,
75 /// The [`BlockHash`] of the block the [`Transaction`] was confirmed in.
76 pub block_hash: Option<BlockHash>,
77 /// The time that the block was mined at, as a UNIX timestamp.
78 /// Note: this timestamp is set by the miner and may not reflect the exact time of mining.
79 pub block_time: Option<u64>,
80}
81
82/// A Merkle inclusion proof for a transaction, given it's [`Txid`].
83#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
84pub struct MerkleProof {
85 /// The height of the block the [`Transaction`] was confirmed in.
86 pub block_height: u32,
87 /// A list of transaction hashes the current hash is paired with,
88 /// recursively, in order to trace up to obtain the Merkle root of the
89 /// [`Block`], deepest pairing first.
90 pub merkle: Vec<Txid>,
91 /// The 0-based index of the position of the [`Transaction`] in the
92 /// ordered list of [`Transaction`]s in the [`Block`].
93 pub pos: usize,
94}
95
96/// The spend status of a [`TxOut`].
97#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
98pub struct OutputStatus {
99 /// Whether the [`TxOut`] is spent or not.
100 pub spent: bool,
101 /// The [`Txid`] that spent this [`TxOut`].
102 pub txid: Option<Txid>,
103 /// The input index of this [`TxOut`] in the [`Transaction`] that spent it.
104 pub vin: Option<u64>,
105 /// Information about the [`Transaction`] that spent this [`TxOut`].
106 pub status: Option<TxStatus>,
107}
108
109/// Information about a [`Block`]s status.
110#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
111pub struct BlockStatus {
112 /// Whether this [`Block`] belongs to the chain with the most
113 /// Proof-of-Work (false for [`Block`]s that belong to a stale chain).
114 pub in_best_chain: bool,
115 /// The height of this [`Block`].
116 pub height: Option<u32>,
117 /// The [`BlockHash`] of the [`Block`] that builds on top of this one.
118 pub next_best: Option<BlockHash>,
119}
120
121/// A [`Transaction`] in the format returned by Esplora.
122#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
123pub struct Tx {
124 /// The [`Txid`] of the [`Transaction`].
125 pub txid: Txid,
126 /// The version number of the [`Transaction`].
127 pub version: i32,
128 /// The locktime of the [`Transaction`].
129 /// Sets a time or height after which the [`Transaction`] can be mined.
130 pub locktime: u32,
131 /// The array of inputs in the [`Transaction`].
132 pub vin: Vec<Vin>,
133 /// The array of outputs in the [`Transaction`].
134 pub vout: Vec<Vout>,
135 /// The [`Transaction`] size in raw bytes (NOT virtual bytes).
136 pub size: usize,
137 /// The [`Transaction`]'s weight units.
138 pub weight: u64,
139 /// The confirmation status of the [`Transaction`].
140 pub status: TxStatus,
141 /// The fee amount paid by the [`Transaction`], in satoshis.
142 pub fee: u64,
143}
144
145/// Information about a bitcoin [`Block`].
146#[derive(Debug, Clone, Deserialize)]
147pub struct BlockInfo {
148 /// The [`Block`]'s [`BlockHash`].
149 pub id: BlockHash,
150 /// The [`Block`]'s height.
151 pub height: u32,
152 /// The [`Block`]'s version.
153 pub version: block::Version,
154 /// The [`Block`]'s UNIX timestamp.
155 pub timestamp: u64,
156 /// The [`Block`]'s [`Transaction`] count.
157 pub tx_count: u64,
158 /// The [`Block`]'s size, in bytes.
159 pub size: usize,
160 /// The [`Block`]'s weight.
161 pub weight: u64,
162 /// The Merkle root of the transactions in the block.
163 pub merkle_root: hash_types::TxMerkleNode,
164 /// The [`BlockHash`] of the previous [`Block`] (`None` for the genesis block).
165 pub previousblockhash: Option<BlockHash>,
166 /// The [`Block`]'s MTP (Median Time Past).
167 pub mediantime: u64,
168 /// The [`Block`]'s nonce value.
169 pub nonce: u32,
170 /// The [`Block`]'s `bits` value as a [`CompactTarget`].
171 pub bits: CompactTarget,
172 /// The [`Block`]'s difficulty target value.
173 pub difficulty: f64,
174}
175
176impl PartialEq for BlockInfo {
177 fn eq(&self, other: &Self) -> bool {
178 let Self { difficulty: d1, .. } = self;
179 let Self { difficulty: d2, .. } = other;
180
181 self.id == other.id
182 && self.height == other.height
183 && self.version == other.version
184 && self.timestamp == other.timestamp
185 && self.tx_count == other.tx_count
186 && self.size == other.size
187 && self.weight == other.weight
188 && self.merkle_root == other.merkle_root
189 && self.previousblockhash == other.previousblockhash
190 && self.mediantime == other.mediantime
191 && self.nonce == other.nonce
192 && self.bits == other.bits
193 && ((d1.is_nan() && d2.is_nan()) || (d1 == d2))
194 }
195}
196impl Eq for BlockInfo {}
197
198/// Time-related information about a [`Block`].
199#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
200pub struct BlockTime {
201 /// The [`Block`]'s timestamp.
202 pub timestamp: u64,
203 /// The [`Block`]'s height.
204 pub height: u32,
205}
206
207/// Summary about a [`Block`].
208#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
209pub struct BlockSummary {
210 /// The [`Block`]'s hash.
211 pub id: BlockHash,
212 /// The [`Block`]'s timestamp and height.
213 #[serde(flatten)]
214 pub time: BlockTime,
215 /// The [`BlockHash`] of the previous [`Block`] (`None` for the genesis [`Block`]).
216 pub previousblockhash: Option<BlockHash>,
217 /// The Merkle root of the [`Block`]'s [`Transaction`]s.
218 pub merkle_root: TxMerkleNode,
219}
220
221/// Statistics about an [`Address`].
222#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
223pub struct AddressStats {
224 /// The [`Address`].
225 pub address: String,
226 /// The summary of confirmed [`Transaction`]s for this [`Address`].
227 pub chain_stats: AddressTxsSummary,
228 /// The summary of mempool [`Transaction`]s for this [`Address`].
229 pub mempool_stats: AddressTxsSummary,
230}
231
232/// A summary of [`Transaction`]s in which an [`Address`] was involved.
233#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
234pub struct AddressTxsSummary {
235 /// The number of funded [`TxOut`]s.
236 pub funded_txo_count: u32,
237 /// The sum of the funded [`TxOut`]s, in satoshis.
238 pub funded_txo_sum: u64,
239 /// The number of spent [`TxOut`]s.
240 pub spent_txo_count: u32,
241 /// The sum of the spent [`TxOut`]s, in satoshis.
242 pub spent_txo_sum: u64,
243 /// The total number of [`Transaction`]s.
244 pub tx_count: u32,
245}
246
247/// Statistics about a particular [`Script`] hash's confirmed and mempool transactions.
248#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
249pub struct ScriptHashStats {
250 /// The summary of confirmed [`Transaction`]s for this [`Script`] hash.
251 pub chain_stats: ScriptHashTxsSummary,
252 /// The summary of mempool [`Transaction`]s for this [`Script`] hash.
253 pub mempool_stats: ScriptHashTxsSummary,
254}
255
256/// Contains a summary of the [`Transaction`]s for a particular [`Script`] hash.
257pub type ScriptHashTxsSummary = AddressTxsSummary;
258
259/// Information about a [`TxOut`]'s status: confirmation status,
260/// confirmation height, confirmation block hash and confirmation block time.
261#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
262pub struct UtxoStatus {
263 /// Whether or not the [`TxOut`] is confirmed.
264 pub confirmed: bool,
265 /// The block height in which the [`TxOut`] was confirmed.
266 pub block_height: Option<u32>,
267 /// The block hash in which the [`TxOut`] was confirmed.
268 pub block_hash: Option<BlockHash>,
269 /// The UNIX timestamp in which the [`TxOut`] was confirmed.
270 pub block_time: Option<u64>,
271}
272
273/// Information about an [`TxOut`]'s outpoint, confirmation status and value.
274#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
275pub struct Utxo {
276 /// The [`Txid`] of the [`Transaction`] that created the [`TxOut`].
277 pub txid: Txid,
278 /// The output index of the [`TxOut`] in the [`Transaction`] that created it.
279 pub vout: u32,
280 /// The confirmation status of the [`TxOut`].
281 pub status: UtxoStatus,
282 /// The value of the [`TxOut`] as an [`Amount`].
283 pub value: Amount,
284}
285
286/// Statistics about the mempool.
287#[derive(Clone, Debug, PartialEq, Deserialize)]
288pub struct MempoolStats {
289 /// The number of [`Transaction`]s in the mempool.
290 pub count: usize,
291 /// The total size of mempool [`Transaction`]s, in virtual bytes.
292 pub vsize: usize,
293 /// The total fee paid by mempool [`Transaction`]s, in satoshis.
294 pub total_fee: u64,
295 /// The mempool's fee rate distribution histogram.
296 ///
297 /// An array of `(feerate, vsize)` tuples, where each entry's `vsize` is the total vsize
298 /// of [`Transaction`]s paying more than `feerate` but less than the previous entry's `feerate`
299 /// (except for the first entry, which has no upper bound).
300 pub fee_histogram: Vec<(f64, usize)>,
301}
302
303/// A [`Transaction`] that recently entered the mempool.
304#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
305pub struct MempoolRecentTx {
306 /// The [`Transaction`]'s ID, as a [`Txid`].
307 pub txid: Txid,
308 /// The [`Amount`] of fees paid by the transaction, in satoshis.
309 pub fee: u64,
310 /// The [`Transaction`]'s size, in virtual bytes.
311 pub vsize: usize,
312 /// Combined [`Amount`] of the [`Transaction`], in satoshis.
313 pub value: u64,
314}
315
316/// The result for a broadcasted package of [`Transaction`]s.
317#[derive(Deserialize, Debug)]
318pub struct SubmitPackageResult {
319 /// The transaction package result message. "success" indicates all transactions were accepted
320 /// into or are already in the mempool.
321 pub package_msg: String,
322 /// Transaction results keyed by [`Wtxid`].
323 #[serde(rename = "tx-results")]
324 pub tx_results: HashMap<Wtxid, TxResult>,
325 /// List of txids of replaced transactions.
326 #[serde(rename = "replaced-transactions")]
327 pub replaced_transactions: Option<Vec<Txid>>,
328}
329
330/// The result [`Transaction`] for a broadcasted package of [`Transaction`]s.
331#[derive(Deserialize, Debug)]
332pub struct TxResult {
333 /// The transaction id.
334 pub txid: Txid,
335 /// The [`Wtxid`] of a different transaction with the same [`Txid`] but different witness found
336 /// in the mempool.
337 ///
338 /// If set, this means the submitted transaction was ignored.
339 #[serde(rename = "other-wtxid")]
340 pub other_wtxid: Option<Wtxid>,
341 /// Sigops-adjusted virtual transaction size.
342 pub vsize: Option<u32>,
343 /// Transaction fees.
344 pub fees: Option<MempoolFeesSubmitPackage>,
345 /// The transaction error string, if it was rejected by the mempool
346 pub error: Option<String>,
347}
348
349/// The mempool fees for a resulting [`Transaction`] broadcasted by a package of [`Transaction`]s.
350#[derive(Deserialize, Debug)]
351pub struct MempoolFeesSubmitPackage {
352 /// Transaction fee.
353 #[serde(with = "bitcoin::amount::serde::as_btc")]
354 pub base: Amount,
355 /// The effective feerate.
356 ///
357 /// Will be `None` if the transaction was already in the mempool. For example, the package
358 /// feerate and/or feerate with modified fees from the `prioritisetransaction` JSON-RPC method.
359 #[serde(
360 rename = "effective-feerate",
361 default,
362 deserialize_with = "deserialize_feerate"
363 )]
364 pub effective_feerate: Option<FeeRate>,
365 /// If [`Self::effective_feerate`] is provided, this holds the [`Wtxid`]s of the transactions
366 /// whose fees and vsizes are included in effective-feerate.
367 #[serde(rename = "effective-includes")]
368 pub effective_includes: Option<Vec<Wtxid>>,
369}
370
371impl Tx {
372 /// Convert a transaction from the format returned by Esplora into a `rust-bitcoin`
373 /// [`Transaction`].
374 pub fn to_tx(&self) -> Transaction {
375 Transaction {
376 version: transaction::Version::non_standard(self.version),
377 lock_time: bitcoin::absolute::LockTime::from_consensus(self.locktime),
378 input: self
379 .vin
380 .iter()
381 .cloned()
382 .map(|vin| TxIn {
383 previous_output: OutPoint {
384 txid: vin.txid,
385 vout: vin.vout,
386 },
387 script_sig: vin.scriptsig,
388 sequence: bitcoin::Sequence(vin.sequence),
389 witness: Witness::from_slice(&vin.witness),
390 })
391 .collect(),
392 output: self
393 .vout
394 .iter()
395 .cloned()
396 .map(|vout| TxOut {
397 value: Amount::from_sat(vout.value),
398 script_pubkey: vout.scriptpubkey,
399 })
400 .collect(),
401 }
402 }
403
404 /// Get the confirmation time from a [`Tx`].
405 pub fn confirmation_time(&self) -> Option<BlockTime> {
406 match self.status {
407 TxStatus {
408 confirmed: true,
409 block_height: Some(height),
410 block_time: Some(timestamp),
411 ..
412 } => Some(BlockTime { timestamp, height }),
413 _ => None,
414 }
415 }
416
417 /// Get a list of the [`Tx`]'s previous outputs.
418 pub fn previous_outputs(&self) -> Vec<Option<TxOut>> {
419 self.vin
420 .iter()
421 .cloned()
422 .map(|vin| {
423 vin.prevout.map(|po| TxOut {
424 script_pubkey: po.scriptpubkey,
425 value: Amount::from_sat(po.value),
426 })
427 })
428 .collect()
429 }
430
431 /// Get the weight of a [`Tx`].
432 pub fn weight(&self) -> Weight {
433 Weight::from_wu(self.weight)
434 }
435
436 /// Get the fee paid by a [`Tx`].
437 pub fn fee(&self) -> Amount {
438 Amount::from_sat(self.fee)
439 }
440}
441
442fn deserialize_witness<'de, D>(d: D) -> Result<Vec<Vec<u8>>, D::Error>
443where
444 D: serde::de::Deserializer<'de>,
445{
446 let list = Vec::<String>::deserialize(d)?;
447 list.into_iter()
448 .map(|hex_str| Vec::<u8>::from_hex(&hex_str))
449 .collect::<Result<Vec<Vec<u8>>, _>>()
450 .map_err(serde::de::Error::custom)
451}
452
453fn deserialize_feerate<'de, D>(d: D) -> Result<Option<FeeRate>, D::Error>
454where
455 D: serde::de::Deserializer<'de>,
456{
457 use serde::de::Error;
458
459 let btc_per_kvb = match Option::<f64>::deserialize(d)? {
460 Some(v) => v,
461 None => return Ok(None),
462 };
463 let sat_per_kwu = btc_per_kvb * 25_000_000.0;
464 if sat_per_kwu.is_infinite() {
465 return Err(D::Error::custom("feerate overflow"));
466 }
467 Ok(Some(FeeRate::from_sat_per_kwu(sat_per_kwu as u64)))
468}