bitski_provider/
rest_web3_provider.rs

1use crate::USER_AGENT;
2use bitski_chain_models::networks::Network;
3use jsonrpc_core::Call;
4use serde::de::DeserializeOwned;
5use serde_json::Value;
6use std::sync::atomic::{AtomicUsize, Ordering};
7use std::sync::Arc;
8use web3::error::TransportError;
9use web3::futures::future::BoxFuture;
10use web3::{helpers, Error, RequestId, Transport};
11
12use once_cell::sync::Lazy;
13
14#[derive(Debug, Clone)]
15pub struct RestWeb3Provider {
16    client: reqwest::Client,
17    pub network: Network,
18    client_id: String,
19    id: Arc<AtomicUsize>,
20}
21
22static CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
23    reqwest::ClientBuilder::new()
24        .user_agent(USER_AGENT.clone())
25        .build()
26        .expect("could not build REST client")
27});
28
29impl RestWeb3Provider {
30    pub fn new(network: Network, client_id: &dyn ToString) -> Self {
31        RestWeb3Provider {
32            client: CLIENT.clone(),
33            network,
34            client_id: client_id.to_string(),
35            id: Arc::new(AtomicUsize::new(0)),
36        }
37    }
38
39    async fn send<T: DeserializeOwned>(
40        client: &reqwest::Client,
41        request: Call,
42        network: Network,
43        client_id: String,
44    ) -> web3::error::Result<T> {
45        let method_call = match request {
46            Call::MethodCall(ref method_call) => method_call,
47            _ => return Err(Error::Internal),
48        };
49        #[cfg(feature = "tracing")]
50        let request_id = serde_json::to_string(&method_call.id).unwrap_or_default();
51
52        let url = format!(
53            "{}/{}?params={}",
54            network.rpc_url,
55            method_call.method,
56            serde_json::to_string(&method_call.params)?,
57        );
58
59        #[cfg(feature = "tracing")]
60        tracing::debug!(
61            "[id:{}] sending request: {:?} to {}",
62            request_id,
63            serde_json::to_string(&request)?,
64            url
65        );
66        let response = client
67            .get(url)
68            .header("X-API-Key", client_id)
69            .send()
70            .await
71            .map_err(|err| {
72                Error::Transport(TransportError::Message(format!(
73                    "failed to send request: {}",
74                    err
75                )))
76            })?;
77        let status = response.status();
78        let response = response.bytes().await.map_err(|err| {
79            Error::Transport(TransportError::Message(format!(
80                "failed to read response bytes: {}",
81                err
82            )))
83        })?;
84        #[cfg(feature = "tracing")]
85        tracing::debug!(
86            "[id:{}] received response: {:?}",
87            request_id,
88            String::from_utf8_lossy(&response)
89        );
90        if !status.is_success() {
91            return Err(Error::Transport(TransportError::Code(status.as_u16())));
92        }
93        helpers::arbitrary_precision_deserialize_workaround(&response).map_err(|err| {
94            Error::Transport(TransportError::Message(format!(
95                "failed to deserialize response: {}",
96                err
97            )))
98        })
99    }
100}
101
102impl Transport for RestWeb3Provider {
103    type Out = BoxFuture<'static, web3::error::Result<jsonrpc_core::Value>>;
104
105    fn prepare(&self, method: &str, params: Vec<Value>) -> (RequestId, Call) {
106        let id = self.id.fetch_add(1, Ordering::AcqRel);
107        let request = helpers::build_request(id, method, params);
108        (id, request)
109    }
110
111    fn send(&self, _id: RequestId, call: Call) -> Self::Out {
112        let client = self.client.clone();
113        let network = self.network.clone();
114        let client_id = self.client_id.clone();
115        Box::pin(async move {
116            let output: jsonrpc_core::Value = Self::send(&client, call, network, client_id).await?;
117            Ok(output)
118        })
119    }
120}