ethers_middleware/gas_oracle/
blocknative.rs1use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
2use async_trait::async_trait;
3use ethers_core::types::U256;
4use reqwest::{header::AUTHORIZATION, Client};
5use serde::Deserialize;
6use std::collections::HashMap;
7use url::Url;
8
9const URL: &str = "https://api.blocknative.com/gasprices/blockprices";
10
11#[derive(Clone, Debug)]
14#[must_use]
15pub struct BlockNative {
16 client: Client,
17 url: Url,
18 api_key: Option<String>,
19 gas_category: GasCategory,
20}
21
22#[derive(Clone, Debug, Deserialize, PartialEq)]
23#[serde(rename_all = "camelCase")]
24pub struct Response {
25 pub system: String,
26 pub network: String,
27 pub unit: String,
28 pub max_price: u64,
29 pub block_prices: Vec<BlockPrice>,
30 pub estimated_base_fees: Option<Vec<HashMap<String, Vec<BaseFeeEstimate>>>>,
31}
32
33#[derive(Clone, Debug, Deserialize, PartialEq)]
34#[serde(rename_all = "camelCase")]
35pub struct BlockPrice {
36 pub block_number: u64,
37 pub estimated_transaction_count: u64,
38 pub base_fee_per_gas: f64,
39 pub estimated_prices: Vec<GasEstimate>,
40}
41
42#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
43#[serde(rename_all = "camelCase")]
44pub struct GasEstimate {
45 pub confidence: u64,
46 pub price: u64,
47 pub max_priority_fee_per_gas: f64,
48 pub max_fee_per_gas: f64,
49}
50
51#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
52#[serde(rename_all = "camelCase")]
53pub struct BaseFeeEstimate {
54 pub confidence: u64,
55 pub base_fee: f64,
56}
57
58impl Response {
59 #[inline]
60 pub fn estimate_from_category(&self, gas_category: &GasCategory) -> Result<GasEstimate> {
61 let confidence = gas_category_to_confidence(gas_category);
62 let price = self
63 .block_prices
64 .first()
65 .ok_or(GasOracleError::InvalidResponse)?
66 .estimated_prices
67 .iter()
68 .find(|p| p.confidence == confidence)
69 .ok_or(GasOracleError::GasCategoryNotSupported)?;
70 Ok(*price)
71 }
72}
73
74impl Default for BlockNative {
75 fn default() -> Self {
76 Self::new(None)
77 }
78}
79
80#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
81#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
82impl GasOracle for BlockNative {
83 async fn fetch(&self) -> Result<U256> {
84 let estimate = self.query().await?.estimate_from_category(&self.gas_category)?;
85 Ok(U256::from(estimate.price) * GWEI_TO_WEI_U256)
86 }
87
88 async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
89 let estimate = self.query().await?.estimate_from_category(&self.gas_category)?;
90 let max = from_gwei_f64(estimate.max_fee_per_gas);
91 let prio = from_gwei_f64(estimate.max_priority_fee_per_gas);
92 Ok((max, prio))
93 }
94}
95
96impl BlockNative {
97 pub fn new(api_key: Option<String>) -> Self {
99 Self::with_client(Client::new(), api_key)
100 }
101
102 pub fn with_client(client: Client, api_key: Option<String>) -> Self {
104 let url = Url::parse(URL).unwrap();
105 Self { client, api_key, url, gas_category: GasCategory::Standard }
106 }
107
108 pub fn category(mut self, gas_category: GasCategory) -> Self {
110 self.gas_category = gas_category;
111 self
112 }
113
114 pub async fn query(&self) -> Result<Response, GasOracleError> {
116 let mut request = self.client.get(self.url.clone());
117 if let Some(api_key) = self.api_key.as_ref() {
118 request = request.header(AUTHORIZATION, api_key);
119 }
120 let response = request.send().await?.error_for_status()?.json().await?;
121 Ok(response)
122 }
123}
124
125#[inline]
126fn gas_category_to_confidence(gas_category: &GasCategory) -> u64 {
127 match gas_category {
128 GasCategory::SafeLow => 80,
129 GasCategory::Standard => 90,
130 GasCategory::Fast => 95,
131 GasCategory::Fastest => 99,
132 }
133}