This ERC defines a standard interface for verifying AI agents on Ethereum that have been registered via ERC-8004. It enables AI agents to undergo several specialized verification processes defined in this proposal: Ethereum Token Verification (ETV), Media Content Verification (MCV), Solidity Code Verification (SCV), Web Application Verification (WAV), and Wallet Verification (WV). Verification providers implement this standard using Private Data Verification (PDV) to generate Zero-Knowledge Proofs (ZKPs). Detailed verification results are accessible only to the AI agent’s wallet holder and include a unified risk score (0-100) to help users assess the agent’s trustworthiness. Verification attestations can additionally be posted to ERC-8004’s Validation Registry for ecosystem-wide discoverability.
As AI agents become increasingly prevalent in blockchain ecosystems, users need standardized ways to verify their authenticity and trustworthiness. Current solutions are fragmented, with no unified standard for agent registration or verification. This ERC addresses these challenges by providing:
| Term | Definition |
|---|---|
| Agent Wallet | The Ethereum address designated as controlled by the AI agent |
| AI Agent | An autonomous software entity identified by an ERC-8004 Identity Registry token |
| C2PA | Coalition for Content Provenance and Authenticity - the standards body responsible for the open specification used to embed tamper-evident provenance and authenticity information in digital media |
| ETV | Ethereum Token Verification - validates smart contract presence and legitimacy |
| MCV | Media Content Verification - validates the authenticity, provenance, and integrity of digital media |
| OWASP | Open Worldwide Application Security Project - nonprofit organization providing security standards and testing guides for web and smart contract applications |
| PDV | Private Data Verification - generates ZKPs from verification results |
| Proof ID | A unique identifier for a ZKP generated during verification |
| QCV | Quantum Cryptography Verification - provides quantum-resistant encryption for sensitive data |
| Risk Score | A numerical value from 0-100 indicating the assessed risk level, where 0 is the lowest risk, and 100 is the highest risk |
| SCV | Solidity Code Verification - validates Solidity Code security |
| Verification Provider | A service implementing this standard's verification types (ETV, MCV, PDV, QCV, SCV, WAV, WV) |
| WAV | Web Application Verification - checks endpoint security and accessibility |
| WV | Wallet Verification - assesses wallet history and threat database status |
| ZKP | Zero-Knowledge Proof - cryptographic proof that verification occurred without revealing underlying data |
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
The following diagram illustrates the verification process:
Verification requests MUST reference an ERC-8004 agentId (uint256 token ID from the Identity Registry).
The verification provider MUST call tokenURI(agentId) on the canonical Identity Registry contract and resolve the returned URI to fetch the agent registration JSON file. All metadata fields (agentWallet/walletAddress, chain_id, contractAddress, endpoints/url, image/imageUrl, platform_id, solidityCode, etc.) MUST be extracted from this JSON in accordance with the ERC-8004 registration schema.
Direct submission of individual parameters (agentWallet/walletAddress, chain_id, contractAddress, endpoints/url, image/imageUrl, platform_id, solidityCode, etc.) without an agentId is NOT permitted.
Compliant verification providers MUST implement all of the following verification types and apply them using the resolved metadata:
Validates the legitimacy and security of the smart contract when a contractAddress is present in the resolved metadata.
contractAddress is a deployed smart contract on the resolved chain_id by calling eth_getCode and confirming the returned bytecode is not 0xValidates the authenticity, provenance, and integrity of the media content when imageUrl is present in the resolved metadata.
Validates the legitimacy and security of the solidity code when solidityCode is present in the resolved metadata.
solidityCode is deployed on the resolved chain_id by calling eth_getCode and confirming the returned bytecode is not 0xsolidityCode vulnerabilities (reentrancy, flash loan attacks)Ensures the agent's web endpoint is accessible and secure using resolved metadata.
url or endpoints array)Confirms wallet ownership and assesses on-chain risk profile using resolved metadata.
walletAddress/agentWallet)Verification providers MAY post final risk scores and Proof IDs as attestations to the ERC-8004 Validation Registry using its pluggable validation interface. This enables portable, discoverable security attestations alongside reputation signals.
Verification is performed off-chain to:
Verification providers MAY charge fees for verification services. When fees are required:
TransferWithAuthorization for gasless paymentsThe overall risk score MUST be calculated as the mean of all applicable verification scores:
| Tier | Score Range | Description |
|---|---|---|
| Low Risk | 0-20 | Minimal concerns identified |
| Moderate | 21-40 | Some concerns, review recommended |
| Elevated | 41-60 | Notable concerns, caution advised |
| High Risk | 61-80 | Significant concerns detected |
| Critical | 81-100 | Severe concerns, avoid interaction |
Implementations MUST use the following standardized error codes:
| Error Code | Name | Description |
|---|---|---|
0x01 |
InvalidAddress |
Provided address is not a valid Ethereum address |
0x02 |
InvalidURL |
Provided URL is malformed or not HTTPS |
0x03 |
AgentNotFound |
No agent exists with the specified agentId |
0x04 |
VerificationFailed |
Verification provider returned an error |
0x05 |
InsufficientCredits |
No verification credits available |
0x06 |
InvalidProof |
PDV proof validation failed |
0x07 |
ProviderUnavailable |
Verification provider is not responding |
0x08 |
InvalidScore |
Risk score outside valid range (0-100) |
0x09 |
ContractNotFound |
Specified contract does not exist on chain |
0x0A |
SolidityCodeNotFound |
Specified Solidity Code does not exist |
0x0B |
ImageNotFound |
Image could not be resolved or fetched from agent registration metadata |
0x0C |
SteganographyFailed |
Steganographic payload extraction failed (technical error) |
0x0D |
MediaVerificationFailed |
General failure in media integrity or provenance verification |
Implementations SHOULD revert with these error codes:
error InvalidAddress();
error InvalidURL();
error AgentNotFound();
error VerificationFailed();
error InsufficientCredits();
error InvalidProof();
error ProviderUnavailable();
error InvalidScore();
error ContractNotFound();
error SolidityCodeNotFound();
error ImageNotFound();
error SteganographyFailed();
error MediaVerificationFailed();
This ERC is primarily an off-chain standard for verification providers. No on-chain smart contract interface is required to submit verification requests or perform the verification types (ETV, MCV, SCV, WAV, WV, PDV, QCV).
Optional on-chain components MAY be implemented by providers or integrators to:
If an on-chain component is deployed, it SHOULD include at minimum:
```solidity
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;
interface IERC8126 {
/// @notice Emitted when an agent is verified
event AgentVerified(
uint256 indexed agentId, // Token ID (ERC-721) from ERC-8004 Identity Registry
uint8 overallRiskScore,
bytes32 etvProofId,
bytes32 mcvProofId,
bytes32 scvProofId,
bytes32 wavProofId,
bytes32 wvProofId,
bytes32 summaryProofId
);
/// @notice Emitted when an attestation is posted to the ERC-8004 Validation Registry
event AttestationPosted(
uint256 indexed agentId,
uint8 riskScore,
bytes32 proofId
);
/// @notice Optional: Query the latest risk score for an agentId
/// @dev MAY revert if no verification exists
function getLatestRiskScore(uint256 agentId) external view returns (uint8);
}
EIP-155 (Replay Protection): Verification requests involve signed messages from agent owners to authorize providers or payments. Without chain ID inclusion EIP-155, a signed request on mainnet could be replayed on testnets or L2s, potentially triggering unwanted verifications or duplicate payments across chains.
EIP-191 (Signed Data Standard): Wallet verification and request authorization require proving control over the agent wallet. EIP-191 provides a standardised prefix for signed messages, ensuring compatibility across wallets and preventing signature malleability during verification.
EIP-712 (Typed Data Signing): Verification requests use structured data (agentId, metadata hashes, nonce, payment details). EIP-712 enables human-readable signing prompts (e.g., "Authorize Verification for Agent ID: 1234 on chain 1") instead of blind hashes, reducing phishing risks and improving UX for agent owners.
ERC-3009 (Transfer With Authorization): Verification fees are paid via ERC-3009, which enables gasless USDC transfers, with the provider covering gas costs, making verification accessible without requiring users to hold ETH.
ERC-8004 (Trustless Agents Registry): Builds on the canonical ERC-721 Non-Fungible Token Standard (with ERC721URIStorage) for the Identity Registry, where agents are minted as unique NFTs for portable, censorship-resistant identities. ERC-8004 defines agentId (as ERC-721 tokenId), tokenURI'-based metadata resolution (e.g.,name,walletAddress,endpoints,contractAddress` in JSON), optional on-chain metadata, and posting to the Validation Registry for composable trust signals (reputation, proofs, validations) after verification flows complete.
The five verification types are presented in alphabetical order (ETV → MCV → SCV → WAV → WV) for clarity and consistency.
The decision to implement five distinct verification types addresses different aspects of agent authenticity:
imageUrl using C2PA/ provenance frameworks and media tamper detectionA unified 0-100 risk scoring system allows:
This standard intentionally separates the interface specification from implementation details. Any verification provider may implement compliant ETV, MCV, SCV, WAV, WV, PDV and QCV services, enabling:
Verification results are processed through PDV, which generates ZKPs. This privacy-first approach:
Verification providers may implement QCV to quantum-resistant encrypt sensitive verification data.
record_id for encrypted datadecryption_url for authorized data retrievalQCV Key Properties: - Provides future-proof protection against quantum computing threats - Military-grade encryption standards (AES-256-GCM) - Enables secure long-term storage of verification records
This ERC introduces a new standard focused on verification and does not modify any existing standards. It requires agents to be registered via ERC-8004, which provides portable ERC-721-based identities and metadata resolution.
Pre-existing implementations that relied on the original custom registration logic in this ERC would need to migrate by:
tokenURI to a compatible metadata JSON file containing name, description, walletAddress, url, contractAddress, solidityCode, etc.Existing AI agents already registered via ERC-8004 can undergo verification without changes. The standard is designed to work alongside existing token standards (ERC-20, ERC-721, ERC-1155) and identity standards.
contractAddressEnsures ETV succeeds when metadata includes contractAddress.
function testETVCompletesWhenContractAddressPresent() public {
uint256 agentId = 1;
vm.prank(user);
// bytes32 proofId = verifier.verifyETV(agentId);
}
imageUrlEnsures SCV succeeds when imageUrl is present.
function testMCVCompletesWhenimageUrlPresent() public {
uint256 agentId = 42;
vm.prank(user);
// verifier.verifyMCV(agentId);
}
solidityCodeEnsures SCV succeeds when solidityCode is present.
function testSCVCompletesWhensolidityCodePresent() public {
uint256 agentId = 42;
vm.prank(user);
// verifier.verifySCV(agentId);
}
agentIdsVerifies WAV executes correctly for a range of agentIds.
function testWAVCompletesForAllAgentIds() public {
uint256;
ids[0] = 10; ids[1] = 20; ids[2] = 30;
for (uint i = 0; i < ids.length; i++) {
vm.prank(user);
// verifier.verifyWAV(ids[i]);
}
}
agentIdsChecks WV runs successfully across multiple agentIds.
function testWVCompletesForAllAgentIds() public {
uint256;
ids[0] = 10; ids[1] = 20; ids[2] = 30;
for (uint i = 0; i < ids.length; i++) {
vm.prank(user);
// verifier.verifyWV(ids[i]);
}
}
Confirms all proof types are generated for an agentId.
function testGeneratesPDVZKPForEachType() public {
uint256 agentId = 100;
vm.prank(user);
// verifier.verifyAgent(agentId);
}
overallRiskScoreValidates the mean overallRiskScore calculation.
function testCalculatesOverallRiskScore() public {
uint8 score = verifier.calculateRiskScore(10, 20, 30, 40);
assertEq(score, 25);
}
AgentVerified EventChecks event emission with proof IDs.
function testEmitsAgentVerifiedEvent() public {
uint256 agentId = 999;
vm.expectEmit(true, true, true, true);
emit AgentVerified(agentId, 45, [bytes32(1), bytes32(2), bytes32(3), bytes32(4)]);
}
VerificationFailedEnsures provider failure triggers VerificationFailed.
function testRevertsWithVerificationFailed() public {
vm.expectRevert(VerificationFailed.selector);
}
InsufficientCreditsChecks users without credits are blocked via InsufficientCredits.
function testRevertsWithInsufficientCredits() public {
vm.prank(poorUser);
vm.expectRevert(InsufficientCredits.selector);
}
Prevents non-owners from accessing proofs.
function testUnauthorizedProofAccess() public {
vm.prank(0xBBBB);
vm.expectRevert(UnauthorizedAccess.selector);
registry.getAgentProofs(1234);
}
Ensures scores map correctly to RiskTier.
function testRiskScoreTiers() public {
assertEq(verifier.getRiskTier(15), RiskTier.Low);
assertEq(verifier.getRiskTier(35), RiskTier.Moderate);
}
import crypto from 'crypto';
import { createPublicClient, http, getAddress, parseAbi } from 'viem';
class ERC8126Error extends Error {
constructor(
public code: number,
message: string
) {
super(message);
this.name = 'ERC8126Error';
}
}
enum VerificationStatus {
Passed = 'passed',
Failed = 'failed',
Inconclusive = 'inconclusive',
}
enum RiskTier {
Low = 'low',
Moderate = 'moderate',
Elevated = 'elevated',
HighRisk = 'high',
Critical = 'critical',
}
interface AgentMetadata {
contractAddress?: string;
solidityCode?: string;
url?: string;
walletAddress?: string;
chain_id?: number;
[key: string]: any;
}
interface VerificationProviderConfig {
chainId?: number;
identityRegistry?: string;
validationRegistry?: string;
rpcUrl?: string;
}
interface VerificationResult {
type: string;
status: VerificationStatus;
score: number;
proofId: string;
}
const ERC8004_ABI = parseAbi([
'function tokenURI(uint256 tokenId) external view returns (string)',
]);
async function resolveAgentMetadata(
agentId: bigint,
config: VerificationProviderConfig
): Promise<AgentMetadata> {
if (!config.identityRegistry) {
throw new ERC8126Error(0x03, 'ERC-8004 Identity Registry address is required');
}
if (!config.rpcUrl) {
throw new ERC8126Error(0x04, 'RPC URL is required to resolve ERC-8004 metadata');
}
const client = createPublicClient({
chain: { id: config.chainId || 1, name: 'Ethereum', nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 } },
transport: http(config.rpcUrl),
});
try {
const uri = await client.readContract({
address: getAddress(config.identityRegistry),
abi: ERC8004_ABI,
functionName: 'tokenURI',
args: [agentId],
});
if (!uri || typeof uri !== 'string') {
throw new ERC8126Error(0x05, 'Invalid tokenURI returned from ERC-8004 registry');
}
const response = await fetch(uri);
if (!response.ok) {
throw new ERC8126Error(0x06, `Failed to fetch metadata from ${uri}`);
}
const metadata: AgentMetadata = await response.json();
return {
contractAddress: metadata.contractAddress ? getAddress(metadata.contractAddress) : undefined,
imageUrl: metadata.imageUrl ? getAddress(metadata.imageUrl) : undefined,
solidityCode: metadata.solidityCode ? getAddress(metadata.solidityCode) : undefined,
walletAddress: metadata.walletAddress ? getAddress(metadata.walletAddress) : undefined,
url: metadata.url,
chain_id: metadata.chain_id,
...metadata,
};
} catch (error) {
if (error instanceof ERC8126Error) throw error;
throw new ERC8126Error(0x07, `Metadata resolution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async function ETV(m: AgentMetadata, c: VerificationProviderConfig): Promise<VerificationResult> {
return {
type: 'ETV',
status: VerificationStatus.Passed,
score: 25,
proofId: generateProofId('ETV', m.contractAddress || ''),
};
}
async function MCV(m: AgentMetadata, c: VerificationProviderConfig): Promise<VerificationResult> {
return {
type: 'MCV',
status: VerificationStatus.Passed,
score: 30,
proofId: generateProofId('MCV', m.imageUrl || ''),
};
}
async function SCV(m: AgentMetadata, c: VerificationProviderConfig): Promise<VerificationResult> {
return {
type: 'SCV',
status: VerificationStatus.Passed,
score: 30,
proofId: generateProofId('SCV', m.solidityCode || ''),
};
}
async function WAV(m: AgentMetadata): Promise<VerificationResult> {
return {
type: 'WAV',
status: VerificationStatus.Passed,
score: 15,
proofId: generateProofId('WAV', m.url || ''),
};
}
async function WV(m: AgentMetadata): Promise<VerificationResult> {
return {
type: 'WV',
status: VerificationStatus.Passed,
score: 20,
proofId: generateProofId('WV', m.walletAddress || ''),
};
}
function calculateOverallRiskScore(scores: number[]): number {
const valid = scores.filter((s) => s >= 0 && s <= 100);
return valid.length ? Math.round(valid.reduce((a, b) => a + b, 0) / valid.length) : 0;
}
function getRiskTier(score: number): RiskTier {
if (score <= 20) return RiskTier.Low;
if (score <= 40) return RiskTier.Moderate;
if (score <= 60) return RiskTier.Elevated;
if (score <= 80) return RiskTier.HighRisk;
return RiskTier.Critical;
}
function generatePDVProof(result: any): string {
return '0x' + crypto.createHash('sha256').update(JSON.stringify(result) + Date.now().toString()).digest('hex');
}
async function QCV(data: any) {
return {
recordId: '0x' + crypto.createHash('sha256').update(Date.now().toString()).digest('hex'),
algorithm: 'AES-256-GCM',
};
}
export async function verifyAgent(agentId: bigint, config: VerificationProviderConfig) {
const metadata = await resolveAgentMetadata(agentId, config);
const [etv, mcv, scv, wav, wv] = await Promise.all([
ETV(metadata, config),
MCV(metadata, config),
SCV(metadata, config),
WAV(metadata),
WV(metadata),
]);
const overallRiskScore = calculateOverallRiskScore([etv.score, mcv.score, scv.score, wav.score, wv.score]);
const riskTier = getRiskTier(overallRiskScore);
const pdvProofId = generatePDVProof({ etv, mcv, scv, wav, wv, overallRiskScore, agentId: agentId.toString() });
const qcvRecord = await QCV({ overallRiskScore, pdvProofId });
const validationRecord = config.validationRegistry
? await submitToValidationRegistry(agentId, overallRiskScore, pdvProofId, config)
: null;
return {
agentId,
overallRiskScore,
riskTier,
etv,
mcv,
scv,
wav,
wv,
pdvProofId,
qcvRecord,
validationRecord,
verifiedAt: new Date().toISOString(),
};
}
async function submitToValidationRegistry(
agentId: bigint,
score: number,
pdvProofId: string,
config: VerificationProviderConfig
) {
return pdvProofId;
}
function generateProofId(type: string, data: string): string {
const input = `${type}:${data}:${Date.now()}`;
return '0x' + crypto.createHash('sha256').update(input).digest('hex');
}
export { verifyAgent };
Users should consider that verification through this standard indicates the agent has passed specific technical checks at a point in time, but does not guarantee the agent's future behavior or intentions. Risk scores provide guidance, but users should exercise their own judgment.
Agents must ensure that their registered wallet addresses are secure. Compromise of a wallet could allow an attacker to impersonate a legitimate agent. Re-verification is available to update risk scores.
If an agent's URL is compromised after registration, the attacker could serve malicious content. Users should consider verifying agents' current status before interacting with them. WAV re-verification can detect compromised endpoints.
For agents with registered contract addresses, standard smart contract security considerations apply. ETV and SCV provide initial verification, but users should consider auditing any contracts they interact with.
PDV implementations SHOULD use established ZKP systems with proven security properties:
Current cryptographic primitives face potential threats from quantum computing:
Users must evaluate their trust in the verification providers they choose. Different providers may have varying levels of thoroughness, independence, and reliability. ZKPs generated by PDV provide verifiable evidence of verification completion that can be independently validated.
Reliance on ERC-8004 registries introduces a dependency risk; implementations must use the canonical deployed addresses and verify the integrity of tokenURIs.
Copyright and related rights waived via CC0.