Skip to main content
Glama

Ethereum MCP Server

mod.rs13.2 kB
//! Ethereum service module - organized into sub-modules for better maintainability //! //! This module provides the main EthereumService that handles: //! - Balance queries (balance.rs) //! - Token pricing (price.rs) //! - Uniswap pool operations (pool.rs) //! - Swap transactions (swap.rs) //! - Utility functions (utils.rs) mod balance; mod price; mod pool; mod swap; mod utils; pub use balance::*; pub use price::*; pub use pool::*; pub use swap::*; pub use utils::*; use crate::types::{BalanceQuery, SwapParams, SwapQuote}; use alloy::primitives::Address; use alloy::providers::Provider; use alloy::providers::ProviderBuilder; use alloy::signers::local::PrivateKeySigner; use anyhow::{Context, Result}; use rust_decimal::Decimal; use std::env; use std::str::FromStr; pub struct EthereumService { rpc_url: String, uniswap_v3_router: Address, uniswap_v3_pool_factory: Address, signer_address: PrivateKeySigner, } impl EthereumService { pub async fn init(rpc_url: Option<String>) -> Result<Self> { let signer_address = Self::load_signer_address()?; let rpc = rpc_url.unwrap_or_else(|| "https://eth.llamarpc.com".to_string()); Ok(Self { rpc_url: rpc, uniswap_v3_router: Address::from_str("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")?, uniswap_v3_pool_factory: Address::from_str("0x1F98431c8aD98523631AE4a59f267346ea31F984")?, signer_address, }) } /// Helper method to create a provider async fn create_provider(&self) -> Result<impl Provider + Send + Sync + 'static> { let provider = ProviderBuilder::new() .wallet(self.signer_address.clone()) .connect(&self.rpc_url) .await .context("Failed to connect to RPC")?; Ok(provider) } /// Load signer address from environment variable ETHEREUM_PRIVATE_KEY fn load_signer_address() -> Result<PrivateKeySigner> { let private_key_hex = env::var("ETHEREUM_PRIVATE_KEY") .context("ETHEREUM_PRIVATE_KEY environment variable is not set. Please set it to your private key.")?; tracing::info!("Found private key in environment variable"); let key_str = private_key_hex.strip_prefix("0x").unwrap_or(&private_key_hex); let wallet = PrivateKeySigner::from_str(key_str) .context("Failed to parse private key from ETHEREUM_PRIVATE_KEY environment variable")?; let address = Address::from(wallet.address()); tracing::info!("Signer address loaded: {:?}", address); Ok(wallet) } /// Get the signer address pub fn get_signer_address(&self) -> Address { Address::from(self.signer_address.address()) } /// Get the signer pub fn get_signer(&self) -> &PrivateKeySigner { &self.signer_address } // Balance operations pub async fn get_token_decimals(&self, token_address: Address) -> Result<u8> { let provider = self.create_provider().await?; balance::get_token_decimals(token_address, &provider).await } pub async fn get_eth_balance(&self, address: &str) -> Result<String> { let provider = self.create_provider().await?; balance::get_eth_balance(address, &provider).await } pub async fn get_token_balance(&self, token_address: &str, holder_address: &str) -> Result<String> { let provider = self.create_provider().await?; balance::get_token_balance(token_address, holder_address, &provider).await } pub async fn query_balance(&self, params: &BalanceQuery) -> Result<String> { let provider = self.create_provider().await?; balance::query_balance(params, &provider).await } // Price operations pub async fn get_token_price(&self, token: &str, currency: Option<&str>) -> Result<String> { price::get_token_price(token, currency).await } // Swap operations pub async fn get_swap_quote(&self, params: &SwapParams) -> Result<SwapQuote> { let provider = self.create_provider().await?; swap::get_swap_quote(params, self.uniswap_v3_pool_factory, &provider).await } pub async fn build_swap_transaction(&self, params: &SwapParams) -> Result<String> { let user_address = self.get_signer_address(); let provider = self.create_provider().await?; swap::build_swap_transaction( params, self.uniswap_v3_router, self.uniswap_v3_pool_factory, user_address, &provider, ).await } pub fn validate_swap_params(&self, params: &SwapParams) -> Result<()> { swap::validate_swap_params(params) } // Pool operations pub async fn get_uniswap_v3_pool_address( &self, token_a: Address, token_b: Address, fee: u64, ) -> Result<Address> { let provider = self.create_provider().await?; pool::get_uniswap_v3_pool_address(token_a, token_b, fee, self.uniswap_v3_pool_factory, &provider).await } pub async fn get_pool_price(&self, pool_address: Address) -> Result<Decimal> { let provider = self.create_provider().await?; pool::get_pool_price(pool_address, &provider).await } pub async fn calculate_min_amount_out( &self, pool_address: Address, from_token: Address, to_token: Address, amount_in: Decimal, slippage_percentage: Decimal, ) -> Result<Decimal> { let provider = self.create_provider().await?; pool::calculate_min_amount_out( pool_address, from_token, to_token, amount_in, slippage_percentage, &provider, ).await } pub async fn check_token_balance_for_swap(&self, token_address: &str, user_address: Address, _amount_in: Decimal) -> Result<String> { let provider = self.create_provider().await?; swap::check_token_balance_for_swap(token_address, user_address, &provider).await } pub async fn check_token_allowance_for_swap(&self, token_address: Address, user_address: Address, required_amount: alloy::primitives::U256) -> Result<String> { let provider = self.create_provider().await?; swap::check_token_allowance_for_swap( token_address, user_address, self.uniswap_v3_router, required_amount, &provider, ).await } pub fn calculate_path(&self, from_token: Address, to_token: Address, fee: u64) -> alloy::primitives::Bytes { utils::calculate_path(from_token, to_token, fee) } } // These basic tests verify module initialization works #[cfg(test)] mod tests { use super::*; use crate::types::*; mod service_tests { use super::*; #[tokio::test] async fn test_new_service_with_custom_rpc() { let custom_rpc = "https://mainnet.infura.io/v3/test".to_string(); let _service = EthereumService::init(Some(custom_rpc.clone())).await.unwrap(); } #[tokio::test] async fn test_new_service_with_default_rpc() { let _service = EthereumService::init(None).await.unwrap(); } } mod query_balance_tests { use super::*; #[tokio::test] async fn test_query_balance_eth() { let service = EthereumService::init(None).await.unwrap(); let params = BalanceQuery { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".to_string(), token_address: None, }; match service.query_balance(&params).await { Ok(balance) => { assert!(balance.contains("ETH")); println!("ETH Balance: {}", balance); }, Err(e) => println!("RPC not available: {}", e), } } #[tokio::test] async fn test_query_balance_token() { let service = EthereumService::init(None).await.unwrap(); let params = BalanceQuery { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".to_string(), token_address: Some("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string()), }; match service.query_balance(&params).await { Ok(balance) => println!("Token balance: {}", balance), Err(e) => println!("Error: {}", e), } } #[tokio::test] async fn test_query_balance_invalid_address() { let service = EthereumService::init(None).await.unwrap(); let params = BalanceQuery { address: "invalid_address".to_string(), token_address: None, }; let result = service.query_balance(&params).await; assert!(result.is_err()); } } mod get_token_price_tests { use super::*; #[tokio::test] async fn test_get_token_price_eth() { let service = EthereumService::init(None).await.unwrap(); match service.get_token_price("ETH", None).await { Ok(price) => { assert!(price.contains("ETH:")); println!("ETH price: {}", price); }, Err(e) => println!("Failed: {}", e), } } #[tokio::test] async fn test_get_token_price_usdc() { let service = EthereumService::init(None).await.unwrap(); match service.get_token_price("USDC", None).await { Ok(price) => println!("USDC price: {}", price), Err(e) => println!("Failed: {}", e), } } #[tokio::test] async fn test_get_token_price_invalid_token() { let service = EthereumService::init(None).await.unwrap(); let result = service.get_token_price("INVALIDTOKEN123", None).await; assert!(result.is_err()); } } mod build_swap_transaction_tests { use super::*; use rust_decimal::Decimal; use std::str::FromStr; #[tokio::test] async fn test_build_swap_transaction_basic() { let service = EthereumService::init(None).await.unwrap(); let params = SwapParams { from_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(), to_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(), amount_in: "100".to_string(), slippage_tolerance: Some(Decimal::from_str("0.5").unwrap()), recipient: None, }; match service.build_swap_transaction(&params).await { Ok(response) => println!("Swap response: {}", response), Err(e) => println!("Swap failed: {}", e), } } #[tokio::test] async fn test_validate_swap_params() { let service = EthereumService::init(None).await.unwrap(); let params = SwapParams { from_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(), to_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(), amount_in: "100".to_string(), slippage_tolerance: Some(Decimal::from_str("0.5").unwrap()), recipient: None, }; assert!(service.validate_swap_params(&params).is_ok()); } #[tokio::test] async fn test_validate_swap_params_invalid() { let service = EthereumService::init(None).await.unwrap(); let params = SwapParams { from_token: "invalid".to_string(), to_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(), amount_in: "100".to_string(), slippage_tolerance: None, recipient: None, }; assert!(service.validate_swap_params(&params).is_err()); } #[tokio::test] async fn test_get_swap_quote() { let service = EthereumService::init(None).await.unwrap(); let params = SwapParams { from_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(), to_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(), amount_in: "1000".to_string(), slippage_tolerance: None, recipient: None, }; match service.get_swap_quote(&params).await { Ok(quote) => { assert_eq!(quote.from_token, params.from_token); assert_eq!(quote.to_token, params.to_token); println!("Quote: {} -> {}", quote.amount_in, quote.estimated_amount_out); }, Err(e) => println!("Quote failed: {}", e), } } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Joshuashen022/ethereum-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server