Skill: tenequm/erc-8004-development (version 0.2.0)

--- name: erc-8004 description: Build with ERC-8004 Trustless Agents - on-chain agent identity, reputation, validation, and discovery on EVM chains. Use when registering AI agents on-chain, building agent reputation systems, searching/discovering agents, working with the Agent0 SDK (agent0-sdk), or implementing the ERC-8004 standard. Triggers on ERC-8004, Agent0, agent identity, agent registry, agent reputation, trustless agents, agent discovery. metadata: version: "0.2.0" openclaw: homepage: https://github.com/tenequm/skills/tree/main/skills/erc-8004 emoji: "🤝" primaryEnv: PRIVATE_KEY envVars: - name: RPC_URL required: false description: EVM JSON-RPC endpoint for the registry chain. - name: PRIVATE_KEY required: false description: Signer key for on-chain registration. Use throwaway/testnet keys. - name: PINATA_JWT required: false description: JWT for IPFS pinning via Pinata. - name: FILECOIN_PRIVATE_KEY required: false description: Optional Filecoin signer for archival pinning. --- # ERC-8004: Trustless Agents ERC-8004 is a Draft EIP for discovering, choosing, and interacting with AI agents across organizational boundaries without pre-existing trust. It defines three on-chain registries deployed as per-chain singletons on any EVM chain. **Authors:** Marco De Rossi (MetaMask), Davide Crapis (EF), Jordan Ellis (Google), Erik Reppel (Coinbase) **Full spec:** [references/spec.md](references/spec.md) ## When to Use This Skill - Registering AI agents on-chain (ERC-721 identity) - Building or querying agent reputation/feedback systems - Searching and discovering agents by capabilities, trust models, or endpoints - Working with the Agent0 TypeScript SDK (`agent0-sdk`) - Implementing ERC-8004 smart contract integrations - Setting up agent wallets, MCP/A2A endpoints, or OASF taxonomies ## Core Architecture Three lightweight registries, each deployed as a UUPS-upgradeable singleton: | Registry | Purpose | Contract | |----------|---------|----------| | **Identity** | ERC-721 NFTs for agent identities + registration files | `IdentityRegistryUpgradeable` | | **Reputation** | Signed fixed-point feedback signals + off-chain detail files | `ReputationRegistryUpgradeable` | | **Validation** | Third-party validator attestations (stake, zkML, TEE) | `ValidationRegistryUpgradeable` | **Agent identity** = `agentRegistry` (string `eip155:{chainId}:{contractAddress}`) + `agentId` (ERC-721 tokenId). Each agent's `agentURI` points to a JSON registration file (IPFS or HTTPS) advertising name, description, endpoints (MCP, A2A, ENS, DID, wallet), OASF skills/domains, trust models, and x402 support. **See:** [references/contracts.md](references/contracts.md) for full contract interfaces and addresses. ## Quick Start with Agent0 SDK (TypeScript) ```bash npm install agent0-sdk ``` ### Register an Agent ```typescript import { SDK } from 'agent0-sdk'; const sdk = new SDK({ chainId: 84532, // Base Sepolia rpcUrl: process.env.RPC_URL, privateKey: process.env.PRIVATE_KEY, ipfs: 'pinata', pinataJwt: process.env.PINATA_JWT, }); const agent = sdk.createAgent( 'MyAgent', 'An AI agent that analyzes crypto markets', 'https://example.com/agent-image.png' ); // Configure endpoints and capabilities await agent.setMCP('https://mcp.example.com', '2025-06-18', true); // auto-fetches tools await agent.setA2A('https://example.com/.well-known/agent-card.json', '0.3.0', true); agent.setENS('myagent.eth'); agent.setActive(true); agent.setX402Support(true); agent.setTrust(true, false, false); // reputation only // Add OASF taxonomy agent.addSkill([REDACTED_SECRET], true); agent.addDomain([REDACTED_SECRET], true); // Register on-chain (mints NFT + uploads to IPFS) const tx = await agent.registerIPFS(); const { result } = await tx.waitConfirmed(); console.log(`Registered: ${result.agentId}`); // e.g. "84532:42" ``` ### Search for Agents ```typescript const sdk = new SDK({ chainId: 84532, rpcUrl: process.env.RPC_URL }); // Search by capabilities const agents = await sdk.searchAgents({ hasMCP: true, active: true, x402support: true, mcpTools: ['financial_analyzer'], supportedTrust: ['reputation'], }); // Get a specific agent const agent = await sdk.getAgent('84532:42'); // Semantic search const results = await sdk.searchAgents( { keyword: 'crypto market analysis' }, { sort: ['semanticScore:desc'] } ); ``` ### Give Feedback ```typescript // Prepare optional off-chain feedback file const feedbackFile = await sdk.prepareFeedbackFile({ text: 'Accurate market analysis', capability: 'tools', name: 'financial_analyzer', proofOfPayment: { txHash: '0x...', chainId: '8453', fromAddress: '0x...', toAddress: '0x...' }, }); // Submit feedback (value=85 out of 100) const tx = await sdk.giveFeedback('84532:42', 85, 'starred', '', '', feedbackFile); await tx.waitConfirmed(); // Read reputation summary const summary = await sdk.getReputationSummary('84532:42'); console.log(`Average: ${summary.averageValue}, Count: ${summary.count}`); ``` **See:** [references/sdk-typescript.md](references/sdk-typescript.md) for full SDK API reference. ## Registration File Format Every agent's `agentURI` resolves to this JSON structure: ```json { "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", "name": "MyAgent", "description": "What it does, pricing, interaction methods", "image": "https://example.com/agent.png", "services": [ { "name": "MCP", "endpoint": "https://mcp.example.com", "version": "2025-06-18", "mcpTools": ["tool1"] }, { "name": "A2A", "endpoint": "https://example.com/.well-known/agent-card.json", "version": "0.3.0" }, { "name": "OASF", "endpoint": "https://github.com/agntcy/oasf/", "version": "v0.8.0", "skills": [[REDACTED_SECRET]], "domains": [[REDACTED_SECRET]] }, { "name": "ENS", "endpoint": "myagent.eth", "version": "v1" }, { "name": "agentWallet", "endpoint": "eip155:8453:0x..." } ], "registrations": [ { "agentId": 42, "agentRegistry": "eip155:84532:0x8004A818BFB912233c491871b3d84c89A494BD9e" } ], "supportedTrust": ["reputation", "crypto-economic", "tee-attestation"], "active": true, "x402Support": true } ``` The `registrations` field creates a bidirectional cryptographic link: the NFT points to this file, and this file points back to the NFT. This enables endpoint domain verification via `/.well-known/agent-registration.json`. **See:** [references/registration.md](references/registration.md) for best practices (Four Golden Rules) and complete field reference. ## Contract Addresses All registries deploy to deterministic vanity addresses via CREATE2 (SAFE Singleton Factory): ### Mainnet (Ethereum, Base, Polygon, Arbitrum, Optimism, etc.) | Registry | Address | |----------|---------| | Identity | [REDACTED_SECRET] | | Reputation | [REDACTED_SECRET] | | Validation | [REDACTED_SECRET] | ### Testnet (Sepolia, Base Sepolia, etc.) | Registry | Address | |----------|---------| | Identity | [REDACTED_SECRET] | | Reputation | [REDACTED_SECRET] | | Validation | [REDACTED_SECRET] | Same proxy addresses on: Ethereum, Base, Arbitrum, Avalanche, Celo, Gnosis, Linea, Mantle, MegaETH, Optimism, Polygon, Scroll, Taiko, Monad, BSC + testnets. ## Reputation System Feedback uses signed fixed-point numbers: `value` (int128) + `valueDecimals` (uint8, 0-18). | tag1 | Measures | Example | value | valueDecimals | |------|----------|---------|-------|---------------| | `starred` | Quality 0-100 | 87/100 | 87 | 0 | | `reachable` | Endpoint up (binary) | true | 1 | 0 | | `uptime` | Uptime % | 99.77% | 9977 | 2 | | `successRate` | Success % | 89% | 89 | 0 | | `responseTime` | Latency ms | 560ms | 560 | 0 | Anti-Sybil: `getSummary()` requires a non-empty `clientAddresses` array (caller must supply trusted reviewer list). Self-feedback is rejected (agent owner/operators cannot submit feedback on their own agent). **See:** [references/reputation.md](references/reputation.md) for full feedback system, off-chain file format, and aggregation details. ## OASF Taxonomy (v0.8.0) Open Agentic Schema Framework provides standardized skills (136) and domains (204) for agent classification. **Top-level skill categories:** `natural_language_processing`, `images_computer_vision`, `audio`, `analytical_skills`, `multi_modal`, `agent_orchestration`, `advanced_reasoning_planning`, `data_engineering`, `security_privacy`, `evaluation_monitoring`, `devops_mlops`, `governance_compliance`, `tool_interaction`, `retrieval_augmented_generation`, `tabular_text` **Top-level domain categories:** `technology`, `finance_and_business`, `healthcare`, `legal`, `education`, `life_science`, `agriculture`, `energy`, `environmental_science`, `government`, `manufacturing`, `transportation`, and more. Use slash-separated paths: `agent.addSkill([REDACTED_SECRET], true)`. ## Key Concepts | Term | Meaning | |------|---------| | `agentRegistry` | `eip155:{chainId}:{contractAddress}` - globally unique registry identifier | | `agentId` | ERC-721 tokenId - numeric on-chain identifier (format in SDK: `"chainId:tokenId"`) | | `agentURI` | URI (IPFS/HTTPS) pointing to agent registration file | | `agentWallet` | Reserved on-chain metadata key for verified payment address (EIP-712/ERC-1271) | | `feedbackIndex` | 1-indexed counter of feedback a clientAddress has given to an agentId | | `supportedTrust` | Array: `"reputation"`, `"crypto-economic"`, `"tee-attestation"` | | `x402Support` | Boolean flag for Coinbase x402 HTTP payment protocol support | | OASF | Open Agentic Schema Framework - standardized agent skills/domains taxonomy | | MCP | Model Context Protocol - tools, prompts, resources, completions | | A2A | Agent2Agent - authentication, skills via AgentCards, task orchestration | ## Reference Index | Reference | Content | |-----------|---------| | [spec.md](references/spec.md) | Complete ERC-8004 specification (EIP text) | | [contracts.md](references/contracts.md) | Smart contract interfaces, storage layout, deployment | | [sdk-typescript.md](references/sdk-typescript.md) | Agent0 TypeScript SDK full API | | [registration.md](references/registration.md) | Registration file format, Four Golden Rules, domain verification | | [reputation.md](references/reputation.md) | Feedback system, off-chain files, value encoding, aggregation | | [search-discovery.md](references/search-discovery.md) | Agent search, subgraph queries, multi-chain discovery | | [oasf-taxonomy.md](references/oasf-taxonomy.md) | Complete OASF v0.8.0 taxonomy: all 136 skills and 204 domains with slugs | ## Official Resources - EIP Discussion: https://ethereum-magicians.org/t/erc-8004-trustless-agents/25098 - Contracts: https://github.com/erc-8004/erc-8004-contracts - Best Practices: https://github.com/erc-8004/best-practices - SDK Docs: https://sdk.ag0.xyz - SDK Docs Source: https://github.com/agent0lab/agent0-sdk-docs - TypeScript SDK: https://github.com/agent0lab/agent0-ts - Python SDK: https://github.com/agent0lab/agent0-py - Subgraph: https://github.com/agent0lab/subgraph - OASF: https://github.com/agntcy/oasf

path: LICENSE.txt
content: Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the
copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all other
entities that control, are controlled by, or are under common control with
that entity. For the purposes of this definition, "control" means (i) the
power, direct or indirect, to cause the direction or management of such
entity, whether by contract or otherwise, or (ii) ownership of fifty percent
(50%) or more of the outstanding shares, or (iii) beneficial ownership of
such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source, and
configuration files.

"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object
code, generated documentation, and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or Object form,
made available under the License, as indicated by a copyright notice that is
included in or attached to the work (an example is provided in the Appendix
below).

"Derivative Works" shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial
revisions, annotations, elaborations, or other modifications represent, as a
whole, an original work of authorship. For the purposes of this License,
Derivative Works shall not include works that remain separable from, or
merely link (or bind by name) to the interfaces of, the Work and Derivative
Works thereof.

"Contribution" shall mean any work of authorship, including the original
version of the Work and any modifications or additions to that Work or
Derivative Works thereof, that is intentionally submitted to Licensor for
inclusion in the Work by the copyright owner or by an individual or Legal
Entity authorized to submit on behalf of the copyright owner. For the
purposes of this definition, "submitted" means any form of electronic, verbal,
or written communication sent to the Licensor or its representatives,
including but not limited to communication on electronic mailing lists, source
code control systems, and issue tracking systems that are managed by, or on
behalf of, the Licensor for the purpose of discussing and improving the Work,
but excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on
behalf of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of this
License, each Contributor hereby grants to You a perpetual, worldwide,
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
reproduce, prepare Derivative Works of, publicly display, publicly perform,
sublicense, and distribute the Work and such Derivative Works in Source or
Object form.

3. Grant of Patent License. Subject to the terms and conditions of this
License, each Contributor hereby grants to You a perpetual, worldwide,
non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this
section) patent license to make, have made, use, offer to sell, sell, import,
and otherwise transfer the Work, where such license applies only to those
patent claims licensable by such Contributor that are necessarily infringed by
their Contribution(s) alone or by combination of their Contribution(s) with
the Work to which such Contribution(s) was submitted. If You institute patent
litigation against any entity (including a cross-claim or counterclaim in a
lawsuit) alleging that the Work or a Contribution incorporated within the Work
constitutes direct or contributory patent infringement, then any patent
licenses granted to You under this License for that Work shall terminate as of
the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the Work or
Derivative Works thereof in any medium, with or without modifications, and in
Source or Object form, provided that You meet the following conditions:

(a) You must give any other recipients of the Work or Derivative Works a copy
of this License; and

(b) You must cause any modified files to carry prominent notices stating that
You changed the files; and

(c) You must retain, in the Source form of any Derivative Works that You
distribute, all copyright, patent, trademark, and attribution notices from
the Source form of the Work, excluding those notices that do not pertain to
any part of the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its distribution,
then any Derivative Works that You distribute must include a readable copy of
the attribution notices contained within such NOTICE file, excluding those
notices that do not pertain to any part of the Derivative Works, in at least
one of the following places: within a NOTICE text file distributed as part of
the Derivative Works; within the Source form or documentation, if provided
along with the Derivative Works; or, within a display generated by the
Derivative Works, if and wherever such third-party notices normally appear.
The contents of the NOTICE file are for informational purposes only and do not
modify the License. You may add Your own attribution notices within Derivative
Works that You distribute, alongside or as an addendum to the NOTICE text from
the Work, provided that such additional attribution notices cannot be
construed as modifying the License.

You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a
whole, provided Your use, reproduction, and distribution of the Work otherwise
complies with the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise, any
Contribution intentionally submitted for inclusion in the Work by You to the
Licensor shall be under the terms and conditions of this License, without any
additional terms or conditions. Notwithstanding the above, nothing herein
shall supersede or modify the terms of any separate license agreement you may
have executed with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade names,
trademarks, service marks, or product names of the Licensor, except as
required for reasonable and customary use in describing the origin of the Work
and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
writing, Licensor provides the Work (and each Contributor provides its
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied, including, without limitation, any warranties
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any risks
associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory, whether in
tort (including negligence), contract, or otherwise, unless required by
applicable law (such as deliberate and grossly negligent acts) or agreed to in
writing, shall any Contributor be liable to You for damages, including any
direct, indirect, special, incidental, or consequential damages of any
character arising as a result of this License or out of the use or inability to
use the Work (including but not limited to damages for loss of goodwill, work
stoppage, computer failure or malfunction, or any and all other commercial
damages or losses), even if such Contributor has been advised of the
possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing the Work
or Derivative Works thereof, You may choose to offer, and charge a fee for,
acceptance of support, warranty, indemnity, or other liability obligations
and/or rights consistent with this License. However, in accepting such
obligations, You may act only on Your own behalf and on Your sole
responsibility, not on behalf of any other Contributor, and only if You agree
to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS


path: references/contracts.md
content: # ERC-8004 Smart Contracts

**Repository:** https://github.com/erc-8004/erc-8004-contracts

All contracts are UUPS-upgradeable (OpenZeppelin v5.4.0), using ERC-7201 namespaced storage. Solidity 0.8.24, Shanghai EVM, optimizer enabled (200 runs, viaIR).

## Contract Addresses

Deterministic cross-chain vanity addresses via CREATE2 (SAFE Singleton Factory at [REDACTED_SECRET]).

### Mainnet (Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, Celo, Gnosis, Linea, Mantle, MegaETH, Scroll, Taiko, Monad, BSC)

| Contract | Address |
|----------|---------|
| IdentityRegistry | [REDACTED_SECRET] |
| ReputationRegistry | [REDACTED_SECRET] |
| ValidationRegistry | [REDACTED_SECRET] |

### Testnet (Sepolia, Base Sepolia, + corresponding testnets)

| Contract | Address |
|----------|---------|
| IdentityRegistry | [REDACTED_SECRET] |
| ReputationRegistry | [REDACTED_SECRET] |
| ValidationRegistry | [REDACTED_SECRET] |

**Owner:** [REDACTED_SECRET]

## IdentityRegistryUpgradeable

**Inherits:** `ERC721URIStorageUpgradeable`, `OwnableUpgradeable`, `UUPSUpgradeable`, `EIP712Upgradeable`

**Version:** 2.0.0

### Storage Layout

```solidity
// Slot 0 (shared with MinimalUUPS for upgrade persistence)
address private _identityRegistry;

// ERC-7201 namespaced storage
struct IdentityRegistryStorage {
    uint256 _lastId;
    mapping(uint256 => mapping(string => bytes)) _metadata;  // agentId => key => value
}
```

### Registration Functions

```solidity
// Mint without URI (set later via setAgentURI)
function register() external returns (uint256 agentId)

// Mint with URI
function register(string agentURI) external returns (uint256 agentId)

// Mint with URI and initial metadata
function register(string agentURI, MetadataEntry[] calldata metadata) external returns (uint256 agentId)

struct MetadataEntry {
    string metadataKey;
    bytes metadataValue;
}
```

All variants auto-set `agentWallet` metadata to `msg.sender`. The reserved `agentWallet` key is rejected in the metadata array.

### URI Management

```solidity
function setAgentURI(uint256 agentId, string calldata newURI) external
// Owner or operator only. Emits URIUpdated.
```

### Metadata Functions

```solidity
function getMetadata(uint256 agentId, string memory metadataKey) external view returns (bytes memory)
function setMetadata(uint256 agentId, string memory metadataKey, bytes memory metadataValue) external
// Owner or operator only. Reverts if metadataKey == "agentWallet".
```

### Agent Wallet (EIP-712 Verified)

```solidity
// Requires signature from newWallet (EIP-712 for EOA, ERC-1271 for smart contract wallet)
// Deadline max 5 minutes from current block.timestamp
function setAgentWallet(uint256 agentId, address newWallet, uint256 deadline, bytes calldata signature) external

function getAgentWallet(uint256 agentId) external view returns (address)

function unsetAgentWallet(uint256 agentId) external
```

EIP-712 domain: name `"ERC8004IdentityRegistry"`, version `"1"`. Typed data: `AgentWalletSet(uint256 agentId, address newWallet, address owner, uint256 deadline)`.

On NFT transfer, `agentWallet` is automatically cleared (Checks-Effects-Interactions pattern in `_update`).

### Authorization

```solidity
function isAuthorizedOrOwner(address spender, uint256 agentId) external view returns (bool)
// Used by Reputation and Validation registries to check permissions
```

### Events

```solidity
event Registered(uint256 indexed agentId, string agentURI, address indexed owner)
event MetadataSet(uint256 indexed agentId, string indexed indexedMetadataKey, string metadataKey, bytes metadataValue)
event URIUpdated(uint256 indexed agentId, string newURI, address indexed updatedBy)
```

## ReputationRegistryUpgradeable

**Inherits:** `OwnableUpgradeable`, `UUPSUpgradeable`

**Version:** 2.0.0

### Storage Layout

```solidity
struct Feedback {
    int128 value;       // signed fixed-point (max abs 1e38)
    uint8 valueDecimals; // 0-18
    bool isRevoked;
    string tag1;
    string tag2;
}

struct ReputationRegistryStorage {
    // agentId => clientAddr => feedbackIndex (1-indexed) => Feedback
    mapping(uint256 => mapping(address => mapping(uint64 => Feedback))) _feedback;
    mapping(uint256 => mapping(address => uint64)) _lastIndex;
    // Response tracking (counters only, not stored content)
    mapping(uint256 => mapping(address => mapping(uint64 => mapping(address => uint64)))) _responseCount;
    mapping(uint256 => mapping(address => mapping(uint64 => address[]))) _responders;
    mapping(uint256 => mapping(address => mapping(uint64 => mapping(address => bool)))) _responderExists;
    // Client tracking
    mapping(uint256 => address[]) _clients;
    mapping(uint256 => mapping(address => bool)) _clientExists;
}
```

### Write Functions

```solidity
function giveFeedback(
    uint256 agentId, int128 value, uint8 valueDecimals,
    string calldata tag1, string calldata tag2,
    string calldata endpoint, string calldata feedbackURI, bytes32 feedbackHash
) external
// Self-feedback rejected: calls isAuthorizedOrOwner() and reverts if true.
// endpoint, feedbackURI, feedbackHash emitted but NOT stored.

function revokeFeedback(uint256 agentId, uint64 feedbackIndex) external
// Only the original clientAddress can revoke.

function appendResponse(
    uint256 agentId, address clientAddress, uint64 feedbackIndex,
    string calldata responseURI, bytes32 responseHash
) external
// Anyone can respond. Only counters tracked, not content.
```

### Read Functions

```solidity
function readFeedback(uint256 agentId, address clientAddress, uint64 feedbackIndex)
    external view returns (int128 value, uint8 valueDecimals, string tag1, string tag2, bool isRevoked)
// feedbackIndex is 1-indexed

function getSummary(uint256 agentId, address[] calldata clientAddresses, string tag1, string tag2)
    external view returns (uint64 count, int128 summaryValue, uint8 summaryValueDecimals)
// clientAddresses MUST be non-empty - REVERTS if empty (Sybil protection).
// Callers must supply a trusted reviewer list.
// Empty strings for tag1/tag2 act as wildcards.
// Aggregation: normalizes to 18-decimal WAD, averages, scales to mode precision.

function readAllFeedback(uint256 agentId, address[] calldata clientAddresses,
    string tag1, string tag2, bool includeRevoked) external view returns (...)
// If clientAddresses is empty, uses all tracked clients.

function getResponseCount(uint256 agentId, address clientAddress,
    uint64 feedbackIndex, address[] responders) external view returns (uint64)
// address(0) = all clients; feedbackIndex 0 = all feedbacks.

function getClients(uint256 agentId) external view returns (address[] memory)
function getLastIndex(uint256 agentId, address clientAddress) external view returns (uint64)
```

### Events

```solidity
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)
event FeedbackRevoked(uint256 indexed agentId, address indexed clientAddress, uint64 indexed feedbackIndex)
event ResponseAppended(uint256 indexed agentId, address indexed clientAddress,
    uint64 feedbackIndex, address indexed responder, string responseURI, bytes32 responseHash)
```

## ValidationRegistryUpgradeable

**Inherits:** `OwnableUpgradeable`, `UUPSUpgradeable`

**Version:** 2.0.0

### Storage Layout

```solidity
struct ValidationStatus {
    address validatorAddress;
    uint256 agentId;
    uint8 response;        // 0-100
    bytes32 responseHash;
    string tag;
    uint256 lastUpdate;
    bool hasResponse;
}

struct ValidationRegistryStorage {
    mapping(bytes32 => ValidationStatus) validations;        // requestHash => status
    mapping(uint256 => bytes32[]) _agentValidations;         // agentId => requestHashes
    mapping(address => bytes32[]) _validatorRequests;        // validatorAddress => requestHashes
}
```

### Functions

```solidity
function validationRequest(
    address validatorAddress, uint256 agentId,
    string requestURI, bytes32 requestHash
) external
// Must be called by owner/operator. Duplicate requestHash rejected.

function validationResponse(
    bytes32 requestHash, uint8 response,
    string responseURI, bytes32 responseHash, string tag
) external
// Must be called by the validatorAddress from the request.
// response: 0=failed, 100=passed, intermediate for spectrum.
// Can be called multiple times (progressive finality via tag).

function getValidationStatus(bytes32 requestHash)
    external view returns (address, uint256, uint8, bytes32, string, uint256)

function getSummary(uint256 agentId, address[] calldata validatorAddresses, string tag)
    external view returns (uint64 count, uint8 avgResponse)
// ACCEPTS empty validatorAddresses (includes ALL validators - no filter).
// This differs from ReputationRegistry.getSummary() which REVERTS on empty.
// Only counts requests with hasResponse == true.

function getAgentValidations(uint256 agentId) external view returns (bytes32[] memory)
function getValidatorRequests(address validatorAddress) external view returns (bytes32[] memory)
```

### Events

```solidity
event ValidationRequest(address indexed validatorAddress, uint256 indexed agentId,
    string requestURI, bytes32 indexed requestHash)
event ValidationResponse(address indexed validatorAddress, uint256 indexed agentId,
    bytes32 indexed requestHash, uint8 response,
    string responseURI, bytes32 responseHash, string tag)
```

## Deployment Architecture

### Two-Phase Vanity Deployment

1. Deploy `MinimalUUPS` via CREATE2 (stores `_identityRegistry` at slot 0)
2. Deploy three vanity proxies via CREATE2 pointing to MinimalUUPS
3. Deploy three real implementations via CREATE2
4. Upgrade proxies to real implementations (slot 0 data persists)

This enables deterministic vanity addresses across all EVM chains.

### Dependencies

- OpenZeppelin Contracts Upgradeable v5.4.0
- SAFE Singleton Factory ([REDACTED_SECRET])
- Hardhat v3 with viem v2.38 for testing/deployment

### ABI Files

Pre-built ABI JSON files available in the contracts repository:
- `abis/IdentityRegistry.json`
- `abis/ReputationRegistry.json`
- `abis/ValidationRegistry.json`


path: references/registration.md
content: # Agent Registration Best Practices

**Source:** https://github.com/erc-8004/best-practices

## Registration File Format

Every agent's `agentURI` MUST resolve to a JSON file with this structure:

```json
{
  "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
  "name": "CryptoTrader Alpha",
  "description": "AI agent that analyzes crypto markets using on-chain data. Supports real-time price feeds, portfolio analysis, and trading signals. Pricing: $0.01 per request via x402. Interact via MCP tools or A2A tasks.",
  "image": "https://example.com/cryptotrader-alpha.png",
  "services": [
    {
      "name": "MCP",
      "endpoint": "https://mcp.cryptotrader.example.com",
      "version": "2025-06-18",
      "mcpTools": ["get_price", "analyze_portfolio", "trading_signal"]
    },
    {
      "name": "A2A",
      "endpoint": "https://cryptotrader.example.com/.well-known/agent-card.json",
      "version": "0.3.0",
      "a2aSkills": ["analytical_skills/mathematical_reasoning/quantitative_analysis"]
    },
    {
      "name": "OASF",
      "endpoint": "https://github.com/agntcy/oasf/",
      "version": "v0.8.0",
      "skills": [
        "analytical_skills/mathematical_reasoning/quantitative_analysis",
        "data_engineering/data_transformation_pipeline"
      ],
      "domains": [
        "finance_and_business/investment_services",
        "technology/blockchain_and_web3"
      ]
    },
    {
      "name": "ENS",
      "endpoint": "cryptotrader.eth",
      "version": "v1"
    },
    {
      "name": "agentWallet",
      "endpoint": "eip155:8453:0x1234567890abcdef1234567890abcdef12345678"
    },
    {
      "name": "web",
      "endpoint": "https://cryptotrader.example.com"
    },
    {
      "name": "email",
      "endpoint": "[REDACTED_SECRET]"
    }
  ],
  "registrations": [
    {
      "agentId": 42,
      "agentRegistry": "eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432"
    }
  ],
  "supportedTrust": ["reputation"],
  "active": true,
  "x402Support": true
}
```

## The Four Golden Rules

### Rule 1: Name, Image, Description

- **Name**: Clear and memorable. Should convey what the agent does.
- **Image**: PNG, SVG, or WebP. High quality, distinctive. This is how the agent appears in NFT apps and catalogs.
- **Description**: Elevator pitch covering:
  - What the agent does
  - How to interact with it
  - Pricing information
  - Key capabilities

### Rule 2: Always Include a Service

At minimum, one service endpoint. Service-specific guidelines:

**MCP service:**
- Include `mcpTools` array listing tool names
- Set `version` to the MCP protocol version (e.g., `"2025-06-18"`)

**A2A service:**
- Set `endpoint` to the agent card URL (`/.well-known/agent-card.json`)
- Include `a2aSkills` using OASF slash-separated identifiers

**ENS service:**
- `endpoint` is the ENS name (e.g., `"myagent.eth"`)

**DID service:**
- `endpoint` is the full DID string (e.g., `"did:ethr:0x..."`)

**agentWallet service:**
- Format: `eip155:{chainId}:{address}`
- Can advertise wallets on chains other than where registered

### Rule 3: Declare Skills and Domains Using OASF

OASF (Open Agentic Schema Framework, Linux Foundation) provides standardized taxonomy:

- **Domains**: Fields of application (e.g., `finance_and_business/investment_services`)
- **Skills**: Specific capabilities (e.g., `natural_language_processing/natural_language_generation/summarization`)

Use the `OASF` service entry with `skills` and `domains` arrays. v0.8.0 includes 136 skills and 204 domains.

### Rule 4: Include Registrations Back-Reference

The `registrations` array creates a bidirectional cryptographic link:
- On-chain NFT -> `agentURI` -> registration file
- Registration file -> `registrations` -> on-chain NFT

Both fields are mandatory in each registration entry:
- `agentId`: The ERC-721 tokenId
- `agentRegistry`: `eip155:{chainId}:{identityRegistryAddress}`

## Nice-to-Have Extras

- `"x402Support": true` - Signals support for Coinbase x402 HTTP payment protocol
- `"active": true/false` - Set `false` until agent is tested and ready for discovery
- Set `agentWallet` on-chain via `setAgentWallet()` if payment address differs from owner

## Endpoint Domain Verification

Optional but recommended. Publish `https://{endpoint-domain}/.well-known/agent-registration.json` containing at least a `registrations` list matching the on-chain identity.

Verification logic:
1. File reachable over HTTPS
2. Contains `registrations` entry where `agentRegistry` and `agentId` match on-chain values
3. If endpoint-domain is the same as `agentURI` domain, this check is redundant

## Registration Flows

### IPFS Registration (Recommended)

```typescript
const sdk = new SDK({
  chainId: 84532,
  rpcUrl: '...',
  privateKey: '...',
  ipfs: 'pinata',       // or 'filecoinPin' (free for ERC-8004), or 'node'
  pinataJwt: '...',
});

const agent = sdk.createAgent('MyAgent', 'Description');
await agent.setMCP('https://mcp.example.com');
agent.setActive(true);

const tx = await agent.registerIPFS();
const { result } = await tx.waitConfirmed();
// result.agentId = "84532:42", result.agentURI = "ipfs://Qm..."
```

IPFS providers:
- **Filecoin Pin** (`filecoinPin`): Free for ERC-8004 agents via Protocol Labs
- **Pinata** (`pinata`): Free for ERC-8004 agents via Pinata
- **IPFS Node** (`node`): Self-hosted node

### HTTP Registration

```typescript
const agent = sdk.createAgent('MyAgent', 'Description');
await agent.setMCP('https://mcp.example.com');
const regFile = agent.getRegistrationFile();
// Host regFile JSON at your URL
const tx = await agent.registerHTTP('https://example.com/agent.json');
```

### Updating Registration

```typescript
const agent = await sdk.loadAgent('84532:42');
agent.updateInfo(undefined, 'Updated description');
await agent.setMCP('https://new-mcp.example.com');
const tx = await agent.registerIPFS();  // uploads new file, updates URI
```

## Field Reference

| Field | Required | Description |
|-------|----------|-------------|
| `type` | MUST | `"https://eips.ethereum.org/EIPS/eip-8004#registration-v1"` |
| `name` | MUST | Agent display name |
| `description` | MUST | Natural language description |
| `image` | SHOULD | Agent image URL |
| `services` | SHOULD | Array of service endpoints |
| `services[].name` | MUST | Service type (`MCP`, `A2A`, `OASF`, `ENS`, `DID`, `web`, `email`, `agentWallet`) |
| `services[].endpoint` | MUST | Service URL or identifier |
| `services[].version` | SHOULD | Protocol version |
| `registrations` | SHOULD | On-chain back-references |
| `registrations[].agentId` | MUST | ERC-721 tokenId |
| `registrations[].agentRegistry` | MUST | `eip155:{chainId}:{address}` |
| `supportedTrust` | MAY | Trust model array |
| `active` | MAY | Visibility flag |
| `x402Support` | MAY | Payment protocol flag |


path: references/reputation.md
content: # ERC-8004 Reputation and Feedback System

**Best Practices Source:** https://github.com/erc-8004/best-practices

## Overview

The Reputation Registry stores agent feedback as signed fixed-point numbers on-chain, with optional rich metadata off-chain (IPFS/HTTPS). Anyone can post feedback about any agent, but self-feedback is rejected (owner/operators cannot review their own agent).

## Value/ValueDecimals Encoding

Feedback uses `int128 value` + `uint8 valueDecimals` (0-18):

| tag1 | Measures | Human Example | value | valueDecimals |
|------|----------|---------------|-------|---------------|
| `starred` | Quality rating 0-100 | 87/100 | 87 | 0 |
| `reachable` | Endpoint reachable (binary) | true | 1 | 0 |
| `ownerVerified` | Endpoint owned by agent | true | 1 | 0 |
| `uptime` | Endpoint uptime % | 99.77% | 9977 | 2 |
| `successRate` | Request success rate % | 89% | 89 | 0 |
| `responseTime` | Latency in ms | 560ms | 560 | 0 |
| `blocktimeFreshness` | Avg block delay | 4 blocks | 4 | 0 |
| `revenues` | Cumulative revenues | $560 | 560 | 0 |
| `tradingYield` | Yield (tag2=day/week/month/year) | -3.2% | -32 | 1 |

### 5-Star to 0-100 Mapping (for catalogs)

| Stars | Value |
|-------|-------|
| 1 | 20 |
| 2 | 40 |
| 3 | 60 |
| 4 | 80 |
| 5 | 100 |

## On-Chain Storage

Stored in contract state (queryable by smart contracts):
- `value` (int128)
- `valueDecimals` (uint8)
- `tag1` (string)
- `tag2` (string)
- `isRevoked` (bool)
- `feedbackIndex` (uint64, 1-indexed per clientAddress per agentId)

Emitted in events but NOT stored:
- `endpoint` (string)
- `feedbackURI` (string)
- `feedbackHash` (bytes32)

## Off-Chain Feedback File

Optional file at `feedbackURI` for rich metadata:

```json
{
  "agentRegistry": "eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
  "agentId": 42,
  "clientAddress": "eip155:8453:0xReviewerAddress",
  "createdAt": "2025-09-23T12:00:00Z",
  "value": 85,
  "valueDecimals": 0,
  "tag1": "starred",
  "tag2": "finance",
  "endpoint": "https://mcp.agent.example.com",
  "mcp": {
    "tool": "financial_analyzer"
  },
  "a2a": {
    "skills": ["trading_analysis"],
    "contextId": "ctx-123",
    "taskId": "task-456"
  },
  "oasf": {
    "skills": ["analytical_skills/mathematical_reasoning"],
    "domains": ["finance_and_business"]
  },
  "proofOfPayment": {
    "fromAddress": "0xReviewer...",
    "toAddress": "0xAgent...",
    "chainId": "8453",
    "txHash": "0xPaymentTx..."
  }
}
```

IPFS is recommended for integrity (CID verifies content). For HTTPS files, `feedbackHash` (keccak256) guarantees integrity on-chain.

## SDK Usage

### Give Feedback

```typescript
// Minimal - just value
const tx = await sdk.giveFeedback('84532:42', 85);
await tx.waitConfirmed();

// With tags and endpoint
const tx = await sdk.giveFeedback(
  '84532:42',   // agentId
  85,            // value (0-100 for starred)
  'starred',     // tag1
  'finance',     // tag2
  'https://mcp.example.com',  // endpoint
);

// Full with off-chain file
const feedbackFile = await sdk.prepareFeedbackFile({
  text: 'Accurate market analysis with fast response times',
  capability: 'tools',          // MCP capability type
  name: 'financial_analyzer',   // MCP tool name
  skill: 'trading_analysis',    // A2A skill
  task: 'analyze_portfolio',    // A2A task
  context: { sessionId: 'abc', duration: 1200 },
  proofOfPayment: {
    txHash: '0x...', chainId: '8453',
    fromAddress: '0x...', toAddress: '0x...',
  },
});

const tx = await sdk.giveFeedback('84532:42', 85, 'starred', '', '', feedbackFile);
await tx.waitConfirmed();
```

### Read Feedback

```typescript
// Single feedback entry
const feedback = await sdk.getFeedback('84532:42', '0xClientAddr', 0);
// feedback.value, feedback.tags, feedback.text, feedback.isRevoked

// Search feedback for an agent
const results = await sdk.searchFeedback({
  agentId: '84532:42',
  tags: ['starred'],
});

// Search by reviewer (across all agents, requires subgraph)
const results = await sdk.searchFeedback({
  reviewers: ['0xReviewerAddr'],
});

// Multi-agent search
const results = await sdk.searchFeedback({
  agents: ['84532:42', '84532:43', '84532:44'],
  tags: ['starred'],
});
```

### Reputation Summary

```typescript
const summary = await sdk.getReputationSummary('84532:42');
// { count: 15, averageValue: 87.5 }

// With tag filters
const summary = await sdk.getReputationSummary('84532:42', 'starred', 'finance');
```

### Respond to Feedback

```typescript
// Agent responds to specific feedback
await sdk.appendResponse('84532:42', '0xClientAddr', 0, {
  uri: 'ipfs://QmResponseFile',
  hash: '0x...',
});
```

### Revoke Feedback

```typescript
// Only original submitter can revoke
await sdk.revokeFeedback('84532:42', 0);
```

## On-Chain Aggregation (getSummary)

The `getSummary()` function computes averages on-chain:

1. Normalizes all `value`/`valueDecimals` pairs to 18-decimal WAD
2. Sums all values
3. Finds mode (most common) `valueDecimals` across included feedbacks
4. Computes average
5. Scales back to mode precision

**Anti-Sybil requirement:** `clientAddresses` parameter MUST be non-empty. Callers must supply a trusted reviewer list. Results without filtering are vulnerable to Sybil/spam attacks.

**Tag filtering:** Empty strings for `tag1`/`tag2` act as wildcards (match all).

## Self-Feedback Prevention

The ReputationRegistry calls `isAuthorizedOrOwner()` on the IdentityRegistry before accepting feedback. If the feedback submitter is the agent owner or any approved operator, the transaction reverts.

When agent-to-agent feedback occurs, the reviewing agent SHOULD use its on-chain `agentWallet` as the `clientAddress` for reputation aggregation.

## Feedback ID Format

In the SDK: `"agentId:clientAddress:feedbackIndex"` (e.g., `"84532:42:0x742d...:0"`).

In TypeScript, `Feedback.id` is a tuple `[AgentId, Address, number]`.

The `feedbackIndex` is 0-based in the SDK (converted to 1-based when calling the contract).

## Reputation-Gated Agent Search

The SDK supports finding agents filtered by reputation thresholds:

```typescript
const agents = await sdk.searchAgents({
  active: true,
  hasMCP: true,
  feedback: {
    hasFeedback: true,
    minValue: 80,        // average value >= 80
    maxValue: 100,       // average value <= 100 (upper bound)
    minCount: 5,         // at least 5 feedbacks
    maxCount: 1000,      // at most 1000 feedbacks (upper bound)
    tag1: 'starred',     // only starred feedback
    fromReviewers: ['0xTrustedReviewer'],  // specific reviewers
  },
}, {
  sort: ['averageValue:desc'],
});
```

This uses two-phase filtering: first queries feedback subgraph for matching agentIds, then intersects with the main agent query.

## Ecosystem Patterns

### Agent-as-Rater

When the rater is itself an agent, it SHOULD submit feedback from its on-chain `agentWallet` address so `clientAddress == agentWallet`. This lets UIs resolve the wallet back to an agent identity - finding which agent has that `agentWallet`, then reading its registration file for `name` and `image` to show a profile for the rater.

### Watchtower Monitoring

Trusted third parties (infra companies, watchtowers, data providers) can routinely probe agents and publish periodic signals (e.g., once per week) so anyone can reuse them. Common reliability dimensions:

- `tag1=reachable` - Endpoint reachable (binary)
- `tag1=uptime` - Uptime over a period (percentage)
- `tag1=successRate` - Fraction of successful requests (percentage)
- `tag1=responseTime` - Average latency (milliseconds)

Consumers choose their own trusted sources by filtering feedback by `clientAddress` (e.g., "I trust this watchtower for reachability pings"). Different applications can compose the same underlying signals into dashboards, rankings, alerts, and SLAs.

### Revenue Signals

Cumulative revenue (`tag1=revenues`) helps clients assess whether an agent is battle-tested and in production use. Revenue is NOT directly computable on-chain in a clean way - smart contracts can't conveniently aggregate all payments, and indexers can't reliably distinguish between generic transfers and x402 payments.

In practice, revenue data is best known by the **facilitator** (or specialized infrastructure that interprets payment + service context). These parties publish standardized revenue signals on-chain, making them public so catalogs can filter/weight by trusted `clientAddress`.


path: references/search-discovery.md
content: # Agent Search and Discovery

## Overview

ERC-8004 agents are discoverable through a subgraph (The Graph) that indexes both on-chain data and IPFS registration files. The Agent0 SDK provides a unified search API with multi-chain support, semantic search, and reputation-gated filtering.

## Search Methods

### Basic Search

```typescript
const sdk = new SDK({ chainId: 84532, rpcUrl: '...' });

// All active MCP agents
const agents = await sdk.searchAgents({ hasMCP: true, active: true });

// By name
const agents = await sdk.searchAgents({ name: 'crypto' });

// Get single agent
const agent = await sdk.getAgent('84532:42');
```

### Capability Search

```typescript
// By MCP tools (ANY semantics - matches if at least one listed tool found)
const agents = await sdk.searchAgents({
  mcpTools: ['financial_analyzer', 'price_feed'],
});

// By A2A skills
const agents = await sdk.searchAgents({
  a2aSkills: ['trading_analysis'],
});

// By OASF taxonomy
const agents = await sdk.searchAgents({
  oasfSkills: ['natural_language_processing/summarization'],
  oasfDomains: ['finance_and_business'],
});

// By trust model
const agents = await sdk.searchAgents({
  supportedTrust: ['reputation'],
});
```

### Multi-Chain Search

```typescript
// Default: queries chain 1 + SDK's chainId (de-duplicated)
const agents = await sdk.searchAgents({ active: true });

// Specific chains
const agents = await sdk.searchAgents({
  chains: [1, 8453, 137],  // Mainnet, Base, Polygon
  active: true,
});

// All indexed chains
const agents = await sdk.searchAgents({
  chains: 'all',  // 1, 8453, 137, 11155111, 84532
  active: true,
});
```

Results are merged and sorted client-side. AgentId format includes chainId prefix: `"8453:42"`.

### Semantic Search

```typescript
const agents = await sdk.searchAgents(
  { keyword: 'crypto market analysis' },
  {
    sort: ['semanticScore:desc'],
    semanticMinScore: 0.5,
    semanticTopK: 100,
  }
);
```

Uses external semantic search endpoint (`semantic-search.ag0.xyz`) for vector-based matching, then fetches agents from subgraph by ID.

### Reputation-Gated Search

```typescript
const agents = await sdk.searchAgents({
  active: true,
  feedback: {
    hasFeedback: true,
    minValue: 80,
    minCount: 5,
    tag1: 'starred',
    fromReviewers: ['0xTrustedReviewer'],
    endpoint: 'mcp',  // substring match on endpoint
    hasResponse: true,
  },
}, {
  sort: ['averageValue:desc'],
});
```

Two-phase filtering: queries feedback subgraph first for matching agentIds, then intersects with main query.

### Combined Search

```typescript
const agents = await sdk.searchAgents(
  {
    // Endpoint requirements
    hasMCP: true,
    hasOASF: true,

    // Capability requirements
    mcpTools: ['analyzer'],
    oasfDomains: ['finance_and_business'],

    // Status
    active: true,
    x402support: true,

    // Reputation
    feedback: {
      minValue: 80,
      minCount: 3,
    },

    // Time range
    updatedAtFrom: Math.floor(Date.now() / 1000) - 30 * 86400,  // last 30 days

    // Metadata
    hasMetadataKey: 'verified',
  },
  {
    sort: ['averageValue:desc', 'updatedAt:desc'],
  }
);
```

## Search Filters Reference

### Pushdown Filters (Sent to Subgraph)

| Filter | Type | Description |
|--------|------|-------------|
| `name` | string | Substring match on agent name |
| `description` | string | Substring match |
| `hasMCP` | boolean | Has MCP endpoint |
| `hasA2A` | boolean | Has A2A endpoint |
| `hasWeb` | boolean | Has web endpoint |
| `hasOASF` | boolean | Has OASF endpoint |
| `hasEndpoints` | boolean | Has any endpoints |
| `mcpContains` | string | MCP URL substring |
| `a2aContains` | string | A2A URL substring |
| `ensContains` | string | ENS name substring |
| `didContains` | string | DID substring |
| `mcpTools` | string[] | Match any listed tool |
| `mcpPrompts` | string[] | Match any listed prompt |
| `mcpResources` | string[] | Match any listed resource |
| `a2aSkills` | string[] | Match any listed skill |
| `oasfSkills` | string[] | Match any listed OASF skill |
| `oasfDomains` | string[] | Match any listed OASF domain |
| `supportedTrust` | string[] | Match any listed trust model |
| `active` | boolean | Active status |
| `x402support` | boolean | x402 payment support |
| `chains` | number[] or 'all' | Target chains |
| `agentIds` | string[] | Specific agent IDs |
| `owners` | string[] | Owner addresses |
| `operators` | string[] | Operator addresses |
| `walletAddress` | string | Agent wallet address |
| `registeredAtFrom` | number | Registration timestamp >= |
| `registeredAtTo` | number | Registration timestamp <= |
| `updatedAtFrom` | number | Update timestamp >= |
| `updatedAtTo` | number | Update timestamp <= |

### Two-Phase Prefilters (Separate Query Then Intersect)

| Filter | Type | Description |
|--------|------|-------------|
| `hasMetadataKey` | string | Agent has this metadata key |
| `metadataValue` | string | Metadata value match |
| `keyword` | string | Semantic vector search |
| `feedback.hasFeedback` | boolean | Has any feedback |
| `feedback.hasNoFeedback` | boolean | Has no feedback |
| `feedback.minValue` | number | Average value >= |
| `feedback.maxValue` | number | Average value <= |
| `feedback.minCount` | number | Feedback count >= |
| `feedback.maxCount` | number | Feedback count <= |
| `feedback.tag1` | string | Feedback tag1 filter |
| `feedback.tag2` | string | Feedback tag2 filter |
| `feedback.tag` | string | Matches tag1 OR tag2 |
| `feedback.fromReviewers` | string[] | Specific reviewer addresses |
| `feedback.endpoint` | string | Feedback endpoint substring |
| `feedback.hasResponse` | boolean | Has response from agent |
| `feedback.includeRevoked` | boolean | Include revoked feedback |

### Search Options

```typescript
interface SearchOptions {
  sort?: string[];           // e.g., ["averageValue:desc", "name:asc"]
  semanticMinScore?: number; // keyword searches only (default 0.5)
  semanticTopK?: number;     // keyword searches only
}
```

Sortable fields: `name`, `updatedAt`, `createdAt`, `averageValue`, `feedbackCount`, `semanticScore`, `lastActivity`.

## Subgraph Data Model

### Core Entities

```graphql
type Agent @entity {
  id: ID!                          # "chainId:agentId"
  chainId: Int!
  agentId: BigInt!
  agentURI: String
  agentURIType: String             # "ipfs" or "http"
  owner: Bytes!
  operators: [Bytes!]!
  createdAt: BigInt!
  updatedAt: BigInt!
  registrationFile: AgentRegistrationFile
  feedback: [Feedback!]! @derivedFrom(field: "agent")
  validations: [Validation!]! @derivedFrom(field: "agent")
  metadata: [AgentMetadata!]! @derivedFrom(field: "agent")
  totalFeedback: Int!
  lastActivity: BigInt
}

type AgentRegistrationFile @entity {
  id: ID!
  cid: String
  name: String
  description: String
  image: String
  active: Boolean
  x402Support: Boolean
  supportedTrusts: [String!]
  mcpEndpoint: String
  a2aEndpoint: String
  webEndpoint: String
  emailEndpoint: String
  ens: String
  did: String
  mcpTools: [String!]
  mcpPrompts: [String!]
  mcpResources: [String!]
  a2aSkills: [String!]
  oasfSkills: [String!]
  oasfDomains: [String!]
  hasOASF: Boolean
}

type Feedback @entity {
  id: ID!                          # "chainId:agentId:clientAddress:feedbackIndex"
  agent: Agent!
  clientAddress: Bytes!
  value: BigDecimal
  tag1: String
  tag2: String
  feedbackUri: String
  feedbackURIType: String
  feedbackHash: Bytes
  isRevoked: Boolean!
  createdAt: BigInt!
  feedbackFile: FeedbackFile
  responses: [FeedbackResponse!]! @derivedFrom(field: "feedback")
}

type AgentStats @entity {
  id: ID!                          # "chainId:agentId"
  totalFeedback: Int!
  averageValue: BigDecimal
  totalValidations: Int!
  completedValidations: Int!
  averageValidationScore: BigDecimal
  lastActivity: BigInt
}

type GlobalStats @entity {
  id: ID!                          # "global"
  totalAgents: Int!
  totalFeedback: Int!
  totalValidations: Int!
}
```

### Example GraphQL Queries

**Find all active MCP agents:**

```graphql
{
  agents(
    where: {
      registrationFile_: {
        mcpEndpoint_not: null,
        active: true
      }
    }
    orderBy: updatedAt
    orderDirection: desc
    first: 20
  ) {
    id
    agentId
    owner
    registrationFile {
      name
      description
      mcpEndpoint
      mcpTools
      active
      x402Support
    }
  }
}
```

**Find high-rated agents:**

```graphql
{
  agentStats(
    where: { averageValue_gte: "80.0", totalFeedback_gte: 5 }
    orderBy: averageValue
    orderDirection: desc
  ) {
    id
    totalFeedback
    averageValue
  }
}
```

**Search by capability:**

```graphql
{
  agents(where: { registrationFile_: { mcpTools_contains: ["financial_analyzer"] } }) {
    id
    registrationFile {
      name
      mcpTools
      oasfSkills
      oasfDomains
    }
  }
}
```

**Agent with feedback details:**

```graphql
{
  agent(id: "84532:42") {
    id
    agentId
    owner
    registrationFile { name description mcpEndpoint a2aEndpoint }
    feedback(where: { isRevoked: false }) {
      clientAddress
      value
      tag1
      tag2
      createdAt
      responses { responder createdAt }
    }
  }
}
```

**Global statistics:**

```graphql
{
  globalStats(id: "global") {
    totalAgents
    totalFeedback
    totalValidations
  }
}
```

## Indexed Networks

URL pattern: `https://gateway.thegraph.com/api/<API_KEY>/subgraphs/id/<SUBGRAPH_ID>`

| Network | Chain ID | Subgraph ID |
|---------|----------|-------------|
| Ethereum Mainnet | 1 | [REDACTED_SECRET] |
| Base Mainnet | 8453 | [REDACTED_SECRET] |
| Polygon Mainnet | 137 | [REDACTED_SECRET] |
| Ethereum Sepolia | 11155111 | [REDACTED_SECRET] |
| Base Sepolia | 84532 | [REDACTED_SECRET] |

The SDK embeds sponsored API keys per chain. Override with `subgraphOverrides` per chain in SDK config.

## Architecture Notes

- Subgraph indexes both on-chain events and IPFS registration files
- SDK auto-uses default subgraph URL per chain
- Search returns ALL matching results (no pagination needed)
- Multi-chain results merged and sorted client-side
- Semantic search hits `semantic-search.ag0.xyz`, then subgraph for full data
- Two-phase prefilters (metadata, feedback, keyword) run separate queries then intersect IDs


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)