Skip to main content
Glama

Ethereum MCP Server

swap.rs11.6 kB
//! Swap transaction building and simulation use crate::abis::{UniswapV3SwapRouter, ERC20}; use crate::ethereum::{pool, utils}; use crate::types::{SwapParams, SwapQuote}; use alloy::primitives::{Address, U256}; use anyhow::{Context, Result}; use rust_decimal::Decimal; use rust_decimal::prelude::ToPrimitive; use std::str::FromStr; /// Get a swap quote without executing - simulates the transaction pub async fn get_swap_quote<P: alloy::providers::Provider>( params: &SwapParams, pool_factory_address: Address, provider: &P, ) -> Result<SwapQuote> { tracing::info!("Getting quote for swap: {} -> {}", params.from_token, params.to_token); // Parse addresses and amounts let from_token: Address = params.from_token.parse()?; let to_token: Address = params.to_token.parse()?; let amount_in = Decimal::from_str(&params.amount_in).context("Invalid amount")?; // Get the pool address and calculate estimated output based on pool price let fee_tier = 500u64; // 0.05% let pool_address = pool::get_uniswap_v3_pool_address(from_token, to_token, fee_tier, pool_factory_address, provider).await?; let estimated_out = if pool_address != Address::ZERO { // Calculate expected output using pool price let pool_price = pool::get_pool_price(pool_address, provider).await?; let (token0, token1) = pool::get_pool_tokens(pool_address, provider).await?; // Determine output based on swap direction let expected_out = if from_token == token0 && to_token == token1 { amount_in * pool_price } else if from_token == token1 && to_token == token0 { amount_in / pool_price } else { // Fallback value if tokens don't match pool structure Decimal::ZERO }; expected_out.to_string() } else { // No pool found, return placeholder "0".to_string() }; Ok(SwapQuote { from_token: params.from_token.clone(), to_token: params.to_token.clone(), amount_in: params.amount_in.clone(), estimated_amount_out: estimated_out, price_impact: "0.5%".to_string(), gas_estimate: "350000".to_string(), }) } /// Check token balance for a swap pub async fn check_token_balance_for_swap<P: alloy::providers::Provider>( token_address: &str, user_address: Address, provider: &P, ) -> Result<String> { tracing::info!("Checking balance for token: {:?}", token_address); match get_token_balance_for_swap_impl(token_address, user_address, provider).await { Ok(balance_str) => { tracing::info!("Balance check result: {}", balance_str); Ok(format!("✓ Balance: {}", balance_str)) } Err(e) => { tracing::warn!("Failed to check balance: {}", e); Ok(format!("⚠ Could not check balance: {}", e)) } } } async fn get_token_balance_for_swap_impl<P: alloy::providers::Provider>( token_address: &str, user_address: Address, provider: &P, ) -> Result<String> { let token_addr: Address = token_address.parse() .context("Invalid token address")?; let erc20 = ERC20::new(token_addr, provider); let balance_result = erc20.balanceOf(user_address).call().await .context("Failed to call balanceOf")?; Ok(format!("{} tokens", balance_result)) } /// Check token allowance for a swap pub async fn check_token_allowance_for_swap<P: alloy::providers::Provider>( token_address: Address, user_address: Address, router_address: Address, required_amount: U256, provider: &P, ) -> Result<String> { tracing::info!("Checking allowance for router: {:?}", router_address); let erc20 = ERC20::new(token_address, provider); let allowance_result = erc20.allowance(user_address, router_address).call().await; // Get token decimals let decimals = get_token_decimals_helper(token_address, provider).await.unwrap_or(18u8); match allowance_result { Ok(allowance) => { let allowance_val = allowance; let denominator = utils::power_of_10(decimals as i32); let allowance_decimal = Decimal::from(allowance_val.to::<u128>()) / denominator; if allowance_val < required_amount { Err(anyhow::anyhow!( "Insufficient allowance. Required amount exceeds current allowance: {:.6}", allowance_decimal )) } else { Ok(format!("✓ Allowance: {:.6} tokens", allowance_decimal)) } } Err(e) => { tracing::warn!("Failed to check allowance: {}", e); Ok(format!("⚠ Could not check allowance: {}", e)) } } } async fn get_token_decimals_helper<P: alloy::providers::Provider>( token_address: Address, provider: &P, ) -> Result<u8> { let erc20 = ERC20::new(token_address, provider); let decimals_result = erc20.decimals().call().await .context("Failed to call decimals()")?; Ok(decimals_result) } /// Build and simulate a swap transaction using eth_call and eth_estimateGas pub async fn build_swap_transaction<P: alloy::providers::Provider>( params: &SwapParams, router_address: Address, pool_factory_address: Address, user_address: Address, provider: &P, ) -> Result<String> { tracing::info!("Building swap transaction for {} -> {}", params.from_token, params.to_token); // Parse addresses and amounts let from_token: Address = params.from_token.parse()?; let to_token: Address = params.to_token.parse()?; let amount_in = Decimal::from_str(&params.amount_in).context("Invalid amount")?; tracing::info!( "Parsed inputs -> from_token: {:?}, to_token: {:?}, amount_in: {}", from_token, to_token, amount_in ); // Check that a Uniswap V3 pool exists for this pair at the selected fee tier let fee_tier = 500u64; // 0.05% let pool_address = pool::get_uniswap_v3_pool_address(from_token, to_token, fee_tier, pool_factory_address, provider).await?; tracing::info!("Resolved pool address: {:?}", pool_address); if pool_address == Address::ZERO { return Err(anyhow::anyhow!( "No Uniswap V3 pool found for the token pair {}-{} at fee tier {} (0.05%)", from_token, to_token, fee_tier )); } // Get token decimals let from_decimals = get_token_decimals_helper(from_token, provider).await.unwrap_or(18u8); let denominator = utils::power_of_10(from_decimals as i32); let scaled_amount = U256::from((amount_in * denominator).to_u64().context("Amount too large for scaling")?); tracing::info!( "From token decimals: {}, scaled amount_in (wei units): {}", from_decimals, scaled_amount ); let quote = get_swap_quote(params, pool_factory_address, provider).await?; tracing::info!("Quote estimated_amount_out: {} {}", quote.estimated_amount_out, params.to_token); // Get token decimals for output scaling let to_decimals = get_token_decimals_helper(to_token, provider).await.unwrap_or(18u8); // Calculate minimum amount out based on price, amount_in, and slippage let slippage = params.slippage_tolerance.unwrap_or(Decimal::from_str("0.5")?); tracing::info!("Using slippage_tolerance: {}%", slippage); // Use the dedicated function to calculate min amount out let min_amount_out = pool::calculate_min_amount_out( pool_address, from_token, to_token, amount_in, slippage, provider, ).await .map_err(|e| { tracing::warn!("Failed to calculate min amount out using pool price: {}, falling back to quote estimate", e); anyhow::anyhow!("Calculation error: {}", e) }) .or_else(|_| -> Result<Decimal> { // Fallback to using quote estimate if pool price calculation fails tracing::info!("Falling back to quote-based min amount out calculation"); let amount_out = Decimal::from_str(&quote.estimated_amount_out).unwrap_or(Decimal::ZERO); let min = amount_out * (Decimal::from(1) - slippage / Decimal::from(100)); Ok(min) })?; tracing::info!("Calculated min_amount_out: {} {}", min_amount_out, params.to_token); let to_denominator = utils::power_of_10(to_decimals as i32); let scaled_min_out = U256::from((min_amount_out * to_denominator).to_u64().context("Min amount out too large")?); tracing::debug!("Scaled min_amount_out (wei units): {}", scaled_min_out); // Create Uniswap V3 Router instance let router = UniswapV3SwapRouter::new(router_address, provider); // Recipient and deadline for swap let recipient = params.recipient.clone() .unwrap_or_else(|| format!("{:?}", user_address)) .parse::<Address>()?; tracing::info!("Recipient: {:?}", recipient); // Calculate swap path using the dedicated function let path_bytes = utils::calculate_path(from_token, to_token, fee_tier); // Build the input parameters struct let exact_input_params = UniswapV3SwapRouter::ExactInputParams { path: path_bytes, recipient, amountIn: scaled_amount, amountOutMinimum: scaled_min_out, }; // Use eth_call to simulate the swap (no actual execution) let simulate_result = router.exactInput(exact_input_params.clone()).call().await; tracing::info!("simulate_result {:?}", simulate_result); // Also estimate gas for the actual transaction let estimate_result = router.exactInput(exact_input_params).estimate_gas().await; tracing::info!("estimate_result {:?}", estimate_result); if let Err(ref e) = estimate_result { tracing::warn!("Gas estimation error: {}", e); } // Format results let gas_estimate_msg = match estimate_result { Ok(gas) => format!("Estimated Gas: {} wei", gas), Err(e) => format!("Gas estimation failed: {}", e) }; let simulation_msg = match simulate_result { Ok(n) => format!("✓ Simulation successful({}) - transaction would succeed", n), Err(e) => format!("⚠ Simulation warning: {}", e) }; Ok(format!( "Swap Transaction Simulation (using eth_call):\nSwap Details:\n From: {} {}\n To: ~{} {}\n Min Out ({}% slippage): ~{} {}\n {}\n {}\n\nNote: This used eth_call for simulation. No transaction has been submitted to the blockchain.", params.amount_in, params.from_token, quote.estimated_amount_out, params.to_token, slippage, min_amount_out, params.to_token, gas_estimate_msg, simulation_msg )) } /// Validate swap parameters pub fn validate_swap_params(params: &SwapParams) -> Result<()> { // Validate addresses are valid Ethereum addresses if !params.from_token.starts_with("0x") || params.from_token.len() != 42 { return Err(anyhow::anyhow!("Invalid from_token address")); } if !params.to_token.starts_with("0x") || params.to_token.len() != 42 { return Err(anyhow::anyhow!("Invalid to_token address")); } // Validate amount Decimal::from_str(&params.amount_in) .context("Invalid amount_in")?; // Validate slippage if let Some(slippage) = params.slippage_tolerance { if slippage < Decimal::ZERO || slippage > Decimal::from(100) { return Err(anyhow::anyhow!("Slippage must be between 0 and 100")); } } Ok(()) }

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