signet_tx_cache/
client.rs

1use crate::types::{
2    TxCacheBundle, TxCacheBundleResponse, TxCacheBundlesResponse, TxCacheOrdersResponse,
3    TxCacheSendBundleResponse, TxCacheSendTransactionResponse, TxCacheTransactionsResponse,
4};
5use alloy::consensus::TxEnvelope;
6use eyre::Error;
7use serde::{de::DeserializeOwned, Serialize};
8use signet_bundle::SignetEthBundle;
9use signet_constants::pecorino;
10use signet_types::SignedOrder;
11use tracing::{instrument, warn};
12
13/// The endpoints for the transaction cache.
14const TRANSACTIONS: &str = "transactions";
15const BUNDLES: &str = "bundles";
16const ORDERS: &str = "orders";
17
18/// Signet's Transaction Cache helper.
19/// Forwards GET and POST requests to a tx cache URL.
20#[derive(Debug, Clone)]
21pub struct TxCache {
22    /// The URL of the transaction cache.
23    url: reqwest::Url,
24    /// The reqwest client used to send requests.
25    client: reqwest::Client,
26}
27
28impl TxCache {
29    /// Create a new cache with the given URL and client.
30    pub const fn new_with_client(url: reqwest::Url, client: reqwest::Client) -> Self {
31        Self { url, client }
32    }
33
34    /// Instantiate a new cache with the given URL and a new reqwest client.
35    pub fn new(url: reqwest::Url) -> Self {
36        Self { url, client: reqwest::Client::new() }
37    }
38
39    /// Create a new cache given a string URL.
40    pub fn new_from_string(url: &str) -> Result<Self, Error> {
41        let url = reqwest::Url::parse(url)?;
42        Ok(Self::new(url))
43    }
44
45    /// Connect to the Pecorino tx cache.
46    pub fn pecorino() -> Self {
47        Self::new_from_string(pecorino::TX_CACHE_URL).expect("pecorino tx cache URL invalid")
48    }
49
50    /// Connect to the Pecornio tx cache, using a specific [`Client`].
51    pub fn pecorino_with_client(client: reqwest::Client) -> Self {
52        let url =
53            reqwest::Url::parse(pecorino::TX_CACHE_URL).expect("pecorino tx cache URL invalid");
54        Self::new_with_client(url, client)
55    }
56
57    async fn forward_inner<T: Serialize + Send, R: DeserializeOwned>(
58        &self,
59        join: &'static str,
60        obj: T,
61    ) -> Result<R, Error> {
62        self.forward_inner_raw(join, obj)
63            .await?
64            .json::<R>()
65            .await
66            .inspect_err(|e| warn!(%e, "Failed to parse response from transaction cache"))
67            .map_err(Into::into)
68    }
69
70    async fn forward_inner_raw<T: Serialize + Send>(
71        &self,
72        join: &'static str,
73        obj: T,
74    ) -> Result<reqwest::Response, Error> {
75        // Append the path to the URL.
76        let url = self
77            .url
78            .join(join)
79            .inspect_err(|e| warn!(%e, "Failed to join URL. Not forwarding transaction."))?;
80
81        // Send the object and check for success.
82        self.client.post(url).json(&obj).send().await?.error_for_status().map_err(Into::into)
83    }
84
85    async fn get_inner<T>(&self, join: &'static str) -> Result<T, Error>
86    where
87        T: DeserializeOwned,
88    {
89        // Append the path to the URL.
90        let url = self
91            .url
92            .join(join)
93            .inspect_err(|e| warn!(%e, "Failed to join URL. Not querying transaction cache."))?;
94
95        // Get the result.
96        self.client
97            .get(url)
98            .send()
99            .await
100            .inspect_err(|e| warn!(%e, "Failed to get object from transaction cache"))?
101            .json::<T>()
102            .await
103            .map_err(Into::into)
104    }
105
106    /// Forwards a raw transaction to the URL.
107    #[instrument(skip_all)]
108    pub async fn forward_raw_transaction(
109        &self,
110        tx: TxEnvelope,
111    ) -> Result<TxCacheSendTransactionResponse, Error> {
112        self.forward_inner(TRANSACTIONS, tx).await
113    }
114
115    /// Forward a bundle to the URL.
116    #[instrument(skip_all)]
117    pub async fn forward_bundle(
118        &self,
119        bundle: SignetEthBundle,
120    ) -> Result<TxCacheSendBundleResponse, Error> {
121        self.forward_inner(BUNDLES, bundle).await
122    }
123
124    /// Forward an order to the URL.
125    #[instrument(skip_all)]
126    pub async fn forward_order(&self, order: SignedOrder) -> Result<(), Error> {
127        self.forward_inner_raw(ORDERS, order).await.map(drop)
128    }
129
130    /// Get transactions from the URL.
131    #[instrument(skip_all)]
132    pub async fn get_transactions(&self) -> Result<Vec<TxEnvelope>, Error> {
133        let response: TxCacheTransactionsResponse =
134            self.get_inner::<TxCacheTransactionsResponse>(TRANSACTIONS).await?;
135        Ok(response.transactions)
136    }
137
138    /// Get bundles from the URL.
139    #[instrument(skip_all)]
140    pub async fn get_bundles(&self) -> Result<Vec<TxCacheBundle>, Error> {
141        let response: TxCacheBundlesResponse =
142            self.get_inner::<TxCacheBundlesResponse>(BUNDLES).await?;
143        Ok(response.bundles)
144    }
145
146    /// Get a bundle from the URL.
147    #[instrument(skip_all)]
148    pub async fn get_bundle(&self) -> Result<TxCacheBundle, Error> {
149        let response: TxCacheBundleResponse =
150            self.get_inner::<TxCacheBundleResponse>(BUNDLES).await?;
151        Ok(response.bundle)
152    }
153
154    /// Get signed orders from the URL.
155    #[instrument(skip_all)]
156    pub async fn get_orders(&self) -> Result<Vec<SignedOrder>, Error> {
157        let response: TxCacheOrdersResponse =
158            self.get_inner::<TxCacheOrdersResponse>(ORDERS).await?;
159        Ok(response.orders)
160    }
161}