use std::sync::Arc;
use alloy::{eips::BlockNumberOrTag, primitives::utils::parse_units};
use async_trait::async_trait;
use super::base::{BaseGasFeeEstimator, GasEstimatorError, GasEstimatorResult, GasPriceResult};
use crate::{
gas::types::{MaxFee, MaxPriorityFee},
network::ChainId,
provider::RelayerProvider,
};
#[derive(Clone)]
pub struct FallbackGasFeeEstimator {
provider: Arc<RelayerProvider>,
}
impl FallbackGasFeeEstimator {
pub fn new(provider: Arc<RelayerProvider>) -> Self {
FallbackGasFeeEstimator { provider }
}
async fn estimate_with_fee_history(
&self,
chain_id: &ChainId,
) -> Result<(u128, u128), GasEstimatorError> {
let ethereum_or_ethereum_testnet = chain_id.u64() == 1 || chain_id.u64() == 11155111;
let past_blocks = if ethereum_or_ethereum_testnet { 20 } else { 60 };
let reward_percentile = if ethereum_or_ethereum_testnet { 60.0 } else { 25.0 };
let fee_history = self
.provider
.get_fee_history(past_blocks, BlockNumberOrTag::Latest, &[reward_percentile])
.await
.map_err(|e| GasEstimatorError::CustomError(e.to_string()))?;
let base_fee_per_gas = match fee_history.latest_block_base_fee() {
Some(base_fee) if base_fee != 0 => base_fee,
_ => self
.provider
.get_block_by_number(BlockNumberOrTag::Latest)
.await
.map_err(|e| GasEstimatorError::CustomError(e.to_string()))?
.ok_or_else(|| {
GasEstimatorError::CustomError("Latest block not found".to_string())
})?
.header
.base_fee_per_gas
.ok_or_else(|| {
GasEstimatorError::CustomError("EIP-1559 not supported".to_string())
})?
.into(),
};
let priority_fee = if let Some(rewards) = &fee_history.reward {
if !rewards.is_empty() {
let mut all_rewards: Vec<u128> = rewards
.iter()
.filter_map(|block_rewards| block_rewards.first().copied())
.collect();
if !all_rewards.is_empty() {
all_rewards.sort();
let median_idx = all_rewards.len() / 2;
all_rewards[median_idx]
} else if ethereum_or_ethereum_testnet {
parse_units("2", "gwei").unwrap().try_into().unwrap() } else {
parse_units("0.01", "gwei").unwrap().try_into().unwrap()
}
} else if ethereum_or_ethereum_testnet {
parse_units("2", "gwei").unwrap().try_into().unwrap() } else {
parse_units("0.01", "gwei").unwrap().try_into().unwrap() }
} else if ethereum_or_ethereum_testnet {
parse_units("2", "gwei").unwrap().try_into().unwrap() } else {
parse_units("0.01", "gwei").unwrap().try_into().unwrap() };
let max_fee = if chain_id.u64() == 1 {
(base_fee_per_gas + priority_fee).max(priority_fee * 2)
} else {
base_fee_per_gas + (priority_fee * 2)
};
Ok((priority_fee, max_fee))
}
}
#[async_trait]
impl BaseGasFeeEstimator for FallbackGasFeeEstimator {
async fn get_gas_prices(
&self,
_chain_id: &ChainId,
) -> Result<GasEstimatorResult, GasEstimatorError> {
let (base_priority_fee, base_max_fee) =
match self.estimate_with_fee_history(_chain_id).await {
Ok(fees) => fees,
Err(_) => {
let suggested = self
.provider
.estimate_eip1559_fees()
.await
.map_err(|e| GasEstimatorError::CustomError(e.to_string()))?;
let priority_fee = suggested.max_priority_fee_per_gas;
let max_fee = if _chain_id.u64() == 1 {
suggested.max_fee_per_gas.max(priority_fee * 2) } else {
suggested.max_fee_per_gas };
(priority_fee, max_fee)
}
};
let current_base_fee = self
.provider
.get_block_by_number(BlockNumberOrTag::Latest)
.await
.map_err(|e| GasEstimatorError::CustomError(e.to_string()))?
.and_then(|block| block.header.base_fee_per_gas)
.map(|fee| fee as u128)
.unwrap_or(0);
Ok(GasEstimatorResult {
slow: GasPriceResult {
max_priority_fee: MaxPriorityFee::new((base_priority_fee * 80) / 100), max_fee: MaxFee::new(
((base_max_fee * 90) / 100).max(current_base_fee + base_priority_fee),
),
min_wait_time_estimate: Some(120), max_wait_time_estimate: Some(300), },
medium: GasPriceResult {
max_priority_fee: MaxPriorityFee::new(base_priority_fee),
max_fee: MaxFee::new(base_max_fee.max(current_base_fee + base_priority_fee)),
min_wait_time_estimate: Some(30), max_wait_time_estimate: Some(120), },
fast: GasPriceResult {
max_priority_fee: MaxPriorityFee::new((base_priority_fee * 130) / 100), max_fee: MaxFee::new(
((base_max_fee * 120) / 100).max(current_base_fee + base_priority_fee),
),
min_wait_time_estimate: Some(15), max_wait_time_estimate: Some(60), },
super_fast: GasPriceResult {
max_priority_fee: MaxPriorityFee::new((base_priority_fee * 180) / 100), max_fee: MaxFee::new(
((base_max_fee * 150) / 100).max(current_base_fee + base_priority_fee),
),
min_wait_time_estimate: Some(5), max_wait_time_estimate: Some(30), },
})
}
fn is_chain_supported(&self, _: &ChainId) -> bool {
true
}
}