gas-network-sdk 0.1.0

Rust SDK for Gas Network API - gas price prediction and optimization
Documentation
use crate::{
    error::{GasNetworkError, Result},
    types::*,
};
use reqwest::{Client, header::HeaderMap};
use url::Url;

const BASE_URL: &str = "https://api.blocknative.com";
const RPC_URL: &str = "https://rpc.gas.network";

#[derive(Debug, Clone)]
pub struct GasNetworkClient {
    client: Client,
    api_key: String,
    base_url: Url,
    rpc_url: Url,
}

impl GasNetworkClient {
    pub fn new(api_key: String) -> Result<Self> {
        let mut headers = HeaderMap::new();
        headers.insert("Authorization", format!("Bearer {}", api_key).parse().unwrap());
        headers.insert("Content-Type", "application/json".parse().unwrap());

        let client = Client::builder()
            .default_headers(headers)
            .build()?;

        Ok(Self {
            client,
            api_key,
            base_url: Url::parse(BASE_URL)?,
            rpc_url: Url::parse(RPC_URL)?,
        })
    }

    pub async fn get_gas_prices(&self, chain: Chain) -> Result<GasPriceResponse> {
        let url = self.base_url
            .join(&format!("/gasprices/blockprices?chain={}", chain.as_str()))?;

        let response = self.client.get(url).send().await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            return Err(GasNetworkError::Api {
                message: error_text,
            });
        }

        let gas_prices: GasPriceResponse = response.json().await?;
        Ok(gas_prices)
    }

    pub async fn get_base_fee_estimates(&self, chain: Chain) -> Result<BaseFeeResponse> {
        if chain != Chain::Ethereum {
            return Err(GasNetworkError::UnsupportedChain(
                "Base fee estimates are only available for Ethereum".to_string()
            ));
        }

        let url = self.base_url
            .join("/gasprices/basefee-estimates")?;

        let response = self.client.get(url).send().await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            return Err(GasNetworkError::Api {
                message: error_text,
            });
        }

        let base_fee: BaseFeeResponse = response.json().await?;
        Ok(base_fee)
    }

    pub async fn get_gas_distribution(&self, chain: Chain) -> Result<DistributionResponse> {
        if chain != Chain::Ethereum {
            return Err(GasNetworkError::UnsupportedChain(
                "Gas distribution is only available for Ethereum".to_string()
            ));
        }

        let url = self.base_url
            .join(&format!("/gasprices/distribution?chain={}", chain.as_str()))?;

        let response = self.client.get(url).send().await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            return Err(GasNetworkError::Api {
                message: error_text,
            });
        }

        let distribution: DistributionResponse = response.json().await?;
        Ok(distribution)
    }

    pub async fn get_oracle_data(&self, chain_id: u64) -> Result<OraclePayload> {
        let url = self.rpc_url
            .join(&format!("/oracle?chainId={}", chain_id))?;

        let response = self.client.get(url).send().await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            return Err(GasNetworkError::Api {
                message: error_text,
            });
        }

        let oracle_data: OraclePayload = response.json().await?;
        Ok(oracle_data)
    }

    pub async fn get_next_block_estimate(
        &self,
        chain: Chain,
        confidence_level: Option<u8>,
    ) -> Result<GasPriceEstimate> {
        let gas_prices = self.get_gas_prices(chain).await?;
        
        let confidence = confidence_level.unwrap_or(90);
        
        // Get estimates from the first block in block_prices
        if let Some(block_price) = gas_prices.block_prices.first() {
            block_price
                .estimated_prices
                .iter()
                .find(|estimate| estimate.confidence >= confidence)
                .cloned()
                .ok_or_else(|| GasNetworkError::Api {
                    message: format!("No estimate found for confidence level {}", confidence),
                })
        } else {
            Err(GasNetworkError::Api {
                message: "No block prices available".to_string(),
            })
        }
    }

    pub fn supported_chains() -> Vec<Chain> {
        vec![
            Chain::Ethereum,
            Chain::Polygon,
            Chain::Bitcoin,
            Chain::Sei,
            Chain::Optimism,
            Chain::Arbitrum,
            Chain::Base,
            Chain::Linea,
            Chain::Unichain,
        ]
    }

    pub fn chains_supporting_base_fee() -> Vec<Chain> {
        vec![Chain::Ethereum]
    }

    pub fn chains_supporting_distribution() -> Vec<Chain> {
        vec![Chain::Ethereum]
    }

    /// Get the API key used by this client
    pub fn api_key(&self) -> &str {
        &self.api_key
    }
}