E
Eth Trading MCP
MCP server by JungleLiu-LHJ
Created 11/6/2025
Updated about 1 month ago
README
Repository documentation and setup instructions
WalletMcp — Ethereum Trading MCP Server
Stdio JSON‑RPC server exposing three tools for Ethereum mainnet:
get_balance— ETH or ERC‑20 balance lookupget_token_price— Chainlink‑first price with Uniswap V3 fallbackswap_tokens— Build real Uniswap V3 calldata and simulate (no broadcast)
Design Decisions
Code structure
- Three layers: MCP (stdio/JSON‑RPC) → Service (orchestration) → Implementations (balance/price/swap). This isolates concerns, keeps handlers thin, and makes core logic testable without I/O.
- Shared context (
ServiceContext) holdsprovider, and a tokenregistrybehindArc/RwLock. This enables safe concurrent reads with occasional writes when discovering new tokens. - **JSON‑RPC over stdio keeps the binary host‑agnostic and MCP‑compatible; stdout is reserved for protocol payloads, logs go to stderr via **
tracing.
Core Flows
- Token metadata and registry
- Defaults metadata are saved in (
config/token_defaults.json) for deterministic behavior and quick startup. - On‑demand discovery: if a token isn’t in the registry but an address is provided, the server fetches minimal ERC‑20 metadata and caches it, avoiding a hard dependency on static config.
- I decoupled the registry and main flow to facilitate later maintenance.
- Defaults metadata are saved in (
- Pricing policy
- Firstly request prices from Chainlink for integrity and resilience
- If not exist, then falls back to Uniswap V3 Quoter
- Swap simulation by construction
- Call Uniswap V3 QuoterV2 a single-hop output, then calculate the slippage.
- Build the exact SwapRouter calldata send on-chain, then simulate it via the node (eth_estimateGas + eth_call) using the private key
Setup
- Dependencies
- Rust (stable) and Cargo
- An HTTPS Ethereum RPC endpoint (e.g., Alchemy/Infura/Public)
- Clone and build
cargo build --release
- Configuration
- Option A: environment variables (dotenv supported)
ETH_RPC_URL— HTTPS RPC URL (required)PRIVATE_KEY— hex private key, with or without0x(optional; required for swap simulation)DEFAULT_CHAIN_ID— defaults to1(mainnet)
- **Option B: **
Config.toml(preferred in production). Example:eth_rpc_url = "https://..." private_key = "0xabc..." default_chain_id = 1
- Option A: environment variables (dotenv supported)
- Token registry defaults
- **in **
config/token_defaults.json(symbols, addresses, decimals, Chainlink feeds, default Uniswap fee tiers).
- **in **
Run
- Start the MCP server over stdio (reads JSON‑RPC lines from stdin, writes responses to stdout):
cargo run --release
- Or run the compiled binary:
target/release/walletmcp
- **The server logs to stderr via **
tracing; stdout is reserved for JSON‑RPC payloads.
Example MCP Calls (JSON‑RPC over stdio)
- **Get balance **
-
Parameters
address: holder address (checksummed or lowercased0x...).token(optional): ERC‑20 address or known symbol; omit for ETH.
-
ETH
- Request:
{"jsonrpc":"2.0","id":"1","method":"get_balance","params":{"address":"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"}}- Example response:
{"jsonrpc":"2.0","result":{"decimals":18,"formatted":"3.758181447334319014","raw":"3758181447334319014","symbol":"ETH"},"id":"1"} -
USDT
- Request:
{"jsonrpc":"2.0","id":"bal-usdt","method":"get_balance","params":{"address":"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045","token":"0xdAC17F958D2ee523a2206206994597C13D831ec7"}}- Example response:
{"jsonrpc":"2.0","id":"bal-usdc","method":"get_balance","params":{"address":"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045","token":"USDC"}}
-
- **Get token price **
-
Parameters
base: token to price (address or symbol).quote(optional):"USD"or"ETH"(defaults to"USD").
-
USDC → USD
- Request:
{"jsonrpc":"2.0","id":"price-1","method":"get_token_price","params":{"base":"USDC"}}- response:
{"jsonrpc":"2.0","result":{"base":"USDC","decimals":8,"price":"0.99981815","quote":"USD","source":"chainlink"},"id":"price-1"}
-
- **Simulate swap **
- Parameters
from_token/to_token: address or known symbol; symbols resolve via the registry.amount_in_wei: input amount as a decimal string in wei.slippage_bps(optional): basis points tolerance (default 100 = 1%).fee(optional): Uniswap V3 fee tier.recipient(optional): output receiver; defaults to the signer address.sqrt_price_limit(optional): X96 price boundary;"0"or omit for no limit.
- Request:
{"jsonrpc":"2.0","id":"swap-1","method":"swap_tokens","params":{"from_token":"....","to_token":"0x6B175474E89094C44Da98b954EedeAC495271d0F","amount_in_wei":"10000000000000","slippage_bps":100,"fee":3000,"recipient":"0xYourAddressHere"}} - Example response:
{"jsonrpc":"2.0","result":{"amount_out_estimate":"0.033810900284009015","amount_out_min":"0.033472791281168924","calldata_hex":"0x414bf389000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000004fc74ba63ddd9c685f8bb59f25d2c9345d3c72e600000000000000000000000000000000000000000000000000000000690c4e19000000000000000000000000000000000000000000000000000009184e72a0000000000000000000000000000000000000000000000000000076eb5389f4721c0000000000000000000000000000000000000000000000000000000000000000","gas_estimate":"121109","router":"0xe592427a0aece92de3edee1f18e0157c05861564"},"id":"swap-1"}
- Parameters
Notes
- The exact numbers vary with market conditions and RPC provider state.
- **Swap simulation requires **
PRIVATE_KEYto be configured to derive a sender and build a realistic transaction context.
Descriptions
get_balance- Params
addressstring — holder address (0x+ 40 hex chars).tokenstring|null — optional ERC‑20 address or known symbol (perconfig/token_defaults.json). Omit to fetch native ETH balance.
- **Returns **
BalanceOut—{ symbol, raw, decimals, formatted }whereformatted = raw / 10^decimals. - Errors — invalid address/symbol, RPC failures.
- Params
get_token_price- Params
basestring — token address or symbol (known to the registry or discoverable via on‑chain ERC‑20 metadata).quotestring (optional, default"USD") — one of"USD"or"ETH".
- **Returns **
PriceOut—{ base, quote, price, source, decimals }wheresourceischainlink,chainlink (via USD/ETH), oruniswap_v3 (fee N). - Notes — Chainlink first; falls back to Uniswap V3 Quoter using default fee from the token registry.
- Errors — unsupported token, missing quote token configuration, RPC failures.
- Params
swap_tokens- Params
from_token/to_tokenstring — address or known symbol.amount_in_weistring — decimal string of input amount in wei.slippage_bpsinteger (default100) — basis points (max10000).feeinteger (default3000) — Uniswap V3 fee tier (e.g., 500 / 3000 / 10000).recipientstring (optional) — address to receive output; defaults to signer address.sqrt_price_limitstring (optional, advanced) — rawX96limit; omit for no limit.
- **Returns **
SwapSimOut—{ amount_out_estimate, amount_out_min, gas_estimate, calldata_hex, router }. - **Requirements — **
PRIVATE_KEYmust be configured to derive a sender for realistic calldata and gas estimation. - Errors — invalid numeric input, slippage > 10000, quote returned 0, gas estimation/eth_call failures, RPC issues.
- Params
Error Codes
-32602invalid params;-32601method not found;-32603internal/serialization.-32001config;-32002RPC;-32010price;-32020swap;-32030wallet;-32040I/O.
Known Limitations & Assumptions
- registry are static and did not update
- Single‑hop Uniswap V3 only; no route search or multi‑hop paths.
- Fallback DEX prices can be noisy/manipulable on thin liquidity pools; treat as indicative.
- **Limited Chainlink coverage: only feeds listed in **
token_defaults.json(unless extended in code). - Real execution would require ERC‑20 approvals and balances; this project only simulates and never broadcasts.
- Mainnet contract addresses are hard‑coded for Uniswap V3 QuoterV2 and SwapRouter.
Core Function Logic (high‑level)
get_balance (balance lookup)
- Accepts an address and an optional token identifier (address or symbol).
- If no token is provided, read the native ETH balance and format using 18 decimals.
- If a token is provided, resolve symbols via the in‑memory registry (or use the given address), fetch minimal token metadata as needed, read the holder’s token balance, and format using that token’s decimals.
- Returns symbol, raw amount, decimals, and a human‑readable formatted string. Invalid inputs or network issues surface as errors.
get_token_price (pricing)
- Resolve the base asset (address or symbol). If it’s not already known, discover minimal metadata on chain and add it to the registry cache; if it still can’t be recognized, return “unsupported token”.
- Apply a Chainlink‑first policy:
- If a direct base/quote oracle feed exists, use it.
- If not, try to pivot via USD or ETH using well‑known feeds (base/USD + WETH/USD or base/ETH + WETH/USD).
- If no oracle path is available, fall back to a single‑hop DEX spot quote using a default fee tier and representative quote tokens (USDC for USD, WETH for ETH).
- Return a decimal string price with a source label. If required quote‑token configuration is missing, return an error.
swap_tokens (simulation, no broadcast)
- Resolve input and output assets and require a signer to build a realistic transaction context (sender and execution domain).
- Validate amount and slippage; default the recipient to the signer when not supplied.
- Obtain a read‑only output estimate for the chosen fee tier.
- Derive a minimum acceptable output from the slippage tolerance and assemble real router calldata with a deadline and recipient.
- Estimate gas and dry‑run the transaction; format outputs using the output token’s decimals.
- Return the estimated output, minimum output, gas estimate, calldata, and router address. Real execution would require allowances and sufficient balances; this tool only simulates.
Network Calls & Data Sources
- get_balance
- Ethereum RPC only.
- **ETH: **
eth_getBalance(address, latest). - **ERC‑20: **
eth_callto token contract forbalanceOf(address), plus optional metadata readsdecimals()andsymbol()for formatting.
- get_token_price
- Ethereum RPC + on‑chain data sources.
- **Chainlink (preferred): **
eth_callto AggregatorV3 contracts fordecimals()andlatestRoundData()on mainnet feeds (e.g., WETH/USD, USDC/USD). - Pivoting: combines two Chainlink feeds (base/USD with WETH/USD, or base/ETH with WETH/USD) when a direct feed is missing.
- **Uniswap V3 fallback: **
eth_callto QuoterV2 at0x61fFE014bA17989E743c5F6cB21bF9697530B21eusingquoteExactInputSingle(...)for a single‑hop spot quote. - **Registry ensure step (as needed): **
eth_callto the token contract fordecimals()/symbol()when a token is first seen.
- swap_tokens (simulation)
- Ethereum RPC + Uniswap V3 contracts.
- **Quote: **
eth_callto Uniswap QuoterV2 for a single‑hop output estimate. - **Calldata: build Uniswap V3 SwapRouter **
exactInputSingle(...)transaction targeting0xE592427A0AEce92De3Edee1F18E0157C05861564. - **Simulation: **
eth_estimateGasfor the router transaction, theneth_callto dry‑run it; noeth_sendRawTransaction(never broadcasts). - **Metadata: if needed, **
eth_callto token contracts for decimals to format output amounts.
Testing
Remiding: some unit-test need complete Configurations(ETH_RPC_URL,PRIVATE_KEY)!!!
- **Run fast unit tests: **
cargo test - **Live‑network tests are marked **
#[ignore]and require env vars (seetests/and comments). Enable them manually if you have real RPC and funded keys.
Security Notes
- Never commit real private keys. Use environment variables locally and a secure secret manager in production.
- **The server never broadcasts transactions; simulation uses **
eth_estimateGasandeth_callonly.
Quick Setup
Installation guide for this server
Installation Command (package not published)
git clone https://github.com/JungleLiu-LHJ/ETH-trading-MCP
Manual Installation: Please check the README for detailed setup instructions and any additional dependencies required.
Cursor configuration (mcp.json)
{
"mcpServers": {
"jungleliu-lhj-eth-trading-mcp": {
"command": "git",
"args": [
"clone",
"https://github.com/JungleLiu-LHJ/ETH-trading-MCP"
]
}
}
}