signet_tx_cache/
client.rs

1use crate::types::{
2    CacheObject, CacheResponse, OrderKey, TxCacheOrdersResponse, TxCacheSendBundleResponse,
3    TxCacheSendTransactionResponse, TxCacheTransactionsResponse, TxKey,
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, query: Option<T::Key>) -> Result<T, Error>
97    where
98        T: DeserializeOwned + CacheObject,
99    {
100        let url = self
101            .url
102            .join(join)
103            .inspect_err(|e| warn!(%e, "Failed to join URL. Not querying transaction cache."))?;
104
105        self.client
106            .get(url)
107            .query(&query)
108            .send()
109            .await
110            .inspect_err(|e| warn!(%e, "Failed to get object from transaction cache."))?
111            .json::<T>()
112            .await
113            .map_err(Into::into)
114    }
115
116    /// Forwards a raw transaction to the URL.
117    #[instrument(skip_all)]
118    pub async fn forward_raw_transaction(
119        &self,
120        tx: TxEnvelope,
121    ) -> Result<TxCacheSendTransactionResponse, Error> {
122        self.forward_inner(TRANSACTIONS, tx).await
123    }
124
125    /// Forward a bundle to the URL.
126    #[instrument(skip_all)]
127    pub async fn forward_bundle(
128        &self,
129        bundle: SignetEthBundle,
130    ) -> Result<TxCacheSendBundleResponse, Error> {
131        self.forward_inner(BUNDLES, bundle).await
132    }
133
134    /// Forward an order to the URL.
135    #[instrument(skip_all)]
136    pub async fn forward_order(&self, order: SignedOrder) -> Result<(), Error> {
137        self.forward_inner_raw(ORDERS, order).await.map(drop)
138    }
139
140    /// Get transactions from the URL.
141    #[instrument(skip_all)]
142    pub async fn get_transactions(
143        &self,
144        query: Option<TxKey>,
145    ) -> Result<CacheResponse<TxCacheTransactionsResponse>, Error> {
146        self.get_inner(TRANSACTIONS, query).await
147    }
148
149    /// Get signed orders from the URL.
150    #[instrument(skip_all)]
151    pub async fn get_orders(
152        &self,
153        query: Option<OrderKey>,
154    ) -> Result<CacheResponse<TxCacheOrdersResponse>, Error> {
155        self.get_inner(ORDERS, query).await
156    }
157}