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        // Append the path to the URL.
63        let url = self
64            .url
65            .join(join)
66            .inspect_err(|e| warn!(%e, "Failed to join URL. Not forwarding transaction."))?;
67
68        // Send the object.
69        self.client
70            .post(url)
71            .json(&obj)
72            .send()
73            .await
74            .inspect_err(|e| warn!(%e, "Failed to forward object"))?
75            .json::<R>()
76            .await
77            .map_err(Into::into)
78            .inspect_err(|e| warn!(%e, "Failed to parse response from transaction cache"))
79    }
80
81    async fn get_inner<T>(&self, join: &'static str) -> Result<T, Error>
82    where
83        T: DeserializeOwned,
84    {
85        // Append the path to the URL.
86        let url = self
87            .url
88            .join(join)
89            .inspect_err(|e| warn!(%e, "Failed to join URL. Not querying transaction cache."))?;
90
91        // Get the result.
92        self.client
93            .get(url)
94            .send()
95            .await
96            .inspect_err(|e| warn!(%e, "Failed to get object from transaction cache"))?
97            .json::<T>()
98            .await
99            .map_err(Into::into)
100    }
101
102    /// Forwards a raw transaction to the URL.
103    #[instrument(skip_all)]
104    pub async fn forward_raw_transaction(
105        &self,
106        tx: TxEnvelope,
107    ) -> Result<TxCacheSendTransactionResponse, Error> {
108        self.forward_inner(TRANSACTIONS, tx).await
109    }
110
111    /// Forward a bundle to the URL.
112    #[instrument(skip_all)]
113    pub async fn forward_bundle(
114        &self,
115        bundle: SignetEthBundle,
116    ) -> Result<TxCacheSendBundleResponse, Error> {
117        self.forward_inner(BUNDLES, bundle).await
118    }
119
120    /// Forward an order to the URL.
121    #[instrument(skip_all)]
122    pub async fn forward_order(&self, order: SignedOrder) -> Result<(), Error> {
123        self.forward_inner(ORDERS, order).await
124    }
125
126    /// Get transactions from the URL.
127    #[instrument(skip_all)]
128    pub async fn get_transactions(&self) -> Result<Vec<TxEnvelope>, Error> {
129        let response: TxCacheTransactionsResponse =
130            self.get_inner::<TxCacheTransactionsResponse>(TRANSACTIONS).await?;
131        Ok(response.transactions)
132    }
133
134    /// Get bundles from the URL.
135    #[instrument(skip_all)]
136    pub async fn get_bundles(&self) -> Result<Vec<TxCacheBundle>, Error> {
137        let response: TxCacheBundlesResponse =
138            self.get_inner::<TxCacheBundlesResponse>(BUNDLES).await?;
139        Ok(response.bundles)
140    }
141
142    /// Get a bundle from the URL.
143    #[instrument(skip_all)]
144    pub async fn get_bundle(&self) -> Result<TxCacheBundle, Error> {
145        let response: TxCacheBundleResponse =
146            self.get_inner::<TxCacheBundleResponse>(BUNDLES).await?;
147        Ok(response.bundle)
148    }
149
150    /// Get signed orders from the URL.
151    #[instrument(skip_all)]
152    pub async fn get_orders(&self) -> Result<Vec<SignedOrder>, Error> {
153        let response: TxCacheOrdersResponse =
154            self.get_inner::<TxCacheOrdersResponse>(ORDERS).await?;
155        Ok(response.orders)
156    }
157}