Skip to main content
Glama

Farol

The registry warns, the wallet signs.

Farol is Portuguese for lighthouse: it warns of the rocks, it does not promise the ship a clear crossing.

Farol is an MCP server that gives an AI agent a curator-signed catalog of smart contracts and turns an intent into a transaction plan a human reviews on a local page before signing. The MCP process never holds a private key. No MCP tool signs a transaction or broadcasts one; signing happens only on the review page, driven by the wallet already injected in the user's browser.

Built for the EAG Global Buildathon Floripa, on HashKey Chain.


Submission

EAG Global Buildathon Floripa, 12 September 2026 · https://devfolio.co/projects/farol-1d90

Track

Why

Brazil Hackathon

Built in person at InPETU Hub, Sapiens Parque, Florianópolis.

EAG track 4 — Application Middleware & Open-Source Tooling

Farol is not an application, it is the layer under one: an MCP server any agent host can mount, a documented signed-catalog format, and a review page any wallet or agent framework can adopt without adopting Farol.

HSK Chain — Blockchain Infrastructure (also fits AI Agents)

See Technical integration: HashKey Chain below. No contract of ours is deployed; the product runs against contracts already on HSK.

Where the rest lives: architecture in Architecture and How it works; features in Key features and The tools; install and run in Install and Run the demo; what is proven and what is not in What is verified and Limits; iteration plans in Roadmap.

Related MCP server: crypto-knowledge

The problem

An agent that can transact has to answer a question no chain answers for it: is this address the contract it claims to be? Today the agent guesses from a name, and the human is asked to approve calldata they cannot read.

Name collisions are not hypothetical. On HashKey Chain two verified contracts share the name "HyperIndex V3 SwapRouter". A contract whose verified source calls itself "Mock USDC" reports its symbol() as exactly USDC.

How it works

  1. The agent calls plan_build with a catalogued runbook, explicit calls, or a revoke request.

  2. The server resolves the plan into {to, data, value} calls, computes a tier per call by reading extcodehash live, and stores the result as an immutable plan on disk, keyed by the hash of its canonical JSON.

  3. A review URL on 127.0.0.1 opens. The page fetches the plan from the server by id, never from the agent or the URL.

  4. The page runs eth_simulateV1 when it opens and re-checks flags at sign time, so a flag raised after the plan was built still blocks the sign button for the affected call.

  5. The human reads the tier, the simulation and the trust line, then signs with their own wallet. The page posts the txHash back to the server; the agent learns it by polling plan_status.

Key features

Four tiers, one refusal. Every call carries a tier computed against the chain, not against a cached list:

Tier

Meaning

T0

catalogued, codehash matches what a curator signed

T1

verified source, no curated entry

T2

unverified source

T3

catalogued address, deployed code does not match — hard refusal

T3 is the only refusal in the system. Everything else proceeds with a badge, because eth_simulateV1 runs and a decode is attempted on any bytecode, catalogued or not.

Proxies. An entry pins two codehashes: the proxy's and its implementation's. At plan time the client reads the EIP-1967 implementation slot and compares; if the implementation changed since the entry was reviewed, the call demotes from T0 to T1 rather than hard-failing — an upgrade is not by itself an attack. The admin slot is read too, so the page shows who can replace the contract underneath the user. For UUPS patterns where the slot is empty it says "not determinable from storage" rather than guessing. Circle's FiatTokenProxy keeps those values in the pre-EIP-1967 ZeppelinOS slots; Farol reads those as well.

Flags name selectors, not contracts, so an exit path such as a revoke stays callable on a flagged contract. A flag blocks signing only when its signer is on the curator trust list and its severity is not info; a flag from anyone else is still shown, never hidden. If the flag feed cannot be read at all, plan_build refuses outright rather than proceeding as if there were no flags — negative information fails closed.

Discovery filters, it never ranks. catalog_search returns results alphabetically and always states how many catalogued entries matched out of how many exist on that chain, plus a note that uncatalogued contracts may exist. Token symbols resolve only against the curator-signed token list; an ambiguous symbol is refused rather than guessed.

The catalog carries the trust model, never a price. Who can upgrade the code, how funds exit, who can pause it — and the pause switch is read live, because a state cannot be signed in advance. No yield figure, no recommendation: a registry that carries those becomes a thing to spoof.

Audits are bound to deployed bytecode. An audit report scopes to a git commit; what a human signs is bytecode at an address. Farol keys on (chainId, address, codehash) and says on every read whether an attestation still describes the code that is deployed now. The shipped catalog carries zero audit attestations, because nobody has audited these contracts, and every entry says so.

The tools

Five MCP tools. Nothing here signs, broadcasts, or marks a plan as signed.

Tool

In

Out

catalog_search

chain, optional text/address/intent/asset filter

matching entries, alphabetical, ambiguity stated, coverage count

catalog_get

chain, address

entry, attestations, audit attestations with the codehash each auditor signed over and whether it still matches, active flags, upgrade authority, current tier, and the exit path with the pause switch read live

plan_build

chain, from, one of {entry, runbook, bindings} | {calls} | {revoke}

plan_id, review URL, tier per call

plan_simulate

plan_id

eth_simulateV1 over the steps not yet sent: logs and custom errors named with catalogued ABIs, return data, native transfers

plan_status

plan_id

created · reviewed · signed · confirmed · rejected, plus txHashes

There is no send, sign, or mark-signed tool, by design. Only a local HTTP endpoint the review page calls (POST /t/<token>/plan/<id>/result) can record a txHash, and that endpoint is not reachable from the MCP surface an agent uses.


Install

Prereqs: Node 20+.

git clone https://github.com/ronoel/farol-mcp.git
cd farol-mcp
npm install

Claude Code

The repo ships .mcp.json, picked up automatically from the project root. Add your own signing address, which is the operator's setting and never comes from the agent:

{
  "mcpServers": {
    "farol": {
      "command": "npx",
      "args": ["tsx", "src/index.ts"],
      "env": { "FAROL_POLICY": "graduated", "FAROL_FROM": "0xYourAddress" }
    }
  }
}

For Claude Desktop, use an absolute path to src/index.ts, since Desktop does not run from the repo's working directory.

Environment variables

Variable

Purpose

FAROL_FROM

The signing address. Set by the operator; if it is absent, plan_build refuses and says so.

FAROL_POLICY

graduated (default) or strict. Strict allows only T0 calls.

FAROL_CURATOR_KEY

Curator private key. Read only by the signing scripts, never by the running server — the server process never holds a key.

FAROL_PORT

Fixed port for the review server. Default 0, a random free port.

FAROL_NO_OPEN

1 stops the server from calling xdg-open on the review URL.

FAROL_RPC_177 / FAROL_RPC_133 / FAROL_RPC_1 / FAROL_RPC_8453 / FAROL_RPC_84532

RPC overrides for HashKey Chain, HashKey Chain Testnet, Ethereum, Base and Base Sepolia.

FAROL_CURATORS

Override for the curator trust list. Default: catalog/curators.json.

Run the demo

No MCP host needed. This starts the review server, builds a wrap plan of 0.01 HSK on HashKey Chain Testnet (133) and prints a review URL — open it in the browser that holds your wallet:

npm run demo:plan -- <your-address> 133

Use 177 for HashKey Chain mainnet. Testnet funds: https://hsk.xyz/faucet

Rehearse the one refusal. T3 is a catalogued address whose live code no longer matches the signed codehash, and a live chain will not produce one on demand. This copies the catalog to a temp directory, rewrites one codehash to a value the chain does not have, re-signs it with the real curator key, and prints a FAROL_CATALOG to run against. plan_build then refuses for real, naming both hashes. The repo's catalog is never touched.

npm run demo:t3 -- 8453 0x4200000000000000000000000000000000000006

Rehearse an audit bound to a deployed codehash:

npm run demo:audit -- 8453             # the attestation matches the deployed code
npm run demo:audit -- 8453 0x4200000000000000000000000000000000000006 --stale

Technical integration: HashKey Chain

Farol deploys no contract of its own. It is middleware over contracts that are already on HSK Chain, and everything it says about them is re-read from the chain at plan time.

  • 5 curator-signed entries on HSK mainnet (177), each pinning a codehash read from mainnet:

    • Wrapped HSK 0xB210D2120d57b758EE163cFfb43e73728c471Cf1 — wrap / unwrap.

    • Tether USD, OP Stack bridged 0xF1B50eD67A9e2CC94Ad3c477779E2d4cBfFf9029 — the canonical bridged USDT; withdrawals to L1 burn it, so no approve is needed on that path.

    • OpenUSDT, CCIP-bridged 0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189 — what a CCIP bridge UI delivers when it says "USDT". Not the Tether USDT the HSK pools use.

    • HyperIndex V3 SwapRouter 0xC161Db322043e94D0395673ab5A74F89088EC3b8 — pins the codehash of one of the two verified contracts sharing this name on HSK. The other has no entry and resolves at T1.

    • Chainlink CCIP Router 0xf2Fd62c083F3BF324e99ce157D1a42d7EbA77f1d — read-only entry, trust model recorded as facts, no execution runbook.

  • 1 entry on HSK testnet (133): the OP Stack WETH predeploy 0x4200000000000000000000000000000000000006, whose on-chain name() reads "Wrapped HashKey EcoPoints" — the wrap/unwrap rehearsal.

  • Curator-signed token lists for 177 and 133, so an agent resolving "USDT" on HSK resolves it against signed data instead of guessing.

  • Tiering is live against HSK, not precomputed: every plan_build re-reads extcodehash on 177 or 133 and compares it to the signed entry.

  • Transactions are built and human-signed on HSK: the wrap runbook on 133 produces a real signed transaction through the review page; the swap runbook on 177 produces the two-step approve + exactInputSingle plan, with the approve scoped to the exact amount, never unlimited.

  • 4 of the 12 live network tests run against HSK (chains 177 and 133).

Entries also ship for Ethereum (1), Base (8453) and Base Sepolia (84532), which is what shows the runbook templates are chain-agnostic rather than hardcoded to one deployment.

Catalog format

An entry is produced, then signed, then loaded only if the signature checks out:

npm run keygen >> .env
# add the printed address to catalog/curators.json (or set FAROL_CURATORS)
npm run entry:make -- <chainId> <address> --kind token --name X
# edit runbooks, intents, attestations and notes by hand in the written JSON
npm run entry:sign -- catalog/entries/<chain>/<file>.json

The signature is an EIP-191 personal_sign over the canonical JSON of the raw body: keys sorted recursively, undefined dropped, no schema defaults applied — so what gets signed is exactly the bytes on disk. entry:sign --role author produces a protocol team's own signature over the same entry.

Architecture

src/mcp.ts        the five tools; the whole surface an agent can reach
src/plan.ts       intent or runbook -> {to, data, value} calls, tier per call
src/tier.ts       T0/T1/T2/T3 against a live extcodehash, proxy slots included
src/catalog.ts    load + verify signatures against the curator trust list
src/canonical.ts  canonical JSON: what a signature actually covers
src/chain.ts      RPC: extcodehash, storage slots, eth_simulateV1
src/http.ts       the local review server; the only path that can record a txHash
src/page/         the review page: plain HTML and JS, no framework
src/store.ts      immutable plans on disk, keyed by their own hash
catalog/          signed entries, token lists, flags, curator trust list

What is verified

npm test8 suites, 108 tests: canonical JSON and signatures, the tier system, plan-building rules including flags and revoke, the review server's HTTP rules, the MCP tool surface, audit attestations and their codehash binding, the review page driven in jsdom against a fake wallet provider, and repo hygiene (no console.log in src/, since stdout is the MCP transport; every shipped catalog document verifying against the shipped curator list).

npm run test:live12 network tests against real chains: 4 on HashKey Chain mainnet, HashKey Chain testnet and Ethereum; 8 on Base Sepolia.

Not verified, and worth saying so:

  • Signing with a real MetaMask on 133 or 177 is not covered by an automated test; the page's wallet interaction is tested only against a fake injected provider in jsdom.

  • EIP-7702 batching (wallet_getCapabilities) is not implemented.

Limits

eth_simulateV1 returns logs, return data and native transfers; it does not return a storage diff. A call can move state the simulation does not surface as a log.

A compromised curator key can sign a forged entry, and the codehash check will not catch it — the attacker computes the codehash correctly. The floor under that failure is the review-screen invariant: what the human reads on the page derives from calldata, the ABI and the simulation, never from runbook prose an agent or a curator wrote.

The catalog raises the ceiling; the simulation holds the floor. Neither claims more than what it checked.

Roadmap

  • A public, read-only Streamable HTTP surface (catalog_search, catalog_get, tx_explain) with no wallet path, so the catalog is usable without running a server.

  • The signed flag feed as a published format with a small reference client, and its timestamp anchored on Ethereum L1 via EAS.

  • Real audit attestations from auditors willing to sign over a deployment rather than a commit — the format is implemented and shipping empty on purpose.

  • EIP-7702 batching, so a multi-step runbook is one signature instead of several.

License

GNU Affero General Public License v3.0 only (AGPL-3.0-only). See LICENSE.

Open source, and deliberately the strongest copyleft: anyone who modifies Farol and runs it as a network service has to publish their changes under the same licence. The review page is a trust boundary — a closed fork of it would be a fork nobody can audit, which is the one thing this project cannot allow. Commercial licences under other terms are available on request.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to resolve smart contract ABIs, read, encode, simulate, and prepare transactions across multiple blockchains via a REST API or MCP server, with no signing required.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to perform complex crypto operations like cross-chain routing, contract decoding, portfolio management, and anti-rug security checks, returning unsigned transactions for safe signing by the agent.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to analyze Ethereum wallets, simulate transactions, and draft transfers with deterministic policy and risk scoring, requiring human approval before on-chain execution.
    7 npm
    ISC
  • A
    license
    A
    quality
    A
    maintenance
    MCP server that enables AI signing agents to pre-flight transactions by checking ERC-7730 descriptor safety, explaining function semantics, and scanning contracts for danger primitives.
    4
    MIT