esplora_client/
api.rs

1//! Structs from the Esplora API
2//!
3//! See: <https://github.com/Blockstream/esplora/blob/master/API.md>
4
5pub use bitcoin::consensus::{deserialize, serialize};
6pub use bitcoin::hex::FromHex;
7use bitcoin::Weight;
8pub use bitcoin::{
9    transaction, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness,
10};
11
12use serde::Deserialize;
13
14#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
15pub struct PrevOut {
16    pub value: u64,
17    pub scriptpubkey: ScriptBuf,
18}
19
20#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
21pub struct Vin {
22    pub txid: Txid,
23    pub vout: u32,
24    // None if coinbase
25    pub prevout: Option<PrevOut>,
26    pub scriptsig: ScriptBuf,
27    #[serde(deserialize_with = "deserialize_witness", default)]
28    pub witness: Vec<Vec<u8>>,
29    pub sequence: u32,
30    pub is_coinbase: bool,
31}
32
33#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
34pub struct Vout {
35    pub value: u64,
36    pub scriptpubkey: ScriptBuf,
37}
38
39#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
40pub struct TxStatus {
41    pub confirmed: bool,
42    pub block_height: Option<u32>,
43    pub block_hash: Option<BlockHash>,
44    pub block_time: Option<u64>,
45}
46
47#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
48pub struct MerkleProof {
49    pub block_height: u32,
50    pub merkle: Vec<Txid>,
51    pub pos: usize,
52}
53
54#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
55pub struct OutputStatus {
56    pub spent: bool,
57    pub txid: Option<Txid>,
58    pub vin: Option<u64>,
59    pub status: Option<TxStatus>,
60}
61
62#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
63pub struct BlockStatus {
64    pub in_best_chain: bool,
65    pub height: Option<u32>,
66    pub next_best: Option<BlockHash>,
67}
68
69#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
70pub struct Tx {
71    pub txid: Txid,
72    pub version: i32,
73    pub locktime: u32,
74    pub vin: Vec<Vin>,
75    pub vout: Vec<Vout>,
76    /// Transaction size in raw bytes (NOT virtual bytes).
77    pub size: usize,
78    /// Transaction weight units.
79    pub weight: u64,
80    pub status: TxStatus,
81    pub fee: u64,
82}
83
84#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
85pub struct BlockTime {
86    pub timestamp: u64,
87    pub height: u32,
88}
89
90#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
91pub struct BlockSummary {
92    pub id: BlockHash,
93    #[serde(flatten)]
94    pub time: BlockTime,
95    /// Hash of the previous block, will be `None` for the genesis block.
96    pub previousblockhash: Option<bitcoin::BlockHash>,
97    pub merkle_root: bitcoin::hash_types::TxMerkleNode,
98}
99
100/// Address statistics, includes the address, and the utxo information for the address.
101#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
102pub struct AddressStats {
103    /// The address.
104    pub address: String,
105    /// The summary of transactions for this address, already on chain.
106    pub chain_stats: AddressTxsSummary,
107    /// The summary of transactions for this address, currently in the mempool.
108    pub mempool_stats: AddressTxsSummary,
109}
110
111/// Contains a summary of the transactions for an address.
112#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
113pub struct AddressTxsSummary {
114    /// The number of funded transaction outputs.
115    pub funded_txo_count: u32,
116    /// The sum of the funded transaction outputs, in satoshis.
117    pub funded_txo_sum: u64,
118    /// The number of spent transaction outputs.
119    pub spent_txo_count: u32,
120    /// The sum of the spent transaction outputs, in satoshis.
121    pub spent_txo_sum: u64,
122    /// The total number of transactions.
123    pub tx_count: u32,
124}
125
126impl Tx {
127    pub fn to_tx(&self) -> Transaction {
128        Transaction {
129            version: transaction::Version::non_standard(self.version),
130            lock_time: bitcoin::absolute::LockTime::from_consensus(self.locktime),
131            input: self
132                .vin
133                .iter()
134                .cloned()
135                .map(|vin| TxIn {
136                    previous_output: OutPoint {
137                        txid: vin.txid,
138                        vout: vin.vout,
139                    },
140                    script_sig: vin.scriptsig,
141                    sequence: bitcoin::Sequence(vin.sequence),
142                    witness: Witness::from_slice(&vin.witness),
143                })
144                .collect(),
145            output: self
146                .vout
147                .iter()
148                .cloned()
149                .map(|vout| TxOut {
150                    value: Amount::from_sat(vout.value),
151                    script_pubkey: vout.scriptpubkey,
152                })
153                .collect(),
154        }
155    }
156
157    pub fn confirmation_time(&self) -> Option<BlockTime> {
158        match self.status {
159            TxStatus {
160                confirmed: true,
161                block_height: Some(height),
162                block_time: Some(timestamp),
163                ..
164            } => Some(BlockTime { timestamp, height }),
165            _ => None,
166        }
167    }
168
169    pub fn previous_outputs(&self) -> Vec<Option<TxOut>> {
170        self.vin
171            .iter()
172            .cloned()
173            .map(|vin| {
174                vin.prevout.map(|po| TxOut {
175                    script_pubkey: po.scriptpubkey,
176                    value: Amount::from_sat(po.value),
177                })
178            })
179            .collect()
180    }
181
182    pub fn weight(&self) -> Weight {
183        Weight::from_wu(self.weight)
184    }
185
186    pub fn fee(&self) -> Amount {
187        Amount::from_sat(self.fee)
188    }
189}
190
191fn deserialize_witness<'de, D>(d: D) -> Result<Vec<Vec<u8>>, D::Error>
192where
193    D: serde::de::Deserializer<'de>,
194{
195    let list = Vec::<String>::deserialize(d)?;
196    list.into_iter()
197        .map(|hex_str| Vec::<u8>::from_hex(&hex_str))
198        .collect::<Result<Vec<Vec<u8>>, _>>()
199        .map_err(serde::de::Error::custom)
200}