waterfalls_client/
api.rs

1pub use bitcoin::consensus::{deserialize, serialize};
2pub use bitcoin::hex::FromHex;
3use bitcoin::Weight;
4pub use bitcoin::{
5    transaction, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness,
6};
7
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeMap;
10
11/// Response from the waterfalls endpoint
12#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
13pub struct WaterfallResponse {
14    pub txs_seen: BTreeMap<String, Vec<Vec<TxSeen>>>,
15    pub page: u16,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub tip: Option<BlockHash>,
18
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub tip_meta: Option<BlockMeta>,
21}
22
23#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Ord, PartialOrd)]
24pub struct BlockMeta {
25    /// The block hash
26    pub b: BlockHash,
27
28    /// The block timestamp
29    pub t: u32,
30
31    /// The block height
32    pub h: u32,
33}
34
35/// A transaction seen in the blockchain for a specific script
36#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
37pub struct TxSeen {
38    pub txid: Txid,
39    pub height: u32,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub block_hash: Option<BlockHash>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub block_timestamp: Option<u32>,
44    #[serde(skip_serializing_if = "V::is_undefined", default)]
45    pub v: V,
46}
47
48/// Enum representing whether a transaction was seen in a vout or vin
49#[derive(Clone, PartialEq, Eq, Debug, Default)]
50pub enum V {
51    #[default]
52    Undefined,
53    Vin(u32),
54    Vout(u32),
55}
56
57impl V {
58    pub fn is_undefined(&self) -> bool {
59        matches!(self, V::Undefined)
60    }
61
62    pub fn raw(&self) -> i32 {
63        match self {
64            V::Undefined => 0,
65            V::Vout(n) => *n as i32,
66            V::Vin(n) => -(*n as i32) - 1,
67        }
68    }
69
70    pub fn from_raw(raw: i32) -> Self {
71        match raw {
72            0 => V::Undefined,
73            x if x > 0 => V::Vout(x as u32),
74            x => V::Vin((-x - 1) as u32),
75        }
76    }
77}
78
79impl Serialize for V {
80    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81    where
82        S: serde::Serializer,
83    {
84        serializer.serialize_i32(self.raw())
85    }
86}
87
88impl<'de> Deserialize<'de> for V {
89    fn deserialize<D>(deserializer: D) -> Result<V, D::Error>
90    where
91        D: serde::Deserializer<'de>,
92    {
93        let raw = i32::deserialize(deserializer)?;
94        Ok(V::from_raw(raw))
95    }
96}
97
98impl WaterfallResponse {
99    pub fn is_empty(&self) -> bool {
100        self.txs_seen
101            .iter()
102            .flat_map(|(_, v)| v.iter())
103            .all(|a| a.is_empty())
104    }
105}
106
107#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
108pub struct PrevOut {
109    pub value: u64,
110    pub scriptpubkey: ScriptBuf,
111}
112
113#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
114pub struct Vin {
115    pub txid: Txid,
116    pub vout: u32,
117    // None if coinbase
118    pub prevout: Option<PrevOut>,
119    pub scriptsig: ScriptBuf,
120    #[serde(deserialize_with = "deserialize_witness", default)]
121    pub witness: Vec<Vec<u8>>,
122    pub sequence: u32,
123    pub is_coinbase: bool,
124}
125
126#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
127pub struct Vout {
128    pub value: u64,
129    pub scriptpubkey: ScriptBuf,
130}
131
132#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
133pub struct TxStatus {
134    pub confirmed: bool,
135    pub block_height: Option<u32>,
136    pub block_hash: Option<BlockHash>,
137    pub block_time: Option<u64>,
138}
139
140#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
141pub struct MerkleProof {
142    pub block_height: u32,
143    pub merkle: Vec<Txid>,
144    pub pos: usize,
145}
146
147#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
148pub struct OutputStatus {
149    pub spent: bool,
150    pub txid: Option<Txid>,
151    pub vin: Option<u64>,
152    pub status: Option<TxStatus>,
153}
154
155#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
156pub struct BlockStatus {
157    pub in_best_chain: bool,
158    pub height: Option<u32>,
159    pub next_best: Option<BlockHash>,
160}
161
162#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
163pub struct Tx {
164    pub txid: Txid,
165    pub version: i32,
166    pub locktime: u32,
167    pub vin: Vec<Vin>,
168    pub vout: Vec<Vout>,
169    /// Transaction size in raw bytes (NOT virtual bytes).
170    pub size: usize,
171    /// Transaction weight units.
172    pub weight: u64,
173    pub status: TxStatus,
174    pub fee: u64,
175}
176
177#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
178pub struct BlockTime {
179    pub timestamp: u64,
180    pub height: u32,
181}
182
183#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
184pub struct BlockSummary {
185    pub id: BlockHash,
186    #[serde(flatten)]
187    pub time: BlockTime,
188    /// Hash of the previous block, will be `None` for the genesis block.
189    pub previousblockhash: Option<bitcoin::BlockHash>,
190    pub merkle_root: bitcoin::hash_types::TxMerkleNode,
191}
192
193/// Address statistics, includes the address, and the utxo information for the address.
194#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
195pub struct AddressStats {
196    /// The address.
197    pub address: String,
198    /// The summary of transactions for this address, already on chain.
199    pub chain_stats: AddressTxsSummary,
200    /// The summary of transactions for this address, currently in the mempool.
201    pub mempool_stats: AddressTxsSummary,
202}
203
204/// Contains a summary of the transactions for an address.
205#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
206pub struct AddressTxsSummary {
207    /// The number of funded transaction outputs.
208    pub funded_txo_count: u32,
209    /// The sum of the funded transaction outputs, in satoshis.
210    pub funded_txo_sum: u64,
211    /// The number of spent transaction outputs.
212    pub spent_txo_count: u32,
213    /// The sum of the spent transaction outputs, in satoshis.
214    pub spent_txo_sum: u64,
215    /// The total number of transactions.
216    pub tx_count: u32,
217}
218
219impl Tx {
220    pub fn to_tx(&self) -> Transaction {
221        Transaction {
222            version: transaction::Version::non_standard(self.version),
223            lock_time: bitcoin::absolute::LockTime::from_consensus(self.locktime),
224            input: self
225                .vin
226                .iter()
227                .cloned()
228                .map(|vin| TxIn {
229                    previous_output: OutPoint {
230                        txid: vin.txid,
231                        vout: vin.vout,
232                    },
233                    script_sig: vin.scriptsig,
234                    sequence: bitcoin::Sequence(vin.sequence),
235                    witness: Witness::from_slice(&vin.witness),
236                })
237                .collect(),
238            output: self
239                .vout
240                .iter()
241                .cloned()
242                .map(|vout| TxOut {
243                    value: Amount::from_sat(vout.value),
244                    script_pubkey: vout.scriptpubkey,
245                })
246                .collect(),
247        }
248    }
249
250    pub fn confirmation_time(&self) -> Option<BlockTime> {
251        match self.status {
252            TxStatus {
253                confirmed: true,
254                block_height: Some(height),
255                block_time: Some(timestamp),
256                ..
257            } => Some(BlockTime { timestamp, height }),
258            _ => None,
259        }
260    }
261
262    pub fn previous_outputs(&self) -> Vec<Option<TxOut>> {
263        self.vin
264            .iter()
265            .cloned()
266            .map(|vin| {
267                vin.prevout.map(|po| TxOut {
268                    script_pubkey: po.scriptpubkey,
269                    value: Amount::from_sat(po.value),
270                })
271            })
272            .collect()
273    }
274
275    pub fn weight(&self) -> Weight {
276        Weight::from_wu(self.weight)
277    }
278
279    pub fn fee(&self) -> Amount {
280        Amount::from_sat(self.fee)
281    }
282}
283
284fn deserialize_witness<'de, D>(d: D) -> Result<Vec<Vec<u8>>, D::Error>
285where
286    D: serde::de::Deserializer<'de>,
287{
288    let list = Vec::<String>::deserialize(d)?;
289    list.into_iter()
290        .map(|hex_str| Vec::<u8>::from_hex(&hex_str))
291        .collect::<Result<Vec<Vec<u8>>, _>>()
292        .map_err(serde::de::Error::custom)
293}