use crate::constants::{BASE_FEE_ATTO, DEFAULT_GAS_LIMIT, USDA_ADDRESS};
use crate::nonce::NonceManager;
use crate::rpc::RpcClient;
use crate::signer::EcdsaSigner;
use crate::transaction::{Signer as SignerTrait, SignedTx, TxBuilder};
use crate::Result;
use alloy_primitives::{Address, U256};
use std::sync::Arc;
pub struct TxClient {
signer: EcdsaSigner,
rpc: Arc<RpcClient>,
nonce_manager: NonceManager,
fee_token: Address,
base_fee: u64,
gas_limit: u64,
chain_id: u64,
}
impl TxClient {
pub fn new(private_key: &str, rpc_url: &str, fee_token: &str) -> Result<Self> {
let signer = EcdsaSigner::from_private_key(private_key)?;
let rpc = Arc::new(RpcClient::new(rpc_url));
let fee_token: Address = fee_token.parse().unwrap();
let nonce_manager = NonceManager::new(Arc::clone(&rpc), signer.address());
Ok(Self {
signer,
rpc,
nonce_manager,
fee_token,
base_fee: BASE_FEE_ATTO,
gas_limit: DEFAULT_GAS_LIMIT,
chain_id: 0,
})
}
pub fn address(&self) -> Address {
self.signer.address()
}
pub async fn chain_id(&mut self) -> Result<u64> {
if self.chain_id == 0 {
self.chain_id = self.rpc.get_chain_id().await?;
}
Ok(self.chain_id)
}
pub async fn get_nonce(&self) -> Result<u64> {
self.nonce_manager.get_nonce().await
}
pub async fn build_erc20_transfer(&mut self, token: Address, recipient: Address, amount: U256) -> Result<TxBuilder> {
let chain_id = self.chain_id().await?;
let nonce = self.nonce_manager.get_and_increment_nonce().await?;
Ok(TxBuilder::new()
.chain_id(chain_id)
.nonce(nonce)
.gas_limit(self.gas_limit)
.erc20_transfer(token, recipient, amount)
.fee_token(self.fee_token)
.max_fee_per_gas_usd((self.base_fee as u128) * 2))
}
pub async fn sign(&mut self, tx: TxBuilder) -> Result<SignedTx> {
let _ = self.chain_id().await?;
let signed = tx.sign(&self.signer)
.map_err(|e| crate::Error::SigningFailed(e.to_string()))?;
Ok(signed)
}
pub async fn send(&mut self, signed: SignedTx) -> Result<String> {
let tx_hash = self.rpc.send_raw_transaction(signed.raw_transaction).await?;
Ok(tx_hash)
}
pub async fn send_erc20_transfer(&mut self, token: Address, recipient: Address, amount: U256) -> Result<String> {
let tx = self.build_erc20_transfer(token, recipient, amount).await?;
let signed = self.sign(tx).await?;
self.send(signed).await
}
pub async fn send_and_wait(&mut self, signed: SignedTx, timeout_ms: u64) -> Result<crate::rpc::TransactionReceipt> {
let tx_hash = self.send(signed).await?;
self.rpc.wait_for_receipt(tx_hash, timeout_ms).await
}
pub async fn transfer_usda(&mut self, recipient: Address, amount_micro: u64) -> Result<String> {
let amount_wei = U256::from(amount_micro) * U256::from(1_000_000_000_000_u64);
self.send_erc20_transfer(self.fee_token, recipient, amount_wei).await
}
pub async fn fee_token_balance(&self, address: Address) -> Result<U256> {
self.rpc.get_erc20_balance(self.fee_token, address).await
}
pub async fn native_balance(&self, address: Address) -> Result<U256> {
self.rpc.get_balance(address).await
}
pub fn reset_nonce(&self) {
self.nonce_manager.reset_nonce();
}
}
pub struct TxClientBuilder {
private_key: String,
rpc_url: String,
fee_token: String,
base_fee: u64,
gas_limit: u64,
}
impl TxClientBuilder {
pub fn new(private_key: &str, rpc_url: &str) -> Self {
Self {
private_key: private_key.to_string(),
rpc_url: rpc_url.to_string(),
fee_token: USDA_ADDRESS.to_string(),
base_fee: BASE_FEE_ATTO,
gas_limit: DEFAULT_GAS_LIMIT,
}
}
pub fn fee_token(mut self, fee_token: &str) -> Self {
self.fee_token = fee_token.to_string();
self
}
pub fn base_fee(mut self, base_fee: u64) -> Self {
self.base_fee = base_fee;
self
}
pub fn gas_limit(mut self, gas_limit: u64) -> Self {
self.gas_limit = gas_limit;
self
}
pub fn build(&self) -> Result<TxClient> {
let mut client = TxClient::new(&self.private_key, &self.rpc_url, &self.fee_token)?;
client.base_fee = self.base_fee;
client.gas_limit = self.gas_limit;
Ok(client)
}
}
pub fn create_tx_client(private_key: &str, rpc_url: &str, fee_token: &str) -> Result<TxClient> {
TxClient::new(private_key, rpc_url, fee_token)
}