signet_tx_cache/
client.rs

1use crate::types::{
2    TxCacheOrdersResponse, TxCacheSendBundleResponse, TxCacheSendTransactionResponse,
3    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 transaction cache with the Pecorino URL.
46    pub fn pecorino() -> Self {
47        Self::new_from_string(pecorino::TX_CACHE_URL).expect("pecorino tx cache URL is invalid")
48    }
49
50    /// Connect to the transaction cache with the Pecorino URL and a specific [`reqwest::Client`].
51    pub fn pecorino_with_client(client: reqwest::Client) -> Self {
52        Self::new_with_client(
53            pecorino::TX_CACHE_URL.parse().expect("pecorino tx cache URL is invalid"),
54            client,
55        )
56    }
57
58    /// Get the client used to send requests
59    pub const fn client(&self) -> &reqwest::Client {
60        &self.client
61    }
62
63    /// Get the URL of the transaction cache.
64    pub const fn url(&self) -> &reqwest::Url {
65        &self.url
66    }
67
68    async fn forward_inner<T: Serialize + Send, R: DeserializeOwned>(
69        &self,
70        join: &'static str,
71        obj: T,
72    ) -> Result<R, Error> {
73        self.forward_inner_raw(join, obj)
74            .await?
75            .json::<R>()
76            .await
77            .inspect_err(|e| warn!(%e, "Failed to parse response from transaction cache"))
78            .map_err(Into::into)
79    }
80
81    async fn forward_inner_raw<T: Serialize + Send>(
82        &self,
83        join: &'static str,
84        obj: T,
85    ) -> Result<reqwest::Response, Error> {
86        // Append the path to the URL.
87        let url = self
88            .url
89            .join(join)
90            .inspect_err(|e| warn!(%e, "Failed to join URL. Not forwarding transaction."))?;
91
92        // Send the object and check for success.
93        self.client.post(url).json(&obj).send().await?.error_for_status().map_err(Into::into)
94    }
95
96    async fn get_inner<T>(&self, join: &'static str) -> Result<T, Error>
97    where
98        T: DeserializeOwned,
99    {
100        // Append the path to the URL.
101        let url = self
102            .url
103            .join(join)
104            .inspect_err(|e| warn!(%e, "Failed to join URL. Not querying transaction cache."))?;
105
106        // Get the result.
107        self.client
108            .get(url)
109            .send()
110            .await
111            .inspect_err(|e| warn!(%e, "Failed to get object from transaction cache"))?
112            .json::<T>()
113            .await
114            .map_err(Into::into)
115    }
116
117    /// Forwards a raw transaction to the URL.
118    #[instrument(skip_all)]
119    pub async fn forward_raw_transaction(
120        &self,
121        tx: TxEnvelope,
122    ) -> Result<TxCacheSendTransactionResponse, Error> {
123        self.forward_inner(TRANSACTIONS, tx).await
124    }
125
126    /// Forward a bundle to the URL.
127    #[instrument(skip_all)]
128    pub async fn forward_bundle(
129        &self,
130        bundle: SignetEthBundle,
131    ) -> Result<TxCacheSendBundleResponse, Error> {
132        self.forward_inner(BUNDLES, bundle).await
133    }
134
135    /// Forward an order to the URL.
136    #[instrument(skip_all)]
137    pub async fn forward_order(&self, order: SignedOrder) -> Result<(), Error> {
138        self.forward_inner_raw(ORDERS, order).await.map(drop)
139    }
140
141    /// Get transactions from the URL.
142    #[instrument(skip_all)]
143    pub async fn get_transactions(&self) -> Result<Vec<TxEnvelope>, Error> {
144        let response: TxCacheTransactionsResponse =
145            self.get_inner::<TxCacheTransactionsResponse>(TRANSACTIONS).await?;
146        Ok(response.transactions)
147    }
148
149    /// Get signed orders from the URL.
150    #[instrument(skip_all)]
151    pub async fn get_orders(&self) -> Result<Vec<SignedOrder>, Error> {
152        let response: TxCacheOrdersResponse =
153            self.get_inner::<TxCacheOrdersResponse>(ORDERS).await?;
154        Ok(response.orders)
155    }
156}