1use bitcoin::address::{Address, NetworkUnchecked};
4use bitcoin::{Amount, BlockHash, FeeRate, TxMerkleNode, Weight};
5use serde::{Deserialize, Serialize};
6
7use crate::deser;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11pub struct Prices {
12 #[serde(rename = "time")]
14 pub timestamp: u64,
15 #[serde(rename = "USD")]
17 pub usd: u32,
18 #[serde(rename = "EUR")]
20 pub eur: u32,
21 #[serde(rename = "GBP")]
23 pub gbp: u32,
24 #[serde(rename = "CAD")]
26 pub cad: u32,
27 #[serde(rename = "CHF")]
29 pub chf: u32,
30 #[serde(rename = "AUD")]
32 pub aud: u32,
33 #[serde(rename = "JPY")]
35 pub jpy: u32,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
40pub struct DifficultyAdjustment {
41 #[serde(rename = "progressPercent")]
43 pub progress_percent: f64,
44 #[serde(rename = "difficultyChange")]
46 pub difficulty_change: f64,
47 #[serde(rename = "estimatedRetargetDate")]
49 pub estimated_retarget_date: u64,
50 #[serde(rename = "remainingBlocks")]
52 pub remaining_blocks: u32,
53 #[serde(rename = "remainingTime")]
55 pub remaining_time: u64,
56 #[serde(rename = "previousRetarget")]
58 pub previous_retarget: f64,
59 #[serde(rename = "nextRetargetHeight")]
61 pub next_retarget_height: u32,
62 #[serde(rename = "timeAvg")]
64 pub time_avg: u64,
65 #[serde(rename = "adjustedTimeAvg")]
67 pub adjusted_time_avg: u64,
68 #[serde(rename = "timeOffset")]
70 pub time_offset: i64,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
75pub struct AddressStats {
76 pub address: Address<NetworkUnchecked>,
78 pub chain_stats: TransactionStats,
80 pub mempool_stats: TransactionStats,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
86pub struct TransactionStats {
87 pub funded_txo_count: u32,
89 pub funded_txo_sum: Amount,
91 pub spent_txo_count: u32,
93 pub spent_txo_sum: Amount,
95 pub tx_count: u32,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
101pub struct BlockInfo {
102 pub id: BlockHash,
104 pub height: u32,
106 pub version: u32,
108 pub timestamp: u64,
110 pub tx_count: u32,
112 pub size: u32,
114 #[serde(with = "deser::weight_serde")]
116 pub weight: Weight,
117 pub merkle_root: TxMerkleNode,
119 #[serde(rename = "previousblockhash")]
121 pub previous_block_hash: BlockHash,
122 #[serde(rename = "mediantime")]
124 pub median_time: u64,
125 pub nonce: u32,
127 pub bits: u32,
129 pub difficulty: f64,
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
135pub struct HashrateEntry {
136 #[serde(alias = "time")]
138 pub timestamp: u64,
139 #[serde(rename = "avgHashrate")]
141 pub avg_hashrate: f64,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
146pub struct DifficultyEntry {
147 #[serde(alias = "time")]
149 pub timestamp: u64,
150 pub height: u32,
152 pub difficulty: f64,
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub adjustment: Option<f32>,
157}
158
159#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
161pub struct HashrateStats {
162 pub hashrates: Vec<HashrateEntry>,
164 pub difficulty: Vec<DifficultyEntry>,
166 #[serde(rename = "currentHashrate")]
168 pub current_hashrate: f64,
169 #[serde(rename = "currentDifficulty")]
171 pub current_difficulty: f64,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
176pub struct FeeRecommendations {
177 #[serde(rename = "fastestFee")]
179 #[serde(with = "deser::fee_rate_u64_serde")]
180 pub fastest_fee: FeeRate,
181 #[serde(rename = "halfHourFee")]
183 #[serde(with = "deser::fee_rate_u64_serde")]
184 pub half_hour_fee: FeeRate,
185 #[serde(rename = "hourFee")]
187 #[serde(with = "deser::fee_rate_u64_serde")]
188 pub hour_fee: FeeRate,
189 #[serde(rename = "economyFee")]
191 #[serde(with = "deser::fee_rate_u64_serde")]
192 pub economy_fee: FeeRate,
193 #[serde(rename = "minimumFee")]
195 #[serde(with = "deser::fee_rate_u64_serde")]
196 pub minimum_fee: FeeRate,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
201pub struct FeeHistogramEntry {
202 #[serde(with = "deser::fee_rate_f64_serde")]
204 pub fee_rate: FeeRate,
205 #[serde(with = "deser::weight_serde")]
207 pub vsize: Weight,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
212pub struct MempoolStats {
213 pub count: u32,
215 pub vsize: Weight,
217 pub total_fee: Amount,
219 #[serde(with = "deser::fee_histogram_serde")]
221 pub fee_histogram: Vec<FeeHistogramEntry>,
222}
223
224impl MempoolStats {
225 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 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#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
250pub struct MempoolBlockFees {
251 #[serde(rename = "blockSize")]
253 pub block_size: u64,
254 #[serde(rename = "blockVSize")]
256 pub block_v_size: f64,
257 #[serde(rename = "nTx")]
259 pub n_tx: u64,
260 #[serde(rename = "totalFees")]
262 pub total_fees: Amount,
263 #[serde(rename = "medianFee")]
265 #[serde(with = "deser::fee_rate_f64_serde")]
266 pub median_fee: FeeRate,
267 #[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 assert_eq!(stats.count, 4364);
341 assert_eq!(stats.vsize.to_wu(), 4407712);
342 assert_eq!(stats.total_fee.to_sat(), 7277557);
343
344 assert_eq!(stats.fee_histogram.len(), 3);
346
347 let first_entry = &stats.fee_histogram[0];
349 assert_eq!(first_entry.fee_rate.to_sat_per_kwu(), 8687); assert_eq!(first_entry.vsize.to_vbytes_ceil(), 50041);
351
352 let last_entry = &stats.fee_histogram[1];
354 assert_eq!(last_entry.fee_rate.to_sat_per_kwu(), 5135); assert_eq!(last_entry.vsize.to_vbytes_ceil(), 50041);
356
357 let middle_entry = &stats.fee_histogram[2];
359 assert_eq!(middle_entry.fee_rate.to_sat_per_kwu(), 4070); assert_eq!(middle_entry.vsize.to_vbytes_ceil(), 50231);
361
362 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 let fee_rate_sat_per_vb = 8.6875;
398 let expected_sat_per_kwu = (fee_rate_sat_per_vb * 1000.0) as u64; 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 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}