use std::ops::Deref;
use std::sync::Arc;
use alloy::eips::BlockNumberOrTag;
use alloy::primitives::Address;
use alloy::providers::{DynProvider, Provider, ProviderBuilder};
use alloy::rpc::client::ClientRef;
use alloy::signers::local::PrivateKeySigner;
use alloy::transports::http::reqwest::Url;
use bigdecimal::BigDecimal;
use bon::bon;
use tokio::sync::Mutex;
use crate::utils::wei_to_eth;
pub struct NonceManager {
pub base_nonce: u64,
pub in_flight: u64,
}
impl NonceManager {
pub async fn next_nonce(&mut self) -> u64 {
let nonce = self.base_nonce + self.in_flight;
self.in_flight += 1;
nonce
}
pub async fn complete(&mut self) {
if self.in_flight > 0 {
self.in_flight -= 1;
}
}
}
#[derive(Clone)]
pub struct ArkivRoClient {
pub(crate) provider: DynProvider,
}
#[bon]
impl ArkivRoClient {
#[builder]
pub fn builder(rpc_url: Url, provider: Option<DynProvider>) -> Self {
let provider = provider.unwrap_or_else(|| {
ProviderBuilder::new()
.connect_http(rpc_url.clone())
.erased()
});
Self { provider }
}
}
#[derive(Clone)]
pub struct ArkivClient {
pub(crate) ro_client: ArkivRoClient,
pub(crate) wallet: PrivateKeySigner,
pub(crate) nonce_manager: Arc<Mutex<NonceManager>>,
}
impl Deref for ArkivClient {
type Target = ArkivRoClient;
fn deref(&self) -> &Self::Target {
&self.ro_client
}
}
#[bon]
impl ArkivClient {
#[builder]
pub fn builder(wallet: PrivateKeySigner, rpc_url: Url) -> Self {
let provider = ProviderBuilder::new()
.wallet(wallet.clone())
.connect_http(rpc_url.clone())
.erased();
let ro_client = ArkivRoClient::builder()
.rpc_url(rpc_url)
.provider(provider)
.build();
Self {
ro_client,
wallet,
nonce_manager: Arc::new(Mutex::new(NonceManager {
base_nonce: 0,
in_flight: 0,
})),
}
}
pub fn get_reqwest_client(&self) -> ClientRef<'_> {
self.provider.client()
}
pub fn get_owner_address(&self) -> Address {
self.wallet.address()
}
pub async fn get_chain_id(&self) -> anyhow::Result<u64> {
self.provider
.get_chain_id()
.await
.map_err(|e| anyhow::anyhow!("Failed to get chain ID: {e}"))
}
pub async fn get_balance(&self, account: Address) -> anyhow::Result<BigDecimal> {
let balance = self.provider.get_balance(account).await?;
Ok(wei_to_eth(balance))
}
pub async fn get_current_block_number(&self) -> anyhow::Result<u64> {
let latest_block = self
.provider
.get_block_by_number(BlockNumberOrTag::Latest)
.await?
.ok_or_else(|| anyhow::anyhow!("Failed to get latest block"))?;
Ok(latest_block.header.number)
}
}