hopr_chain_rpc/
middleware.rs1use 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#[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 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 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 pub fn category(mut self, gas_category: GasCategory) -> Self {
96 self.gas_category = gas_category;
97 self
98 }
99
100 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}