nado-sdk 0.3.6

Official Rust SDK for the Nado Protocol API
Documentation
use std::fmt::Debug;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use async_trait::async_trait;
use ethers::core::k256;
use ethers_middleware::SignerMiddleware;
use ethers_providers::{
    Http, HttpClientError, HttpRateLimitRetryPolicy, JsonRpcClient, JsonRpcError, Middleware,
    Provider, RetryClient,
};
use ethers_signers::{LocalWallet, Signer, Wallet};
use eyre::Result;
use serde::de::DeserializeOwned;
use serde::Serialize;

pub type HttpRetryProvider = Provider<RetryClient<Http>>;

pub type HttpEnsembleRetryProvider = Provider<RetryClient<HttpEnsemble>>;

pub type NadoProvider = SignerMiddleware<HttpRetryProvider, Wallet<k256::ecdsa::SigningKey>>;

pub type NadoEnsembleProvider =
    SignerMiddleware<HttpEnsembleRetryProvider, Wallet<k256::ecdsa::SigningKey>>;

/// Client that uses a different node url on failure
#[derive(Debug, Clone)]
pub struct HttpEnsemble {
    pub providers: Vec<Http>,
}

impl HttpEnsemble {
    pub fn new(node_urls: &[String]) -> Result<Self> {
        let mut providers = vec![];
        for url in node_urls.iter() {
            providers.push(Http::from_str(url)?)
        }
        Ok(HttpEnsemble { providers })
    }

    pub fn new_provider(node_urls: &[String]) -> Result<Provider<Self>> {
        let http_ensemble = Self::new(node_urls)?;
        Ok(Provider::new(http_ensemble))
    }

    pub fn new_retry_provider(node_urls: &[String]) -> Result<HttpEnsembleRetryProvider> {
        let http_ensemble = Self::new(node_urls)?;
        Ok(Provider::new(RetryClient::new(
            http_ensemble,
            Box::new(HttpRateLimitRetryPolicy),
            15,
            500,
        )))
    }

    pub async fn new_nado_provider(
        node_urls: &[String],
        private_key: String,
    ) -> Result<Arc<NadoEnsembleProvider>> {
        let provider = HttpEnsemble::new_retry_provider(node_urls)?;
        let chain_id = provider.get_chainid().await?;
        let wallet = private_key
            .clone()
            .parse::<LocalWallet>()
            .unwrap()
            .with_chain_id(chain_id.as_u64());
        Ok(Arc::new(SignerMiddleware::new(
            provider.interval(Duration::from_millis(500)),
            wallet,
        )))
    }
}

#[async_trait]
impl JsonRpcClient for HttpEnsemble {
    type Error = HttpClientError;

    async fn request<A, R>(&self, method: &str, params: A) -> Result<R, Self::Error>
    where
        A: Debug + Serialize + Send + Sync,
        R: DeserializeOwned + Send,
    {
        let mut last_res = Err(Self::Error::JsonRpcError(JsonRpcError {
            code: 0,
            message: "list of providers was empty".to_string(),
            data: None,
        }));

        for (i, p) in self.providers.iter().enumerate() {
            if i != 0 {
                if let Err(e) = &last_res {
                    let err_str = e.to_string();
                    // gas data returned in revert msg
                    if err_str.contains(": G ") {
                        break;
                    }
                    println!(
                        "http_ensemble: using backup node_url: {}\n{err_str}",
                        p.url(),
                    );
                }
            }
            last_res = p.request(method, &params).await;
            if last_res.is_ok() {
                break;
            }
        }
        last_res
    }
}