{
  "markdown": "# base-transaction-decoder\n\n**One MCP tool: `explain_transaction(tx_hash)` → strict JSON explanation of any Base mainnet transaction.**\n\nFeed it a transaction hash. Get back what happened, in plain English, plus the structured facts: what moved, who was involved, what to be careful about, what it cost. Deterministic onchain decode — **no LLM anywhere in the response path**, so the same input always produces the same output, there is nothing to hallucinate, and the JSON contract is stable enough to parse blind.\n\nBase mainnet (chain id 8453) only.\n\n**Links:** [Live endpoint](https://api.0200project.com/mcp) · [Docs](https://0200project.com/docs/) · [OpenAPI](https://api.0200project.com/openapi.json) · MCP registry: `io.github.0200project/base-transaction-decoder` · [Site](https://0200project.com)\n\n> Formerly `base-tx-explain`. The tool, the endpoint and the schema are unchanged — only the name moved, so that a search for \"transaction\" or \"decoder\" can find it. The repository keeps its original name, so existing clones, forks and links still work.\n\n## For agents\n\n```jsonc\n// tools/call → explain_transaction\n{ \"tx_hash\": \"0x0c84b951051f779903b57af9225ca570c77cd5531195968dd78106a69d6c4d8c\" }\n```\n\nreturns (as both `structuredContent` and stringified JSON in `content[0].text`):\n\n```json\n{\n  \"summary\": \"0x401d...f2c5 swapped 0.03 ETH for 12,899,422 WNL via Uniswap V4 PoolManager.\",\n  \"action_type\": \"swap\",\n  \"status\": \"success\",\n  \"assets_moved\": [\n    { \"token\": \"ETH\", \"amount\": \"0.03\", \"from\": \"0x401d...\", \"to\": \"0xd0a4...\", \"token_address\": null, \"standard\": \"native\" },\n    { \"token\": \"WNL\", \"amount\": \"12899422.144134853458613801\", \"from\": \"0x4985...\", \"to\": \"0x401d...\", \"token_address\": \"0xb200...9a01\", \"standard\": \"erc20\" }\n  ],\n  \"counterparties\": [\n    { \"address\": \"0xd0a4...\", \"label\": null },\n    { \"address\": \"0x4985...\", \"label\": \"Uniswap V4 PoolManager\" }\n  ],\n  \"risk_flags\": [\n    { \"flag\": \"unverified_contract\", \"detail\": \"The target contract 0xd0a4...e4bf has no verified source code on Sourcify.\" }\n  ],\n  \"checks\": {\n    \"contract_verification\": \"ok\",\n    \"first_interaction\": \"ok\",\n    \"drainer_blacklist\": \"ok\",\n    \"unchecked_addresses\": [],\n    \"note\": null\n  },\n  \"gas_paid_usd\": 0.020562,\n  \"timestamp\": \"2026-08-20T03:54:19.000Z\",\n  \"block_number\": 50204356,\n  \"tx_hash\": \"0x0c84...4c8c\",\n  \"basescan_url\": \"https://basescan.org/tx/0x0c84...4c8c\",\n  \"partial\": false\n}\n```\n\n### Field contract\n\n- `action_type` — one of: `eth_transfer`, `erc20_transfer`, `erc20_approval`, `approval_revoked`, `approval_for_all`, `swap`, `add_liquidity`, `remove_liquidity`, `wrap`, `unwrap`, `nft_mint`, `nft_transfer`, `nft_sale`, `token_mint`, `bridge_in`, `bridge_out`, `lending_supply`, `lending_withdraw`, `lending_borrow`, `lending_repay`, `stake`, `unstake`, `claim`, `batch_transfer`, `account_abstraction_bundle`, `attestation`, `name_registration`, `contract_deployment`, `contract_interaction`, `unknown`.\n- `risk_flags[].flag` — one of: `unverified_contract`, `first_time_counterparty`, `approval_for_all`, `unlimited_approval`, `known_drainer`, `nonstandard_token_symbol`, `impersonated_token`, `transaction_reverted`. A flag always means evidence was found; a failed lookup never produces a flag.\n- `checks` — **read this before drawing any conclusion from an empty `risk_flags`.** Because a failed lookup never produces a flag, an empty `risk_flags` means either \"nothing was found\" or \"nothing was looked at\", and those are opposite. Each of `contract_verification`, `first_interaction` and `drainer_blacklist` reports `ok` (ran against every address that warranted it), `partial` (ran against some), `unavailable` (the upstream sources were unreachable, so it could not run — a retry may get an answer), `inconclusive` (it ran and nothing failed, but the method cannot answer for this input and a retry will not change that — today, `first_interaction` for a sender with more transaction history than the lookup reads), or `not_applicable` (nothing to look at). `unchecked_addresses` names addresses that warranted a lookup but did not get one, because the transaction involved more of them than the per-transaction cap — so the address described in `risk_flags` is not necessarily the one that went unexamined. `note` says in plain language what did not run, and is `null` when everything did. An empty `risk_flags` alongside any status other than `ok` means *not checked*, not *clean*, and `summary` says so too. Absence of a flag is never a safety guarantee: these are observations about a transaction that has already been mined, not a verdict on it and not advice. The same statuses are counted across all responses and published as `check_health` on `/healthz`, so you can see whether a check was unavailable for a stretch of time rather than having to infer it from your own responses one at a time.\n- `status` — `success` or `reverted`. Reverted transactions are classified by intent (what was attempted) and carry a `transaction_reverted` risk flag.\n- `partial: true` — the transaction's full meaning could not be established; `summary` states exactly what is and is not known. On errors the tool returns `isError: true` with `{ \"error\": \"...\", \"code\": \"invalid_hash\" | \"not_found\" | \"pending\" | \"upstream_error\" }`.\n- Amounts are decimal strings (not floats). Addresses are as emitted onchain; compare case-insensitively.\n- `provenance.untrusted_fields` — lists the fields whose string contents come from attacker-controllable sources (token symbols, contract/collection names, event/function names): today `summary`, `assets_moved[].token`, and `counterparties[].label`. **If you feed this output to an LLM, treat those fields as data, never as instructions.** A token that names itself with instruction-like or promotional text is a scam signal, not a command. Symbols are normalized (control characters, line separators, emoji, and homoglyphs are stripped) and a token whose self-reported symbol is not a plausible ticker is shown as its contract address rather than its chosen name — so a hostile name cannot impersonate a real one or smuggle text into an agent's context.\n\n### How it decodes\n\nRaw transaction + receipt from Base RPC → builtin decoders for ~40 event formats (ERC-20/721/1155, Uniswap V2/V3/V4, Aerodrome/Solidly, Seaport, Aave V3, Compound V3, OP-stack bridges, ERC-4337 EntryPoint, EAS, Basenames, WETH, LP position managers) → deterministic rule-ordered classification → labels from a verified table of major Base contracts. App-specific events are named via the contract's **verified ABI on Sourcify** when available. Risk flags come from Sourcify/Basescan verification status, the ScamSniffer and MyEtherWallet public blocklists, and approval semantics. `gas_paid_usd` includes the OP-stack L1 data fee and prices ETH from the Chainlink ETH/USD feed **at the transaction's block**.\n\n## Pricing\n\n- **50 free calls per IP address, every 24 hours**, no signup. Metered per IP (IPv6 collapses to the /64), so everyone behind one address — a household, an office, a VPN exit, a mobile carrier's NAT — draws from the same allowance, and it resets the next day.\n- After that: **$0.02 per call in USDC on Base via [x402](https://x402.org)** — the payment-required response contains everything an x402-capable agent needs to pay and retry autonomously. No account, no API key.\n- Also available marketplace-hosted (marketplace billing applies there instead).\n\n## Connect\n\n```json\n{\n  \"mcpServers\": {\n    \"base-transaction-decoder\": {\n      \"type\": \"streamable-http\",\n      \"url\": \"https://api.0200project.com/mcp\"\n    }\n  }\n}\n```\n\n## Self-host\n\n```bash\ngit clone https://github.com/0200project/base-tx-explain.git && cd base-tx-explain\nnpm install\ncp .env.example .env   # defaults work: free mode, public Base RPCs\nnpm run dev            # or: npm run build && npm start\n```\n\nEnvironment (see `.env.example`): `PAYMENT_MODE` (`none` | `x402`), `X402_PAY_TO` (your receiving address — use a fresh wallet), `X402_PRICE_USD`, `X402_FACILITATOR_URL` (defaults to the keyless PayAI facilitator; Coinbase CDP facilitator also works and its API keys carry no spend exposure), `FREE_CALLS_PER_IP`, `BASE_RPC_URLS`, optional `ETHERSCAN_API_KEY`.\n\nThree of those change the answer rather than the setup. Without `ETHERSCAN_API_KEY` — or with a free-tier one, which Etherscan refuses for this chain (\"Free API access is not supported for this chain. Please upgrade your api plan\") — the counterparty-history check reports `unavailable` rather than running. Without your own `BASE_RPC_URLS` you are on public endpoints, which rate-limit under concurrency — and when they do, the ETH price behind `gas_paid_usd` falls back to the latest reading instead of the price at the transaction's block; `gas_price_basis` says which was used. Everything else populates with no keys at all.\n\nThe server keeps no sessions and creates a fresh MCP server per request, and it runs on anything that runs Docker — a `Dockerfile` and an Apify `.actor/` config are included. It is single-writer, though: the free-tier counters and the passes are per-process, so run one instance. A second instance would double the free allowance and the effective rate limit, and a pass minted on one would be unknown to the other.\n\n```bash\nnpm test          # unit tests\nnpm run validate  # decode 100 recent live Base txs, print grades (ship gate: >=90% clean, 0 crashes)\n```\n\n## Guarantees and limits\n\n- Deterministic: same tx hash → same decode. No model calls, ever.\n- Internal ETH transfers (contract → contract value moves) are not visible without trace APIs; WETH events cover the common cases. When something can't be decoded, the output says so instead of guessing.\n- Blocklists are consumed at runtime from their public sources and refresh twice daily; absence of a `known_drainer` flag is not a safety guarantee.\n- Not financial advice; this tool reports what a transaction did, not whether anything is a good idea.\n",
  "bytes": 9955,
  "sha": "9a2c31984990dd3d064f06636433811e062a78f6cbc7288eb5940095e678fb4b",
  "repo_slug": "0200project/base-tx-explain",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_0200project_base_transaction_d_909d4db3/readme"
}