gas_network_sdk/
client.rs

1use crate::{
2    error::{GasNetworkError, Result},
3    types::*,
4};
5use reqwest::{Client, header::HeaderMap};
6use url::Url;
7
8const BASE_URL: &str = "https://api.blocknative.com";
9const RPC_URL: &str = "https://rpc.gas.network";
10
11#[derive(Debug, Clone)]
12pub struct GasNetworkClient {
13    client: Client,
14    api_key: String,
15    base_url: Url,
16    rpc_url: Url,
17}
18
19impl GasNetworkClient {
20    pub fn new(api_key: String) -> Result<Self> {
21        let mut headers = HeaderMap::new();
22        headers.insert("Authorization", format!("Bearer {}", api_key).parse().unwrap());
23        headers.insert("Content-Type", "application/json".parse().unwrap());
24
25        let client = Client::builder()
26            .default_headers(headers)
27            .build()?;
28
29        Ok(Self {
30            client,
31            api_key,
32            base_url: Url::parse(BASE_URL)?,
33            rpc_url: Url::parse(RPC_URL)?,
34        })
35    }
36
37    pub async fn get_gas_prices(&self, chain: Chain) -> Result<GasPriceResponse> {
38        let url = self.base_url
39            .join(&format!("/gasprices/blockprices?chain={}", chain.as_str()))?;
40
41        let response = self.client.get(url).send().await?;
42
43        if !response.status().is_success() {
44            let error_text = response.text().await?;
45            return Err(GasNetworkError::Api {
46                message: error_text,
47            });
48        }
49
50        let gas_prices: GasPriceResponse = response.json().await?;
51        Ok(gas_prices)
52    }
53
54    pub async fn get_base_fee_estimates(&self, chain: Chain) -> Result<BaseFeeResponse> {
55        if chain != Chain::Ethereum {
56            return Err(GasNetworkError::UnsupportedChain(
57                "Base fee estimates are only available for Ethereum".to_string()
58            ));
59        }
60
61        let url = self.base_url
62            .join("/gasprices/basefee-estimates")?;
63
64        let response = self.client.get(url).send().await?;
65
66        if !response.status().is_success() {
67            let error_text = response.text().await?;
68            return Err(GasNetworkError::Api {
69                message: error_text,
70            });
71        }
72
73        let base_fee: BaseFeeResponse = response.json().await?;
74        Ok(base_fee)
75    }
76
77    pub async fn get_gas_distribution(&self, chain: Chain) -> Result<DistributionResponse> {
78        if chain != Chain::Ethereum {
79            return Err(GasNetworkError::UnsupportedChain(
80                "Gas distribution is only available for Ethereum".to_string()
81            ));
82        }
83
84        let url = self.base_url
85            .join(&format!("/gasprices/distribution?chain={}", chain.as_str()))?;
86
87        let response = self.client.get(url).send().await?;
88
89        if !response.status().is_success() {
90            let error_text = response.text().await?;
91            return Err(GasNetworkError::Api {
92                message: error_text,
93            });
94        }
95
96        let distribution: DistributionResponse = response.json().await?;
97        Ok(distribution)
98    }
99
100    pub async fn get_oracle_data(&self, chain_id: u64) -> Result<OraclePayload> {
101        let url = self.rpc_url
102            .join(&format!("/oracle?chainId={}", chain_id))?;
103
104        let response = self.client.get(url).send().await?;
105
106        if !response.status().is_success() {
107            let error_text = response.text().await?;
108            return Err(GasNetworkError::Api {
109                message: error_text,
110            });
111        }
112
113        let oracle_data: OraclePayload = response.json().await?;
114        Ok(oracle_data)
115    }
116
117    pub async fn get_next_block_estimate(
118        &self,
119        chain: Chain,
120        confidence_level: Option<u8>,
121    ) -> Result<GasPriceEstimate> {
122        let gas_prices = self.get_gas_prices(chain).await?;
123        
124        let confidence = confidence_level.unwrap_or(90);
125        
126        // Get estimates from the first block in block_prices
127        if let Some(block_price) = gas_prices.block_prices.first() {
128            block_price
129                .estimated_prices
130                .iter()
131                .find(|estimate| estimate.confidence >= confidence)
132                .cloned()
133                .ok_or_else(|| GasNetworkError::Api {
134                    message: format!("No estimate found for confidence level {}", confidence),
135                })
136        } else {
137            Err(GasNetworkError::Api {
138                message: "No block prices available".to_string(),
139            })
140        }
141    }
142
143    pub fn supported_chains() -> Vec<Chain> {
144        vec![
145            Chain::Ethereum,
146            Chain::Polygon,
147            Chain::Bitcoin,
148            Chain::Sei,
149            Chain::Optimism,
150            Chain::Arbitrum,
151            Chain::Base,
152            Chain::Linea,
153            Chain::Unichain,
154        ]
155    }
156
157    pub fn chains_supporting_base_fee() -> Vec<Chain> {
158        vec![Chain::Ethereum]
159    }
160
161    pub fn chains_supporting_distribution() -> Vec<Chain> {
162        vec![Chain::Ethereum]
163    }
164
165    /// Get the API key used by this client
166    pub fn api_key(&self) -> &str {
167        &self.api_key
168    }
169}