builder_relayer_client_rust/
client.rs

1use 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;
12// use serde_json::json;
13
14pub 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            // Try to include response body for diagnostics
95            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    /// Execute transactions with an optional explicit Safe address
237    ///
238    /// If `safe_address` is provided, it will be used directly instead of deriving from signer address.
239    /// This is useful when the Safe address is already known (e.g., from Polymarket account).
240    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        // Debug: compare derived safe and provided safe (if any)
251        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        // Check if Safe is deployed
269        if !self.get_deployed(&safe_to_check).await? {
270            return Err(RelayClientError::SafeNotDeployed);
271        }
272
273        // Use EOA `from` to fetch nonce, matching the TypeScript SDK behaviour.
274        // The relayer expects the nonce query to be done against the signer EOA
275        // even when a proxyWallet (Safe address) is provided in the request.
276        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        // Resolve signature mode from env
286        // let mode_env = std::env::var("RELAYER_SIG_MODE").unwrap_or_else(|_| "auto".into());
287        // let initial_mode = match mode_env.to_lowercase().as_str() {
288        //     "structhash" => SignatureMode::Eip191StructHash,
289        //     "digest" => SignatureMode::Eip712Digest,
290        //     "eip191_digest" => SignatureMode::Eip191Digest,
291        //     _ => SignatureMode::Eip191Digest, // 默认与 TS signMessage(hashTypedData digest) 行为一致
292        // };
293
294        // Force Eip191Digest as verified correct mode
295        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 = if mode_env.to_lowercase() == "auto" {
300        //     3
301        // } else {
302        //     1
303        // };
304        let max_attempts = 1; // Disable retry loop
305
306        loop {
307            attempt += 1;
308            let mode = initial_mode;
309            // let mode = if attempt == 1 {
310            //     initial_mode
311            // } else {
312            //     match (initial_mode, attempt) {
313            //         (SignatureMode::Eip191Digest, 2) => SignatureMode::Eip712Digest,
314            //         (SignatureMode::Eip191Digest, 3) => SignatureMode::Eip191StructHash,
315            //         (SignatureMode::Eip712Digest, 2) => SignatureMode::Eip191Digest,
316            //         (SignatureMode::Eip712Digest, 3) => SignatureMode::Eip191StructHash,
317            //         (SignatureMode::Eip191StructHash, 2) => SignatureMode::Eip191Digest,
318            //         (SignatureMode::Eip191StructHash, 3) => SignatureMode::Eip712Digest,
319            //         _ => initial_mode,
320            //     }
321            // };
322
323            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}