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}