builder_relayer_client_rust/
client.rs1use crate::builder::create::AbstractSignerForCreate;
2use crate::builder::safe::{AbstractSigner, SafeContractConfig, SignatureMode};
3use crate::builder::{
4 build_safe_create_transaction_request, build_safe_transaction_request, derive_safe,
5};
6use crate::endpoints::*;
7use crate::errors::{RelayClientError, Result};
8use crate::types::*;
9use crate::utils::sleep_ms;
10use builder_signing_sdk_rs::{BuilderApiKeyCreds, BuilderSigner};
11use reqwest::Client as HttpClient;
12pub struct RelayClient {
15 pub relayer_url: String,
16 pub chain_id: u64,
17 pub contract_config: SafeContractConfig,
18 http: HttpClient,
19 signer: Option<Box<dyn AbstractSigner + Send + Sync>>,
20 typed_signer: Option<Box<dyn AbstractSignerForCreate + Send + Sync>>,
21 builder_signer: Option<BuilderSigner>,
22}
23
24impl RelayClient {
25 pub fn new(relayer_url: impl Into<String>, chain_id: u64) -> Self {
26 let url = relayer_url.into();
27 let contract_config = match chain_id {
28 137 | 80002 => SafeContractConfig {
29 safe_factory: "0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b".into(),
30 safe_multisend: "0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761".into(),
31 },
32 _ => SafeContractConfig {
33 safe_factory: String::new(),
34 safe_multisend: String::new(),
35 },
36 };
37 Self {
38 relayer_url: url.trim_end_matches('/').to_string(),
39 chain_id,
40 contract_config,
41 http: HttpClient::new(),
42 signer: None,
43 typed_signer: None,
44 builder_signer: None,
45 }
46 }
47
48 pub fn with_signer(
49 mut self,
50 signer: Box<dyn AbstractSigner + Send + Sync>,
51 typed: Box<dyn AbstractSignerForCreate + Send + Sync>,
52 ) -> Self {
53 self.signer = Some(signer);
54 self.typed_signer = Some(typed);
55 self
56 }
57
58 pub fn with_builder_api_key(mut self, creds: BuilderApiKeyCreds) -> Self {
59 self.builder_signer = Some(builder_signing_sdk_rs::BuilderSigner::new(creds));
60 self
61 }
62
63 async fn send<T: for<'de> serde::Deserialize<'de>>(
64 &self,
65 path: &str,
66 method: &str,
67 body: Option<String>,
68 params: Option<Vec<(String, String)>>,
69 builder_headers: Option<&std::collections::HashMap<String, String>>,
70 ) -> Result<T> {
71 let url = format!("{}{}", self.relayer_url, path);
72 let mut req = match method {
73 "GET" => self.http.get(&url),
74 "POST" => self.http.post(&url),
75 _ => return Err(RelayClientError::Http("unsupported method".into())),
76 };
77 if let Some(p) = params {
78 req = req.query(&p);
79 }
80 if let Some(b) = body {
81 req = req.body(b);
82 }
83 if let Some(h) = builder_headers {
84 for (k, v) in h {
85 req = req.header(k, v);
86 }
87 }
88 let resp = req
89 .send()
90 .await
91 .map_err(|e| RelayClientError::Http(e.to_string()))?;
92 let status = resp.status();
93 if !status.is_success() {
94 let text = resp.text().await.unwrap_or_default();
96 let snippet = if text.len() > 512 {
97 &text[..512]
98 } else {
99 &text
100 };
101 return Err(RelayClientError::Http(format!(
102 "status {} body: {}",
103 status, snippet
104 )));
105 }
106 resp.json::<T>()
107 .await
108 .map_err(|e| RelayClientError::Serde(e.to_string()))
109 }
110
111 pub async fn get_nonce(&self, signer_address: &str, signer_type: &str) -> Result<NoncePayload> {
112 self.send(
113 GET_NONCE,
114 "GET",
115 None,
116 Some(vec![
117 ("address".into(), signer_address.into()),
118 ("type".into(), signer_type.into()),
119 ]),
120 None,
121 )
122 .await
123 }
124
125 pub async fn get_transaction(&self, transaction_id: &str) -> Result<Vec<RelayerTransaction>> {
126 self.send(
127 GET_TRANSACTION,
128 "GET",
129 None,
130 Some(vec![("id".into(), transaction_id.into())]),
131 None,
132 )
133 .await
134 }
135
136 pub async fn get_transactions(&self) -> Result<Vec<RelayerTransaction>> {
137 self.authed_get(GET_TRANSACTIONS).await
138 }
139
140 async fn authed_get<T: for<'de> serde::Deserialize<'de>>(&self, path: &str) -> Result<T> {
141 if let Some(bs) = &self.builder_signer {
142 let headers = bs
143 .create_builder_header_payload("GET", path, None, None)
144 .map_err(RelayClientError::Http)?;
145 return self.send(path, "GET", None, None, Some(&headers)).await;
146 }
147 self.send(path, "GET", None, None, None).await
148 }
149
150 async fn authed_post<T: for<'de> serde::Deserialize<'de>>(
151 &self,
152 path: &str,
153 body: &str,
154 ) -> Result<T> {
155 if let Some(bs) = &self.builder_signer {
156 let headers = bs
157 .create_builder_header_payload("POST", path, Some(body), None)
158 .map_err(RelayClientError::Http)?;
159 return self
160 .send(path, "POST", Some(body.to_string()), None, Some(&headers))
161 .await;
162 }
163 self.send(path, "POST", Some(body.to_string()), None, None)
164 .await
165 }
166
167 fn ensure_signer(&self) -> Result<()> {
168 if self.signer.is_none() {
169 Err(RelayClientError::SignerUnavailable)
170 } else {
171 Ok(())
172 }
173 }
174
175 pub async fn get_deployed(&self, safe_address: &str) -> Result<bool> {
176 let resp: GetDeployedResponse = self
177 .send(
178 GET_DEPLOYED,
179 "GET",
180 None,
181 Some(vec![("address".into(), safe_address.into())]),
182 None,
183 )
184 .await?;
185 Ok(resp.deployed)
186 }
187
188 pub async fn deploy(&self) -> Result<RelayerTransactionResponse> {
189 self.ensure_signer()?;
190 let signer = self.signer.as_ref().unwrap();
191 let addr = signer.get_address()?;
192 let safe = derive_safe(&addr, &self.contract_config.safe_factory);
193
194 if self.get_deployed(&safe).await? {
195 return Err(RelayClientError::SafeDeployed);
196 }
197 self._deploy().await
198 }
199
200 async fn _deploy(&self) -> Result<RelayerTransactionResponse> {
201 self.ensure_signer()?;
202 let signer = self.signer.as_ref().unwrap();
203 let typed = self
204 .typed_signer
205 .as_ref()
206 .ok_or(RelayClientError::SignerUnavailable)?;
207 let from = signer.get_address()?;
208 let args = SafeCreateTransactionArgs {
209 from: from.clone(),
210 chain_id: self.chain_id,
211 payment_token: "0x0000000000000000000000000000000000000000".into(),
212 payment: "0".into(),
213 payment_receiver: "0x0000000000000000000000000000000000000000".into(),
214 };
215 let req = build_safe_create_transaction_request(
216 typed.as_ref(),
217 &self.contract_config.safe_factory,
218 args,
219 )
220 .await?;
221 let payload =
222 serde_json::to_string(&req).map_err(|e| RelayClientError::Serde(e.to_string()))?;
223 let resp: RelayerTransactionResponse =
224 self.authed_post(SUBMIT_TRANSACTION, &payload).await?;
225 Ok(resp)
226 }
227
228 pub async fn execute(
229 &self,
230 txns: Vec<SafeTransaction>,
231 metadata: Option<String>,
232 ) -> Result<RelayerTransactionResponse> {
233 self.execute_with_safe(txns, metadata, None).await
234 }
235
236 pub async fn execute_with_safe(
241 &self,
242 txns: Vec<SafeTransaction>,
243 metadata: Option<String>,
244 safe_address: Option<String>,
245 ) -> Result<RelayerTransactionResponse> {
246 self.ensure_signer()?;
247 let signer = self.signer.as_ref().unwrap();
248 let from = signer.get_address()?;
249
250 let derived_safe = derive_safe(&from, &self.contract_config.safe_factory);
252 let safe_to_check = if let Some(ref provided_safe) = safe_address {
253 eprintln!(
254 "[RelayClient][execute] derived_safe={} provided_safe={} equal? {}",
255 derived_safe,
256 provided_safe,
257 (derived_safe.to_lowercase() == provided_safe.to_lowercase())
258 );
259 provided_safe.clone()
260 } else {
261 eprintln!(
262 "[RelayClient][execute] derived_safe={} (no provided safe)",
263 derived_safe
264 );
265 derived_safe.clone()
266 };
267
268 if !self.get_deployed(&safe_to_check).await? {
270 return Err(RelayClientError::SafeNotDeployed);
271 }
272
273 let nonce_payload = self.get_nonce(&from, "SAFE").await?;
277
278 let args = SafeTransactionArgs {
279 from: from.clone(),
280 nonce: nonce_payload.nonce.clone(),
281 chain_id: self.chain_id,
282 transactions: txns,
283 safe_address,
284 };
285 let initial_mode = SignatureMode::Eip191Digest;
296
297 let mut _last_err: Option<crate::errors::RelayClientError> = None;
298 let mut attempt = 0;
299 let max_attempts = 1; loop {
307 attempt += 1;
308 let mode = initial_mode;
309 let req = build_safe_transaction_request(
324 signer.as_ref(),
325 args.clone(),
326 self.contract_config.clone(),
327 metadata.clone(),
328 mode,
329 )
330 .await?;
331 let body =
332 serde_json::to_string(&req).map_err(|e| RelayClientError::Serde(e.to_string()))?;
333 eprintln!("[RelayClient][execute] outbound body: {}", body);
334 let res = self.authed_post(SUBMIT_TRANSACTION, &body).await;
335 match res {
336 Ok(resp) => {
337 eprintln!(
338 "[RelayClient][execute] Transaction submitted successfully using signature mode: {:?}",
339 mode
340 );
341 return Ok(resp);
342 }
343 Err(e) => {
344 let msg = format!("{}", e);
345 let is_invalid_sig =
346 msg.contains("invalid signature") || msg.contains("validation error");
347 if attempt < max_attempts && is_invalid_sig {
348 eprintln!("[RelayClient][execute] invalid signature, retrying with alternate signature mode...");
349 _last_err = Some(e);
350 continue;
351 } else {
352 return Err(e);
353 }
354 }
355 }
356 }
357 }
358
359 pub async fn poll_until_state(
360 &self,
361 transaction_id: &str,
362 states: &[RelayerTransactionState],
363 fail_state: Option<RelayerTransactionState>,
364 max_polls: usize,
365 poll_freq_ms: u64,
366 ) -> Result<Option<RelayerTransaction>> {
367 let mut count = 0usize;
368 while count < max_polls {
369 let txns = self.get_transaction(transaction_id).await?;
370 if let Some(first) = txns.get(0) {
371 if states.iter().any(|s| first.state == format!("{:?}", s)) {
372 return Ok(Some(first.clone()));
373 }
374 if let Some(ref fail) = fail_state {
375 if first.state == format!("{:?}", fail) {
376 return Ok(None);
377 }
378 }
379 }
380 count += 1;
381 sleep_ms(poll_freq_ms).await;
382 }
383 Ok(None)
384 }
385}