- Go 98.9%
- Shell 0.5%
- Makefile 0.4%
- Dockerfile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
release / release (push) Failing after 31m39s
Cap persistent RPC payloads at 4 GiB by default, reserve 1 GiB of free space, and expose configurable disk, memory, and pruning thresholds. Keep memory-only operation available while protecting non-cache database state. Add deterministic receipt batching with durable per-hash checkpoints and running totals. Completed batches release hot receipt data, retries skip processed hashes, and completed jobs collapse to their final result. Document the storage tradeoffs and update release packaging for v0.2.0. |
||
| .agents/skills/icu-evm-mcp | ||
| .forgejo/workflows | ||
| cmd | ||
| docs | ||
| internal | ||
| scripts | ||
| .dockerignore | ||
| .git-allowed-signers | ||
| .gitignore | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| CONTRIBUTING.md | ||
| default.json | ||
| docker-compose.yml | ||
| Dockerfile | ||
| gas-policy.example.json | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| Makefile | ||
| minisign.pub | ||
| README.md | ||
| SECURITY.md | ||
icu-evm-mcp
A read-first EVM forensics MCP server that works on any Ethereum chain or fork. It gives an LLM Otterscan-style exploration and deep token-value forensics, with an optional gas-gated relay for already-signed transactions.
Signing and key handling remain outside the MCP server. Broadcast is disabled
by default and the server never accepts a private key, keystore, secret, or
credential path. The distribution includes a separate, offline
icu-evm-signer executable for non-interactive local signing.
Quick start (local, stdio)
go build -o icu-evm-mcp ./cmd/icu-evm-mcp
go build -o icu-evm-signer ./cmd/icu-evm-signer
./icu-evm-mcp --config default.json --db icuevmmcp.db --transport stdio
To expose the signed-transaction relay, opt in explicitly:
./icu-evm-mcp --config default.json --db icuevmmcp.db --transport stdio \
--gas-policy gas-policy.example.json --enable-broadcast
default.json seeds the database on first run (chains, endpoints, DEX
config). After that the database is authoritative and you manage it at runtime
through the tools (add_endpoint, upsert_chain, set_label, …).
MCP client setup
Codex
codex mcp add web3 -- /absolute/path/to/icu-evm-mcp \
--config /absolute/path/to/default.json --db /absolute/path/to/icuevmmcp.db
Claude Code
claude mcp add web3 -- /absolute/path/to/icu-evm-mcp \
--config /absolute/path/to/default.json --db /absolute/path/to/icuevmmcp.db
Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"web3": {
"command": "/absolute/path/to/icu-evm-mcp",
"args": ["--config", "/absolute/path/to/default.json", "--db", "/absolute/path/to/icuevmmcp.db"]
}
}
}
Docker
stdio (local MCP client):
docker build -t icu-evm-mcp .
# Local stdio MCP-client command:
docker run -i --rm -v icu-evm-mcp-data:/data icu-evm-mcp
HTTP (networked / multi-client):
docker compose up --build # serves the streamable-HTTP endpoint on :8080
The /data volume persists the database (config, labels, capabilities, bounded
cache, and receipt-aggregation checkpoints) across restarts.
The image also contains /usr/local/bin/icu-evm-signer for one-shot, networkless
signing. Never mount credentials into the long-running MCP container; see
Non-interactive transaction signing.
Configuration (default.json)
Fully neutral — no chains are hardcoded. Each chain specifies its endpoints, wrapped-native, stablecoins, USD reference, and DEXes (V2-style routers / factories). The shipped example is PulseChain (chainId 369) + Ethereum.
{
"defaultChain": 369,
"chains": [{
"chainId": 369,
"name": "PulseChain",
"nativeSymbol": "PLS",
"endpoints": ["https://rpc.pulsechain.com"],
"wrappedNative": "0xA1077a294dDE1B09bB078844df40758a5D0f9a27",
"usdReference": "0xefD766cCb38EaF1dfd701853BFCe31359239F305",
"dexes": [{
"name": "PulseX V2", "version": "v2",
"routers": ["0x165C3410fC91EF562C50559f7d2289fEbed552d9"],
"factories": ["0x29eA7545DEf87022BAdc76323F373EA1e707C523"]
}]
}]
}
Cache and receipt aggregation
The on-disk RPC cache has a 4 GiB logical cap and starts expelling cached
payloads when filesystem free space reaches 1 GiB. Both thresholds are
configurable, and --cache-max-size 0 keeps RPC payloads in memory only.
Use aggregate_receipts for large receipt sets. It checkpoints every completed
hash and the running tally, then releases each processed batch's receipt and log
payloads from the hot cache. A resumed job skips completed hashes, and passing
only jobId returns its saved status. See
Cache and receipt aggregation for settings and
storage tradeoffs.
Gas policy
gas-policy.example.json controls the default gas mode, history size, median
threshold, and optional per-chain ceiling. It is loaded at startup with
--gas-policy; omitting the flag uses the same hybrid defaults without an
absolute chain ceiling.
Available modes are median, request_cap, chain_policy, and hybrid.
A configured chain ceiling applies in every mode. request_cap requires an
exact decimal maxBlockBaseFeeGwei; hybrid accepts it optionally.
The write flow is: call gas_oracle, sign with icu-evm-signer or an existing
project signer, submit only the raw signed transaction to
broadcast_transaction, then confirm receipt status and canonical block
membership. A wait or unknown decision never contacts a relay endpoint.
The signed transaction reaches broadcast_transaction as either rawTx (the
hex) or rawTxFile (a path to a file holding only that hex). Prefer the file
for a contract deployment: creation bytecode runs to tens of thousands of hex
characters, and one wrong character changes what the signature covers, so the
transaction is refused as coming from an address that never existed. The file
must contain a signed transaction and nothing else — it is never a path to a
keystore or secret, and its contents are never echoed back, including in an
error.
Offline companion signer
icu-evm-signer signs complete legacy, access-list, and EIP-1559 transactions
without RPC access or manual interaction. It reads an owner-only encrypted
keystore and one-line secret file, verifies the requested from address, and
emits only signed bytes plus a transaction preview. Literal secrets are never
accepted through arguments, environment variables, stdin, or MCP.
See Non-interactive transaction signing for credential storage, transaction JSON, native and container commands, exit codes, and the required agent workflow.
The signer is also available as the standalone
icu-evm-signer repository. Cloning this repository does not
fetch that separate repository: the bundled signer source is already included
under cmd/icu-evm-signer and is built directly with the server distribution.
Tools
Chain & node: get_chain_info, list_chains, add_endpoint,
remove_endpoint, set_endpoint_priority, upsert_chain
Blocks & transactions: latest_block, get_block, gas_oracle,
get_transaction, get_transaction_receipt, aggregate_receipts
Accounts: get_balance, get_code, get_nonce, get_storage_at
Tokens: token_metadata, token_balances, detect_token_standard
Deep forensics (Otterscan-parity + fallbacks): trace_transaction,
get_contract_creator, get_transaction_by_sender_and_nonce,
search_address_transactions, get_logs, token_transfer_history,
detect_proxy, check_sourcify_verification, read_contract
DEX / value forensics: discover_pools, pool_reserves, price_token,
value_received, value_sent
Address label book: set_label, delete_label, list_labels,
resolve_label
Batched reads: multi_read, inspect_transactions, inspect_accounts,
inspect_blocks, inspect_sender_nonces, simulate_calls, price_tokens,
check_sourcify_verifications, checksum_addresses, convert_units_batch,
decode_calldatas
— see Batched reads below; prefer these over calling a
single-target tool in a loop.
Simulation & decoding: estimate_gas, create_access_list,
decode_calldata
Utilities: keccak256 (pass inputs[] to hash many at once),
checksum_address, convert_units
RPC discovery and optional relay: discover_chain,
suggest_rpc_endpoints, broadcast_transaction (only with
--enable-broadcast)
Most address inputs accept either a hex address or a label name.
Batched reads
Prefer these over calling a single-target tool in a loop — one request instead of one per target, and the RPC work collapses into a JSON-RPC batch or a Multicall3 aggregate.
| Tool | Replaces looping | Notes |
|---|---|---|
multi_read |
read_contract |
Same function across many addresses, many functions on one address, or a mix. |
inspect_transactions |
get_transaction, get_transaction_receipt, trace_transaction, value_received, value_sent |
Defaults to tx, receipt, and canonical block-membership verification. |
aggregate_receipts |
Repeated receipt calls used only to calculate totals | Checkpoints processed hashes and running totals between bounded batches. |
inspect_accounts |
get_balance, get_nonce, get_code, get_storage_at, get_contract_creator, detect_proxy, detect_token_standard, token_balances |
Supports shared storage slots and token lists across all addresses. |
inspect_blocks |
get_block |
Fetches number, hash, tag, or timestamp targets together. |
inspect_sender_nonces |
get_transaction_by_sender_and_nonce |
Batches Otterscan lookups before bounded archive fallbacks. |
simulate_calls |
read_contract, estimate_gas, create_access_list |
Mixes the three simulations across many call objects in one JSON-RPC batch. |
price_tokens |
price_token |
Shares token metadata, pool discovery, reserve snapshots, and the native/USD quote. |
Composite outputs preserve the single-target fields and input order, with errors attached to the affected item.
inspect_transactions includes the receipt's exact executionStatus (0x1,
0x0, or absent when unknown). Set includeRawLogs: true to receive original
topics and data alongside canonical block information. logOffset and
logLimit page each receipt's logs; nextLogOffset continues a receipt, while
logCount remains the complete count. The default page size is 200 and the
maximum is 2,000.
multi_read sends a real eth_call per entry by default, so semantics match
read_contract exactly — including the shared from and stateOverrides.
Setting atomic: true routes through Multicall3 so every read observes one
block state; in that mode msg.sender becomes the Multicall3 contract for every
sub-call, so from/stateOverrides are refused. The address is resolved from
the upstream Multicall3 deployment registry, cached, and verified live; atomic
mode fails explicitly if no verified deployment is available.
Exact-parity single-target tools remain registered for compatibility but are marked deprecated for new calls in their descriptions and tool metadata.
Development
make check # the gate: fmt, vet, staticcheck, govulncheck, mod verify,
# test, race, build, docker build, stdio smoke test
make test # hermetic unit tests only
make test-live # opt-in live RPC tests (PulseChain public RPC)
make check needs a running Docker daemon, python3, a C toolchain (for
-race), and network access on first run so go run can fetch the pinned
staticcheck/govulncheck. Run individual targets if you can't satisfy one.
See CONTRIBUTING.md.
Security
Endpoint URLs supplied at runtime are validated: only http/https/ws/wss
are dialed, and they must resolve to public addresses unless
--allow-private-endpoints is set. Endpoints from your config file are not
address-restricted, so pointing at a node on your LAN works as it always did.
Do not bind --http-addr to a public interface — the HTTP transport has no
authentication and the tool surface includes add_endpoint and upsert_chain.
It defaults to loopback.
Configured endpoint URLs are returned verbatim by list_chains,
get_chain_info and suggest_rpc_endpoints, so avoid embedding provider API
keys in them if callers should not see them. Keys are redacted in logs.
The configurable cache limits apply only to reusable RPC payloads. Chain configuration, labels, endpoint capabilities, and receipt-job checkpoints are not cache-pruning targets.
See SECURITY.md for the threat model and how to report an issue.
License
MIT — see LICENSE.