hopr_chain_rpc/
middleware.rs

1use async_trait::async_trait;
2use ethers::{
3    middleware::{
4        gas_oracle::{GasCategory, GasOracleError},
5        GasOracle,
6    },
7    utils::parse_units,
8};
9use primitive_types::U256;
10use serde::Deserialize;
11use url::Url;
12
13use crate::HttpRequestor;
14
15pub const EIP1559_FEE_ESTIMATION_DEFAULT_MAX_FEE_GNOSIS: u64 = 3_000_000_000;
16pub const EIP1559_FEE_ESTIMATION_DEFAULT_PRIORITY_FEE_GNOSIS: u64 = 100_000_000;
17
18/// Use the underlying gas tracker API of GnosisScan to populate the gas price.
19/// It returns gas price in gwei.
20/// It implements the `GasOracle` trait.
21/// If no Oracle URL is given, it returns no values.
22#[derive(Clone, Debug)]
23#[must_use]
24pub struct GnosisScan<C> {
25    client: C,
26    url: Option<Url>,
27    gas_category: GasCategory,
28}
29
30#[derive(Clone, Debug, Deserialize, PartialEq)]
31pub struct Response {
32    pub status: String,
33    pub message: String,
34    pub result: ResponseResult,
35}
36
37#[derive(Clone, Debug, Deserialize, PartialEq)]
38#[serde(rename_all = "PascalCase")]
39pub struct ResponseResult {
40    pub last_block: String,
41    pub safe_gas_price: String,
42    pub propose_gas_price: String,
43    pub fast_gas_price: String,
44}
45
46impl Response {
47    #[inline]
48    pub fn gas_from_category(&self, gas_category: GasCategory) -> String {
49        self.result.gas_from_category(gas_category)
50    }
51}
52
53impl ResponseResult {
54    fn gas_from_category(&self, gas_category: GasCategory) -> String {
55        match gas_category {
56            GasCategory::SafeLow => self.safe_gas_price.clone(),
57            GasCategory::Standard => self.propose_gas_price.clone(),
58            GasCategory::Fast => self.fast_gas_price.clone(),
59            GasCategory::Fastest => self.fast_gas_price.clone(),
60        }
61    }
62}
63
64#[async_trait]
65impl<C: HttpRequestor + std::fmt::Debug> GasOracle for GnosisScan<C> {
66    async fn fetch(&self) -> Result<U256, GasOracleError> {
67        let res: Response = self.query().await?;
68        let gas_price_in_gwei = res.gas_from_category(self.gas_category);
69        let gas_price = parse_units(gas_price_in_gwei, "gwei")?.into();
70        Ok(gas_price)
71    }
72
73    // returns hardcoded (max_fee_per_gas, max_priority_fee_per_gas)
74    // Due to foundry is unable to estimate EIP-1559 fees for L2s https://github.com/foundry-rs/foundry/issues/5709,
75    // a hardcoded value of (3 gwei, 0.1 gwei) for Gnosischain is returned.
76    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
77        Ok((
78            U256::from(EIP1559_FEE_ESTIMATION_DEFAULT_MAX_FEE_GNOSIS),
79            U256::from(EIP1559_FEE_ESTIMATION_DEFAULT_PRIORITY_FEE_GNOSIS),
80        ))
81    }
82}
83
84impl<C: HttpRequestor> GnosisScan<C> {
85    /// Same as [`Self::new`] but with a custom [`Client`].
86    pub fn with_client(client: C, url: Option<Url>) -> Self {
87        Self {
88            client,
89            url,
90            gas_category: GasCategory::Standard,
91        }
92    }
93
94    /// Sets the gas price category to be used when fetching the gas price.
95    pub fn category(mut self, gas_category: GasCategory) -> Self {
96        self.gas_category = gas_category;
97        self
98    }
99
100    /// Perform a request to the gas price API and deserialize the response.
101    pub async fn query(&self) -> Result<Response, GasOracleError> {
102        self.client
103            .http_get(self.url.as_ref().ok_or(GasOracleError::NoValues)?.as_str())
104            .await
105            .map_err(|error| {
106                tracing::error!(%error, "failed to query gas price API");
107                GasOracleError::InvalidResponse
108            })
109            .and_then(|response| {
110                serde_json::from_slice(response.as_ref()).map_err(|error| {
111                    tracing::error!(%error, "failed to deserialize gas price API response");
112                    GasOracleError::InvalidResponse
113                })
114            })
115    }
116}