|
1 | 1 | //! Commonly used helpers to construct `Provider`s |
2 | 2 |
|
3 | | -use crate::{ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT}; |
| 3 | +use crate::{runtime_client::RuntimeClient, ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT}; |
4 | 4 | use ethers_core::types::{Chain, U256}; |
5 | 5 | use ethers_middleware::gas_oracle::{GasCategory, GasOracle, Polygon}; |
6 | | -use ethers_providers::{ |
7 | | - is_local_endpoint, Authorization, Http, HttpRateLimitRetryPolicy, JwtAuth, JwtKey, Middleware, |
8 | | - Provider, RetryClient, RetryClientBuilder, DEFAULT_LOCAL_POLL_INTERVAL, |
9 | | -}; |
| 6 | +use ethers_providers::{is_local_endpoint, Middleware, Provider, DEFAULT_LOCAL_POLL_INTERVAL}; |
10 | 7 | use eyre::WrapErr; |
11 | | -use reqwest::{header::HeaderValue, IntoUrl, Url}; |
12 | | -use std::{borrow::Cow, time::Duration}; |
| 8 | +use reqwest::{IntoUrl, Url}; |
| 9 | +use std::{borrow::Cow, env, path::Path, time::Duration}; |
| 10 | +use url::ParseError; |
13 | 11 |
|
14 | 12 | /// Helper type alias for a retry provider |
15 | | -pub type RetryProvider = Provider<RetryClient<Http>>; |
| 13 | +pub type RetryProvider = Provider<RuntimeClient>; |
16 | 14 |
|
17 | 15 | /// Helper type alias for a rpc url |
18 | 16 | pub type RpcUrl = String; |
@@ -68,9 +66,38 @@ impl ProviderBuilder { |
68 | 66 | // prefix |
69 | 67 | return Self::new(format!("http://{url_str}")) |
70 | 68 | } |
71 | | - let err = format!("Invalid provider url: {url_str}"); |
| 69 | + |
| 70 | + let url = Url::parse(url_str) |
| 71 | + .or_else(|err| { |
| 72 | + match err { |
| 73 | + ParseError::RelativeUrlWithoutBase => { |
| 74 | + let path = Path::new(url_str); |
| 75 | + let absolute_path = if path.is_absolute() { |
| 76 | + path.to_path_buf() |
| 77 | + } else { |
| 78 | + // Assume the path is relative to the current directory. |
| 79 | + // Don't use `std::fs::canonicalize` as it requires the path to exist. |
| 80 | + // It should be possible to construct a provider and only |
| 81 | + // attempt to establish a connection later |
| 82 | + let current_dir = |
| 83 | + env::current_dir().expect("Current directory should exist"); |
| 84 | + current_dir.join(path) |
| 85 | + }; |
| 86 | + |
| 87 | + let path_str = |
| 88 | + absolute_path.to_str().expect("Path should be a valid string"); |
| 89 | + |
| 90 | + // invalid url: non-prefixed URL scheme is not allowed, so we assume the URL |
| 91 | + // is for a local file |
| 92 | + Url::parse(format!("file://{path_str}").as_str()) |
| 93 | + } |
| 94 | + _ => Err(err), |
| 95 | + } |
| 96 | + }) |
| 97 | + .wrap_err(format!("Invalid provider url: {url_str}")); |
| 98 | + |
72 | 99 | Self { |
73 | | - url: url.into_url().wrap_err(err), |
| 100 | + url, |
74 | 101 | chain: Chain::Mainnet, |
75 | 102 | max_retry: 100, |
76 | 103 | timeout_retry: 5, |
@@ -176,43 +203,18 @@ impl ProviderBuilder { |
176 | 203 | } = self; |
177 | 204 | let url = url?; |
178 | 205 |
|
179 | | - let mut client_builder = reqwest::Client::builder().timeout(timeout); |
180 | | - |
181 | | - // Set the JWT auth as a header if present |
182 | | - if let Some(jwt) = jwt { |
183 | | - // Decode jwt from hex, then generate claims (iat with current timestamp) |
184 | | - let jwt = hex::decode(jwt)?; |
185 | | - let secret = |
186 | | - JwtKey::from_slice(&jwt).map_err(|err| eyre::eyre!("Invalid JWT: {}", err))?; |
187 | | - let auth = JwtAuth::new(secret, None, None); |
188 | | - let token = auth.generate_token()?; |
189 | | - |
190 | | - // Essentially unrolled ethers-rs new_with_auth to accomodate the custom timeout |
191 | | - let auth = Authorization::Bearer(token); |
192 | | - let mut auth_value = HeaderValue::from_str(&auth.to_string())?; |
193 | | - auth_value.set_sensitive(true); |
194 | | - |
195 | | - let mut headers = reqwest::header::HeaderMap::new(); |
196 | | - headers.insert(reqwest::header::AUTHORIZATION, auth_value); |
197 | | - |
198 | | - client_builder = client_builder.default_headers(headers); |
199 | | - } |
| 206 | + let mut provider = Provider::new(RuntimeClient::new( |
| 207 | + url.clone(), |
| 208 | + max_retry, |
| 209 | + timeout_retry, |
| 210 | + initial_backoff, |
| 211 | + timeout, |
| 212 | + compute_units_per_second, |
| 213 | + jwt, |
| 214 | + )); |
200 | 215 |
|
201 | | - let client = client_builder.build()?; |
202 | 216 | let is_local = is_local_endpoint(url.as_str()); |
203 | 217 |
|
204 | | - let provider = Http::new_with_client(url, client); |
205 | | - |
206 | | - #[allow(clippy::box_default)] |
207 | | - let mut provider = Provider::new( |
208 | | - RetryClientBuilder::default() |
209 | | - .initial_backoff(Duration::from_millis(initial_backoff)) |
210 | | - .rate_limit_retries(max_retry) |
211 | | - .timeout_retries(timeout_retry) |
212 | | - .compute_units_per_second(compute_units_per_second) |
213 | | - .build(provider, Box::new(HttpRateLimitRetryPolicy)), |
214 | | - ); |
215 | | - |
216 | 218 | if is_local { |
217 | 219 | provider = provider.interval(DEFAULT_LOCAL_POLL_INTERVAL); |
218 | 220 | } else if let Some(blocktime) = chain.average_blocktime_hint() { |
|
0 commit comments