use alloy::primitives::U256;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::types::Call;
#[derive(Error, Debug)]
pub enum PaymasterError {
#[error("Gas estimation failed: {0}")]
EstimationFailed(String),
#[error("Sponsorship not available for this transaction")]
SponsorshipUnavailable,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GasEstimate {
pub total_gas: U256,
pub per_call_gas: Vec<U256>,
pub sponsored: bool,
pub sponsor_signature: Option<String>,
}
pub struct LocalPaymaster {
base_gas_per_call: u64,
sponsorship_enabled: bool,
}
impl LocalPaymaster {
pub fn new(sponsorship_enabled: bool) -> Self {
Self {
base_gas_per_call: 21000,
sponsorship_enabled,
}
}
pub async fn estimate_batch_gas(&self, calls: &[Call]) -> Result<GasEstimate, PaymasterError> {
let mut per_call_gas = Vec::new();
let mut total_gas = U256::ZERO;
for call in calls {
let estimated = self.estimate_single_call(call).await?;
per_call_gas.push(estimated);
total_gas += estimated;
}
let batch_overhead = U256::from(5000 * calls.len() as u64);
total_gas += batch_overhead;
let sponsor_signature = if self.sponsorship_enabled {
Some(self.generate_mock_sponsor_signature(&total_gas))
} else {
None
};
Ok(GasEstimate {
total_gas,
per_call_gas,
sponsored: self.sponsorship_enabled,
sponsor_signature,
})
}
async fn estimate_single_call(&self, call: &Call) -> Result<U256, PaymasterError> {
if let Some(gas) = call.gas {
return Ok(gas);
}
let base = U256::from(self.base_gas_per_call);
let data_cost = U256::from(call.data.len() as u64 * 16);
let value_cost = if call.value.is_some() {
U256::from(9000)
} else {
U256::ZERO
};
Ok(base + data_cost + value_cost)
}
fn generate_mock_sponsor_signature(&self, total_gas: &U256) -> String {
let mock_sig = format!(
"0xsponsor_{:x}_mock_signature_v1",
total_gas.to_string().len()
);
mock_sig
}
pub fn apply_sponsorship(&self, estimate: &mut GasEstimate) {
if self.sponsorship_enabled {
estimate.sponsored = true;
estimate.sponsor_signature = Some(self.generate_mock_sponsor_signature(&estimate.total_gas));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::primitives::{address, Bytes};
#[tokio::test]
async fn test_gas_estimation() {
let paymaster = LocalPaymaster::new(true);
let call = Call {
to: address!("0000000000000000000000000000000000000001"),
data: Bytes::from(vec![0u8; 100]),
value: None,
gas: None,
};
let estimate = paymaster.estimate_batch_gas(&[call]).await.unwrap();
assert!(estimate.total_gas > U256::ZERO);
assert!(estimate.sponsored);
assert!(estimate.sponsor_signature.is_some());
}
}