1use ethers::abi::{encode, Token};
19use ethers::middleware::SignerMiddleware;
20use ethers::providers::{Http, Middleware, Provider};
21use ethers::signers::{LocalWallet, Signer};
22use ethers::types::{
23 Address, Bytes, Eip1559TransactionRequest, H256, TransactionReceipt, U256,
24};
25use ethers::utils::keccak256;
26
27use crate::builder::derive;
28use crate::error::{RelayerError, Result};
29use crate::types::{RelayerTxType, Transaction};
30
31const DEFAULT_GAS_LIMIT: u64 = 500_000;
32
33#[derive(Debug)]
35pub struct DirectTxResult {
36 pub tx_hash: String,
37 pub success: bool,
38 pub gas_used: u64,
39 pub gas_cost_matic: f64,
40 pub block_number: u64,
41}
42
43pub struct DirectExecutor {
47 provider: SignerMiddleware<Provider<Http>, LocalWallet>,
48 signer_address: Address,
49 wallet_address: Address,
50 wallet_type: RelayerTxType,
51 #[allow(dead_code)]
52 chain_id: u64,
53}
54
55impl DirectExecutor {
56 pub fn new(rpc_url: &str, signer: LocalWallet, chain_id: u64) -> Result<Self> {
58 Self::with_type(rpc_url, signer, chain_id, RelayerTxType::Safe)
59 }
60
61 pub fn new_proxy(rpc_url: &str, signer: LocalWallet, chain_id: u64) -> Result<Self> {
63 Self::with_type(rpc_url, signer, chain_id, RelayerTxType::Proxy)
64 }
65
66 pub fn with_type(
68 rpc_url: &str,
69 signer: LocalWallet,
70 chain_id: u64,
71 wallet_type: RelayerTxType,
72 ) -> Result<Self> {
73 let signer_address = signer.address();
74 let wallet_address = match wallet_type {
75 RelayerTxType::Eoa => signer_address,
76 RelayerTxType::Safe => derive::derive_safe_address(signer_address)?,
77 RelayerTxType::Proxy => derive::derive_proxy_address(signer_address)?,
78 };
79
80 let provider = Provider::<Http>::try_from(rpc_url)
81 .map_err(|e| RelayerError::Other(format!("Invalid RPC URL: {e}")))?;
82 let provider = SignerMiddleware::new(provider, signer.with_chain_id(chain_id));
83
84 Ok(Self {
85 provider,
86 signer_address,
87 wallet_address,
88 wallet_type,
89 chain_id,
90 })
91 }
92
93 pub fn new_proxy_with_address(
96 rpc_url: &str,
97 signer: LocalWallet,
98 chain_id: u64,
99 proxy_address: Address,
100 ) -> Result<Self> {
101 let signer_address = signer.address();
102 let provider = Provider::<Http>::try_from(rpc_url)
103 .map_err(|e| RelayerError::Other(format!("Invalid RPC URL: {e}")))?;
104 let provider = SignerMiddleware::new(provider, signer.with_chain_id(chain_id));
105
106 Ok(Self {
107 provider,
108 signer_address,
109 wallet_address: proxy_address,
110 wallet_type: RelayerTxType::Proxy,
111 chain_id,
112 })
113 }
114
115 pub fn wallet_address(&self) -> Address {
117 self.wallet_address
118 }
119
120 pub fn safe_address(&self) -> Address {
122 self.wallet_address
123 }
124
125 pub fn signer_address(&self) -> Address {
126 self.signer_address
127 }
128
129 pub fn wallet_type(&self) -> RelayerTxType {
130 self.wallet_type
131 }
132
133 pub async fn get_matic_balance(&self) -> Result<f64> {
135 let balance = self
136 .provider
137 .get_balance(self.signer_address, None)
138 .await
139 .map_err(|e| RelayerError::Other(format!("Failed to get balance: {e}")))?;
140 let matic = balance.as_u128() as f64 / 1e18;
141 Ok(matic)
142 }
143
144 pub async fn execute(&self, tx: &Transaction) -> Result<DirectTxResult> {
148 match self.wallet_type {
149 RelayerTxType::Eoa => self.execute_eoa(tx).await,
150 RelayerTxType::Safe => self.execute_safe(tx).await,
151 RelayerTxType::Proxy => self.execute_proxy(tx).await,
152 }
153 }
154
155 async fn execute_eoa(&self, tx: &Transaction) -> Result<DirectTxResult> {
161 let target: Address = tx
162 .to
163 .parse()
164 .map_err(|e: <Address as std::str::FromStr>::Err| {
165 RelayerError::InvalidAddress(e.to_string())
166 })?;
167 let calldata = hex::decode(tx.data.strip_prefix("0x").unwrap_or(&tx.data))
168 .map_err(|e| RelayerError::Abi(format!("Invalid calldata hex: {e}")))?;
169
170 tracing::debug!(target = ?target, "Executing direct EOA call");
171 self.send_raw_tx(target, calldata).await
172 }
173
174 async fn execute_safe(&self, tx: &Transaction) -> Result<DirectTxResult> {
178 let target: Address = tx
179 .to
180 .parse()
181 .map_err(|e: <Address as std::str::FromStr>::Err| {
182 RelayerError::InvalidAddress(e.to_string())
183 })?;
184 let inner_calldata = hex::decode(tx.data.strip_prefix("0x").unwrap_or(&tx.data))
185 .map_err(|e| RelayerError::Abi(format!("Invalid calldata hex: {e}")))?;
186
187 let safe_nonce = self.read_safe_nonce().await?;
189 tracing::debug!(safe_nonce, "Safe nonce");
190
191 let safe_tx_hash = self
193 .get_transaction_hash_onchain(target, &inner_calldata, safe_nonce)
194 .await?;
195 tracing::debug!(hash = ?safe_tx_hash, "Safe tx hash from contract");
196
197 let signature = self
199 .provider
200 .signer()
201 .sign_hash(safe_tx_hash)
202 .map_err(|e| RelayerError::Signing(e.to_string()))?;
203
204 let mut packed_sig = Vec::with_capacity(65);
206 let mut r_bytes = [0u8; 32];
207 signature.r.to_big_endian(&mut r_bytes);
208 packed_sig.extend_from_slice(&r_bytes);
209 let mut s_bytes = [0u8; 32];
210 signature.s.to_big_endian(&mut s_bytes);
211 packed_sig.extend_from_slice(&s_bytes);
212 packed_sig.push(signature.v as u8);
213
214 let exec_calldata =
216 self.encode_exec_transaction(target, &inner_calldata, &packed_sig);
217
218 self.send_raw_tx(self.wallet_address, exec_calldata).await
220 }
221
222 async fn execute_proxy(&self, tx: &Transaction) -> Result<DirectTxResult> {
229 let target: Address = tx
230 .to
231 .parse()
232 .map_err(|e: <Address as std::str::FromStr>::Err| {
233 RelayerError::InvalidAddress(e.to_string())
234 })?;
235 let inner_calldata = hex::decode(tx.data.strip_prefix("0x").unwrap_or(&tx.data))
236 .map_err(|e| RelayerError::Abi(format!("Invalid calldata hex: {e}")))?;
237 let value = U256::from_dec_str(&tx.value)
238 .map_err(|e| RelayerError::Abi(format!("Invalid value: {e}")))?;
239
240 let call_tuple = Token::Tuple(vec![
242 Token::Uint(U256::one()), Token::Address(target),
244 Token::Uint(value),
245 Token::Bytes(inner_calldata),
246 ]);
247
248 let selector = &keccak256(b"proxy((uint8,address,uint256,bytes)[])")[..4];
249 let encoded = encode(&[Token::Array(vec![call_tuple])]);
250 let mut calldata = selector.to_vec();
251 calldata.extend_from_slice(&encoded);
252
253 tracing::debug!(
254 proxy_address = ?self.wallet_address,
255 target = ?target,
256 "Executing direct proxy call"
257 );
258
259 self.send_raw_tx(self.wallet_address, calldata).await
261 }
262
263 async fn send_raw_tx(&self, to: Address, calldata: Vec<u8>) -> Result<DirectTxResult> {
267 let gas_price = self
268 .provider
269 .get_gas_price()
270 .await
271 .map_err(|e| RelayerError::Other(format!("Failed to get gas price: {e}")))?;
272
273 let tx_request = Eip1559TransactionRequest::new()
274 .to(to)
275 .data(calldata)
276 .gas(DEFAULT_GAS_LIMIT)
277 .max_fee_per_gas(gas_price * 3 / 2)
278 .max_priority_fee_per_gas(U256::from(30_000_000_000u64)); let pending = self
281 .provider
282 .send_transaction(tx_request, None)
283 .await
284 .map_err(|e| RelayerError::Other(format!("Failed to send tx: {e}")))?;
285
286 let tx_hash = format!("{:?}", pending.tx_hash());
287 tracing::info!(tx_hash = %tx_hash, "Direct tx sent");
288
289 let receipt: TransactionReceipt = pending
290 .await
291 .map_err(|e| RelayerError::Other(format!("Tx failed: {e}")))?
292 .ok_or_else(|| RelayerError::Other("No receipt".to_string()))?;
293
294 let gas_used = receipt.gas_used.map(|g| g.as_u64()).unwrap_or(0);
295 let effective_gas_price = receipt
296 .effective_gas_price
297 .map(|p| p.as_u128())
298 .unwrap_or(0);
299 let gas_cost_matic = gas_used as f64 * effective_gas_price as f64 / 1e18;
300 let block_number = receipt.block_number.map(|b| b.as_u64()).unwrap_or(0);
301 let success = receipt.status.map(|s| s.as_u64() == 1).unwrap_or(false);
302
303 if success {
304 tracing::info!(block = block_number, gas = gas_used, "Direct tx confirmed");
305 } else {
306 tracing::warn!(tx_hash = %tx_hash, "Direct tx reverted");
307 }
308
309 Ok(DirectTxResult {
310 tx_hash,
311 success,
312 gas_used,
313 gas_cost_matic,
314 block_number,
315 })
316 }
317
318 async fn read_safe_nonce(&self) -> Result<u64> {
322 let selector = &keccak256(b"nonce()")[..4];
323 let result = self.eth_call(self.wallet_address, selector).await.map_err(|e| {
324 RelayerError::Other(format!(
325 "Failed to read Safe nonce from {:?}: {}. \
326 Check that the Safe is deployed and the RPC URL is reachable.",
327 self.wallet_address, e
328 ))
329 })?;
330 if result.is_empty() {
331 return Err(RelayerError::Other(format!(
332 "Empty nonce response from Safe {:?} — wallet may not be deployed",
333 self.wallet_address
334 )));
335 }
336 if result.len() < 32 {
337 return Err(RelayerError::Other(format!(
338 "Invalid nonce response ({} bytes, expected 32) from Safe {:?}",
339 result.len(),
340 self.wallet_address
341 )));
342 }
343 Ok(U256::from_big_endian(&result[..32]).as_u64())
344 }
345
346 async fn get_transaction_hash_onchain(
348 &self,
349 to: Address,
350 data: &[u8],
351 nonce: u64,
352 ) -> Result<H256> {
353 let selector = &keccak256(
354 b"getTransactionHash(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256)",
355 )[..4];
356
357 let encoded_args = encode(&[
358 Token::Address(to),
359 Token::Uint(U256::zero()), Token::Bytes(data.to_vec()), Token::Uint(U256::zero()), Token::Uint(U256::zero()), Token::Uint(U256::zero()), Token::Uint(U256::zero()), Token::Address(Address::zero()), Token::Address(Address::zero()), Token::Uint(U256::from(nonce)), ]);
369
370 let mut calldata = selector.to_vec();
371 calldata.extend_from_slice(&encoded_args);
372
373 let result = self.eth_call(self.wallet_address, &calldata).await?;
374 if result.len() < 32 {
375 return Err(RelayerError::Other(
376 "Invalid getTransactionHash response".to_string(),
377 ));
378 }
379 Ok(H256::from_slice(&result[..32]))
380 }
381
382 async fn eth_call(&self, to: Address, calldata: &[u8]) -> Result<Bytes> {
384 let selector_hex = if calldata.len() >= 4 {
385 format!("0x{}", hex::encode(&calldata[..4]))
386 } else {
387 "empty".to_string()
388 };
389 self.provider
390 .call(
391 ðers::types::transaction::eip2718::TypedTransaction::Eip1559(
392 Eip1559TransactionRequest::new()
393 .to(to)
394 .data(Bytes::from(calldata.to_vec())),
395 ),
396 None,
397 )
398 .await
399 .map_err(|e| RelayerError::Other(format!(
400 "eth_call to {:?} (selector {}) failed: {e}",
401 to, selector_hex
402 )))
403 }
404
405 fn encode_exec_transaction(
407 &self,
408 to: Address,
409 inner_data: &[u8],
410 signature: &[u8],
411 ) -> Vec<u8> {
412 let selector = &keccak256(
413 b"execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)",
414 )[..4];
415
416 let encoded = encode(&[
417 Token::Address(to),
418 Token::Uint(U256::zero()),
419 Token::Bytes(inner_data.to_vec()),
420 Token::Uint(U256::zero()), Token::Uint(U256::zero()), Token::Uint(U256::zero()), Token::Uint(U256::zero()), Token::Address(Address::zero()), Token::Address(Address::zero()), Token::Bytes(signature.to_vec()),
427 ]);
428
429 let mut calldata = selector.to_vec();
430 calldata.extend_from_slice(&encoded);
431 calldata
432 }
433}