2023-05-11 23:07:31 +03:00
|
|
|
use anyhow::anyhow;
|
2022-08-24 02:56:47 +03:00
|
|
|
use derive_more::From;
|
2023-05-11 23:07:31 +03:00
|
|
|
use ethers::providers::{Authorization, ConnectionDetails};
|
|
|
|
use std::{borrow::Cow, time::Duration};
|
|
|
|
use url::Url;
|
2022-08-24 02:56:47 +03:00
|
|
|
|
2023-02-12 12:22:53 +03:00
|
|
|
// TODO: our own structs for these that handle streaming large responses
|
|
|
|
type EthersHttpProvider = ethers::providers::Provider<ethers::providers::Http>;
|
|
|
|
type EthersWsProvider = ethers::providers::Provider<ethers::providers::Ws>;
|
|
|
|
|
2022-08-24 02:56:47 +03:00
|
|
|
/// Use HTTP and WS providers.
|
|
|
|
// TODO: instead of an enum, I tried to use Box<dyn Provider>, but hit <https://github.com/gakonst/ethers-rs/issues/592>
|
2023-02-12 12:22:53 +03:00
|
|
|
// TODO: custom types that let us stream JSON responses
|
2022-08-24 02:56:47 +03:00
|
|
|
#[derive(From)]
|
|
|
|
pub enum Web3Provider {
|
2023-02-12 12:22:53 +03:00
|
|
|
Both(EthersHttpProvider, EthersWsProvider),
|
|
|
|
Http(EthersHttpProvider),
|
|
|
|
// TODO: deadpool? custom tokio-tungstenite
|
|
|
|
Ws(EthersWsProvider),
|
|
|
|
#[cfg(test)]
|
2022-11-23 01:45:22 +03:00
|
|
|
Mock,
|
2022-08-24 02:56:47 +03:00
|
|
|
}
|
2022-08-24 03:11:49 +03:00
|
|
|
|
|
|
|
impl Web3Provider {
|
2023-02-12 12:22:53 +03:00
|
|
|
pub fn http(&self) -> Option<&EthersHttpProvider> {
|
|
|
|
match self {
|
|
|
|
Self::Http(x) => Some(x),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ws(&self) -> Option<&EthersWsProvider> {
|
|
|
|
match self {
|
|
|
|
Self::Both(_, x) | Self::Ws(x) => Some(x),
|
|
|
|
_ => None,
|
2022-09-06 19:49:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-11 23:07:31 +03:00
|
|
|
/// Note, if the http url has an authority the http_client param is ignored and a dedicated http_client will be used
|
|
|
|
/// TODO: take a reqwest::Client or a reqwest::ClientBuilder. that way we can do things like set compression even when auth is set
|
|
|
|
pub async fn new(
|
|
|
|
mut url: Cow<'_, Url>,
|
2022-08-24 03:11:49 +03:00
|
|
|
http_client: Option<reqwest::Client>,
|
|
|
|
) -> anyhow::Result<Self> {
|
2023-05-11 23:07:31 +03:00
|
|
|
let auth = if let Some(pass) = url.password().map(|x| x.to_string()) {
|
|
|
|
// to_string is needed because we are going to remove these items from the url
|
|
|
|
let user = url.username().to_string();
|
2022-08-24 03:11:49 +03:00
|
|
|
|
2023-05-11 23:07:31 +03:00
|
|
|
// clear username and password from the url
|
|
|
|
let mut_url = url.to_mut();
|
2022-08-24 03:11:49 +03:00
|
|
|
|
2023-05-11 23:07:31 +03:00
|
|
|
mut_url
|
|
|
|
.set_username("")
|
|
|
|
.map_err(|_| anyhow!("unable to clear username on websocket"))?;
|
|
|
|
mut_url
|
|
|
|
.set_password(None)
|
|
|
|
.map_err(|_| anyhow!("unable to clear password on websocket"))?;
|
|
|
|
|
|
|
|
// keep them
|
|
|
|
Some(Authorization::basic(user, pass))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let provider = if url.scheme().starts_with("http") {
|
|
|
|
let provider = if let Some(auth) = auth {
|
|
|
|
ethers::providers::Http::new_with_auth(url.into_owned(), auth)?
|
|
|
|
} else if let Some(http_client) = http_client {
|
|
|
|
ethers::providers::Http::new_with_client(url.into_owned(), http_client)
|
|
|
|
} else {
|
|
|
|
ethers::providers::Http::new(url.into_owned())
|
|
|
|
};
|
2022-08-24 03:11:49 +03:00
|
|
|
|
|
|
|
// TODO: i don't think this interval matters for our uses, but we should probably set it to like `block time / 2`
|
|
|
|
ethers::providers::Provider::new(provider)
|
2023-01-03 22:37:42 +03:00
|
|
|
.interval(Duration::from_secs(12))
|
2022-08-24 03:11:49 +03:00
|
|
|
.into()
|
2023-05-11 23:07:31 +03:00
|
|
|
} else if url.scheme().starts_with("ws") {
|
|
|
|
let provider = if auth.is_some() {
|
|
|
|
let connection_details = ConnectionDetails::new(url.as_str(), auth);
|
|
|
|
|
|
|
|
ethers::providers::Ws::connect(connection_details).await?
|
|
|
|
} else {
|
|
|
|
ethers::providers::Ws::connect(url.as_str()).await?
|
|
|
|
};
|
2022-08-24 03:11:49 +03:00
|
|
|
|
|
|
|
// TODO: dry this up (needs https://github.com/gakonst/ethers-rs/issues/592)
|
|
|
|
// TODO: i don't think this interval matters
|
|
|
|
ethers::providers::Provider::new(provider).into()
|
|
|
|
} else {
|
|
|
|
return Err(anyhow::anyhow!("only http and ws servers are supported"));
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(provider)
|
|
|
|
}
|
|
|
|
}
|