{
  "markdown": "# ERC-8004 Agent Liveness\n\nChecks whether an agent registered in the real ERC-8004 \"Trustless Agents\" Identity Registry (Base\nmainnet -- both the payment and the registry read, see \"Chain scope\" below)\nis actually alive right now -- not just that it was registered once. NEXUS candidate #10 --\n**manual build, not FORGE-generated**, same manual-Cloud-Run-asset pattern as candidates #3/#4/#6/#8/#9/#13/#16.\n\n- `POST /verify-registered-agent {\"agent_id\": 3}` -- **$0.10/call.**\n- MCP tool `verify_registered_agent` at `/mcp`, same params -- **currently free**, see \"Known limitations\".\n- `GET /health`, `GET /.well-known/agent-card.json`, `GET /openapi.json` (has `x-payment-info`),\n  `GET /.well-known/402index-verify.txt` (402index claim verification file).\n\n## What this is (and why registration alone isn't enough)\n\n[ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) is a real, live Ethereum standard for on-chain agent\nidentity: an agent mints an ERC-721 token in an `IdentityRegistry`, whose `tokenURI` points to an off-chain\nregistration file (JSON: name, description, declared endpoints, `active` flag, supported trust methods). It\nwent live on Ethereum mainnet 2026-01-29, with reference deployments on Base mainnet and Base/Ethereum/Linea\nSepolia testnets. Registration is a one-time on-chain action -- a real registered agent can go completely\ndark (process killed, domain expired, endpoint changed) while its on-chain record persists unchanged forever.\nThis asset closes that gap: it resolves the real on-chain registration AND performs a real MCP `initialize`\nhandshake against whatever endpoint the registration declares, right now, at call time -- the same\nliveness-vs-registration distinction `agent-verification-api` (candidate #3) already draws for\ndomain-claimed identities, applied here to on-chain-registered ones.\n\n## Grounding (verified live this session, not assumed from any single source)\n\n- **Contract addresses**, initially from a third-party summary, verified independently via `eth_getCode`\n  against `https://sepolia.base.org` before being trusted: `IdentityRegistry`\n  (`0x8004A818BFB912233c491871b3d84c89A494BD9e`) and `ReputationRegistry`\n  (`0x8004B663056A597Dffe9eCcC1965A193B7388713`) both have real, non-empty deployed bytecode.\n- **ABI**, pulled from the reference implementation (`github.com/erc-8004/erc-8004-contracts/abis`), tested\n  live against 3 real registered agents (`agentId` 1-3) before being trusted for this asset:\n  - Agent 1's `tokenURI` resolves to a `data:application/json;base64,...` URI.\n  - Agent 2's resolves to `ipfs://bafkreiff...`.\n  - Agent 3's resolves to a real `https://api.snack.money/agent/.../registration.json` URL.\n  All 3 of ERC-8004's real URI schemes confirmed working live, not just the one the spec's own examples show.\n- **`getSummary` requires a non-empty `clientAddresses` array** -- confirmed live (reverts with\n  `\"clientAddresses required\"` otherwise). This asset calls `getClients(agentId)` first and only calls\n  `getSummary` if that returns at least one address; agents with zero feedback correctly report\n  `feedback_count: 0` without an RPC error.\n- **`agentId=999999` (a real nonexistent token)** correctly reverts with a custom error, confirmed live --\n  mapped to `AGENT_NOT_FOUND`, not a crash.\n\n## MCP handshake engine: reused, not reimplemented\n\nPer the task brief's explicit instruction, `_nexus_validate_public_url`, `_nexus_no_redirect_mcp_http_client`,\nand `_mcp_handshake_check` are ported **verbatim** from `manual_assets/agent-verification-api/main.py`\n(candidate #3) -- same SSRF pre-check, same no-redirect posture (the exact fix applied there after the\n2026-08-22 security review found a redirect-based SSRF bypass), same bounded timeout. Not modified beyond the\nasset-name constant. This asset's own contribution is upstream of that: resolving an ERC-8004 registration\nfile (3 URI schemes) and picking a real endpoint out of its `endpoints` array to hand to that engine.\n\n## Chain scope\n\n**Unified, as of 2026-09-04.** x402 payment settles in real USDC on **Base mainnet**. The on-chain\n`IdentityRegistry` read -- the actual identity/liveness check -- is now also on **Base mainnet**, against the\nverified `IdentityRegistryUpgradeable` deployment (`0x8004A169FB4a3325136EB29fA0ceB6D2e539a432`) -- see\n\"Mainnet registry corrected\" below. `ReputationRegistry` reads still use the original (Sepolia-verified)\naddress -- its real mainnet counterpart was not verified this session, see \"Pending\". This asset still doesn't\nexpose a caller-selectable chain -- no evidence a buyer needs one for either rail.\n\n## Mainnet registry corrected (2026-09-04)\n\nThe 2026-09-03 partial revert below was investigating the wrong contract. `0x8004A818BFB912233c491871b3d84c89A494BD9e`\nis itself the deterministic Base SEPOLIA IdentityRegistry address from `erc-8004/erc-8004-contracts`'s\nvanity-address deployment pattern -- it was never the real Base mainnet deployment, which is a *different*\ndeterministic address per network. The real Base mainnet IdentityRegistry is\n`0x8004A169FB4a3325136EB29fA0ceB6D2e539a432`: implementation `0x7274e874ca62410a93bd8bf61c69d8045e399c02`\nverified on Etherscan as `IdentityRegistryUpgradeable` (exact name match with the reference repo, compiler\nv0.8.24+commit.e11b9ed9), `ownerOf` reads succeed, and a Basescan export shows 25 real Base mainnet\ntransactions against it (2026-09-04 09:02-10:06 UTC, 25/25 successful: 5x `register`, 20x `setMetadata`) --\nversus 19 historical transactions against the old address (2026-01-31 to 2026-04-20), 17/19 reverted. Full\nverification trail: `scripts/verify_mainnet_registry_candidate.py`, branch\n`claude/erc8004-registry-address-verify-u2db8u`. `ReputationRegistry`'s real mainnet address was not verified\nthis session -- left unchanged, see \"Pending\".\n\n## Mainnet cutover (2026-09-03) and same-day partial revert\n\nOriginally built and measured on Base Sepolia testnet (x402 payment + registry reads both). Cut over to Base\nmainnet same session: x402 settlement moved to the CDP facilitator (`create_facilitator_config()`, same swap\nalready applied to `ws`/`live-entity-verification`), the payto wallet moved to `NEXUS_X402_PAYTO_ADDRESS`\n(fail-fast env var, no placeholder default), and the registry RPC moved to Base mainnet too, at the same two\ncontract addresses -- that address reuse across chains was confirmed by the operator directly on Basescan\nbefore the cutover, not independently re-verified from the session that made the code change (no outbound\nnetwork access to `mainnet.base.org` from that environment).\n\n**Reverted a few hours later, registry-read side only.** Two independent sources (a live `eth_call`, and\nBasescan's own \"Read as Proxy\" tab) confirmed `ownerOf`/`balanceOf` both revert with \"execution reverted, no\ndata\" against the IdentityRegistry proxy (`0x8004A818BFB912233c491871b3d84c89A494BD9e`) on Base mainnet --\nwhile `register()` against that same proxy demonstrably works (19 real confirmed mint transactions). The\nimplementation contract behind the proxy on mainnet (`0xd53dE688e0b0ad436FBdbDa00036832FF6499234`, confirmed\nvia Basescan's proxy-implementation slot) has **no verified source or ABI anywhere on Etherscan/Basescan** --\nso there's no way to confirm the deployed mainnet bytecode still matches\n`github.com/erc-8004/erc-8004-contracts` (commit b9e466c) the way the Sepolia deployment's bytecode was\noriginally confirmed (see \"Grounding\" above). Continuing to call `ownerOf`/`tokenURI`/`getClients`/`getSummary`\nby name against unverified mainnet bytecode risked the exact failure this asset exists to catch, but aimed at\nits own buyers instead: a plausible wrong answer (`AGENT_NOT_FOUND` for a real agent) charged for as if\ncorrect.\n\n`BASE_RPC_URL` (default `https://mainnet.base.org`) and `_IDENTITY_REGISTRY_ADDRESS`\n(`0x8004A169FB4a3325136EB29fA0ceB6D2e539a432`) now both point at the real, verified Base mainnet\nIdentityRegistry -- see \"Mainnet registry corrected\" above. No CDP RPC node pattern exists elsewhere in this\ncodebase to reuse (checked `onchain-activity-index`, `x402-receipt-verifier`, `new-x402-listings-feed` --\nnone of them read on-chain data via RPC at all, only x402 payment went through CDP), so this reuses the same\npublic endpoint and env var name already used once in the original 2026-09-03 cutover attempt. Every\nbuyer-facing surface (agent-card, OpenAPI descriptions, this README) has been updated to say both payment and\nthe registry read are Base mainnet.\n\n## Pending\n\nResolved 2026-09-04: the mainnet cutover this section used to say was blocked on decompiling\n`0xd53dE688e0b0ad436FBdbDa00036832FF6499234` -- that turned out to be unnecessary. That address was never the\nreal mainnet IdentityRegistry implementation; the real one\n(`0x7274e874ca62410a93bd8bf61c69d8045e399c02`) is independently verified on Etherscan as\n`IdentityRegistryUpgradeable`, and the cutover is done (see \"Mainnet registry corrected\" above).\n\nStill open, left explicit rather than guessed at:\n\n- `ReputationRegistry`'s real Base mainnet address was not verified this session -- reputation reads still use\n  the Sepolia-deterministic address over the now-mainnet RPC, which may be wrong. Needs the same\n  bytecode/implementation-verification treatment `IdentityRegistry` just got before it's trusted.\n- `_BASE_RPC` uses the free public `https://mainnet.base.org` -- no SLA. CDP's own SDK (already a dependency\n  here) ships a \"Base Node\" RPC endpoint reusing the same CDP credentials already configured for the x402\n  facilitator (`cdp.base_node_rpc_url.get_base_node_rpc_url`, verified by reading the actual cdp-sdk source),\n  but switching to it needs an async-startup restructure (today's RPC client is built synchronously at module\n  import) and its plan-tier availability isn't confirmed. Deferred until there's a concrete reliability/cost\n  reason to take that on -- see the comment above `_BASE_RPC` in `main.py`.\n\n## Deploy target: Cloud Run\n\nSame pipeline as candidates #4/#3/#6/#8/#9/#13/#16 -- see `skills/infra-deploy-ops`.\n\n```bash\n# 1. First deploy -- PUBLIC_DOMAIN not known yet, every real request 421s until step 2.\n./scripts/deploy_cloud_run.sh erc8004-agent-liveness manual_assets/erc8004-agent-liveness\n\n# 2. Grab the printed *.run.app URL, then (only if it differs from env-vars.deploy.yaml's guess):\ngcloud run services update erc8004-agent-liveness --region us-central1 --project nexus-505016 \\\n    --update-env-vars PUBLIC_DOMAIN=<the-real-domain>\n```\n\n## Known limitations (left unfixed on purpose -- CLAUDE.md SS3, no gate without evidence it's needed)\n\n- **MCP tool calls are not charged.** Same in-process-call pattern as every other manual asset in this\n  codebase.\n- **`active: false` short-circuits to `REGISTERED_INACTIVE` even if the endpoint is actually live.** Trusts\n  the registrant's own self-declaration over an independent liveness check in that one case -- an agent\n  lying about being inactive (unusual incentive) would be misreported. Accepted: `active` is the registrant's\n  own signal by spec design, overriding it would be second-guessing the standard's own field.\n  `REGISTERED_UNREACHABLE` (the opposite failure mode -- declared active, not actually reachable) is this\n  asset's actual value-add and is NOT similarly short-circuited.\n  - **Endpoint selection is a heuristic, not a spec requirement.** ERC-8004's `endpoints` array is free-form\n  (any `name`); this asset prefers `mcp`/`x402`/`a2a`/`web` (in that order) and falls back to the first entry.\n  A registration using an unlisted `name` for its only real MCP-capable endpoint would still be picked (name\n  matching isn't the only path -- unnamed-preference fallback covers it), but a registration with MULTIPLE\n  endpoints where none of the preferred names points to the live one could report `REGISTERED_UNREACHABLE`\n  based on the wrong endpoint.\n- **IPFS resolution uses a single public gateway (`ipfs.io`).** No fallback gateway -- a registration whose\n  CID isn't pinned/reachable via that specific gateway reports `REGISTRATION_FETCH_FAILED` even if the\n  content exists on IPFS generally.\n- **No per-caller rate limiting.** Fine for a 7-day disposable measurement window.\n- **`reputation` is a self-reported, ungated, un-staked signal.** Any EVM address can call\n  `ReputationRegistry.giveFeedback` for any `agentId` -- `feedback_count`/`average_value` are real on-chain\n  numbers, but nothing stops an agent's own owner from Sybil-feeding themselves. Treat as an unverified\n  signal, not a trust score (this caveat is also in the `reputation` field's own description in the API\n  schema, not just here).\n\n## Quality gate (2026-08-23, from design not retroactive)\n\nSame 2-agent process as candidates #3/#4/#6/#8/#9/#13/#16 (security lens; functional+quality+buyer-experience\nlens), run before the first deploy. Real findings, all fixed before going live:\n\n- **Security (0 exploitable findings):** confirmed the 3 functions ported verbatim from\n  `agent-verification-api/main.py` (`_nexus_validate_public_url`, `_nexus_no_redirect_mcp_http_client`,\n  `_mcp_handshake_check`) carry the actual SSRF-redirect fix, byte-for-byte, and that the new\n  `https://`/IPFS registration-fetch path applies the same defense one layer earlier, before any endpoint is\n  extracted -- confirmed NOT to reintroduce that bug class. Two low/informational items, both addressed as\n  defense-in-depth even though neither was a confirmed exploit: IPFS CID concatenation (confirmed live it\n  couldn't escape the `ipfs.io` host, but now uses `urllib.parse.quote` to confine it to a single path\n  segment anyway), and `data:` URI base64 decoding (confirmed linear/non-amplifying, no fix needed).\n- **Functional/buyer-experience (1 must-fix, 1 medium, applied):** a registration file whose top-level JSON\n  is a non-object (array/string/number -- registrant-controlled content) passed through as `\"ok\": True` with\n  a non-dict `registration`, which every downstream consumer (`_pick_liveness_endpoint`, `_classify_verdict`,\n  the response body) assumed was a dict -- an uncaught `AttributeError` became an unhandled 500 **after x402\n  payment had already settled**. Fixed: both JSON-parse sites in `_resolve_registration_file` now reject\n  non-dict results as `registration_not_an_object` before returning `\"ok\": True`. Also added the reputation\n  gameability caveat (see \"Known limitations\" above and the `reputation` field's own schema description).\n  Two low/nice-to-have items (duplicate-`name` endpoint shadowing, unverified field casing on 2 optional\n  registration keys) left as-is -- no evidence yet either matters for real registrations, consistent with\n  CLAUDE.md SS3.\n- Verified end-to-end against real production data before AND after fixes: 4 real ERC-8004 agent IDs on Base\n  Sepolia (1, 2, 3, and a real nonexistent 999999) each produced the correct verdict --\n  `REGISTERED_UNREACHABLE` (real registration, endpoint doesn't answer MCP), `REGISTRATION_FETCH_FAILED` x2\n  (a real IPFS gateway timeout, and a real dead `https://` registration URL -- both legitimate, not bugs),\n  and `AGENT_NOT_FOUND` (real on-chain revert) -- plus real reputation data (74 feedback entries from 12\n  distinct clients on agent 1).\n\n## Measurement (candidate #10, 7-day window)\n\n7-day window from 2026-08-23 (real deploy date) -> decision point 2026-08-30. Source of truth:\n`traffic_events`/`revenue_events`/`mcp_call_events` (`asset_name = 'erc8004-agent-liveness'`), not Cloud Run\nlogs. Day 7: if zero real traffic (filtering crawlers), pause/delete the Cloud Run service\n(`gcloud run services delete erc8004-agent-liveness --region us-central1 --project nexus-505016`).\n<!-- _NEXUS_ERC8004_CDP_NODE_RPC_RESEARCH_NOTE -->\n",
  "bytes": 15721,
  "sha": "1de188a0a74ba8cbb8dcb1f61c6e74bbc1cf5c3ae88a7e2ce8de79d8ed90e362",
  "repo_slug": "nexus-mcp-infra/erc8004-agent-liveness",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_nexus_mcp_infra_erc8004_agent__a672d7b6/readme"
}