Skip to main content

mempoolspace/
response.rs

1//! Responses
2
3use bitcoin::address::{Address, NetworkUnchecked};
4use bitcoin::{Amount, BlockHash, FeeRate, TxMerkleNode, Weight};
5use serde::{Deserialize, Serialize};
6
7use crate::deser;
8
9/// Prices
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11pub struct Prices {
12    /// Timestamp
13    #[serde(rename = "time")]
14    pub timestamp: u64,
15    /// USD
16    #[serde(rename = "USD")]
17    pub usd: u32,
18    /// EUR
19    #[serde(rename = "EUR")]
20    pub eur: u32,
21    /// GBP
22    #[serde(rename = "GBP")]
23    pub gbp: u32,
24    /// CAD
25    #[serde(rename = "CAD")]
26    pub cad: u32,
27    /// CHF
28    #[serde(rename = "CHF")]
29    pub chf: u32,
30    /// AUD
31    #[serde(rename = "AUD")]
32    pub aud: u32,
33    /// JPY
34    #[serde(rename = "JPY")]
35    pub jpy: u32,
36}
37
38/// Bitcoin difficulty adjustment information
39#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
40pub struct DifficultyAdjustment {
41    /// Progress percentage towards next difficulty adjustment
42    #[serde(rename = "progressPercent")]
43    pub progress_percent: f64,
44    /// Difficulty change percentage
45    #[serde(rename = "difficultyChange")]
46    pub difficulty_change: f64,
47    /// Estimated timestamp for next retarget
48    #[serde(rename = "estimatedRetargetDate")]
49    pub estimated_retarget_date: u64,
50    /// Number of blocks remaining until retarget
51    #[serde(rename = "remainingBlocks")]
52    pub remaining_blocks: u32,
53    /// Estimated time remaining in seconds
54    #[serde(rename = "remainingTime")]
55    pub remaining_time: u64,
56    /// Previous retarget percentage change
57    #[serde(rename = "previousRetarget")]
58    pub previous_retarget: f64,
59    /// Block height of next retarget
60    #[serde(rename = "nextRetargetHeight")]
61    pub next_retarget_height: u32,
62    /// Average time between blocks
63    #[serde(rename = "timeAvg")]
64    pub time_avg: u64,
65    /// Adjusted average time between blocks
66    #[serde(rename = "adjustedTimeAvg")]
67    pub adjusted_time_avg: u64,
68    /// Time offset
69    #[serde(rename = "timeOffset")]
70    pub time_offset: i64,
71}
72
73/// Bitcoin address statistics
74#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
75pub struct AddressStats {
76    /// Bitcoin address
77    pub address: Address<NetworkUnchecked>,
78    /// On-chain statistics
79    pub chain_stats: TransactionStats,
80    /// Mempool statistics
81    pub mempool_stats: TransactionStats,
82}
83
84/// Transaction statistics for an address
85#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
86pub struct TransactionStats {
87    /// Number of funded transaction outputs
88    pub funded_txo_count: u32,
89    /// Total amount of funded transaction outputs
90    pub funded_txo_sum: Amount,
91    /// Number of spent transaction outputs
92    pub spent_txo_count: u32,
93    /// Total amount of spent transaction outputs
94    pub spent_txo_sum: Amount,
95    /// Total number of transactions
96    pub tx_count: u32,
97}
98
99/// Block Info
100#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
101pub struct BlockInfo {
102    /// Block hash
103    pub id: BlockHash,
104    /// Block height
105    pub height: u32,
106    /// Block version
107    pub version: u32,
108    /// UNIX timestamp
109    pub timestamp: u64,
110    /// Number of transactions in the block
111    pub tx_count: u32,
112    /// Block size in bytes
113    pub size: u32,
114    /// Block weight
115    #[serde(with = "deser::weight_serde")]
116    pub weight: Weight,
117    /// Merkle root hash
118    pub merkle_root: TxMerkleNode,
119    /// Previous block hash
120    #[serde(rename = "previousblockhash")]
121    pub previous_block_hash: BlockHash,
122    /// Median time of the block
123    #[serde(rename = "mediantime")]
124    pub median_time: u64,
125    /// Block nonce
126    pub nonce: u32,
127    /// Bits (difficulty target)
128    pub bits: u32,
129    /// Difficulty
130    pub difficulty: f64,
131}
132
133/// Hashrate stats entry
134#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
135pub struct HashrateEntry {
136    /// Unix timestamp
137    #[serde(alias = "time")]
138    pub timestamp: u64,
139    /// Average hashrate
140    #[serde(rename = "avgHashrate")]
141    pub avg_hashrate: f64,
142}
143
144/// Difficulty entry
145#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
146pub struct DifficultyEntry {
147    /// UNIX timestamp
148    #[serde(alias = "time")]
149    pub timestamp: u64,
150    /// Block height
151    pub height: u32,
152    /// Difficulty
153    pub difficulty: f64,
154    /// Adjustment
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub adjustment: Option<f32>,
157}
158
159/// Hashrate stats
160#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
161pub struct HashrateStats {
162    /// Hashrates
163    pub hashrates: Vec<HashrateEntry>,
164    /// Difficulty
165    pub difficulty: Vec<DifficultyEntry>,
166    /// Current hashrate
167    #[serde(rename = "currentHashrate")]
168    pub current_hashrate: f64,
169    /// Current difficulty
170    #[serde(rename = "currentDifficulty")]
171    pub current_difficulty: f64,
172}
173
174/// Bitcoin fee recommendations
175#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
176pub struct FeeRecommendations {
177    /// Fastest confirmation fee
178    #[serde(rename = "fastestFee")]
179    #[serde(with = "deser::fee_rate_u64_serde")]
180    pub fastest_fee: FeeRate,
181    /// Fee for confirmation within 30 minutes
182    #[serde(rename = "halfHourFee")]
183    #[serde(with = "deser::fee_rate_u64_serde")]
184    pub half_hour_fee: FeeRate,
185    /// Fee for confirmation within 1 hour
186    #[serde(rename = "hourFee")]
187    #[serde(with = "deser::fee_rate_u64_serde")]
188    pub hour_fee: FeeRate,
189    /// Economy fee for slower confirmation
190    #[serde(rename = "economyFee")]
191    #[serde(with = "deser::fee_rate_u64_serde")]
192    pub economy_fee: FeeRate,
193    /// Minimum fee
194    #[serde(rename = "minimumFee")]
195    #[serde(with = "deser::fee_rate_u64_serde")]
196    pub minimum_fee: FeeRate,
197}
198
199/// Fee histogram entry representing transactions at a specific fee rate
200#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
201pub struct FeeHistogramEntry {
202    /// Fee rate (converted from API)
203    #[serde(with = "deser::fee_rate_f64_serde")]
204    pub fee_rate: FeeRate,
205    /// Virtual size of transactions at this fee rate
206    #[serde(with = "deser::weight_serde")]
207    pub vsize: Weight,
208}
209
210/// Bitcoin mempool statistics
211#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
212pub struct MempoolStats {
213    /// Number of transactions in mempool
214    pub count: u32,
215    /// Total virtual size of all transactions in mempool
216    pub vsize: Weight,
217    /// Total fees of all transactions in mempool
218    pub total_fee: Amount,
219    /// Fee histogram distribution
220    #[serde(with = "deser::fee_histogram_serde")]
221    pub fee_histogram: Vec<FeeHistogramEntry>,
222}
223
224impl MempoolStats {
225    /// Total size (MB) of all transactions in mempool
226    pub fn size_mb(&self) -> f64 {
227        let vbyte: u64 = self.vsize.to_vbytes_ceil();
228        vbyte as f64 / 1_000_000.0
229    }
230
231    /// Calculate average fee rate
232    pub fn avg_fee_rate(&self) -> FeeRate {
233        if self.fee_histogram.is_empty() {
234            return FeeRate::BROADCAST_MIN;
235        }
236
237        let fee_rate_sum: u64 = self
238            .fee_histogram
239            .iter()
240            .map(|e| e.fee_rate.to_sat_per_kwu())
241            .sum();
242        let total: u64 = self.fee_histogram.len() as u64;
243        let fee_rate_avg: u64 = fee_rate_sum / total;
244        FeeRate::from_sat_per_kwu(fee_rate_avg)
245    }
246}
247
248/// Mempool block fees
249#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
250pub struct MempoolBlockFees {
251    /// Block size
252    #[serde(rename = "blockSize")]
253    pub block_size: u64,
254    /// Block vSize
255    #[serde(rename = "blockVSize")]
256    pub block_v_size: f64,
257    /// Number of transactions
258    #[serde(rename = "nTx")]
259    pub n_tx: u64,
260    /// Total fees
261    #[serde(rename = "totalFees")]
262    pub total_fees: Amount,
263    /// Median fee rate
264    #[serde(rename = "medianFee")]
265    #[serde(with = "deser::fee_rate_f64_serde")]
266    pub median_fee: FeeRate,
267    /// Fee rate range
268    #[serde(rename = "feeRange")]
269    #[serde(with = "deser::fee_rate_vec_serde")]
270    pub fee_range: Vec<FeeRate>,
271}
272
273#[cfg(test)]
274mod tests {
275    use std::str::FromStr;
276
277    use super::*;
278
279    #[test]
280    fn test_block_info_deserialization() {
281        let json_data = r#"{"id":"0000000000000000000384f28cb3b9cf4377a39cfd6c29ae9466951de38c0529","height":730000,"version":536870912,"timestamp":1648829449,"tx_count":1627,"size":1210916,"weight":3993515,"merkle_root":"efa344bcd6c0607f93b709515dd6dc5496178112d680338ebea459e3de7b4fbc","previousblockhash":"00000000000000000008b6f6fb83f8d74512ef1e0af29e642dd20daddd7d318f","mediantime":1648827418,"nonce":3580664066,"bits":386521239,"difficulty":28587155782195.14}"#;
282
283        let block_info: BlockInfo = serde_json::from_str(json_data).unwrap();
284
285        assert_eq!(
286            block_info,
287            BlockInfo {
288                id: BlockHash::from_str(
289                    "0000000000000000000384f28cb3b9cf4377a39cfd6c29ae9466951de38c0529"
290                )
291                .unwrap(),
292                height: 730_000,
293                version: 536870912,
294                timestamp: 1648829449,
295                tx_count: 1627,
296                size: 1210916,
297                weight: Weight::from_vb_unchecked(3993515),
298                merkle_root: TxMerkleNode::from_str(
299                    "efa344bcd6c0607f93b709515dd6dc5496178112d680338ebea459e3de7b4fbc"
300                )
301                .unwrap(),
302                previous_block_hash: BlockHash::from_str(
303                    "00000000000000000008b6f6fb83f8d74512ef1e0af29e642dd20daddd7d318f"
304                )
305                .unwrap(),
306                median_time: 1648827418,
307                nonce: 3580664066,
308                bits: 386521239,
309                difficulty: 28587155782195.14,
310            }
311        );
312    }
313
314    #[test]
315    fn test_fee_recommendations_deserialization() {
316        let json_data =
317            r#"{"fastestFee":10,"halfHourFee":8,"hourFee":5,"economyFee":2,"minimumFee":1}"#;
318
319        let fees: FeeRecommendations = serde_json::from_str(json_data).unwrap();
320
321        assert_eq!(
322            fees,
323            FeeRecommendations {
324                fastest_fee: FeeRate::from_sat_per_vb_unchecked(10),
325                half_hour_fee: FeeRate::from_sat_per_vb_unchecked(8),
326                hour_fee: FeeRate::from_sat_per_vb_unchecked(5),
327                economy_fee: FeeRate::from_sat_per_vb_unchecked(2),
328                minimum_fee: FeeRate::from_sat_per_vb_unchecked(1),
329            }
330        );
331    }
332
333    #[test]
334    fn test_mempool_stats_deser() {
335        let json_data = r#"{"count":4364,"vsize":4407712,"total_fee":7277557,"fee_histogram":[[8.687,50041],[5.135,50041],[4.07,50231]]}"#;
336
337        let stats: MempoolStats = serde_json::from_str(json_data).unwrap();
338
339        // Test basic fields
340        assert_eq!(stats.count, 4364);
341        assert_eq!(stats.vsize.to_wu(), 4407712);
342        assert_eq!(stats.total_fee.to_sat(), 7277557);
343
344        // Test fee histogram length
345        assert_eq!(stats.fee_histogram.len(), 3);
346
347        // Test first entry
348        let first_entry = &stats.fee_histogram[0];
349        assert_eq!(first_entry.fee_rate.to_sat_per_kwu(), 8687); // 8.6875 * 1000
350        assert_eq!(first_entry.vsize.to_vbytes_ceil(), 50041);
351
352        // Test second entry
353        let last_entry = &stats.fee_histogram[1];
354        assert_eq!(last_entry.fee_rate.to_sat_per_kwu(), 5135); // 5.1351 * 1000
355        assert_eq!(last_entry.vsize.to_vbytes_ceil(), 50041);
356
357        // Test third entry
358        let middle_entry = &stats.fee_histogram[2];
359        assert_eq!(middle_entry.fee_rate.to_sat_per_kwu(), 4070); // 4.071 * 1000
360        assert_eq!(middle_entry.vsize.to_vbytes_ceil(), 50231);
361
362        // Serialize
363        let serialized = serde_json::to_string(&stats).unwrap();
364        assert_eq!(serialized, json_data);
365    }
366
367    #[test]
368    fn test_mempool_block_fees_deserialization() {
369        let json_data = r#"{
370    "blockSize": 873046,
371    "blockVSize": 746096.5,
372    "nTx": 863,
373    "totalFees": 8875608,
374    "medianFee": 10.79646017699115,
375    "feeRange": [
376      1,
377      2.4242424242424243,
378      8.107816711590296
379    ]
380  }"#;
381
382        let block_fee: MempoolBlockFees = serde_json::from_str(json_data).unwrap();
383        assert_eq!(block_fee.block_size, 873046);
384        assert_eq!(block_fee.block_v_size, 746096.5);
385        assert_eq!(block_fee.n_tx, 863);
386        assert_eq!(block_fee.total_fees.to_sat(), 8875608);
387        assert_eq!(block_fee.median_fee.to_sat_per_vb_ceil(), 10);
388        assert_eq!(block_fee.fee_range.len(), 3);
389        assert_eq!(block_fee.fee_range[0].to_sat_per_kwu(), 1000);
390        assert_eq!(block_fee.fee_range[1].to_sat_per_kwu(), 2424);
391        assert_eq!(block_fee.fee_range[2].to_sat_per_kwu(), 8107);
392    }
393
394    #[test]
395    fn test_fee_rate_conversion() {
396        // Test that fee rate conversion works correctly
397        let fee_rate_sat_per_vb = 8.6875;
398        let expected_sat_per_kwu = (fee_rate_sat_per_vb * 1000.0) as u64; // 8.6875 * 1000, truncated to u64
399
400        let fee_rate = FeeRate::from_sat_per_kwu(expected_sat_per_kwu);
401        let converted_back = fee_rate.to_sat_per_kwu() as f64 / 1000.0;
402
403        // Should be close due to precision loss from float->int->float conversion
404        assert!((converted_back - 8.687).abs() < 0.001);
405    }
406
407    #[test]
408    fn test_mempool_size_conversion_to_mb() {
409        let stats = MempoolStats {
410            count: 1,
411            vsize: Weight::from_vb_unchecked(2345565),
412            total_fee: Amount::from_sat(1000),
413            fee_histogram: vec![],
414        };
415
416        assert_eq!(stats.size_mb(), 2.345565);
417    }
418
419    #[test]
420    fn test_mempool_avg_fee_rate() {
421        let stats = MempoolStats {
422            count: 1,
423            vsize: Weight::from_vb_unchecked(2345565),
424            total_fee: Amount::from_sat(1000),
425            fee_histogram: vec![],
426        };
427        assert_eq!(stats.avg_fee_rate(), FeeRate::from_sat_per_vb_unchecked(1));
428
429        let stats = MempoolStats {
430            count: 1,
431            vsize: Weight::from_vb_unchecked(2345565),
432            total_fee: Amount::from_sat(1000),
433            fee_histogram: vec![
434                FeeHistogramEntry {
435                    fee_rate: FeeRate::from_sat_per_vb_unchecked(18),
436                    vsize: Weight::from_vb_unchecked(1000),
437                },
438                FeeHistogramEntry {
439                    fee_rate: FeeRate::from_sat_per_vb_unchecked(8),
440                    vsize: Weight::from_vb_unchecked(1000),
441                },
442                FeeHistogramEntry {
443                    fee_rate: FeeRate::from_sat_per_vb_unchecked(4),
444                    vsize: Weight::from_vb_unchecked(1000),
445                },
446            ],
447        };
448        assert_eq!(stats.avg_fee_rate(), FeeRate::from_sat_per_vb_unchecked(10));
449    }
450}