Skip to main content

gelato_sdk/
lib.rs

1//! A Gelato relay SDK in rust
2
3#![warn(missing_docs)]
4#![warn(unused_extern_crates)]
5#![forbid(unsafe_code)]
6#![forbid(where_clauses_object_safety)]
7
8mod types;
9use std::str::FromStr;
10
11use reqwest::{IntoUrl, Url};
12pub use types::*;
13
14/// Re-export reqwest for convenience
15pub use reqwest;
16
17use ethers_core::types::{Bytes, H160, H256, U64};
18use once_cell::sync::Lazy;
19
20static DEFAULT_URL: Lazy<reqwest::Url> =
21    Lazy::new(|| "https://relay.gelato.digital/".parse().unwrap());
22
23/// A Gelato Relay Client
24#[derive(Debug, Clone)]
25pub struct GelatoClient {
26    url: reqwest::Url,
27    client: reqwest::Client,
28}
29
30impl Default for GelatoClient {
31    fn default() -> Self {
32        Self {
33            url: DEFAULT_URL.clone(),
34            client: Default::default(),
35        }
36    }
37}
38
39impl GelatoClient {
40    /// Instantiate a new client with a specific URL
41    ///
42    /// # Errors
43    ///
44    /// If the url param cannot be parsed as a URL
45    pub fn new<S>(url: S) -> Result<Self, reqwest::Error>
46    where
47        S: IntoUrl,
48    {
49        Ok(Self {
50            url: url.into_url()?,
51            ..Default::default()
52        })
53    }
54
55    async fn get(&self, url: Url) -> Result<reqwest::Response, reqwest::Error> {
56        self.client.get(url).send().await
57    }
58
59    /// Instantiate a new client with a specific URL and a reqwest Client
60    ///
61    /// # Errors
62    ///
63    /// If the url param cannot be parsed as a URL
64    pub fn new_with_client<S>(
65        url: S,
66        client: reqwest::Client,
67    ) -> Result<Self, <reqwest::Url as FromStr>::Err>
68    where
69        S: AsRef<str>,
70    {
71        Ok(Self {
72            url: url.as_ref().parse()?,
73            client,
74        })
75    }
76
77    /// Send a transaction over the relay
78    pub async fn send_relay_transaction(
79        &self,
80        chain_id: usize,
81        dest: H160,
82        data: Bytes,
83        fee_token: FeeToken,
84        relayer_fee: U64,
85    ) -> Result<RelayResponse, reqwest::Error> {
86        let params = RelayRequest {
87            dest,
88            data,
89            token: fee_token,
90            relayer_fee,
91        };
92
93        let url = format!("{}/relays/{}", &self.url, chain_id);
94        let res = reqwest::Client::new()
95            .post(url)
96            .json(&params)
97            .send()
98            .await?;
99
100        res.json().await
101    }
102
103    fn send_forward_request_url(&self, chain_id: usize) -> Url {
104        self.url
105            .join("metabox-relays/")
106            .unwrap()
107            .join(&format!("{}", chain_id))
108            .unwrap()
109    }
110
111    /// Send a transaction forward request
112    ///
113    /// <https://docs.gelato.network/developer-products/gelato-relay-sdk/request-types#forwardrequest>
114    ///
115    /// ForwardRequest is designed to handle payments of type 1, 2 and 3, in
116    /// cases where all meta-transaction related logic (or other kinds of
117    /// replay protection mechanisms such as hash based commitments) is already
118    /// implemented inside target smart contract. The sponsor is still required
119    /// to EIP-712 sign this request, in order to ensure the integrity of
120    /// payments. Optionally, nonce may or may not be enforced, by setting
121    /// enforceSponsorNonce. Some dApps may not need to rely on a nonce for
122    /// ForwardRequest if they already implement strong forms of replay
123    /// protection.
124    pub async fn send_forward_request(
125        &self,
126        params: &ForwardRequest,
127    ) -> Result<RelayResponse, reqwest::Error> {
128        self.client
129            .post(self.send_forward_request_url(params.chain_id))
130            .json(&params)
131            .send()
132            .await?
133            .json()
134            .await
135    }
136
137    /// Check if a chain id is supported by Gelato API
138    pub async fn is_chain_supported(&self, chain_id: usize) -> Result<bool, reqwest::Error> {
139        Ok(self.get_gelato_relay_chains().await?.contains(&chain_id))
140    }
141
142    fn relay_chains_url(&self) -> reqwest::Url {
143        self.url.join("relays/").unwrap()
144    }
145
146    /// Get a list of supported chains
147    pub async fn get_gelato_relay_chains(&self) -> Result<Vec<usize>, reqwest::Error> {
148        let res = self.client.get(self.relay_chains_url()).send().await?;
149        Ok(res.json::<RelayChainsResponse>().await?.relays().collect())
150    }
151
152    fn get_estimated_fee_url(
153        &self,
154        chain_id: usize,
155        payment_token: FeeToken,
156        gas_limit: usize,
157        is_high_priority: bool,
158    ) -> Url {
159        let mut url = self
160            .url
161            .join("oracles/")
162            .unwrap()
163            .join(&format!("{}/", chain_id))
164            .unwrap()
165            .join("estimate")
166            .unwrap();
167
168        let payment_token = format!("{:?}", *payment_token);
169
170        url.query_pairs_mut()
171            .append_pair("paymentToken", &payment_token)
172            .append_pair("gasLimit", &gas_limit.to_string())
173            .append_pair("isHighPriority", &is_high_priority.to_string());
174        url
175    }
176
177    /// Get the estimated fee for a specific amount of gas on a specific chain,
178    /// denominated in a specific payment token./
179    ///
180    ///
181    pub async fn get_estimated_fee(
182        &self,
183        chain_id: usize,
184        payment_token: impl Into<FeeToken>,
185        gas_limit: usize,
186        is_high_priority: bool,
187    ) -> Result<usize, reqwest::Error> {
188        let url =
189            self.get_estimated_fee_url(chain_id, payment_token.into(), gas_limit, is_high_priority);
190
191        Ok(reqwest::get(url)
192            .await?
193            .json::<EstimatedFeeResponse>()
194            .await?
195            .estimated_fee())
196    }
197
198    fn get_task_status_url(&self, task_id: H256) -> Url {
199        self.url
200            .join("/tasks/GelatoMetaBox/")
201            .unwrap()
202            .join(&format!("{:?}/", task_id))
203            .unwrap()
204    }
205
206    /// Fetch the status of a task
207    pub async fn get_task_status(
208        &self,
209        task_id: H256,
210    ) -> Result<Option<TransactionStatus>, reqwest::Error> {
211        Ok(self
212            .get(self.get_task_status_url(task_id))
213            .await?
214            .json::<TaskStatusResponse>()
215            .await?
216            .into_iter()
217            .next())
218    }
219}