bitski_provider/
rest_web3_provider.rs1use 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}