diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index fb51899fb39..3fda009b291 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -32,6 +32,7 @@ alloy-rpc-types-eth.workspace = true alloy-rpc-types-trace = { workspace = true, optional = true } alloy-rpc-types-txpool = { workspace = true, optional = true } alloy-rpc-types-engine = { workspace = true, optional = true } +alloy-rpc-types = { workspace = true, optional = true } alloy-transport-http = { workspace = true, optional = true } alloy-transport-ipc = { workspace = true, optional = true } alloy-transport-ws = { workspace = true, optional = true } @@ -98,4 +99,5 @@ debug-api = ["dep:alloy-rpc-types-trace"] engine-api = ["dep:alloy-rpc-types-engine"] net-api = [] trace-api = ["dep:alloy-rpc-types-trace"] +rpc-api = ["dep:alloy-rpc-types"] txpool-api = ["dep:alloy-rpc-types-txpool"] diff --git a/crates/provider/src/ext/mod.rs b/crates/provider/src/ext/mod.rs index 8e06ca3042e..7b6c0ffc1bf 100644 --- a/crates/provider/src/ext/mod.rs +++ b/crates/provider/src/ext/mod.rs @@ -30,6 +30,11 @@ mod trace; #[cfg(feature = "trace-api")] pub use trace::{TraceApi, TraceCallList}; +#[cfg(feature = "rpc-api")] +mod rpc; +#[cfg(feature = "rpc-api")] +pub use rpc::RpcApi; + #[cfg(feature = "txpool-api")] mod txpool; #[cfg(feature = "txpool-api")] diff --git a/crates/provider/src/ext/rpc.rs b/crates/provider/src/ext/rpc.rs new file mode 100644 index 00000000000..317a603e807 --- /dev/null +++ b/crates/provider/src/ext/rpc.rs @@ -0,0 +1,27 @@ +//! This module extends the Ethereum JSON-RPC provider with the Rpc namespace's RPC methods. +use crate::Provider; +use alloy_network::Network; +use alloy_rpc_types::RpcModules; +use alloy_transport::{Transport, TransportResult}; + +/// The rpc API provides methods to get information about the RPC server itself, such as the enabled +/// namespaces. +#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] +pub trait RpcApi: Send + Sync { + /// Lists the enabled RPC namespaces and the versions of each. + async fn rpc_modules(&self) -> TransportResult; +} + +#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] +impl RpcApi for P +where + N: Network, + T: Transport + Clone, + P: Provider, +{ + async fn rpc_modules(&self) -> TransportResult { + self.client().request("rpc_modules", ()).await + } +} diff --git a/crates/rpc-types/Cargo.toml b/crates/rpc-types/Cargo.toml index fedb641cde3..48288e15ac1 100644 --- a/crates/rpc-types/Cargo.toml +++ b/crates/rpc-types/Cargo.toml @@ -28,6 +28,10 @@ alloy-rpc-types-eth = { workspace = true, optional = true } alloy-rpc-types-mev = { workspace = true, optional = true } alloy-rpc-types-trace = { workspace = true, optional = true } alloy-rpc-types-txpool = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive", "std"]} + +[dev-dependencies] +serde_json.workspace = true [features] default = ["eth"] diff --git a/crates/rpc-types/src/lib.rs b/crates/rpc-types/src/lib.rs index 003a3797db7..b158ec7df41 100644 --- a/crates/rpc-types/src/lib.rs +++ b/crates/rpc-types/src/lib.rs @@ -8,6 +8,9 @@ pub use alloy_serde as serde_helpers; +mod rpc; +pub use rpc::*; + #[cfg(feature = "admin")] pub use alloy_rpc_types_admin as admin; diff --git a/crates/rpc-types/src/rpc.rs b/crates/rpc-types/src/rpc.rs new file mode 100644 index 00000000000..c6f062cfa5e --- /dev/null +++ b/crates/rpc-types/src/rpc.rs @@ -0,0 +1,42 @@ +//! Types for the `rpc` API. +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Represents the `rpc_modules` response, which returns the +/// list of all available modules on that transport and their version +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(transparent)] +pub struct RpcModules { + module_map: HashMap, +} + +impl RpcModules { + /// Create a new instance of `RPCModules` + pub const fn new(module_map: HashMap) -> Self { + Self { module_map } + } + + /// Consumes self and returns the inner hashmap mapping module names to their versions + pub fn into_modules(self) -> HashMap { + self.module_map + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_parse_module_versions_roundtrip() { + let s = r#"{"txpool":"1.0","trace":"1.0","eth":"1.0","web3":"1.0","net":"1.0"}"#; + let module_map = HashMap::from([ + ("txpool".to_owned(), "1.0".to_owned()), + ("trace".to_owned(), "1.0".to_owned()), + ("eth".to_owned(), "1.0".to_owned()), + ("web3".to_owned(), "1.0".to_owned()), + ("net".to_owned(), "1.0".to_owned()), + ]); + let m = RpcModules::new(module_map); + let de_serialized: RpcModules = serde_json::from_str(s).unwrap(); + assert_eq!(de_serialized, m); + } +}