1use crate::{Client, EtherscanError, Response, Result};
2use ethers_core::{types::U256, utils::parse_units};
3use serde::{de, Deserialize, Deserializer};
4use std::{collections::HashMap, str::FromStr};
5
6const WEI_PER_GWEI: u64 = 1_000_000_000;
7
8#[derive(Deserialize, Clone, Debug)]
9#[serde(rename_all = "PascalCase")]
10pub struct GasOracle {
11 #[serde(deserialize_with = "deser_gwei_amount")]
13 pub safe_gas_price: U256,
14 #[serde(deserialize_with = "deser_gwei_amount")]
16 pub propose_gas_price: U256,
17 #[serde(deserialize_with = "deser_gwei_amount")]
19 pub fast_gas_price: U256,
20 #[serde(deserialize_with = "deserialize_number_from_string")]
22 pub last_block: u64,
23 #[serde(deserialize_with = "deser_gwei_amount")]
25 #[serde(rename = "suggestBaseFee")]
26 pub suggested_base_fee: U256,
27 #[serde(deserialize_with = "deserialize_f64_vec")]
29 #[serde(rename = "gasUsedRatio")]
30 pub gas_used_ratio: Vec<f64>,
31}
32
33fn deser_gwei_amount<'de, D>(deserializer: D) -> Result<U256, D::Error>
37where
38 D: Deserializer<'de>,
39{
40 #[derive(Deserialize)]
41 #[serde(untagged)]
42 enum StringOrInt {
43 Number(u64),
44 String(String),
45 }
46
47 match StringOrInt::deserialize(deserializer)? {
48 StringOrInt::Number(i) => Ok(U256::from(i) * WEI_PER_GWEI),
49 StringOrInt::String(s) => {
50 parse_units(s, "gwei").map(Into::into).map_err(serde::de::Error::custom)
51 }
52 }
53}
54
55fn deserialize_number_from_string<'de, T, D>(deserializer: D) -> Result<T, D::Error>
56where
57 D: Deserializer<'de>,
58 T: FromStr + serde::Deserialize<'de>,
59 <T as FromStr>::Err: std::fmt::Display,
60{
61 #[derive(Deserialize)]
62 #[serde(untagged)]
63 enum StringOrInt<T> {
64 String(String),
65 Number(T),
66 }
67
68 match StringOrInt::<T>::deserialize(deserializer)? {
69 StringOrInt::String(s) => s.parse::<T>().map_err(serde::de::Error::custom),
70 StringOrInt::Number(i) => Ok(i),
71 }
72}
73
74fn deserialize_f64_vec<'de, D>(deserializer: D) -> core::result::Result<Vec<f64>, D::Error>
75where
76 D: de::Deserializer<'de>,
77{
78 let str_sequence = String::deserialize(deserializer)?;
79 str_sequence
80 .split(',')
81 .map(|item| f64::from_str(item).map_err(|err| de::Error::custom(err.to_string())))
82 .collect()
83}
84
85impl Client {
86 pub async fn gas_estimate(&self, gas_price: U256) -> Result<u32> {
89 let query = self.create_query(
90 "gastracker",
91 "gasestimate",
92 HashMap::from([("gasprice", gas_price.to_string())]),
93 );
94 let response: Response<String> = self.get_json(&query).await?;
95
96 if response.status == "1" {
97 Ok(u32::from_str(&response.result).map_err(|_| EtherscanError::GasEstimationFailed)?)
98 } else {
99 Err(EtherscanError::GasEstimationFailed)
100 }
101 }
102
103 pub async fn gas_oracle(&self) -> Result<GasOracle> {
109 let query = self.create_query("gastracker", "gasoracle", serde_json::Value::Null);
110 let response: Response<GasOracle> = self.get_json(&query).await?;
111
112 Ok(response.result)
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn response_works() {
122 let v = r#"{
124 "status": "1",
125 "message": "OK",
126 "result": {
127 "LastBlock": "41171167",
128 "SafeGasPrice": "119.9",
129 "ProposeGasPrice": "141.9",
130 "FastGasPrice": "142.9",
131 "suggestBaseFee": "89.82627877",
132 "gasUsedRatio": "0.399191166666667,0.4847166,0.997667533333333,0.538075133333333,0.343416033333333",
133 "UsdPrice": "1.15"
134 }
135 }"#;
136 let gas_oracle: Response<GasOracle> = serde_json::from_str(v).unwrap();
137 assert_eq!(gas_oracle.message, "OK");
138 assert_eq!(
139 gas_oracle.result.propose_gas_price,
140 parse_units("141.9", "gwei").unwrap().into()
141 );
142
143 let v = r#"{
144 "status":"1",
145 "message":"OK",
146 "result":{
147 "LastBlock":"13053741",
148 "SafeGasPrice":"20",
149 "ProposeGasPrice":"22",
150 "FastGasPrice":"24",
151 "suggestBaseFee":"19.230609716",
152 "gasUsedRatio":"0.370119078777807,0.8954731,0.550911766666667,0.212457033333333,0.552463633333333"
153 }
154 }"#;
155 let gas_oracle: Response<GasOracle> = serde_json::from_str(v).unwrap();
156 assert_eq!(gas_oracle.message, "OK");
157 assert_eq!(gas_oracle.result.propose_gas_price, parse_units(22, "gwei").unwrap().into());
158
159 let v = r#"{
161 "status":"1",
162 "message":"OK",
163 "result":{
164 "LastBlock":13053741,
165 "SafeGasPrice":20,
166 "ProposeGasPrice":22,
167 "FastGasPrice":24,
168 "suggestBaseFee":"19.230609716",
169 "gasUsedRatio":"0.370119078777807,0.8954731,0.550911766666667,0.212457033333333,0.552463633333333"
170 }
171 }"#;
172 let gas_oracle: Response<GasOracle> = serde_json::from_str(v).unwrap();
173 assert_eq!(gas_oracle.message, "OK");
174 assert_eq!(gas_oracle.result.propose_gas_price, parse_units(22, "gwei").unwrap().into());
175 }
176}