Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 99 additions & 61 deletions ERCS/erc-8004.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Throughout this document, *tokenId* in ERC-721 is referred to as *agentId* and *

#### Agent URI and Agent Registration File

The *agentURI* MUST resolve to the agent registration file. It MAY use any URI scheme such as `ipfs://` (e.g., `ipfs://cid`) or `https://` (e.g., `https://example.com/agent3.json`). When the registration uri changes, it can be updated with *setAgentURI()*.
The *agentURI* MUST resolve to the agent registration file. It MAY use any URI scheme such as `ipfs://` (e.g., `ipfs://cid`), `https://` (e.g., `https://example.com/agent3.json`), or a base64-encoded `data:` URI (e.g., `data:application/json;base64,eyJ0eXBlIjoi...`) for fully on-chain metadata. When the registration uri changes, it can be updated with *setAgentURI()*.

The registration file MUST have the following structure:

Expand All @@ -59,7 +59,7 @@ The registration file MUST have the following structure:
"name": "myAgentName",
"description": "A natural language description of the Agent, which MAY include what it does, how it works, pricing, and interaction methods",
"image": "https://example.com/agentimage.png",
"endpoints": [
"services": [
{
"name": "web",
"endpoint": "https://web.agentxyz.com/"
Expand All @@ -72,7 +72,6 @@ The registration file MUST have the following structure:
{
"name": "MCP",
"endpoint": "https://mcp.agent.eth/",
"capabilities": [], // OPTIONAL, as per MCP spec
"version": "2025-06-18"
},
{
Expand Down Expand Up @@ -119,28 +118,40 @@ Agents MAY advertise their endpoints, which point to an A2A agent card, an MCP e

#### Endpoint Domain Verification (Optional)

Since endpoints can point to domains not controlled by the agent owner, an agent MAY optionally prove control of an HTTPS endpoint-domain by publishing `https://{endpoint-domain}/.well-known/agent-registration.json` containing at least a `registrations` list (or the full agent registration file). Verifiers MAY treat the endpoint-domain as verified if the file is reachable over HTTPS and includes a `registrations` entry whose `agentRegistry` and `agentId` match the on-chain agent; if the endpoint-domain is the same domain that serves the agent’s primary registration file referenced by `agentURI`, this additional check is not needed because domain control is already demonstrated there.
Since endpoints can point to domains not controlled by the agent owner, an agent MAY optionally prove control of an HTTPS endpoint-domain by publishing `https://{endpoint-domain}/.well-known/agent-registration.json` containing at least a `registrations` list (or the full agent registration file). Users MAY treat the endpoint-domain as verified if the file is reachable over HTTPS and includes a `registrations` entry whose `agentRegistry` and `agentId` match the on-chain agent; if the endpoint-domain is the same domain that serves the agent’s primary registration file referenced by `agentURI`, this additional check is not needed because domain control is already demonstrated there.

Agents SHOULD have at least one registration (multiple are possible), and all fields in the registration are mandatory.
The *supportedTrust* field is OPTIONAL. If absent or empty, this ERC is used only for discovery, not for trust.

#### Onchain metadata
#### On-chain metadata

The registry extends ERC-721 by adding `getMetadata(uint256 agentId, string metadataKey)` and `setMetadata(uint256 agentId, string metadataKey, string metadataValue)` functions for optional extra on-chain agent metadata.
The registry extends ERC-721 by adding `getMetadata(uint256 agentId, string metadataKey)` and `setMetadata(uint256 agentId, string metadataKey, bytes metadataValue)` functions for optional extra on-chain agent metadata:

```solidity
function getMetadata(uint256 agentId, string memory metadataKey) external view returns (bytes memory)
function setMetadata(uint256 agentId, string memory metadataKey, bytes memory metadataValue) external
```

When metadata is set, the following event is emitted:

```solidity
event MetadataSet(uint256 indexed agentId, string indexed indexedMetadataKey, string metadataKey, bytes metadataValue)
```

The key `agentWallet` is reserved and cannot be set via `setMetadata()` or during `register()`. It represents the address where the agent receives payments and is initially set to the owner's address. To change it, the agent owner must prove control of the new wallet by providing a valid [EIP-712](./eip-712.md) signature for EOAs or [ERC-1271](./eip-1271.md) for smart contract wallets—by calling:
The key `agentWallet` is reserved and cannot be set via `setMetadata()` or during `register()` (including the metadata array overload). It represents the address where the agent receives payments and is initially set to the owner's address. To change it, the agent owner must prove control of the new wallet by providing a valid [EIP-712](./eip-712.md) signature for EOAs or [ERC-1271](./eip-1271.md) for smart contract wallets—by calling:

```solidity
function setAgentWallet(uint256 agentId, address newWallet, uint256 deadline, bytes calldata signature) external
```

When the agent is transferred, `agentWallet` is automatically reset to the zero address and must be re-verified by the new owner.
To read and clear the currently set wallet, the following functions are exposed:

```solidity
function getAgentWallet(uint256 agentId) external view returns (address)
function unsetAgentWallet(uint256 agentId) external
```

When the agent is transferred, `agentWallet` is automatically cleared (effectively resetting it to the zero address) and must be re-verified by the new owner.

#### Registration

Expand All @@ -160,7 +171,7 @@ function register(string agentURI) external returns (uint256 agentId)
function register() external returns (uint256 agentId)
```

This emits one Transfer event, one MetadataSet event for each metadata entry, if any, and
This emits one Transfer event, one MetadataSet event for the reserved `agentWallet` key, one MetadataSet event for each additional metadata entry (if any), and

```solidity
event Registered(uint256 indexed agentId, string agentURI, address indexed owner)
Expand All @@ -186,34 +197,95 @@ data:application/json;base64,eyJ0eXBlIjoi...

### Reputation Registry

When the Reputation Registry is deployed, the *identityRegistry* address is passed to the constructor and publicly visible by calling:
When the Reputation Registry is deployed, the *identityRegistry* address is set via `initialize(address identityRegistry_)` and publicly visible by calling:

```solidity
function getIdentityRegistry() external view returns (address identityRegistry)
```

The feedback given by a *clientAddress* to an agent consists of a *score* (0-100), *tag1* and *tag2* (left to developers' discretion to provide maximum on-chain composability and filtering), a *endpoint* uri, a file uri pointing to an off-chain JSON containing additional information, and its KECCAK-256 file hash to guarantee integrity. We suggest using IPFS or equivalent services to make feedback easily indexed by subgraphs or similar technologies. For IPFS uris, the hash is not required.
All fields except the *score* are OPTIONAL, so the off-chain file is not required and can be omitted.
The feedback given by a *clientAddress* to an agent consists of a signed fixed-point *value* (`int128`) and its *valueDecimals* (`uint8`, 0-18), plus optional *tag1* and *tag2* (left to developers' discretion to provide maximum on-chain composability and filtering), an *endpoint* URI, a file URI pointing to an off-chain JSON containing additional information, and its KECCAK-256 file hash to guarantee integrity. We suggest using IPFS or equivalent services to make feedback easily indexed by subgraphs or similar technologies. For IPFS URIs, the hash is not required.
All fields except *value* and *valueDecimals* are OPTIONAL, so the off-chain file is not required and can be omitted.

#### Giving Feedback

New feedback can be added by any *clientAddress* calling:

```solidity
function giveFeedback(uint256 agentId, uint8 score, string tag1, string tag2, string endpoint, string calldata feedbackURI, bytes32 feedbackHash) external
function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string calldata tag1, string calldata tag2, string calldata endpoint, string calldata feedbackURI, bytes32 feedbackHash) external
```

The *agentId* must be a validly registered agent. The *score* MUST be between 0 and 100\. *tag1*, *tag2*, *endpoint*, *feedbackURI*, and *feedbackHash* are OPTIONAL.
The *agentId* must be a validly registered agent. The *valueDecimals* MUST be between 0 and 18. The feedback submitter MUST NOT be the agent owner or an approved operator for *agentId*. *tag1*, *tag2*, *endpoint*, *feedbackURI*, and *feedbackHash* are OPTIONAL.

Where provided, *feedbackHash* is the KECCAK-256 hash (`keccak256`) of the content referenced by *feedbackURI*, enabling verifiable integrity for non-content-addressed URIs. For IPFS (or other content-addressed URIs), *feedbackHash* is OPTIONAL and can be omitted (e.g., set to `bytes32(0)`).

If the procedure succeeds, an event is emitted:

```solidity
event NewFeedback(uint256 indexed agentId, address indexed clientAddress, uint64 feedbackIndex, uint8 score, string indexed tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash)
event NewFeedback(uint256 indexed agentId, address indexed clientAddress, uint64 feedbackIndex, int128 value, uint8 valueDecimals, string indexed indexedTag1, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash)
```

The feedback fields, except *feedbackURI* and *feedbackHash*, are stored in the contract storage along with the feedbackIndex (the number of feedback submissions that *clientAddress* has given to *agentId*). This exposes reputation signals to any smart contract, enabling on-chain composability.
The feedback fields *value*, *valueDecimals*, *tag1*, *tag2*, and *isRevoked* are stored in the contract storage along with the feedbackIndex (a 1-indexed counter of feedback submissions that *clientAddress* has given to *agentId*). The fields *endpoint*, *feedbackURI*, and *feedbackHash* are emitted but are not stored. This exposes reputation signals to any smart contract, enabling on-chain composability.

When the feedback is given by an agent (i.e., the client is an agent), the agent SHOULD use the address set in the on-chain optional `agentWallet` metadata as the clientAddress, to facilitate reputation aggregation.

#### Examples of `value` / `valueDecimals`

| tag1 | What it measures | Example human value | `value` | `valueDecimals` |
| --- | --- | --- | --- | --- |
| `starred` | Quality rating (0-100) | `87/100` | `87` | `0` |
| `reachable` | Endpoint reachable (binary) | `true` | `1` | `0` |
| `ownerVerified` | Endpoint owned by agent owner (binary) | `true` | `1` | `0` |
| `uptime` | Endpoint uptime (%) | `99.77%` | `9977` | `2` |
| `successRate` | Endpoint success rate (%) | `89%` | `89` | `0` |
| `responseTime` | Response time (ms) | `560ms` | `560` | `0` |
| `blocktimeFreshness` | Avg block delay (blocks) | `4 blocks` | `4` | `0` |
| `revenues` | Cumulative revenues (e.g., USD) | `$560` | `560` | `0` |
| `tradingYield` (`tag2` = `day, week, month, year`) | Yield | `-3,2%` | `-32` | `1` |

#### Off-Chain Feedback File Structure

The OPTIONAL file at the URI could look like:

```jsonc
{
// MUST FIELDS
"agentRegistry": "eip155:1:{identityRegistry}",
"agentId": 22,
"clientAddress": "eip155:1:{clientAddress}",
"createdAt": "2025-09-23T12:00:00Z",
"value": 100,
"valueDecimals": 0,

// ALL OPTIONAL FIELDS
"tag1": "foo",
"tag2": "bar",
"endpoint": "https://agent.example.com/GetPrice",

"mcp": { "tool": "ToolName" }, // or: { "prompt": "PromptName" } / { "resource": "ResourceName" }

// A2A: see "Context Identifier Semantics" and Task model in the A2A specification.
"a2a": {
"skills": ["as-defined-by-A2A"], // e.g., AgentSkill identifiers
"contextId": "as-defined-by-A2A",
"taskId": "as-defined-by-A2A"
},

"oasf": {
"skills": ["as-defined-by-OASF"],
"domains": ["as-defined-by-OASF"]
},

"proofOfPayment": { // this can be used for x402 proof of payment
"fromAddress": "0x00...",
"toAddress": "0x00...",
"chainId": "1",
"txHash": "0x00..."
},

When the feedback is given by an agent (i.e., the client is an agent), the agent SHOULD use the address set in the on-chain optional walletAddress metadata as the clientAddress, to facilitate reputation aggregation.
// Other fields
" ... ": { " ... " } // MAY
}
```

#### Revoking Feedback

Expand Down Expand Up @@ -242,22 +314,22 @@ Where *responseHash* is the KECCAK-256 file hash of the *responseURI* file conte
This emits:

```solidity
event ResponseAppended(uint256 indexed agentId, address indexed clientAddress, uint64 feedbackIndex, address indexed responder, string responseURI)
event ResponseAppended(uint256 indexed agentId, address indexed clientAddress, uint64 feedbackIndex, address indexed responder, string responseURI, bytes32 responseHash)
```

#### Read Functions

```solidity
function getSummary(uint256 agentId, address[] calldata clientAddresses, string tag1, string tag2) external view returns (uint64 count, uint8 averageScore)
// agentId is the only mandatory parameter; others are optional filters.
// Without filtering by clientAddresses, results are subject to Sybil/spam attacks. See Security Considerations for details
function getSummary(uint256 agentId, address[] calldata clientAddresses, string tag1, string tag2) external view returns (uint64 count, int128 summaryValue, uint8 summaryValueDecimals)
// agentId and clientAddresses are mandatory; tag1 and tag2 are optional filters.
// clientAddresses MUST be provided (non-empty); results without filtering by clientAddresses are subject to Sybil/spam attacks. See Security Considerations for details

function readFeedback(uint256 agentId, address clientAddress, uint64 feedbackIndex) external view returns (uint8 score, string tag1, string tag2, bool isRevoked)
function readFeedback(uint256 agentId, address clientAddress, uint64 feedbackIndex) external view returns (int128 value, uint8 valueDecimals, string tag1, string tag2, bool isRevoked)

function readAllFeedback(uint256 agentId, address[] calldata clientAddresses, string tag1, string tag2, bool includeRevoked) external view returns (address[] memory clientAddresses, uint64[] memory feedbackIndexes, uint8[] memory scores, string[] memory tag1s, string[] memory tag2s, bool[] memory revokedStatuses)
function readAllFeedback(uint256 agentId, address[] calldata clientAddresses, string tag1, string tag2, bool includeRevoked) external view returns (address[] memory clients, uint64[] memory feedbackIndexes, int128[] memory values, uint8[] memory valueDecimals, string[] memory tag1s, string[] memory tag2s, bool[] memory revokedStatuses)
// agentId is the only mandatory parameter; others are optional filters. Revoked feedback are omitted by default.

function getResponseCount(uint256 agentId, address clientAddress, uint64 feedbackIndex, address[] responders) external view returns (uint64)
function getResponseCount(uint256 agentId, address clientAddress, uint64 feedbackIndex, address[] responders) external view returns (uint64 count)
// agentId is the only mandatory parameter; others are optional filters.

function getClients(uint256 agentId) external view returns (address[] memory)
Expand All @@ -267,46 +339,12 @@ function getLastIndex(uint256 agentId, address clientAddress) external view retu

We expect reputation systems around reviewers/clientAddresses to emerge. **While simple filtering by reviewer (useful to mitigate spam) and by tag are enabled on-chain, more complex reputation aggregation will happen off-chain**.

#### Off-Chain Feedback File Structure

The OPTIONAL file at the URI could look like:

```jsonc
{
// MUST FIELDS
"agentRegistry": "eip155:1:{identityRegistry}",
"agentId": 22,
"clientAddress": "eip155:1:{clientAddress}",
"createdAt": "2025-09-23T12:00:00Z",
"score": 100,

// ALL OPTIONAL FIELDS
"tag1": "foo",
"tag2": "bar",
"endpoint": "https://.../",
"skill": "as-defined-by-A2A-or-OASF",
"domain": "as-defined-by-OASF",
"context": "as-defined-by-A2A",
"task": "as-defined-by-A2A",
"capability": "tools", // As per MCP: "prompts", "resources", "tools" or "completions"
"name": "foo", // As per MCP: the name of the prompt, resource or tool
"proofOfPayment": {
"fromAddress": "0x00...",
"toAddress": "0x00...",
"chainId": "1",
"txHash": "0x00..."
}, // this can be used for x402 proof of payment

// Other fields
" ... ": { " ... " } // MAY
}
```

### Validation Registry

**This registry enables agents to request verification of their work and allows validator smart contracts to provide responses that can be tracked on-chain**. Validator smart contracts could use, for example, stake-secured inference re-execution, zkML verifiers or TEE oracles to validate or reject requests.

When the Validation Registry is deployed, the *identityRegistry* address is passed to the constructor and is visible by calling `getIdentityRegistry()`, as described above.
When the Validation Registry is deployed, the *identityRegistry* address is set via `initialize(address identityRegistry_)` and is visible by calling `getIdentityRegistry()`, as described above.

#### Validation Request

Expand Down Expand Up @@ -342,12 +380,12 @@ Upon successful execution, a *ValidationResponse* event is emitted with all func
event ValidationResponse(address indexed validatorAddress, uint256 indexed agentId, bytes32 indexed requestHash, uint8 response, string responseURI, bytes32 responseHash, string tag)
```

The contract stores *requestHash*, *validatorAddress*, *agentId*, *response*, *lastUpdate*, and *tag* for on-chain querying and composability.
The contract stores *requestHash*, *validatorAddress*, *agentId*, *response*, *responseHash*, *lastUpdate*, and *tag* for on-chain querying and composability.

#### Read Functions

```solidity
function getValidationStatus(bytes32 requestHash) external view returns (address validatorAddress, uint256 agentId, uint8 response, string tag, uint256 lastUpdate)
function getValidationStatus(bytes32 requestHash) external view returns (address validatorAddress, uint256 agentId, uint8 response, bytes32 responseHash, string tag, uint256 lastUpdate)

//Returns aggregated validation statistics for an agent. agentId is the only mandatory parameter; validatorAddresses and tag are optional filters
function getSummary(uint256 agentId, address[] calldata validatorAddresses, string tag) external view returns (uint64 count, uint8 averageResponse)
Expand Down