#[cfg(feature = "sign")]
use crate::account::keys::LocalWallet;
use crate::client::evm::records::{FeeHistory, Receipt};
use crate::client::evm::rpc::{BlockTag, CallRequest, L1Error, Query, RpcError, eth};
use crate::client::evm::tx::{Call, Eip1559, Fees, with_headroom};
use crate::client::evm::u256::{B256, U256};
use crate::client::{Http, Network, Request, TransportError};
use crate::utils::Address;
pub const RECEIPT_POLL_MS: u64 = 3_000;
pub const RECEIPT_TRIES: u32 = 200;
#[derive(Debug, Clone)]
pub struct Rpc<H> {
http: H,
url: String,
}
impl<H: Http> Rpc<H> {
pub fn new(http: H, url: impl Into<String>) -> Self {
Rpc { http, url: url.into() }
}
pub fn l1(http: H, network: Network) -> Self {
Rpc::new(http, network.l1_rpc_url())
}
#[must_use]
pub fn url(&self) -> &str {
&self.url
}
#[must_use]
pub const fn http(&self) -> &H {
&self.http
}
pub async fn run<T>(&self, query: Query<T>) -> Result<T, RpcError> {
let response = self.http.send(Request::post_json(self.url.clone(), query.body())).await?;
if response.status == 429 {
return Err(TransportError::RateLimited { retry_after_ms: response.retry_after_ms() }.into());
}
let text = response.text();
match (response.is_success(), query.answer(&text)) {
(_, Err(refused @ RpcError::Node { .. })) => Err(refused),
(true, answer) => answer,
(false, _) => Err(TransportError::Status { status: response.status, body: text }.into()),
}
}
pub async fn chain_id(&self) -> Result<u64, RpcError> {
self.run(eth::chain_id()).await
}
pub async fn balance(&self, owner: Address, block: BlockTag) -> Result<U256, RpcError> {
self.run(eth::balance(owner, block)).await
}
pub async fn transaction_count(&self, owner: Address, block: BlockTag) -> Result<u64, RpcError> {
self.run(eth::transaction_count(owner, block)).await
}
pub async fn gas_price(&self) -> Result<U256, RpcError> {
self.run(eth::gas_price()).await
}
pub async fn max_priority_fee(&self) -> Result<U256, RpcError> {
self.run(eth::max_priority_fee()).await
}
pub async fn fee_history(&self, blocks: u64, newest: BlockTag, percentiles: &[u8]) -> Result<FeeHistory, RpcError> {
self.run(eth::fee_history(blocks, newest, percentiles)).await
}
pub async fn estimate_gas(&self, call: &CallRequest) -> Result<u64, RpcError> {
self.run(eth::estimate_gas(call)).await
}
pub async fn call(&self, call: &CallRequest, block: BlockTag) -> Result<Vec<u8>, RpcError> {
self.run(eth::call(call, block)).await
}
pub async fn send_raw_transaction(&self, raw: &[u8]) -> Result<B256, RpcError> {
self.run(eth::send_raw_transaction(raw)).await
}
pub async fn receipt(&self, hash: B256) -> Result<Option<Receipt>, RpcError> {
self.run(eth::receipt(hash)).await
}
pub async fn check_chain(&self, chain_id: u64) -> Result<(), L1Error> {
match self.chain_id().await? {
found if found == chain_id => Ok(()),
found => Err(L1Error::WrongChain { expected: chain_id, found }),
}
}
pub async fn fees(&self) -> Result<Fees, RpcError> {
let history = self.fee_history(1, BlockTag::Latest, &[]).await?;
let base = history.next_base_fee().ok_or_else(|| RpcError::Unreadable {
method: "eth_feeHistory",
detail: "no base fee for the next block".to_owned(),
})?;
let tip = self.max_priority_fee().await?;
Ok(Fees::over_base(base, tip))
}
pub async fn prepare(&self, chain_id: u64, from: Address, call: Call) -> Result<Eip1559, RpcError> {
let nonce = self.transaction_count(from, BlockTag::Pending).await?;
let fees = self.fees().await?;
let gas = self.estimate_gas(&CallRequest::of(Some(from), &call)).await?;
Ok(call.transaction(chain_id, nonce, with_headroom(gas), fees))
}
#[cfg(feature = "sign")]
pub async fn sign_call(&self, wallet: &LocalWallet, chain_id: u64, call: Call) -> Result<Vec<u8>, L1Error> {
let tx = self.prepare(chain_id, wallet.address(), call).await?;
Ok(tx.sign(wallet)?)
}
#[cfg(feature = "sign")]
pub async fn send_call(&self, wallet: &LocalWallet, chain_id: u64, call: Call) -> Result<B256, L1Error> {
let raw = self.sign_call(wallet, chain_id, call).await?;
Ok(self.send_raw_transaction(&raw).await?)
}
pub async fn wait_for_receipt(&self, hash: B256, pause: impl AsyncFn(u64)) -> Result<Option<Receipt>, RpcError> {
for attempt in 0..RECEIPT_TRIES {
if attempt > 0 {
pause(RECEIPT_POLL_MS).await;
}
if let Some(receipt) = self.receipt(hash).await? {
return Ok(Some(receipt));
}
}
Ok(None)
}
}