# EVM Client Architecture & Implementation Plan ## Project Goal Build a cross-platform (native + WASM) EVM client that can: - Interact with multiple EVM-compatible networks/providers - Use pluggable signing backends (e.g., SessionManager, hardware wallets, mocks) - Integrate seamlessly with Rhai scripting and the rest of the modular Rust workspace --- ## Requirements & Principles - **Async, modular, and testable**: All APIs are async and trait-based - **Cross-platform**: Native (Rust) and WASM (browser) support - **Multi-network**: Support for multiple EVM networks/providers, switchable at runtime - **Pluggable signing**: No direct dependency on vault/session; uses a generic Signer trait - **Consistency**: Follows conventions in architecture.md and other project docs - **Scripting**: Exposes ergonomic API for both Rust and Rhai scripting --- ## Recommended File Structure ``` evm_client/ ├── Cargo.toml └── src/ ├── lib.rs # Public API ├── provider.rs # EvmProvider abstraction ├── client.rs # EvmClient struct ├── signer.rs # Signer trait └── utils.rs # Helpers (e.g., HTTP, WASM glue) ``` --- ## 1. Pluggable Signer Trait ```rust // signer.rs #[async_trait::async_trait] pub trait Signer { async fn sign(&self, message: &[u8]) -> Result, EvmError>; fn address(&self) -> String; } ``` - `SessionManager` in vault implements this trait. - Any other backend (mock, hardware wallet, etc.) can implement it. --- ## 2. EvmProvider Abstraction ```rust // provider.rs pub enum EvmProvider { Http { name: String, url: String, chain_id: u64 }, // Future: WebSocket, Infura, etc. } impl EvmProvider { pub async fn send_raw_transaction(&self, tx: &Transaction, signer: &S) -> Result { let raw_tx = tx.sign(signer).await?; let body = format!("{{\"raw\":\"{}\"}}", hex::encode(&raw_tx)); match self { EvmProvider::Http { url, .. } => { http_post(url, &body).await } } } } ``` --- ## 3. EvmClient Struct & API ```rust // client.rs use std::collections::HashMap; pub struct EvmClient { providers: HashMap, current: String, signer: S, } impl EvmClient { pub fn new(signer: S) -> Self { Self { providers: HashMap::new(), current: String::new(), signer, } } pub fn add_provider(&mut self, key: String, provider: EvmProvider) { self.providers.insert(key, provider); } pub fn set_current(&mut self, key: &str) -> Result<(), EvmError> { if self.providers.contains_key(key) { self.current = key.to_string(); Ok(()) } else { Err(EvmError::UnknownNetwork) } } pub fn current_provider(&self) -> Option<&EvmProvider> { self.providers.get(&self.current) } pub async fn send_transaction(&self, tx: Transaction) -> Result { let provider = self.current_provider().ok_or(EvmError::NoNetwork)?; provider.send_raw_transaction(&tx, &self.signer).await } } ``` --- ## 4. Cross-Platform Networking (Native + WASM) ```rust // utils.rs pub async fn http_post(url: &str, body: &str) -> Result { #[cfg(not(target_arch = "wasm32"))] { let resp = reqwest::Client::new().post(url).body(body.to_owned()).send().await?; // parse response... Ok(parse_tx_hash(resp.text().await?)) } #[cfg(target_arch = "wasm32")] { let resp = gloo_net::http::Request::post(url).body(body).send().await?; // parse response... Ok(parse_tx_hash(resp.text().await?)) } } ``` --- ## 5. Rhai Scripting Integration ```rust // rhai_bindings.rs pub fn register_rhai_api(engine: &mut Engine) { engine.register_type::>(); engine.register_fn("add_network", EvmClient::add_provider); engine.register_fn("switch_network", EvmClient::set_current); engine.register_fn("send_tx", EvmClient::send_transaction); } ``` --- ## 6. Usage Example ```rust use evm_client::{EvmClient, EvmProvider, Signer}; use vault::SessionManager; let mut client = EvmClient::new(session_manager); client.add_provider("mainnet".into(), EvmProvider::Http { name: "Ethereum Mainnet".into(), url: "...".into(), chain_id: 1 }); client.add_provider("polygon".into(), EvmProvider::Http { name: "Polygon".into(), url: "...".into(), chain_id: 137 }); client.set_current("polygon")?; let tx_hash = client.send_transaction(tx).await?; ``` --- ## 7. Compliance & Consistency - **Async/trait-based**: Like kvstore/vault, all APIs are async and trait-based - **No direct dependencies**: Uses generic Signer, not vault/session directly - **Cross-platform**: Uses conditional networking for native/WASM - **Modular/testable**: Clear separation of provider, client, and signer logic - **Rhai scripting**: Exposes ergonomic scripting API - **Follows architecture.md**: Modular, layered, reusable, and extensible --- ## 8. Open Questions / TODOs - How to handle provider-specific errors and retries? - Should we support WebSocket providers in v1? - What subset of EVM JSON-RPC should be exposed to Rhai? - How to best test WASM networking in CI? --- *Last updated: 2025-05-16*