timerring

ERC 8004 Trustless Agents

October 16, 2025 · 8 min read · Page View:
Tutorial
ETH | AgentFi

Cover from https://www.quillaudits.com/blog/smart-contract/erc-8004

If you have any questions, feel free to comment below. Click the block can copy the code.
And if you think it's helpful to you, just click on the ads which can support this site. Thanks!

This article is my understanding of the ERC-8004: Trustless Agents, which is a standard for Identity Registry, Reputation Registry and Verification Registry. And refer to the Ethereum Improvement Proposals ERC-8004: Trustless Agents[1].

Abstract #

ERC-8004 uses the blockchain as a public index and trust anchor so that agents can be discovered, assessed, and validated in open environments:

  • Identity Registry: Built on ERC-721 + URIStorage, providing each agent with a portable, browsable on-chain handle.
  • Reputation Registry: A standard interface to collect and query feedback signals, supporting on-chain aggregation and off-chain sophisticated scoring.
  • Validation Registry: Composable hooks for independent validators (stake-secured re-execution, zkML, TEE, etc.) to respond to validation requests.

The three trust models can be layered according to value-at-risk: simple reputation for low-stakes, and crypto-economic/zk/TEE validations for high-stakes.

Payments are out of scope for this EIP, but x402 payment proofs can enrich the feedback signals.

Motivation #

  • Existing protocols (e.g., MCP, A2A) define how agents communicate and orchestrate tasks, but not how to discover trustworthy agents.
  • Cross-organizational ecosystems need unified standards for registration, feedback, and validation so that explorers, marketplaces, and auditor networks can emerge.
  • Put “pointers and commitments” (URIs and hashes) on-chain for auditability and composability while leaving heavy logic to specialized off-chain services.

Specification #

Identity Registry #

Based on ERC-721 (using ERC721URIStorage), each agent is a transferable token whose tokenURI points to the Agent Registration File. Content addressing (e.g., IPFS) is recommended; HTTPS is acceptable with hash commitments.

Key identifiers: namespace (eip155), chainId, identityRegistry address, and agentId (ERC-721 tokenId).

Registration file (required/optional fields) example:

{
  "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", // Type marker, compatible with ERC-721 apps
  "name": "myAgentName", // Human-readable agent name
  "description": "Human-readable description, MAY include pricing and interaction methods",
  "image": "https://example.com/agentimage.png", // Agent avatar/icon
  "endpoints": [
    { "name": "A2A", "endpoint": "https://agent.example/.well-known/agent-card.json", "version": "0.3.0" }, // Agent-to-Agent card: auth, skills advertisement, messaging, task orchestration
    { "name": "MCP", "endpoint": "https://mcp.agent.eth/", "capabilities": {}, "version": "2025-06-18" }, // Model Context Protocol endpoint: exposes prompts/resources/tools/completions
    { "name": "OASF", "endpoint": "ipfs://{cid}", "version": "0.7" }, // OASF manifest (v0.7): off-chain service schema/descriptor pinned on IPFS
    { "name": "ENS", "endpoint": "vitalik.eth", "version": "v1" }, // ENS name bound to the agent identity
    { "name": "DID", "endpoint": "did:method:foobar", "version": "v1" }, // Decentralized Identifier of the agent (DID URI)
    { "name": "agentWallet", "endpoint": "eip155:1:0x742d35CcXXXXXXXX" } // Agent wallet (CAIP-10 account id): namespace:chainId:address
  ],
  "registrations": [
    { "agentId": 22, "agentRegistry": "eip155:1:{identityRegistry}" } // At least one registration anchor is recommended
  ],
  "supportedTrust": [ // OPTIONAL: supported trust models
    "reputation",
    "crypto-economic",
    "tee-attestation"
  ]
}

Optional on-chain metadata extension:

// Interface-style for portability across languages/chains; concrete impl can extend ERC721URIStorage
interface IERC8004IdentityRegistry /* is ERC721URIStorage */ {
    // Store arbitrary metadata as bytes with string keys for broad interoperability
    event MetadataSet(uint256 indexed agentId, string indexed indexedKey, string key, bytes value);
    event Registered(uint256 indexed agentId, string tokenURI, address indexed owner);

    struct MetadataEntry {
        string key;   // Recommendation: standardize keys (e.g., agentWallet, agentName) for indexing
        bytes value;  // Self-describing encodings: ABI-encoded bytes, JSON, or multibyte text
    }

    // Register a new Agent and optionally batch-write metadata (saves transactions)
    function register(string calldata tokenURI, MetadataEntry[] calldata metadata)
        external
        returns (uint256 agentId);

    // Simplified variants when only URI is set
    function register(string calldata tokenURI) external returns (uint256 agentId);

    // Reserve an id first, setTokenURI later (for async file generation)
    function register() external returns (uint256 agentId);

    // Read/write optional metadata (access control: owner/operator only for writes)
    function getMetadata(uint256 agentId, string calldata key) external view returns (bytes memory value);
    function setMetadata(uint256 agentId, string calldata key, bytes calldata value) external;
}

Practical notes:

  • Prefer IPFS for tokenURI to get content addressing and longevity; if using HTTPS, also commit a hash on-chain.
  • Use operator approvals to delegate registration file updates while keeping ownership transferable.

Reputation Registry #

The reputation registry is decoupled from the identity registry but stores the identityRegistry address upon deployment to keep queryability:

interface IERC8004ReputationRegistry {
    // Cross-reference identity registry
    function getIdentityRegistry() external view returns (address identityRegistry);

    // Submit feedback (authorized by agent-signed feedbackAuth).
    // score: 0-100; tag1/tag2: free-form bytes32 tags for on-chain filtering; uri/hash: off-chain details + integrity.
    function giveFeedback(
        uint256 agentId,
        uint8 score,
        bytes32 tag1,
        bytes32 tag2,
        string calldata uri,
        bytes32 fileHash,
        bytes calldata feedbackAuth
    ) external;

    // Emitted on new feedback
    event NewFeedback(
        uint256 indexed agentId,
        address indexed clientAddress,
        uint64 indexed feedbackIndex,
        uint8 score,
        bytes32 tag1,
        bytes32 tag2,
        string uri
    );

    // Revoke a previously submitted feedback by the same client
    function revokeFeedback(uint256 agentId, uint64 feedbackIndex) external;

    event FeedbackRevoked(
        uint256 indexed agentId,
        address indexed clientAddress,
        uint64 indexed feedbackIndex
    );

    // Append a response/evidence to an existing feedback (e.g., endorsement, dispute, refund note)
    function appendResponse(
        uint256 agentId,
        address clientAddress,
        uint64 feedbackIndex,
        string calldata responseUri,
        bytes32 responseHash
    ) external;

    event ResponseAppended(
        uint256 indexed agentId,
        address indexed clientAddress,
        uint64 feedbackIndex,
        address indexed responder,
        string responseUri
    );

    // Count responses to a particular feedback (e.g., endorsements or disputes)
    function getResponseCount(
        uint256 agentId,
        address clientAddress,
        uint64 feedbackIndex,
        address[] calldata responders
    ) external view returns (uint64 count);

    function getClients(uint256 agentId) external view returns (address[] memory clients);

    function getLastIndex(uint256 agentId, address clientAddress) external view returns (uint64 lastIndex);
}

Optional feedback file (off-chain, IPFS recommended) example:

{
  "agentRegistry": "eip155:1:{identityRegistry}", // Bind to the identity registry instance
  "agentId": 22,
  "clientAddress": "eip155:1:{clientAddress}",
  "createdAt": "2025-09-23T12:00:00Z",
  "feedbackAuth": "...", // Agent-signed pre-authorization allowing this client to submit feedback
  "score": 100, // Score in [0, 100]

  // Optional fields aligned with A2A/MCP for filtering and aggregation
  "tag1": "foo",
  "tag2": "bar",
  "skill": "as-defined-by-A2A",
  "context": "as-defined-by-A2A",
  "task": "as-defined-by-A2A",
  "capability": "tools", // As per MCP: "prompts", "resources", "tools" or "completions"
  "name": "the-mcp-tool-name",
  // Payment proof (x402 example) to improve signal credibility
  "proof_of_payment": {
    "fromAddress": "0x00...",
    "toAddress": "0x00...",
    "chainId": "1",
    "txHash": "0x00..."
  },
   // Other fields
  " ... ": { " ... " } // MAY
}

Practical notes:

  • Structure indexable fields (tag, capability, name, etc.) for subgraphs and data products.
  • Use pre-authorization-before-feedback to reduce spam; still combine with reviewer reputation and rate limits.

Validation Registry #

Tracks agent-initiated validation requests and validator responses; supports multi-round updates and aggregation:

interface IERC8004ValidationRegistry {
    // Cross-reference identity registry for composability
    function getIdentityRegistry() external view returns (address identityRegistry);

    // Agent (owner or operator) initiates a validation request
    // requestUri points to full inputs/outputs; if not content-addressed (e.g., HTTPS), also submit requestHash
    event ValidationRequest(address indexed validatorAddress, uint256 indexed agentId, string requestUri, bytes32 indexed requestHash);
    function validationRequest(
        address validatorAddress,
        uint256 agentId,
        string calldata requestUri,
        bytes32 requestHash
    ) external;

    // Validator responds (can be repeated for soft/hard finality or status progression)
    // response: 0-100 (binary or graded); responseUri/responseHash can anchor audit evidence
    event ValidationResponse(address indexed validatorAddress, uint256 indexed agentId, bytes32 indexed requestHash, uint8 response, string responseUri, bytes32 tag);
    function validationResponse(
        bytes32 requestHash,
        uint8 response,
        string calldata responseUri,
        bytes32 responseHash,
        bytes32 tag
    ) external;

    // Read-only: fetch latest status by requestHash
    function getValidationStatus(bytes32 requestHash)
        external
        view
        returns (address validatorAddress, uint256 agentId, uint8 response, bytes32 tag, uint256 lastUpdate);

    // Read-only: aggregated stats for an agent (optionally filter by validator addresses and tag)
    function getSummary(uint256 agentId, address[] calldata validatorAddresses, bytes32 tag)
        external
        view
        returns (uint64 count, uint8 avgResponse);

    // Read-only: list requests by agent/validator
    function getAgentValidations(uint256 agentId) external view returns (bytes32[] memory requestHashes);
    function getValidatorRequests(address validatorAddress) external view returns (bytes32[] memory requestHashes);
}

Practical notes:

  • For non-content-addressed requestUri/responseUri, always commit the corresponding hashes on-chain.
  • Multiple validationResponse calls allow soft/hard finality or multi-stage review with tag.

Rationale #

  • Decouple from agent communication protocols: on-chain stores “pointers and commitments”; capabilities evolve via MCP/A2A.
  • Public-good reputation signals: unified schema and minimal interfaces enable interoperable scoring/auditor networks.
  • Gas-friendly: clients need not register; with EIP-7702 and similar, frictionless feedback is possible; heavy aggregation off-chain.
  • Singleton deployments: one registry per chain simplifies discovery while allowing multi-chain registrations and operations.
  • Not reinvent the wheel. The protocol not only expands A2A, but also bases trust assumptions on what builders are already thinking about. So, Stake-secured validation? Use EigenLayer[2]. TEE attestations? Check out Phala[3] its paper[4] and Near.AI[5].

Test Cases #

  • Build agent explorers/marketplaces: reuse ERC-721 browsing/transfer UX to view avatars, descriptions, and endpoints. There are some good DeAI projects listed[6].
  • Reputation systems: simple on-chain aggregates (mean/median) plus off-chain sybil-resistant scoring and visualization.
  • Validation networks: stake re-execution, zkML, TEE attestation—all pluggable via a common interface.

Workflow #

The workflow of the ERC-8004 example[7] is as follows:

sequenceDiagram participant User as User/Client participant Alice as Server Agent
(Alice - local) participant Bob as Validator Agent
(Bob - local) participant Charlie as Client Agent
(Charlie - local) participant IR as IdentityRegistry
(on-chain contract) participant RR as ReputationRegistry
(on-chain contract) participant VR as ValidationRegistry
(on-chain contract) participant Storage as Local Storage
(data/, validations/) Note over User,Storage: Step 1: Deploy contracts User->>IR: Deploy IdentityRegistry User->>RR: Deploy ReputationRegistry User->>VR: Deploy ValidationRegistry Note over User,Storage: Steps 2-3: Agent registration Alice->>IR: newAgent("alice.example.com", 0xAAA) IR-->>Alice: agentId = 1 IR->>IR: emit AgentRegistered(1) Bob->>IR: newAgent("bob.example.com", 0xBBB) IR-->>Bob: agentId = 2 IR->>IR: emit AgentRegistered(2) Charlie->>IR: newAgent("charlie.example.com", 0xCCC) IR-->>Charlie: agentId = 3 IR->>IR: emit AgentRegistered(3) Note over User,Storage: Step 4: Market analysis workflow Alice->>Alice: analyze_market()
CrewAI multi-agent workflow Alice->>Alice: Senior Market Analyst
Analyze BTC trend Alice->>Alice: Risk Assessment Reviewer
Assess risks Alice->>Storage: Write analysis result
data/btc_analysis.json Note over User,Storage: Step 5: Request validation Alice->>VR: validationRequest(
validatorId=2,
serverId=1,
dataHash) VR->>VR: Record validation request VR->>VR: emit ValidationRequestEvent(2, 1, dataHash) Note over User,Storage: Step 6: AI-driven validation Bob->>VR: Read validation request Bob->>Storage: Read analysis file
data/btc_analysis.json Bob->>Bob: validate_analysis()
CrewAI validation workflow Bob->>Bob: Senior Validator
Review methodology Bob->>Bob: QA Specialist
Final assessment Bob->>Storage: Write validation result
validations/validation_result.json Note over User,Storage: Step 7: Submit validation response Bob->>VR: validationResponse(
dataHash,
score=96) VR->>VR: Record validation score VR->>VR: emit ValidationResponseEvent(2, 1, dataHash, 96) Note over User,Storage: Step 8: Feedback authorization Charlie->>RR: acceptFeedback(
clientId=3,
serverId=1) RR->>RR: Record authorization relation RR->>RR: emit AuthFeedback(3, 1, feedbackAuthId) Note over User,Storage: Step 9: Audit trail User->>IR: getAgent(1, 2, 3) IR-->>User: Return agent info User->>VR: getValidationResponse(dataHash) VR-->>User: Return validation score 96 User->>RR: isFeedbackAuthorized(3, 1) RR-->>User: Return authorization status

You can also check the third party explanation[8].

Security Considerations #

  • Pre-authorization mitigates but does not eliminate sybil attacks; combine with reviewer reputation and throttling/deposits.
  • On-chain pointers and hashes are immutable, ensuring audit trails; handle privacy and compliance carefully.
  • Validator incentives/slashing are managed by respective protocols (e.g., staking/penalty curves); out of scope here.
  • Registration files/endpoints assert capabilities but do not prove them; pair with validation models(reputation, validation, and TEE attestation).


<< prev | Fix the Ghost... Continue strolling

If you want to follow my updates, or have a coffee chat with me, feel free to connect with me: