smplx_sdk/provider/core.rs
1use std::collections::HashMap;
2
3use electrsd::bitcoind::bitcoincore_rpc::Auth;
4
5use simplicityhl::elements::{Address, Script, Transaction, Txid};
6
7use crate::provider::SimplicityNetwork;
8use crate::transaction::{TxReceipt, UTXO};
9
10use super::error::ProviderError;
11
12/// The fallback default fee rate (in sats/kvb) to use when dynamic estimates fail.
13pub const DEFAULT_FEE_RATE: f32 = 100.0;
14/// The standard timeout duration (in seconds) applied to Esplora REST API requests.
15pub const DEFAULT_ESPLORA_TIMEOUT_SECS: u64 = 10;
16
17/// Contains foundational configuration elements required for initializing a generic blockchain provider.
18#[derive(Debug, Clone)]
19pub struct ProviderInfo {
20 /// URL of the target Esplora REST service.
21 pub esplora_url: String,
22 /// URL of the target direct `elementsd` or `bitcoind` RPC interface.
23 pub elements_url: Option<String>,
24 /// Authentication settings (e.g. cookie or username/password) for the RPC backend.
25 pub auth: Option<Auth>,
26}
27
28/// Baseline traits detailing required interaction methods between the SDK client and the underlying blockchain node or API.
29pub trait ProviderTrait {
30 /// Retrieves the network configured for this provider.
31 fn get_network(&self) -> &SimplicityNetwork;
32
33 /// Attempts to broadcast a fully compiled transaction to the configured backend.
34 ///
35 /// # Errors
36 /// Returns a `ProviderError` if network transmission fails, or if the backend explicitly rejects the transaction.
37 fn broadcast_transaction(&self, tx: &Transaction) -> Result<TxReceipt<'_>, ProviderError>;
38
39 /// Blocks and repeatedly polls the network until the specified transaction receives its first confirmation.
40 ///
41 /// # Errors
42 /// Returns a `ProviderError` if the network fails or the designated timeout elapses without confirmation.
43 fn wait(&self, txid: &Txid) -> Result<(), ProviderError>;
44
45 /// Retrieves the current block height of the network tip.
46 ///
47 /// # Errors
48 /// Returns a `ProviderError` if the backend request fails or the returned height cannot be parsed.
49 fn fetch_tip_height(&self) -> Result<u32, ProviderError>;
50
51 /// Retrieves the block timestamp representing network consensus clock tip.
52 ///
53 /// # Errors
54 /// Returns a `ProviderError` if the hash query, subsequent block query, or block parsing fails.
55 fn fetch_tip_timestamp(&self) -> Result<u64, ProviderError>;
56
57 /// Retrieves the serialized transaction payload given its hex transaction ID.
58 ///
59 /// # Errors
60 /// Returns a `ProviderError` if the node fails to locate the transaction or serialization fails.
61 fn fetch_transaction(&self, txid: &Txid) -> Result<Transaction, ProviderError>;
62
63 /// Fetches all active unspent transaction outputs correlated to a particular public `Address`.
64 ///
65 /// # Errors
66 /// Returns a `ProviderError` if the backend request fails or if UTXO parsing fails.
67 fn fetch_address_utxos(&self, address: &Address) -> Result<Vec<UTXO>, ProviderError>;
68
69 /// Fetches all active unspent transaction outputs correlated to a given custom `Script` mapping.
70 ///
71 /// # Errors
72 /// Returns a `ProviderError` if the backend request fails or if UTXO parsing fails.
73 fn fetch_scripthash_utxos(&self, script: &Script) -> Result<Vec<UTXO>, ProviderError>;
74
75 /// Fetches network fee estimation models based on varying target confirmation block delays.
76 ///
77 /// # Errors
78 /// Returns a `ProviderError` if the REST request fails or the resulting mappings fail to parse cleanly.
79 fn fetch_fee_estimates(&self) -> Result<HashMap<String, f64>, ProviderError>;
80
81 /// Attempts to extract the specific fee rate (in sats/kvb) necessary for the transaction to be confirmed within `target_blocks`.
82 ///
83 /// # Errors
84 /// Passes along `ProviderError` if `fetch_fee_estimates` fails.
85 #[allow(clippy::cast_possible_truncation)]
86 fn fetch_fee_rate(&self, target_blocks: u32) -> Result<f32, ProviderError> {
87 let estimates = self.fetch_fee_estimates()?;
88 let target_str = target_blocks.to_string();
89
90 if let Some(&rate) = estimates.get(&target_str) {
91 return Ok((rate * 1000.0) as f32); // Convert sat/vB to sats/kvb
92 }
93
94 let fallback_targets = [
95 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 144, 504, 1008,
96 ];
97
98 for &target in fallback_targets.iter().filter(|&&t| t >= target_blocks) {
99 let key = target.to_string();
100
101 if let Some(&rate) = estimates.get(&key) {
102 return Ok((rate * 1000.0) as f32);
103 }
104 }
105
106 for &target in &fallback_targets {
107 let key = target.to_string();
108
109 if let Some(&rate) = estimates.get(&key) {
110 return Ok((rate * 1000.0) as f32);
111 }
112 }
113
114 Ok(DEFAULT_FEE_RATE)
115 }
116}