1use crate::builder::create::AbstractSignerForCreate;
2use crate::builder::proxy::{
3 build_proxy_transaction_request, is_proxy_contract_config_valid, ProxyContractConfig,
4};
5use crate::builder::safe::{AbstractSigner, SafeContractConfig, SignatureMode};
6use crate::builder::{
7 build_safe_create_transaction_request, build_safe_transaction_request, derive_safe,
8};
9use crate::endpoints::*;
10use crate::errors::{RelayClientError, Result};
11use crate::types::*;
12use crate::utils::sleep_ms;
13use builder_signing_sdk_rs::{BuilderApiKeyCreds, BuilderSigner};
14use reqwest::Client as HttpClient;
15use serde_json::json;
16#[derive(Clone, Debug)]
19pub struct ContractConfig {
20 pub safe: SafeContractConfig,
21 pub proxy: ProxyContractConfig,
22}
23
24pub struct RelayClient {
25 pub relayer_url: String,
26 pub chain_id: u64,
27 pub contract_config: ContractConfig,
28 pub relay_tx_type: RelayerTxType,
29 http: HttpClient,
30 gas_estimate_rpc: Option<String>,
31 signer: Option<Box<dyn AbstractSigner + Send + Sync>>,
32 typed_signer: Option<Box<dyn AbstractSignerForCreate + Send + Sync>>,
33 builder_signer: Option<BuilderSigner>,
34}
35
36impl RelayClient {
37 pub fn new(relayer_url: impl Into<String>, chain_id: u64) -> Self {
38 Self::new_with_type(relayer_url, chain_id, RelayerTxType::Safe)
39 }
40
41 pub fn new_with_type(
42 relayer_url: impl Into<String>,
43 chain_id: u64,
44 relay_tx_type: RelayerTxType,
45 ) -> Self {
46 let url = relayer_url.into();
47 let contract_config = match chain_id {
48 137 => ContractConfig {
49 proxy: ProxyContractConfig {
50 proxy_factory: "0xaB45c5A4B0c941a2F231C04C3f49182e1A254052".into(),
51 relay_hub: "0xD216153c06E857cD7f72665E0aF1d7D82172F494".into(),
52 },
53 safe: SafeContractConfig {
54 safe_factory: "0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b".into(),
55 safe_multisend: "0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761".into(),
56 },
57 },
58 80002 => ContractConfig {
59 proxy: ProxyContractConfig {
60 proxy_factory: String::new(),
61 relay_hub: String::new(),
62 },
63 safe: SafeContractConfig {
64 safe_factory: "0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b".into(),
65 safe_multisend: "0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761".into(),
66 },
67 },
68 _ => ContractConfig {
69 proxy: ProxyContractConfig {
70 proxy_factory: String::new(),
71 relay_hub: String::new(),
72 },
73 safe: SafeContractConfig {
74 safe_factory: String::new(),
75 safe_multisend: String::new(),
76 },
77 },
78 };
79
80 Self {
81 relayer_url: url.trim_end_matches('/').to_string(),
82 chain_id,
83 contract_config,
84 relay_tx_type,
85 http: HttpClient::new(),
86 gas_estimate_rpc: None,
87 signer: None,
88 typed_signer: None,
89 builder_signer: None,
90 }
91 }
92
93 pub fn with_relay_tx_type(mut self, relay_tx_type: RelayerTxType) -> Self {
96 self.relay_tx_type = relay_tx_type;
97 self
98 }
99
100 pub fn with_signer(
101 mut self,
102 signer: Box<dyn AbstractSigner + Send + Sync>,
103 typed: Box<dyn AbstractSignerForCreate + Send + Sync>,
104 ) -> Self {
105 self.signer = Some(signer);
106 self.typed_signer = Some(typed);
107 self
108 }
109
110 pub fn with_builder_api_key(mut self, creds: BuilderApiKeyCreds) -> Self {
111 self.builder_signer = Some(builder_signing_sdk_rs::BuilderSigner::new(creds));
112 self
113 }
114
115 pub fn with_gas_estimate_rpc(mut self, rpc: impl Into<String>) -> Self {
117 self.gas_estimate_rpc = Some(rpc.into());
118 self
119 }
120
121 async fn send<T: for<'de> serde::Deserialize<'de>>(
122 &self,
123 path: &str,
124 method: &str,
125 body: Option<String>,
126 params: Option<Vec<(String, String)>>,
127 builder_headers: Option<&std::collections::HashMap<String, String>>,
128 ) -> Result<T> {
129 let url = format!("{}{}", self.relayer_url, path);
130 let mut req = match method {
131 "GET" => self.http.get(&url),
132 "POST" => self.http.post(&url),
133 _ => return Err(RelayClientError::Http("unsupported method".into())),
134 };
135 if let Some(p) = params {
136 req = req.query(&p);
137 }
138 if let Some(b) = body {
139 req = req.body(b);
140 }
141 if let Some(h) = builder_headers {
142 for (k, v) in h {
143 req = req.header(k, v);
144 }
145 }
146 let resp = req
147 .send()
148 .await
149 .map_err(|e| RelayClientError::Http(e.to_string()))?;
150 let status = resp.status();
151 if !status.is_success() {
152 let text = resp.text().await.unwrap_or_default();
154 let snippet = if text.len() > 512 {
155 &text[..512]
156 } else {
157 &text
158 };
159 return Err(RelayClientError::Http(format!(
160 "status {} body: {}",
161 status, snippet
162 )));
163 }
164 resp.json::<T>()
165 .await
166 .map_err(|e| RelayClientError::Serde(e.to_string()))
167 }
168
169 async fn estimate_gas_limit(&self, from: &str, to: &str, data: &str) -> Result<String> {
172 let rpc = if let Some(r) = self.gas_estimate_rpc.as_ref() {
173 r.clone()
174 } else {
175 std::env::var("BLOCK_RPC_URL").unwrap_or_else(|_| "https://polygon-rpc.com".to_string())
176 };
177 let payload = json!({
178 "jsonrpc": "2.0",
179 "id": 1,
180 "method": "eth_estimateGas",
181 "params": [json!({
182 "from": from,
183 "to": to,
184 "data": data,
185 })],
186 });
187
188 let resp = self
189 .http
190 .post(&rpc)
191 .json(&payload)
192 .send()
193 .await
194 .map_err(|e| RelayClientError::Http(e.to_string()))?;
195
196 let value: serde_json::Value = resp
197 .json()
198 .await
199 .map_err(|e| RelayClientError::Serde(e.to_string()))?;
200
201 if let Some(err) = value.get("error") {
202 return Err(RelayClientError::Http(format!("estimateGas error: {err}")));
203 }
204 let res = value
205 .get("result")
206 .and_then(|v| v.as_str())
207 .ok_or_else(|| RelayClientError::Http("missing result in estimateGas".into()))?;
208
209 let gas_limit = u128::from_str_radix(res.trim_start_matches("0x"), 16)
211 .unwrap_or(0)
212 .to_string();
213 Ok(gas_limit)
214 }
215
216 pub async fn get_nonce(&self, signer_address: &str, signer_type: &str) -> Result<NoncePayload> {
217 self.send(
218 GET_NONCE,
219 "GET",
220 None,
221 Some(vec![
222 ("address".into(), signer_address.into()),
223 ("type".into(), signer_type.into()),
224 ]),
225 None,
226 )
227 .await
228 }
229
230 pub async fn get_relay_payload(
231 &self,
232 signer_address: &str,
233 signer_type: &str,
234 ) -> Result<RelayPayload> {
235 self.send(
236 GET_RELAY_PAYLOAD,
237 "GET",
238 None,
239 Some(vec![
240 ("address".into(), signer_address.into()),
241 ("type".into(), signer_type.into()),
242 ]),
243 None,
244 )
245 .await
246 }
247
248 pub async fn get_transaction(&self, transaction_id: &str) -> Result<Vec<RelayerTransaction>> {
249 self.send(
250 GET_TRANSACTION,
251 "GET",
252 None,
253 Some(vec![("id".into(), transaction_id.into())]),
254 None,
255 )
256 .await
257 }
258
259 pub async fn get_transactions(&self) -> Result<Vec<RelayerTransaction>> {
260 self.authed_get(GET_TRANSACTIONS).await
261 }
262
263 async fn authed_get<T: for<'de> serde::Deserialize<'de>>(&self, path: &str) -> Result<T> {
264 if let Some(bs) = &self.builder_signer {
265 let headers = bs
266 .create_builder_header_payload("GET", path, None, None)
267 .map_err(RelayClientError::Http)?;
268 return self.send(path, "GET", None, None, Some(&headers)).await;
269 }
270 self.send(path, "GET", None, None, None).await
271 }
272
273 async fn authed_post<T: for<'de> serde::Deserialize<'de>>(
274 &self,
275 path: &str,
276 body: &str,
277 ) -> Result<T> {
278 if let Some(bs) = &self.builder_signer {
279 let headers = bs
280 .create_builder_header_payload("POST", path, Some(body), None)
281 .map_err(RelayClientError::Http)?;
282 return self
283 .send(path, "POST", Some(body.to_string()), None, Some(&headers))
284 .await;
285 }
286 self.send(path, "POST", Some(body.to_string()), None, None)
287 .await
288 }
289
290 fn ensure_signer(&self) -> Result<()> {
291 if self.signer.is_none() {
292 Err(RelayClientError::SignerUnavailable)
293 } else {
294 Ok(())
295 }
296 }
297
298 pub async fn get_deployed(&self, safe_address: &str) -> Result<bool> {
299 let resp: GetDeployedResponse = self
300 .send(
301 GET_DEPLOYED,
302 "GET",
303 None,
304 Some(vec![("address".into(), safe_address.into())]),
305 None,
306 )
307 .await?;
308 Ok(resp.deployed)
309 }
310
311 pub async fn deploy(&self) -> Result<RelayerTransactionResponse> {
312 self.ensure_signer()?;
313 let signer = self.signer.as_ref().unwrap();
314 let factory = self
315 .contract_config
316 .safe
317 .safe_factory
318 .parse()
319 .map_err(|_| RelayClientError::InvalidAddress)?;
320 let safe_addr = derive_safe(factory, signer.address())?;
321 let safe = safe_addr.to_string();
322
323 if self.get_deployed(&safe).await? {
324 return Err(RelayClientError::SafeDeployed);
325 }
326 self._deploy().await
327 }
328
329 async fn _deploy(&self) -> Result<RelayerTransactionResponse> {
330 self.ensure_signer()?;
331 let signer = self.signer.as_ref().unwrap();
332 let typed = self
333 .typed_signer
334 .as_ref()
335 .ok_or(RelayClientError::SignerUnavailable)?;
336 let from = signer.address().to_string();
337 let args = SafeCreateTransactionArgs {
338 from: from.clone(),
339 chain_id: self.chain_id,
340 payment_token: "0x0000000000000000000000000000000000000000".into(),
341 payment: "0".into(),
342 payment_receiver: "0x0000000000000000000000000000000000000000".into(),
343 };
344 let req = build_safe_create_transaction_request(
345 typed.as_ref(),
346 &self.contract_config.safe.safe_factory,
347 args,
348 )
349 .await?;
350 let payload =
351 serde_json::to_string(&req).map_err(|e| RelayClientError::Serde(e.to_string()))?;
352 let resp: RelayerTransactionResponse =
353 self.authed_post(SUBMIT_TRANSACTION, &payload).await?;
354 Ok(resp)
355 }
356
357 pub async fn execute_transactions(
359 &self,
360 txns: Vec<Transaction>,
361 metadata: Option<String>,
362 ) -> Result<RelayerTransactionResponse> {
363 match self.relay_tx_type {
364 RelayerTxType::Safe => {
365 let safe_txns: Vec<SafeTransaction> = txns
366 .into_iter()
367 .map(|t| SafeTransaction {
368 to: t.to,
369 operation: OperationType::Call,
370 data: t.data,
371 value: "0".to_string(),
373 })
374 .collect();
375 self.execute_with_safe(safe_txns, metadata, None).await
376 }
377 RelayerTxType::Proxy => {
378 let proxy_txns: Vec<ProxyTransaction> = txns
379 .into_iter()
380 .map(|t| ProxyTransaction {
381 to: t.to,
382 type_code: CallType::Call,
383 data: t.data,
384 value: t.value,
385 })
386 .collect();
387 self.execute_proxy_transactions(proxy_txns, metadata).await
388 }
389 }
390 }
391
392 pub async fn execute(
393 &self,
394 txns: Vec<SafeTransaction>,
395 metadata: Option<String>,
396 ) -> Result<RelayerTransactionResponse> {
397 self.execute_with_safe(txns, metadata, None).await
398 }
399
400 pub async fn execute_with_safe(
405 &self,
406 txns: Vec<SafeTransaction>,
407 metadata: Option<String>,
408 safe_address: Option<String>,
409 ) -> Result<RelayerTransactionResponse> {
410 self.ensure_signer()?;
411 let signer = self.signer.as_ref().unwrap();
412 let from = signer.address().to_string();
413
414 let factory = self
416 .contract_config
417 .safe
418 .safe_factory
419 .parse()
420 .map_err(|_| RelayClientError::InvalidAddress)?;
421 let derived_safe_addr = derive_safe(factory, signer.address())?;
422 let derived_safe = derived_safe_addr.to_string();
423 let safe_to_check = if let Some(ref provided_safe) = safe_address {
424 eprintln!(
425 "[RelayClient][execute] derived_safe={} provided_safe={} equal? {}",
426 derived_safe,
427 provided_safe,
428 (derived_safe.to_lowercase() == provided_safe.to_lowercase())
429 );
430 provided_safe.clone()
431 } else {
432 eprintln!(
433 "[RelayClient][execute] derived_safe={} (no provided safe)",
434 derived_safe
435 );
436 derived_safe.clone()
437 };
438
439 if !self.get_deployed(&safe_to_check).await? {
441 return Err(RelayClientError::SafeNotDeployed);
442 }
443
444 let nonce_payload = self.get_nonce(&from, "SAFE").await?;
448
449 let args = SafeTransactionArgs {
450 from: from.clone(),
451 nonce: nonce_payload.nonce.clone(),
452 chain_id: self.chain_id,
453 transactions: txns,
454 safe_address,
455 };
456 let initial_mode = SignatureMode::Eip191Digest;
467
468 let mut _last_err: Option<crate::errors::RelayClientError> = None;
469 let mut attempt = 0;
470 let max_attempts = 1; loop {
478 attempt += 1;
479 let mode = initial_mode;
480 let req = build_safe_transaction_request(
495 signer.as_ref(),
496 &args.clone(),
497 &self.contract_config.safe,
498 metadata.clone(),
499 mode,
500 )
501 .await?;
502 let body =
503 serde_json::to_string(&req).map_err(|e| RelayClientError::Serde(e.to_string()))?;
504 eprintln!("[RelayClient][execute] outbound body: {}", body);
505 let res = self.authed_post(SUBMIT_TRANSACTION, &body).await;
506 match res {
507 Ok(resp) => {
508 eprintln!(
509 "[RelayClient][execute] Transaction submitted successfully using signature mode: {:?}",
510 mode
511 );
512 return Ok(resp);
513 }
514 Err(e) => {
515 let msg = format!("{}", e);
516 let is_invalid_sig =
517 msg.contains("invalid signature") || msg.contains("validation error");
518 if attempt < max_attempts && is_invalid_sig {
519 eprintln!("[RelayClient][execute] invalid signature, retrying with alternate signature mode...");
520 _last_err = Some(e);
521 continue;
522 } else {
523 return Err(e);
524 }
525 }
526 }
527 }
528 }
529
530 pub async fn execute_proxy_transactions(
531 &self,
532 txns: Vec<ProxyTransaction>,
533 metadata: Option<String>,
534 ) -> Result<RelayerTransactionResponse> {
535 self.ensure_signer()?;
536 if !is_proxy_contract_config_valid(&self.contract_config.proxy) {
537 return Err(RelayClientError::InvalidNetwork);
538 }
539
540 let signer = self.signer.as_ref().unwrap();
541 let from = signer.address().to_string();
542
543 let relay_payload = self.get_relay_payload(&from, "PROXY").await?;
544 let encoded_data = crate::encode::proxy::encode_proxy_transaction_data(&txns);
545
546 let gas_limit_est = self
548 .estimate_gas_limit(
549 &from,
550 &self.contract_config.proxy.proxy_factory,
551 &encoded_data,
552 )
553 .await
554 .ok();
555
556 let args = ProxyTransactionArgs {
557 from: from.clone(),
558 nonce: relay_payload.nonce.clone(),
559 gas_price: "0".to_string(),
560 gas_limit: gas_limit_est,
561 data: encoded_data,
562 relay: relay_payload.address.clone(),
563 };
564
565 let req = build_proxy_transaction_request(
566 signer.as_ref(),
567 &args,
568 &self.contract_config.proxy,
569 metadata.clone(),
570 &txns,
571 )
572 .await?;
573
574 let body =
575 serde_json::to_string(&req).map_err(|e| RelayClientError::Serde(e.to_string()))?;
576 self.authed_post(SUBMIT_TRANSACTION, &body).await
577 }
578
579 pub async fn poll_until_state(
580 &self,
581 transaction_id: &str,
582 states: &[RelayerTransactionState],
583 fail_state: Option<RelayerTransactionState>,
584 max_polls: usize,
585 poll_freq_ms: u64,
586 ) -> Result<Option<RelayerTransaction>> {
587 let mut count = 0usize;
588 while count < max_polls {
589 let txns = self.get_transaction(transaction_id).await?;
590 if let Some(first) = txns.get(0) {
591 if states.iter().any(|s| first.state == format!("{:?}", s)) {
592 return Ok(Some(first.clone()));
593 }
594 if let Some(ref fail) = fail_state {
595 if first.state == format!("{:?}", fail) {
596 return Ok(None);
597 }
598 }
599 }
600 count += 1;
601 sleep_ms(poll_freq_ms).await;
602 }
603 Ok(None)
604 }
605}