use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use super::base::{BaseGasFeeEstimator, GasEstimatorError, GasEstimatorResult, GasPriceResult};
const GWEI_TO_WEI: u128 = 1_000_000_000;
use crate::{
gas::types::{MaxFee, MaxPriorityFee},
network::ChainId,
};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BlockNativeGasProviderSetupConfig {
pub api_key: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
struct BlockNativeGasEstimateResult {
#[serde(rename = "estimatedPrices")]
estimated_prices: Vec<BlockNativeEstimatedPrice>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
struct BlockNativeEstimatedPrice {
confidence: u8,
price: u64,
#[serde(rename = "maxPriorityFeePerGas")]
max_priority_fee_per_gas: u64,
#[serde(rename = "maxFeePerGas")]
max_fee_per_gas: u64,
}
impl BlockNativeGasEstimateResult {
fn get_estimate_by_confidence(&self, confidence: u8) -> Option<&BlockNativeEstimatedPrice> {
self.estimated_prices.iter().find(|price| price.confidence == confidence)
}
pub fn to_base_result(&self) -> Result<GasEstimatorResult, GasEstimatorError> {
let slow = self
.get_estimate_by_confidence(70)
.or_else(|| self.estimated_prices.first())
.ok_or_else(|| {
GasEstimatorError::CustomError("No gas estimates available".to_string())
})?;
let medium = self
.get_estimate_by_confidence(80)
.or_else(|| self.get_estimate_by_confidence(70))
.or_else(|| self.estimated_prices.first())
.ok_or_else(|| {
GasEstimatorError::CustomError("No gas estimates available".to_string())
})?;
let fast = self
.get_estimate_by_confidence(90)
.or_else(|| self.get_estimate_by_confidence(95))
.or_else(|| self.estimated_prices.last())
.ok_or_else(|| {
GasEstimatorError::CustomError("No gas estimates available".to_string())
})?;
let super_fast = self
.get_estimate_by_confidence(95)
.or_else(|| self.get_estimate_by_confidence(99))
.or_else(|| self.estimated_prices.last())
.ok_or_else(|| {
GasEstimatorError::CustomError("No gas estimates available".to_string())
})?;
let slow_result = GasPriceResult {
max_priority_fee: MaxPriorityFee::new(
slow.max_priority_fee_per_gas as u128 * GWEI_TO_WEI,
),
max_fee: MaxFee::new(slow.max_fee_per_gas as u128 * GWEI_TO_WEI),
min_wait_time_estimate: None,
max_wait_time_estimate: None,
};
let medium_result = GasPriceResult {
max_priority_fee: MaxPriorityFee::new(
medium.max_priority_fee_per_gas as u128 * GWEI_TO_WEI,
),
max_fee: MaxFee::new(medium.max_fee_per_gas as u128 * GWEI_TO_WEI),
min_wait_time_estimate: None,
max_wait_time_estimate: None,
};
let fast_result = GasPriceResult {
max_priority_fee: MaxPriorityFee::new(
fast.max_priority_fee_per_gas as u128 * GWEI_TO_WEI,
),
max_fee: MaxFee::new(fast.max_fee_per_gas as u128 * GWEI_TO_WEI),
min_wait_time_estimate: None,
max_wait_time_estimate: None,
};
let super_fast_priority =
(super_fast.max_priority_fee_per_gas as u128 * 120 / 100) * GWEI_TO_WEI;
let super_fast_max = (super_fast.max_fee_per_gas as u128 * 120 / 100) * GWEI_TO_WEI;
let super_fast_result = GasPriceResult {
max_priority_fee: MaxPriorityFee::new(super_fast_priority),
max_fee: MaxFee::new(super_fast_max),
min_wait_time_estimate: None,
max_wait_time_estimate: None,
};
Ok(GasEstimatorResult {
slow: slow_result,
medium: medium_result,
fast: fast_result,
super_fast: super_fast_result,
})
}
}
pub struct BlockNativeGasFeeEstimator {
config: BlockNativeGasProviderSetupConfig,
client: reqwest::Client,
}
impl BlockNativeGasFeeEstimator {
pub fn new(config: BlockNativeGasProviderSetupConfig) -> Result<Self, GasEstimatorError> {
let client = reqwest::Client::new();
Ok(Self { config, client })
}
}
#[async_trait]
impl BaseGasFeeEstimator for BlockNativeGasFeeEstimator {
async fn get_gas_prices(
&self,
chain_id: &ChainId,
) -> Result<GasEstimatorResult, GasEstimatorError> {
let url =
format!("https://api.blocknative.com/gasprices/blockprices?chainid={}", chain_id.u64());
let response = self
.client
.get(&url)
.header("Authorization", &self.config.api_key)
.send()
.await
.map_err(GasEstimatorError::ReqwestError)?;
if !response.status().is_success() {
return Err(GasEstimatorError::CustomError(format!(
"BlockNative API returned status: {}",
response.status()
)));
}
let gas_estimates: BlockNativeGasEstimateResult =
response.json().await.map_err(|e| GasEstimatorError::CustomError(e.to_string()))?;
gas_estimates.to_base_result()
}
fn is_chain_supported(&self, chain_id: &ChainId) -> bool {
matches!(chain_id.u64(), 1 | 10 | 56 | 137 | 42161 | 8453 | 43114)
}
}