{
  "markdown": "# 🐴 gulltoppr\n\n![An agent resolving a live unverified MEV bot via gulltoppr: decompiled ABI, provenance warning, registry-proven name, live read](assets/demo.gif)\n\n*A real session: an unverified MEV bot that traded seconds earlier (no source, no ABI anywhere) resolved to a full interface in two MCP tool calls.*\n\nThe REST engine for **gulltoppr**: the resolution ladder + verb surface\nthat lets an AI agent go from `(chain, address)` to a correct, simulated, safe\ncontract interaction. This is \"the engine\" of the four faces (REST → MCP → SDK →\nSkill); see [`../SPEC.md`](../SPEC.md) for the full contract and [`../IDEATION.md`](../IDEATION.md)\nfor the strategy.\n\nTypeScript + [viem](https://viem.sh) + [Hono](https://hono.dev). The heimdall\ndecompile rung is delegated over HTTP to **gulltoppr** (kept out-of-process by\ndesign).\n\n## Run\n\n```bash\nnpm install\nnpm run dev            # REST engine: tsx watch on http://localhost:8787\nnpm run mcp            # MCP server: stdio tools for agent clients\nnpm run mcp:http       # MCP server: Streamable HTTP (remote agents)\nnpm run typecheck      # tsc --noEmit\nnpm test               # vitest unit tests (cache, chains, ladder helpers, args, errors)\nnpm run verify         # root typecheck/test/audit + SDK build/test/audit\nnpm run docker:build   # build the REST engine deployment image\nnpm run docker:build:mcp # build the Streamable HTTP MCP deployment image\nnpm run docker:smoke   # start both deployment images and check health/discovery/registry metadata\nnpm run test:live      # opt-in live contract smoke tests (RPC/decompiler/network)\n```\n\nSet `LIVE_ENGINE_BASE_URL=https://api.gulltoppr.dev` with `npm run test:live` to\nsmoke a deployed engine instead of the local in-process server. The scheduled live\nGitHub Action runs the local path; manual dispatch can set the same deployed URL.\n\n### Env\n\n| var | default | notes |\n|-----|---------|-------|\n| `PORT` | `8787` | |\n| `HEIMDALL_API_URL` | `http://heimdall-api.flycast` | heimdall decompile service (ladder rung 4). Private to the Fly org since 2026-07-27 — point this at your own instance when running outside it, or rung 4 falls back to selector-only via 4byte |\n| `HEIMDALL_CONCURRENCY` | `2` | per-process cap on outbound decompile/decode requests; `0` disables |\n| `HEIMDALL_QUEUE_TIMEOUT_MS` | `5000` | max time a gulltoppr request can wait for an outbound concurrency slot |\n| `ENS_RPC_URL` | `https://ethereum-rpc.publicnode.com` | mainnet RPC for ENS/Basenames Universal Resolver calls; use a private RPC in production |\n| `ETHERSCAN_API_KEY` | _(empty)_ | one multichain v2 key; empty disables rung 1 |\n| `ETHERSCAN_RATE_LIMIT` | `4` | per-process fixed-window budget for the shared Etherscan key; `0` disables |\n| `ETHERSCAN_RATE_WINDOW_SEC` | `1` | Etherscan budget window length |\n| `SIGNING_BASE_URL` | `https://abi.ninja` | base for `prepare_tx` hand-off deeplinks |\n| `RATE_LIMIT` | `120` | per-IP requests per window (fixed window); `0` disables |\n| `RATE_LIMIT_WINDOW_SEC` | `60` | rate-limit window length |\n| `RATE_LIMIT_ALLOW` | _(empty)_ | comma-separated IP allowlist (exempt); private 6PN IPs are always exempt |\n| `ANTHROPIC_API_KEY` | _(empty)_ | enables the registry's LLM propose-and-verify pass on decompiles; empty disables |\n| `REGISTRY_LLM_MODEL` | `claude-opus-4-8` | model for propose-and-verify |\n\n## Endpoints (SPEC §4)\n\n| verb | route |\n|------|-------|\n| discovery | `GET /` · root discovery document with REST/MCP links, verbs, utility tools, and the `prepare_tx` safety gate |\n| OpenAPI | `GET /openapi.json` · machine-readable REST contract for coding agents and integrations |\n| agent guide | `GET /llms.txt` · compact LLM/coding-agent guide; also published at `https://gulltoppr.dev/llms.txt` |\n| `resolve_abi` | `GET /v1/{chain}/{address}/abi?include_abi=&method_q=&method_kind=&method_limit=` · set `include_abi=false` for compact manifest/provenance without raw ABI |\n| `read_contract` | `POST /v1/{chain}/{address}/read` · body `{function, args}` |\n| `encode_call` | `POST /v1/{chain}/{address}/encode` · body `{function, args, value?}` |\n| `simulate` | `POST /v1/{chain}/simulate` · body `{from,to,data,value?}` or `{from,address,function,args,value?}`; never mix both forms |\n| `prepare_tx` | `POST /v1/{chain}/{address}/prepare` · body `{function, args, from, value?}` |\n| `decode_tx` | `GET /v1/{chain}/tx/{hash}` |\n| `resolve_name` | `GET /v1/{chain}/name/{name}` · `GET /v1/{chain}/name/by-address/{address}` |\n| chain catalog | `GET /v1/chains?q=&testnets=&has_default_rpc=` · viem-backed aliases with `testnet`/`has_default_rpc` flags for UI clients |\n| registry lookup | `GET /v1/lookup/{selector}` · 4-byte (function/error) or 32-byte (event topic0), chain-independent |\n| registry stats | `GET /v1/registry/stats` |\n| registry export | `GET /v1/registry/export` · CC0 NDJSON selector commons (`X-License: CC0-1.0`) |\n| runtime metrics | `GET /v1/metrics` · in-process rung/RPC attempts, latency, misses, and failure rates |\n\nGET routes set explicit `Cache-Control`: verified ABI responses cache longest,\nproxy ABI responses are short-lived, transaction decodes are immutable, the OpenAPI\ncontract is cacheable, and operational endpoints such as `/health` and\n`/v1/metrics` are `no-store`. Rate-limited routes expose `RateLimit-Limit`,\n`RateLimit-Remaining`, `RateLimit-Reset`, and `Retry-After` on 429 responses. ABI\nresolves also return `X-Source`, `X-Confidence`, `X-Cache`, `X-Elapsed-Ms`, and\n`X-ABI-Included`.\n\n### The registry (selector commons)\n\nThe engine seeds an open selector→signature registry as a byproduct of resolution:\n\n- Every **verified** resolution (Etherscan/Sourcify) harvests ground-truth\n  `selector → signature` pairs for functions, events (full 32-byte topic0,\n  collision-free), and errors. Proof grade: `verified-source`.\n- Resolutions are also indexed by **skeleton hash** (runtime bytecode with the\n  solc metadata trailer stripped), so byte-identical clones resolve via a new\n  `bytecode-match` rung without re-running the ladder. Verified claims are\n  capped to `partial` for clones (this address's source was never verified), and\n  `provenance.bytecode_match` points at the original chain/address/source/confidence\n  that supplied the reused ABI.\n- Decompiled ABIs get `Unresolved_<selector>` names replaced from proven\n  registry entries, and (when `ANTHROPIC_API_KEY` is set) a fire-and-forget\n  **propose-and-verify** pass asks Claude for candidate signatures and accepts\n  only those where `keccak256(sig)[:4]` reproduces the selector: proof grade\n  `keccak-proven` (signature proven; semantics still inferred).\n- Public 4byte fallback labels are also selector-matched locally before use, but\n  remain unproven labels and never enter the commons as proof.\n\nOnly the engine's own pipeline writes to the registry; no open submissions\n(that's how 4byte got collision-poisoned).\n\nThe accumulated data is published as a **CC0 dataset**:\n[`evm-abi-commons`](https://github.com/portdeveloper/evm-abi-commons)\n(regenerate any time from `GET /v1/registry/export`; the response is NDJSON and\nincludes `X-License: CC0-1.0` plus a license `Link` header). Lookup/export entries\ninclude proof grade and, for harvested verified-source entries when known, the\nsource `chain` and `address`. SDK users can call `lookupSelector`,\n`registryStats`, and `exportRegistry` directly.\n\n`{chain}` is any alias from `GET /v1/chains` (backed by `viem/chains`) or a\nnumeric id. Chain entries include `testnet` and `has_default_rpc` so agents can\ndecide when to ask for `rpc_url`; `q` matches ids, names, aliases, native symbols,\nand multi-word searches such as `bnb chain`. Pass `?rpc_url=` to override the RPC\n(required for chains with no default, e.g. `local`/31337; this is how any EVM\nchain works before it has a built-in alias).\n\nFor agent contexts, prefer `include_abi=false` on `resolve_abi` unless you need the\nraw JSON ABI. The compact response preserves `interface`, `provenance`, `proxy`,\n`token`, and `abi_for`, and marks `abi_omitted: true`. For large contracts, add\n`method_q`, `method_kind=read|write|all`, and `method_limit` to return only the\nmanifest methods relevant to the user's intent.\n\n```bash\ncurl localhost:8787/v1/ethereum/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/abi\ncurl -X POST localhost:8787/v1/ethereum/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/prepare \\\n  -H 'content-type: application/json' \\\n  -d '{\"function\":\"approve\",\"args\":[\"0x1111111254EEB25477B68fb85Ed929f73A960582\",\"1000000000000000000\"],\"from\":\"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\"}'\n```\n\n`prepare_tx.safety.signing_recommended` gates the hand-off. If simulation fails,\n`risk_level` is `blocked`, `deeplink` is empty, and `wallet_request` is omitted.\nWhen signing is recommended, `wallet_request` is an EIP-1193-shaped\n`eth_sendTransaction` payload with hex JSON-RPC quantities for wallet/explorer/app\nintegrations. Decompiled or selector-only writes are `high` risk and require\nexplicit user confirmation of selector + intent. Positive token/NFT spender\napprovals are `medium` risk with `spending_approval`. Token/NFT outflows from the\ntransfer source, whether trace-derived or inferred from a standard token/NFT\ntransfer, are `medium` risk with `asset_outflow`. Clients should show both before\nhand-off.\n\nIntegration recipes for wallets, block explorers, coding agents, and MCP clients\nare published at [`docs/integrations.md`](docs/integrations.md).\n\n## MCP server (SPEC §5)\n\n`npm run mcp` starts a stdio MCP server exposing the seven core verbs plus\nread-only utility tools for chains, selector commons, and runtime metrics. The\ntools are a thin adapter over the deployed REST engine\n(`ENGINE_URL`), so the MCP shares the engine's persistent cache and Etherscan key;\nno duplicated resolution or secrets. Tool descriptions bake in the non-custodial\nhand-off model (`prepare_tx` never signs), lead with provenance warnings for\npartial/proxy/bytecode-match/decompiled ABI results, and require\n`prepare_tx.safety.signing_recommended` before a signing deeplink or wallet request\nis handed to the user. JSON MCP tools expose output schemas and\n`structuredContent`; clients can branch on `provenance`, decoded calls,\nsimulations, selector results, metrics, and `safety` without scraping text.\n\nWire it into an MCP client (Claude Desktop / Claude Code `mcp` config):\n\n```json\n{\n  \"mcpServers\": {\n    \"gulltoppr\": {\n      \"command\": \"npm\",\n      \"args\": [\"run\", \"--silent\", \"mcp\"],\n      \"cwd\": \"/home/ubuntu/repos/abi-agent\",\n      \"env\": { \"ETHERSCAN_API_KEY\": \"\" }\n    }\n  }\n}\n```\n\nCore tools: `resolve_abi`, `read_contract`, `encode_call`, `simulate`,\n`prepare_tx`, `decode_tx`, `resolve_name`. Utility tools: `list_chains`,\n`lookup_selector`, `registry_stats`, `export_registry`, `runtime_metrics`. All are\nread-only-annotated except `prepare_tx` (non-destructive: returns an unsigned\nhand-off, signs nothing).\n\n### Remote (Streamable HTTP)\n\nFor agents that can't run a local stdio server, the same MCP is hosted over HTTP at\n**https://mcp.gulltoppr.dev/mcp** (`npm run mcp:http` locally; stateless). Point\nan HTTP-capable MCP client at that URL:\n\n```json\n{ \"mcpServers\": { \"gulltoppr\": { \"url\": \"https://mcp.gulltoppr.dev/mcp\" } } }\n```\n\nTool registration is shared (`src/mcp-server.ts`) between the stdio entry (`mcp.ts`)\nand the HTTP entry (`mcp-http.ts`), deployed via `Dockerfile.mcp` / `fly.mcp.toml`.\nMCP directory metadata lives in `server.json` and advertises the same remote URL,\nrepository, homepage, and icon. The remote MCP service also serves the same\nmetadata at `https://mcp.gulltoppr.dev/server.json` and\n`https://mcp.gulltoppr.dev/.well-known/mcp-server.json`.\n\n## npm SDK\n\nA typed client over this REST surface lives in [`sdk/`](sdk/) (`gulltoppr`):\n`new Gulltoppr({ baseUrl }).resolveAbi(...)` / `.read(...)` / `.prepareTx(...)`, plus\na `contract()` helper. It's the third face (after REST and MCP) and the basis for\nwallet, explorer, and app integrations. See [`sdk/README.md`](sdk/README.md).\n\n## Deploy\n\nLive at **https://api.gulltoppr.dev** (Fly.io app `gulltoppr`, region `cdg`, co-located with\ngulltoppr to minimize ladder rung-4 latency). Containerized via the `Dockerfile`\n(Node 22, run with `tsx`; ~82 MB image), configured by `fly.toml`.\n\n```bash\nflyctl deploy --remote-only --ha=false\n# optional: set an Etherscan v2 key to enable ladder rung 1\nflyctl secrets set ETHERSCAN_API_KEY=... -a gulltoppr\n```\n\n`HEIMDALL_API_URL` / `SIGNING_BASE_URL` / `PORT` are set in `fly.toml [env]`.\nMachines auto-stop when idle and auto-start on request.\n\n## Claude Skill\n\nThe fourth face: a [Claude Skill](skill/) (`skill/gulltoppr/`) that teaches an agent\nthe workflow (resolve → check provenance → read or prepare → simulate → hand off)\nand the non-custodial safety rules. Install with\n`cp -r skill/gulltoppr ~/.claude/skills/gulltoppr`. See [`skill/README.md`](skill/README.md).\n\n## Layout\n\n```\nsrc/\n  server.ts        REST routes (Hono), BigInt-safe JSON, error mapping\n  index.ts         REST entry / boot\n  mcp.ts           MCP server (stdio): core verbs plus read-only utility tools\n  config.ts        env + defaults\n  metrics.ts       in-process rung/RPC latency and failure counters\n  chains.ts        alias/id → {id, viem chain, rpc}  (SPEC §6)\n  clients.ts       cached viem PublicClients\n  types.ts         the SPEC §2 data types\n  errors.ts        typed ApiError → HTTP status  (SPEC §7)\n  resolve/\n    index.ts       resolve_abi: the ladder orchestrator (the spine)\n    etherscan.ts   rung 1  · sourcify.ts rung 2 · proxy.ts rung 3\n    heimdall.ts    rung 4 (gulltoppr) · fourbyte.ts rung 5\n    interface.ts   capability manifest builder (\"the buttons\", SPEC §2.4a)\n    selectFunction.ts  name/signature → AbiFunction\n  verbs/\n    read.ts encode.ts simulate.ts prepare.ts decodeTx.ts resolveName.ts\n    args.ts        JSON-arg → viem-typed coercion\n```\n\n## Status\n\n**Working end-to-end** (verified against live mainnet): the full ladder, the\ncapability manifest, `read_contract`, `encode_call`, `prepare_tx` (with eth_call\nsimulation + deeplink/wallet hand-off + provenance warnings), `decode_tx` (via gulltoppr plus\noptional resolved-ABI calldata enrichment), and\nchain-aware ENS/Basenames `resolve_name`, all exposed over **both** the REST surface and the **MCP server**\n(stdio handshake + core tools/utilities + a live tool call verified). The live smoke suite\nalso covers proxies, unverified decompiles, arbitrary `rpc_url` chains, Monad,\nMonad testnet, and `prepare_tx`.\n\n**Best-effort caveats**:\n- **`simulate` traces**: `state_diff` comes from `debug_traceCall`\n  (`prestateTracer` diff mode) and `asset_changes`/`logs` come from `callTracer`\n  when the RPC supports those debug APIs; public RPCs often return empty arrays.\n",
  "bytes": 14737,
  "sha": "0e4033283e9c2d763f78c771d0ca9f7be6fc0e8053c9b9313cd956457ac507d7",
  "repo_slug": "portdeveloper/gulltoppr",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_portdeveloper_gulltoppr_3f7da75b/readme"
}