txboost_rs/
relay.rs

1use crate::jsonrpc::{JsonRpcError, Request, Response};
2use ethers::signers::Signer;
3use reqwest::{header::HeaderValue, Client, ClientBuilder, Error as ReqwestError};
4use serde::{de::DeserializeOwned, Serialize};
5use std::sync::atomic::{AtomicU64, Ordering};
6use thiserror::Error;
7use url::Url;
8
9/// A Flashbots relay client.
10///
11/// The client automatically signs every request and sets the Flashbots
12/// authorization header appropriately with the given signer.
13///
14/// **Note**: You probably do not want to use this directly, unless
15/// you want to interact directly with the Relay. Most users should use
16/// [`FlashbotsMiddleware`](crate::FlashbotsMiddleware) instead.
17#[derive(Debug)]
18pub struct Relay<S> {
19    id: AtomicU64,
20    client: Client,
21    url: Url,
22    signer: Option<S>,
23}
24
25/// Errors for relay requests.
26#[derive(Error, Debug)]
27pub enum RelayError {
28    #[error("Client error: {text}")]
29    ClientError { text: String },
30    #[error("Failed to send request")]
31    RequestError(#[from] ReqwestError),
32    #[error("Error response: {text}")]
33    ResponseSerdeJson { text: String },
34}
35
36impl<S: Signer> Relay<S> {
37    /// Initializes a new relay client.
38    pub fn new(url: impl Into<Url>, signer: Option<S>, authorization_key: String) -> Self {
39        let client_builder = ClientBuilder::new();
40
41        let mut headers = reqwest::header::HeaderMap::new();
42        headers.insert(
43            reqwest::header::AUTHORIZATION,
44            HeaderValue::from_str(&authorization_key).unwrap(),
45        );
46
47        let client_builder = client_builder.default_headers(headers);
48
49        Self {
50            id: AtomicU64::new(0),
51            client: client_builder.build().unwrap(),
52            url: url.into(),
53            signer,
54        }
55    }
56
57    /// Sends a request with the provided method to the relay, with the
58    /// parameters serialized as JSON.
59    pub async fn request<T: Serialize + Send + Sync, R: DeserializeOwned>(
60        &self,
61        method: &str,
62        params: T,
63    ) -> Result<Option<R>, RelayError> {
64        let next_id = self.id.load(Ordering::SeqCst) + 1;
65        self.id.store(next_id, Ordering::SeqCst);
66
67        let payload = Request::new(next_id, method, params);
68
69        let req = self.client.post(self.url.as_ref());
70
71        let res = req.json(&payload).send().await?;
72        let status = res.error_for_status_ref();
73
74        match status {
75            Err(err) => {
76                let text = res.text().await?;
77                let status_code = err.status().unwrap();
78                if status_code.is_client_error() {
79                    // Client error (400-499)
80                    Err(RelayError::ClientError { text })
81                } else {
82                    // Internal server error (500-599)
83                    Err(RelayError::RequestError(err))
84                }
85            }
86            Ok(_) => {
87                let text = res.text().await?;
88                let res: Response<R> = serde_json::from_str(&text).unwrap();
89                let result: Result<Option<R>, JsonRpcError> = res.data.into_result();
90
91                match result {
92                    Ok(result) => Ok(result),
93                    Err(err) => Err(RelayError::ResponseSerdeJson {
94                        text: format!("{:?}", err),
95                    }),
96                }
97            }
98        }
99    }
100}
101
102impl<S: Signer + Clone> Clone for Relay<S> {
103    fn clone(&self) -> Self {
104        Self {
105            id: AtomicU64::new(0),
106            client: self.client.clone(),
107            url: self.url.clone(),
108            signer: self.signer.clone(),
109        }
110    }
111}