zeus-userop-kit 0.1.0

ERC-4337 user operation builder and bundler client for EntryPoint 0.7 and 0.8
use std::sync::atomic::{AtomicU64, Ordering};

use reqwest::Client;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Value, json};

pub struct RpcClient {
   client: Client,
   url: reqwest::Url,
   id: AtomicU64,
}

#[derive(Debug, thiserror::Error)]
pub enum RpcClientError {
   #[error("HTTP error: {0}")]
   Http(#[from] reqwest::Error),
   #[error("JSON error: {0}")]
   Json(#[from] serde_json::Error),
   #[error("RPC error {code}: {message}")]
   Rpc { code: i64, message: String },
}

#[derive(Deserialize)]
#[serde(untagged)]
enum RpcResponse<T> {
   Success { result: T },
   Failure { error: RpcError },
}

#[derive(Debug, Deserialize)]
struct RpcError {
   code: i64,
   message: String,
}

impl RpcClient {
   pub fn new(url: reqwest::Url) -> Self {
      Self {
         client: Client::new(),
         url,
         id: AtomicU64::new(1),
      }
   }

   pub async fn request<P, R>(&self, method: &str, params: P) -> Result<R, RpcClientError>
   where
      P: Serialize,
      R: DeserializeOwned,
   {
      let id = self.id.fetch_add(1, Ordering::Relaxed);
      let body = json!({
          "jsonrpc": "2.0",
          "id": id,
          "method": method,
          "params": params,
      });

      let text = self.client.post(self.url.clone()).json(&body).send().await?.text().await?;
      let resp: RpcResponse<Value> = serde_json::from_str(&text)?;

      let resp = match resp {
         RpcResponse::Success { result } => result,
         RpcResponse::Failure { error } => {
            return Err(RpcClientError::Rpc {
               code: error.code,
               message: error.message,
            });
         }
      };

      let result = serde_json::from_value(resp)?;
      Ok(result)
   }
}