1use crate::constants::{BASE_FEE_ATTO, DEFAULT_GAS_LIMIT, USDA_ADDRESS};
4use crate::nonce::NonceManager;
5use crate::rpc::RpcClient;
6use crate::signer::EcdsaSigner;
7use crate::transaction::{Signer as SignerTrait, SignedTx, TxBuilder};
8use crate::Result;
9use alloy_primitives::{Address, U256};
10use std::sync::Arc;
11
12pub struct TxClient {
14 signer: EcdsaSigner,
15 rpc: Arc<RpcClient>,
16 nonce_manager: NonceManager,
17 fee_token: Address,
18 base_fee: u64,
19 gas_limit: u64,
20 chain_id: u64,
21}
22
23impl TxClient {
24 pub fn new(private_key: &str, rpc_url: &str, fee_token: &str) -> Result<Self> {
26 let signer = EcdsaSigner::from_private_key(private_key)?;
27 let rpc = Arc::new(RpcClient::new(rpc_url));
28 let fee_token: Address = fee_token.parse().unwrap();
29 let nonce_manager = NonceManager::new(Arc::clone(&rpc), signer.address());
30
31 Ok(Self {
32 signer,
33 rpc,
34 nonce_manager,
35 fee_token,
36 base_fee: BASE_FEE_ATTO,
37 gas_limit: DEFAULT_GAS_LIMIT,
38 chain_id: 0,
39 })
40 }
41
42 pub fn address(&self) -> Address {
44 self.signer.address()
45 }
46
47 pub async fn chain_id(&mut self) -> Result<u64> {
49 if self.chain_id == 0 {
50 self.chain_id = self.rpc.get_chain_id().await?;
51 }
52 Ok(self.chain_id)
53 }
54
55 pub async fn get_nonce(&self) -> Result<u64> {
57 self.nonce_manager.get_nonce().await
58 }
59
60 pub async fn build_erc20_transfer(&mut self, token: Address, recipient: Address, amount: U256) -> Result<TxBuilder> {
62 let chain_id = self.chain_id().await?;
63 let nonce = self.nonce_manager.get_and_increment_nonce().await?;
64
65 Ok(TxBuilder::new()
66 .chain_id(chain_id)
67 .nonce(nonce)
68 .gas_limit(self.gas_limit)
69 .erc20_transfer(token, recipient, amount)
70 .fee_token(self.fee_token)
71 .max_fee_per_gas_usd((self.base_fee as u128) * 2))
72 }
73
74 pub async fn sign(&mut self, tx: TxBuilder) -> Result<SignedTx> {
76 let _ = self.chain_id().await?;
78
79 let signed = tx.sign(&self.signer)
81 .map_err(|e| crate::Error::SigningFailed(e.to_string()))?;
82
83 Ok(signed)
84 }
85
86 pub async fn send(&mut self, signed: SignedTx) -> Result<String> {
88 let tx_hash = self.rpc.send_raw_transaction(signed.raw_transaction).await?;
90 Ok(tx_hash)
91 }
92
93 pub async fn send_erc20_transfer(&mut self, token: Address, recipient: Address, amount: U256) -> Result<String> {
95 let tx = self.build_erc20_transfer(token, recipient, amount).await?;
96 let signed = self.sign(tx).await?;
97 self.send(signed).await
98 }
99
100 pub async fn send_and_wait(&mut self, signed: SignedTx, timeout_ms: u64) -> Result<crate::rpc::TransactionReceipt> {
102 let tx_hash = self.send(signed).await?;
103 self.rpc.wait_for_receipt(tx_hash, timeout_ms).await
104 }
105
106 pub async fn transfer_usda(&mut self, recipient: Address, amount_micro: u64) -> Result<String> {
108 let amount_wei = U256::from(amount_micro) * U256::from(1_000_000_000_000_u64);
110 self.send_erc20_transfer(self.fee_token, recipient, amount_wei).await
111 }
112
113 pub async fn fee_token_balance(&self, address: Address) -> Result<U256> {
115 self.rpc.get_erc20_balance(self.fee_token, address).await
116 }
117
118 pub async fn native_balance(&self, address: Address) -> Result<U256> {
120 self.rpc.get_balance(address).await
121 }
122
123 pub fn reset_nonce(&self) {
125 self.nonce_manager.reset_nonce();
126 }
127}
128
129pub struct TxClientBuilder {
131 private_key: String,
132 rpc_url: String,
133 fee_token: String,
134 base_fee: u64,
135 gas_limit: u64,
136}
137
138impl TxClientBuilder {
139 pub fn new(private_key: &str, rpc_url: &str) -> Self {
140 Self {
141 private_key: private_key.to_string(),
142 rpc_url: rpc_url.to_string(),
143 fee_token: USDA_ADDRESS.to_string(),
144 base_fee: BASE_FEE_ATTO,
145 gas_limit: DEFAULT_GAS_LIMIT,
146 }
147 }
148
149 pub fn fee_token(mut self, fee_token: &str) -> Self {
150 self.fee_token = fee_token.to_string();
151 self
152 }
153
154 pub fn base_fee(mut self, base_fee: u64) -> Self {
155 self.base_fee = base_fee;
156 self
157 }
158
159 pub fn gas_limit(mut self, gas_limit: u64) -> Self {
160 self.gas_limit = gas_limit;
161 self
162 }
163
164 pub fn build(&self) -> Result<TxClient> {
165 let mut client = TxClient::new(&self.private_key, &self.rpc_url, &self.fee_token)?;
166 client.base_fee = self.base_fee;
167 client.gas_limit = self.gas_limit;
168 Ok(client)
169 }
170}
171
172pub fn create_tx_client(private_key: &str, rpc_url: &str, fee_token: &str) -> Result<TxClient> {
174 TxClient::new(private_key, rpc_url, fee_token)
175}