{
  "markdown": "# Lucent\n\nTooling to author, verify, and attest [ERC-7730](https://eips.ethereum.org/EIPS/eip-7730)\nClear Signing descriptors for the public\n[registry](https://github.com/ethereum/clear-signing-erc7730-registry) that\ncompatible wallets read from.\n\nA descriptor is a JSON file that tells a wallet how to render a contract call in\nplain language, so users see what they are signing instead of raw hex. Lucent\ncovers the full path: find contracts that lack a descriptor, write and harden\none, check it beyond schema validity, prove it against real transactions, and\nproduce an ERC-8176 attestation.\n\n## Hosted preflight API\n\nLucent's primary product surface is a call-scoped HTTP API with a stateless\nanalysis core and bounded access/payment ledgers. It binds\none unsigned EVM call (including sender) to one descriptor deployment, resolves\nthe exact calldata selector, decodes the arguments, and runs the audit,\ncomprehension, and danger checks only for that selected function:\n\n```bash\nmake setup\nmake api\ncurl http://127.0.0.1:8780/health\nopen http://127.0.0.1:8780/docs\n```\n\n`POST /v1/preflight` returns call and assessment fingerprints with a\n`safe_to_present`, `review`, or `block` decision. A blocked call is a successful\nassessment and therefore returns HTTP 200; malformed, unbound, ambiguous, or\nundecodable input returns a stable `application/problem+json` error.\n\nThe words are intentional: **`safe_to_present` never means safe to execute**.\nVersion 1 uses a deliberately narrow scalar presentation profile. Local mode\ncan analyze caller-supplied ABI data; protected deployments can instead require\na finalized runtime-bytecode match and Sourcify ABI before a verdict is\nreturned. Hosted V1 rejects proxy-backed deployments until dispatch and\nupgrade-state semantics can be proven, including custom delegatecall\ndispatchers without EIP-1967 slots. Neither mode simulates state, detects\neconomic exploits, or judges the counterparty. Those limits ride in every\nresponse. Full contract and deployment modes:\n[docs/HOSTED-API.md](docs/HOSTED-API.md).\n\n### Protected access and Base-USDC payments\n\nThe API and container start disabled. `make api` opts into loopback-only local\ndevelopment (`open`, verified source `off`, x402 `off`). Production modes are\nconfigured explicitly at startup and fail closed when their dependencies are\nincoherent:\n\n- `api_key` — hashed tenant keys, per-tenant token buckets, and five-minute\n  idempotent result replay;\n- `x402` — official x402 v2 challenge/signature/receipt headers and exact USDC\n  settlement on Base mainnet;\n- `api_key_or_x402` — subscription/quota access for integrations, with USDC\n  pay-per-request fallback.\n\nAny mode that accepts x402 requires verified-source mode. Payment proofs are\nverified before Lucent uses RPC/Sourcify capacity, settled only after a complete\nresult exists. Exact retries replay the retained receipt, while a separate\nauthorization ledger prevents the same signed EIP-3009 transfer from being\nrewrapped under a new idempotency key. Paid outcomes are not evicted while\nunexpired, and authorizations must expire within the server-owned five-minute\nwindow. The container runs one worker by default; scale-out requires a shared\nquota/idempotency/authorization-claim backend.\n\n## For signing agents: the MCP server\n\nAn AI agent about to sign a transaction faces exactly the question Lucent's\nchecks answer — is this call clear enough to present, and does the screen a\nhuman would see actually describe what it does? `scripts/mcp_server.py` exposes\nthat as an MCP server (JSON-RPC over stdio) so an agent can pre-flight a signature:\n\n- **`preflight_transaction`** — the primary transaction-time gate. It binds\n  `chain_id`, `from`, `to`, `data`, and `value` to a matching descriptor\n  deployment, decodes one unique selector, analyzes only that function, and\n  fingerprints the complete request.\n- **`check_descriptor`** — an authoring-time report over an ERC-7730 descriptor:\n  the audit grade (screen shows the right fields), the comprehension grade (a\n  plain-language consequence sentence + risk tier per function), and the danger\n  scan (structural primitives a clear screen can't make safe). It is not bound\n  to a pending call and must never authorize one.\n- **`explain_signature`** — an unbound actor→action→object sentence + risk tier\n  for descriptor-authoring and UX-copy review, never transaction approval.\n- **`scan_contract`** — danger-scan a deployed contract by address (fetches the\n  verified ABI from Sourcify), so an agent can assess a contract before any\n  transaction is built.\n\n```bash\nmake mcp    # or: .venv/bin/python scripts/mcp_server.py\n```\n\nRegister it as a stdio MCP server pointing at `scripts/mcp_server.py` from the\nrepo root (see `mcp.json`). Same transport shape as the sibling Groundcheck and\nSeiche servers.\n\nFor every pending call, use `preflight_transaction`. Its model-facing output\nhashes arbitrary string calldata, and its server-owned consequence sentence\nnever incorporates descriptor-authored prose.\n\n## Install\n\n```bash\nmake setup     # creates .venv and installs requirements (Python 3.12+)\n```\n\nMost stages that read on-chain data need a free Etherscan API key:\n\n```bash\nexport ETHERSCAN_API_KEY=...\n```\n\n## Pipeline\n\n| Stage | Script | Purpose |\n|-------|--------|---------|\n| Discover | `discover.py` | Classify candidates: verified, signable, and uncovered |\n| Fetch ABI | `fetch_abi.py` | Verified ABI from Sourcify (a registry requirement) |\n| Resolve proxy | `resolve_proxy.py` | Cache an implementation ABI under a proxy address |\n| Generate | `erc7730 generate` | Bootstrap a draft descriptor |\n| Lint | `erc7730 lint` | Schema, selectors, device limits, ABI consistency |\n| Audit | `audit.py` | Grade the descriptor on screen trustworthiness |\n| Comprehend | `comprehend.py` | Grade the descriptor on human comprehension risk |\n| Danger | `danger.py` | Flag structural danger primitives a clear screen can't make safe |\n| Verify | `semverify.py` | Check the screen against real on-chain movements |\n| Prove | `preview.py`, `fetch_tx.py` | Render the screen and build real test vectors |\n| Package | `to_submission.py` | Registry-form output under `dist/`, gated on audit grade |\n| Attest | `attest.py` | ERC-8176 attestation over the descriptor hash |\n| Watch | `watch.py` | Monitor merged descriptors for drift |\n| Review | `review.py` | All checks composed into one publishable report |\n\nA `common.py` module holds the shared Sourcify and Etherscan clients and ABI\nutilities. `lucent/preflight.py` is the shared call-scoped decision core used by\nboth HTTP and MCP, so transports cannot drift on verdict policy.\n\n## Audit\n\n`erc7730 lint` checks that a descriptor is well-formed. `audit.py` checks whether\nthe on-device screen would mislead a user, which lint does not:\n\n- CRITICAL: a payable function that never shows `@.value`, or a `tokenAmount`\n  with no known token.\n- HIGH: a signable function with no intent or no visible field, or an address\n  shown as raw hex.\n- MEDIUM/LOW: labels or intents past the device character limits, missing\n  interpolated summaries.\n\nIt reports a letter grade. `to_submission.py` refuses to package below grade B.\nA raw generated draft of the ENS controller scores F; the hardened descriptors\nscore A.\n\n## Comprehension risk\n\nLint checks that a descriptor is well-formed; `audit.py` checks that the screen\n_shows_ the right fields. Neither asks the question that\n[\"What I Sign Is Not What I See\"](https://arxiv.org/abs/2601.16751) shows is the\nreal failure: users mis-_understand_ a technically-correct screen. Its studies\nfound people fixate on the amount and recipient and miss scope, delegation, and\nunlimited allowances — and that a bare field list, even a complete one, leaves\ncomprehension at chance on the dangerous cases. Its Signature Semantic Decoder\ncut false approvals on unlimited-allowance and phishing transactions by 73% and\n46% by rendering an actor→action→object sentence and a risk tier _with a reason_.\n\n`comprehend.py` brings that to the descriptor. For each signable function it\nemits:\n\n- a **consequence sentence** — who acts on what, plus conditions, built from the\n  ABI and the descriptor's own labels so it renders what the wallet will show:\n  _\"You let {Operator} transfer ANY of your tokens in this contract, at any time,\n  until you revoke it.\"_\n- a **risk tier with the clause that earned it** — the paper's users rejected\n  bare labels and demanded the why. Patterns scored are the ones the study found\n  people miss: operator grants (`setApprovalForAll`), ERC-20 allowances (flagged\n  distinctly from ERC-721 token-id approvals, since `approve(address,uint256)`\n  reads identically but means different things), permits (off-chain, invisible in\n  history), admin/upgrade authority, and raw-hex recipients (the address-poisoning\n  surface).\n\nAn unrecognised function with no on-screen intent is reported as an unexplained\nscreen (a caution), never silently cleared — an unexplained screen invites blind\napproval, which is the failure the paper measures. Run it with `make comprehend\nDESC=…`; NameWrapper's `setApprovalForAll` and the controller's\n`transferOwnership` both surface as CRITICAL comprehension risks that lint and\naudit pass.\n\n## Danger surface\n\nAudit asks whether the screen shows the right fields; comprehend asks whether the\nhuman understands them. `danger.py` asks the third question: can this function,\n_by construction_, do something a clear screen still can't make safe? A descriptor\ncan render a perfectly honest sentence for `execute(address target, bytes data)` —\n\"Call {target} with {data}\" — and that call can still drain the wallet, because\nthe primitive itself is unbounded.\n\nRuntime systems catch this by instrumenting transaction-trace properties\n([arXiv:2408.14621](https://arxiv.org/abs/2408.14621): arbitrary\n`CALL`/`DELEGATECALL`/`SELFDESTRUCT` in the trace). `danger.py` lifts the same\nproperty set to **static ABI analysis**, so the danger is named before anyone\nsigns:\n\n- **CRITICAL** — arbitrary external call (a call-family name, or a target-address\n  + calldata-blob signature), `delegatecall` (foreign code in this contract's\n  context), self-destruct, and upgrade-and-execute.\n- **HIGH** — unbounded delegation (`setApprovalForAll`), authority transfer\n  (ownership / admin / role).\n- **MEDIUM** — value sweep to a caller-supplied address.\n\nPrecision is the whole game: a danger scan that cries wolf on `safeTransferFrom`\nis worse than none. The detector distinguishes calldata from data-as-content by\n_parameter name_ (`target`+`data`, not any address-plus-bytes), excludes `to`\n(a recipient, not a callee), and whitelists the standard ERC receiver hooks — so\nthe shipped ENS bundle raises **zero** false arbitrary-call flags while a real\n`execute(target,data)` drainer is still caught. `--strict` exits non-zero on any\nCRITICAL.\n\n## Semantic verification\n\nLint proves a descriptor is well-formed, not that its summary is honest. A\ndescriptor can pass every schema check and still render a benign screen for a\ncall that sends assets elsewhere.\n\nFor each test vector, `semverify.py` fetches the mined receipt (the record of\nwhat actually moved), extracts the asset movements and approvals (ETH, ERC-20,\nERC-1155, ApprovalForAll), and checks the screen against them: every real\nrecipient and operator is shown, ETH spent is shown, and the field labelled as\nthe recipient matches the address that received the asset.\n\nWorked example, a `safeTransferFrom` descriptor with the To and From labels\nswapped:\n\n| Check | Result |\n|-------|--------|\n| `erc7730 lint` | pass (schema-valid, both fields shown) |\n| `audit.py` | grade A (structurally correct) |\n| `semverify.py` | divergence (labels the sender as recipient) |\n\nThe receipt is exact for mined transactions. The recipient check is heuristic on\nfield labels; it catches recipient hiding and label spoofing, not every possible\nmismatch.\n\n**Unmined calls — fork replay.** A brand-new descriptor for a call that has never\nbeen mined (a fresh contract, a rarely-used function) has no receipt to check\nagainst. `forkreplay.py` closes that gap: given a call spec\n`{signer, function, args, value}` it forks mainnet at HEAD into a local `anvil`,\nimpersonates the signer, executes the call against real on-chain state, and reads\nback the standard eth receipt. That `(tx, receipt)` pair is handed to\n`semverify.verify_one` **unchanged** — so a label swap or hidden recipient on an\nunmined call is caught by the identical, tested code path, not a second\nimplementation. Run it with `make semverify DESC=… SIMULATE=1` on a test file\nwhose vectors carry a `call` object instead of a `txHash`. It needs `anvil` +\n`cast` (`foundryup`) and an RPC URL (`ETH_RPC_URL`); without them the call vector\nis skipped with a reason, never silently passed.\n\n## Reviewing a registry PR\n\nThe clear-signing governance model asks security reviewers to check descriptor\nPRs \"for correctness, clarity, and adversarial edge cases, then publish your\nfindings clearly.\" `review.py` is that role as one command: it composes lint,\nthe screen audit, the comprehension grade, the danger scan, and (when test\nvectors and an `ETHERSCAN_API_KEY` are available) semantic verification into a\nsingle markdown report ready to post on the pull request:\n\n```bash\nmake review DESC=path/to/calldata-Contract.json OUT=review.md\n```\n\n`review.py` and `check_descriptor` are authoring reports over the full\ndescriptor. They intentionally do not replace the call-scoped\n`preflight_transaction` gate: only preflight binds sender, destination,\ncalldata, value, and one selected function. Checks that cannot run are reported\nas explicit skips with the reason, never silently passed. To review a descriptor\nfrom a registry PR, fetch its ABI first\n(`make fetch CHAIN=<id> ADDR=<address>`) so the audit runs against the verified\non-chain ABI.\n\n## Post-quantum co-signing\n\nA descriptor hash is `keccak256`, which is quantum-safe. The ECDSA signature over\nit is not, and attestations are long-lived. `attest.py --pq` adds a post-quantum\nsignature over the same hash so the attestation stays verifiable if the\nsignature scheme is broken. The hash is unchanged; only the signature scheme is\nadded.\n\n| Scheme | Standard | Signature size |\n|--------|----------|----------------|\n| `ml_dsa_65` (default) | FIPS 204 | ~3.3 KB |\n| `ml_dsa_44` / `ml_dsa_87` | FIPS 204 | ~2.4 / ~4.6 KB |\n| `falcon_512` | FIPS 206 draft | ~0.65 KB (float and side-channel risk) |\n| `sphincs_sha2_128s_simple` | FIPS 205 | ~7.9 KB (hash-based) |\n\nThe signature binds the exact descriptor hash. Keys are read from `LUCENT_PQ_*`\nenv vars or a gitignored `.attester-keys/` directory, written owner-only. No\ncryptographically-relevant quantum computer exists yet and there is no standard\nfor post-quantum attestations, so this is forward positioning, not a current\nrequirement.\n\nAttestation and fork replay are offline operator tools. They are deliberately\nabsent from the hosted API container: the current scripts accept RPC/process or\nkey-bearing inputs that belong in isolated workers with strict egress, resource,\nand signing-policy controls.\n\n## Current state\n\nThree ENS descriptors, each grade A and lint clean against the on-chain ABI,\npackaged under `dist/registry-pr/ens/`:\n\n| Descriptor | Functions | Test vectors |\n|------------|-----------|--------------|\n| ETHRegistrarController (`0x2535…303b`) | 7 | 8 |\n| NameWrapper (`0xD441…6401`) | 26 | 6 |\n| BulkRenewal | 1 | 3 |\n\nTest vectors are real historical transactions, built with\n`fetch_tx.py <chain> <address> <descriptor>`.\n\nA registry PR should be submitted by or on behalf of the contract's owner. The\nremaining step for the ENS descriptors is that authorization, not code.\n\n## Attester registration\n\n`attest.py --profile` writes an auditor profile\n(`auditors/eip155-1-<address>/profile.json`) for a registry PR. Signing an EAS\noffchain attestation needs the ERC-8176 schema UID (published on clearsigning.org)\nand an attester key. Without them, `attest.py` writes an unsigned evidence\nbundle so the pipeline can run end to end first.\n",
  "bytes": 16099,
  "sha": "0601fea334ae2423a807cf0d6926dea3d404e5851945415c818545aaf4295e12",
  "repo_slug": "beepboop2025/lucent",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_beepboop2025_lucent_5b0fa173/readme"
}