Introduction
The AI agent economy is coming, and it has a trust problem.
Autonomous AI agents can already call APIs, execute on-chain transactions, route payments, and coordinate with other agents across complex workflows. But there's no standard answer to a deceptively simple question: Who is this agent, and can I trust it?
ERC-8004, titled Trustless Agents, This article breaks down what ERC-8004 is, how its three registries work, and walks through a live Solidity smart contract that demonstrates agent registration and reputation on XDC Network.
What Is ERC-8004?
Imagine you're hiring a contractor for a big job. Before signing anything, you'd check their license, read reviews from past clients, and maybe ask for references who can vouch for their work. You wouldn't just hand over the keys based on a business card.
AI agents need exactly the same thing — and ERC-8004 is the standard that makes it possible on-chain.
.
ERC-8004, titled Trustless Agents,is an open standard that gives every AI agent three things: a verified identity, a track record, and proof that its work is correct.** It is not a token, not a payment protocol, and not a communication framework. It is purely a trust layer — the on-chain equivalent of a license, a review history, and a third-party audit, all in one.
Here is why this matters right now. AI agents today can call APIs, execute smart contracts, route payments, and coordinate complex workflows autonomously. But when Agent A from Company X wants to hire Agent B from Company Y to complete a task, there is no standard way to answer:
Is this agent who it claims to be?
Has it done this kind of work before, and done it well?
Can I verify that the work it just completed is correct, without trusting it blindly?
ERC-8004 answers all three questions through three lightweight on-chain registries:
| Registry | What It Does | Think of It As |
|---|---|---|
| Identity Registry | Gives the agent a permanent, verifiable on-chain ID (as an NFT) linked to a metadata file describing its capabilities and endpoints | The agent's passport and business profile |
| Reputation Registry | Lets anyone who hired the agent post verified feedback — scores, tags, and evidence — that anyone can read on-chain | The agent's Trustpilot / review history |
| Validation Registry | Allows independent parties to confirm the agent's work was done correctly, using staked economic proofs, zero-knowledge proofs, or hardware attestations | The agent's third-party audit certificate |
The Trade Finance DApp: What It Is and How ERC-8004 Powers It
To make ERC-8004 concrete, this article uses a Trade Finance Agent as the running example. Let's break down exactly what that dApp does and where ERC-8004 fits in each step.
What Is the Trade Finance DApp?
Trade finance is the infrastructure that makes international commerce work — letters of credit, invoice financing, shipping document verification, and cross-border payment settlement. Today this process is slow, paper-heavy, and requires trusting a chain of intermediaries: banks, brokers, freight forwarders, and compliance officers.
The XDC Trade Finance Agent is an autonomous AI agent that replaces several of these intermediaries. Here is what it does:
Step 1 — Quote Sourcing A buyer submits a trade request (e.g., "I need financing for a $500,000 shipment of electronics from Singapore to Dubai"). The agent autonomously queries multiple financing providers — banks, alternative lenders, tokenized credit pools — and returns a ranked list of quotes with rates, terms, and collateral requirements.
Step 2 — Document Verification The agent reads the submitted trade documents (Bill of Lading, Commercial Invoice, Letter of Credit draft) and verifies that they are consistent, correctly formatted, and match the terms of the financing request. This is work that currently takes compliance teams days to do manually.
Step 3 — On-Chain Settlement Once a quote is selected and documents verified, the agent triggers settlement on XDC — either releasing tokenized funds from a smart contract escrow, minting a tokenized invoice NFT, or initiating a cross-border XRPL/XDC bridge transaction. Payments flow via x402 micropayments so the agent earns fees atomically per task completed.
Step 4 — Feedback Loop After settlement, the counterparty posts a feedback entry to the Reputation Registry. Was the quote accurate? Were the documents verified correctly? Did settlement happen on time? This builds the agent's on-chain track record.
Where ERC-8004 Gets Used at Each Step
| DApp Step | Which Registry | What Happens |
|---|---|---|
| Agent is deployed | Identity Registry | Developer calls register(), agent gets an NFT with agentId. Registration file published to IPFS listing MCP endpoint and x402 support. |
| Buyer discovers the agent | Identity Registry | Buyer queries the registry, reads the agent's registration file, sees its MCP endpoint and supportedTrust: ["reputation", "crypto-economic"] |
| Buyer evaluates trust | Reputation Registry | Buyer calls getAverageScore(agentId) and getFeedback(agentId), filters by tag1: "trade-finance". Sees 96.5 average from 40 past transactions. |
| Buyer sends payment | Identity Registry | Buyer reads getAgentWallet(agentId) to get the verified payment address. Sends x402 payment to that address. |
| Agent completes task | (Off-chain execution) | Agent sources quotes, verifies documents, triggers settlement |
| Buyer posts review | Reputation Registry | Buyer calls giveFeedback() with score, tags ("invoice-verified", "on-time"), and IPFS link to x402 payment receipt as evidence |
| High-value deal (>$1M) | Validation Registry | Stakers re-verify the document check results. Validation proof recorded on-chain before escrow releases funds. |
| Agent is sold/transferred | Identity Registry | NFT transferred. agentWallet auto-cleared. New owner sets new payment address with EIP-712 proof. All reputation history stays intact. |
Why ERC-8004 Is the Right Fit for Trade Finance
Without ERC-8004, every new counterparty that wants to use the Trade Finance Agent has to trust it blindly — or build their own out-of-band verification process. With ERC-8004:
Banks and lenders can verify the agent's on-chain identity before exposing their quote APIs
Smart contract escrows can gate fund release on a minimum reputation score
Compliance systems can require Validation Registry proofs before accepting document verification results
Agent marketplaces can list and rank agents by their on-chain track record — no central rating authority needed
This is precisely why XDC Network is a strong home for this pattern. XDC already has the enterprise connections, the trade finance use cases, and the x402 payment infrastructure. ERC-8004 adds the trust layer that makes autonomous agent execution safe enough for institutional counterparties to accept.
Live Smart Contract: XDC Agent Registry
Below is a complete, deployable Solidity implementation of a simplified ERC-8004-compatible Identity + Reputation registry, designed for XDC Network. This is intentionally self-contained for demonstration purposes — production deployments should use the audited canonical contracts.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
/**
* @title XDCAgentRegistry
* @notice ERC-8004 compatible agent Identity + Reputation registry for XDC Network.
* @dev Implements Identity Registry (ERC-721 based) and Reputation Registry.
* Chain ID: 50 (XDC Mainnet), 51 (XDC Apothem Testnet)
* Gas: 12.5 gwei minimum. Legacy Type 0 transactions only on XDC.
* RPC: https://rpc.ankr.com/xdc (recommended over rpc.xinfin.network for VPS)
*/
contract XDCAgentRegistry is ERC721URIStorage, EIP712 {
using ECDSA for bytes32;
// ─── Events ───────────────────────────────────────────────────────────────
event AgentRegistered(uint256 indexed agentId, string agentURI, address indexed owner);
event AgentURIUpdated(uint256 indexed agentId, string newURI, address indexed updatedBy);
event MetadataSet(uint256 indexed agentId, string indexed indexedKey, string key, bytes value);
event AgentWalletSet(uint256 indexed agentId, address newWallet);
event FeedbackGiven(
uint256 indexed agentId,
address indexed clientAddress,
int128 value,
uint8 valueDecimals,
string tag1,
string tag2,
string endpoint,
string feedbackURI,
bytes32 feedbackHash
);
// ─── Storage ──────────────────────────────────────────────────────────────
uint256 private _nextAgentId;
// agentId => metadataKey => metadataValue
mapping(uint256 => mapping(string => bytes)) private _metadata;
// agentId => payment wallet (requires EIP-712 proof to set)
mapping(uint256 => address) private _agentWallet;
// agentId => list of feedback entries
struct FeedbackEntry {
address clientAddress;
int128 value;
uint8 valueDecimals;
string tag1;
string tag2;
string endpoint;
string feedbackURI;
bytes32 feedbackHash;
uint256 timestamp;
}
mapping(uint256 => FeedbackEntry[]) private _feedback;
// EIP-712 typehash for agentWallet change authorization
bytes32 private constant AGENT_WALLET_TYPEHASH = keccak256(
"AgentWallet(uint256 agentId,address newWallet,uint256 deadline)"
);
// ─── Constructor ──────────────────────────────────────────────────────────
constructor()
ERC721("XDC Agent Registry", "XAGENT")
EIP712("XDCAgentRegistry", "1")
{
_nextAgentId = 1;
}
// ─── Identity Registry ────────────────────────────────────────────────────
/**
* @notice Register a new agent and mint an ERC-721 token.
* @param agentURI IPFS, HTTPS, or data URI pointing to the agent registration JSON file.
* @return agentId The ERC-721 tokenId assigned to this agent.
*/
function register(string calldata agentURI) external returns (uint256 agentId) {
agentId = _nextAgentId++;
_safeMint(msg.sender, agentId);
_setTokenURI(agentId, agentURI);
// Initialize agentWallet to owner address
_agentWallet[agentId] = msg.sender;
emit MetadataSet(agentId, "agentWallet", "agentWallet", abi.encode(msg.sender));
emit AgentRegistered(agentId, agentURI, msg.sender);
}
/**
* @notice Register without URI (set later via setAgentURI).
*/
function register() external returns (uint256 agentId) {
agentId = _nextAgentId++;
_safeMint(msg.sender, agentId);
_agentWallet[agentId] = msg.sender;
emit AgentRegistered(agentId, "", msg.sender);
}
/**
* @notice Update the agent's registration file URI.
* @dev Only owner or approved operator may call.
*/
function setAgentURI(uint256 agentId, string calldata newURI) external {
require(_isApprovedOrOwner(msg.sender, agentId), "Not authorized");
_setTokenURI(agentId, newURI);
emit AgentURIUpdated(agentId, newURI, msg.sender);
}
/**
* @notice Set arbitrary on-chain metadata for an agent.
* @dev "agentWallet" key is reserved — use setAgentWallet() instead.
*/
function setMetadata(
uint256 agentId,
string calldata metadataKey,
bytes calldata metadataValue
) external {
require(_isApprovedOrOwner(msg.sender, agentId), "Not authorized");
require(
keccak256(bytes(metadataKey)) != keccak256(bytes("agentWallet")),
"agentWallet is reserved"
);
_metadata[agentId][metadataKey] = metadataValue;
emit MetadataSet(agentId, metadataKey, metadataKey, metadataValue);
}
/**
* @notice Read on-chain metadata for an agent.
*/
function getMetadata(uint256 agentId, string memory metadataKey)
external
view
returns (bytes memory)
{
return _metadata[agentId][metadataKey];
}
/**
* @notice Update the agent's payment wallet with EIP-712 proof of new wallet control.
* @param agentId The agent token ID.
* @param newWallet The address to receive payments.
* @param deadline Signature expiry timestamp.
* @param signature EIP-712 signature from newWallet proving control.
*/
function setAgentWallet(
uint256 agentId,
address newWallet,
uint256 deadline,
bytes calldata signature
) external {
require(_isApprovedOrOwner(msg.sender, agentId), "Not authorized");
require(block.timestamp <= deadline, "Signature expired");
bytes32 structHash = keccak256(
abi.encode(AGENT_WALLET_TYPEHASH, agentId, newWallet, deadline)
);
bytes32 digest = _hashTypedDataV4(structHash);
address signer = digest.recover(signature);
require(signer == newWallet, "Invalid wallet signature");
_agentWallet[agentId] = newWallet;
emit AgentWalletSet(agentId, newWallet);
emit MetadataSet(agentId, "agentWallet", "agentWallet", abi.encode(newWallet));
}
/**
* @notice Get the current payment wallet for an agent.
*/
function getAgentWallet(uint256 agentId) external view returns (address) {
address wallet = _agentWallet[agentId];
return wallet == address(0) ? ownerOf(agentId) : wallet;
}
/**
* @notice Clear the agent wallet (resets to owner address).
*/
function unsetAgentWallet(uint256 agentId) external {
require(_isApprovedOrOwner(msg.sender, agentId), "Not authorized");
delete _agentWallet[agentId];
emit AgentWalletSet(agentId, address(0));
}
// Override transfer to clear agentWallet (ERC-8004 requirement)
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal virtual override {
super._beforeTokenTransfer(from, to, tokenId, batchSize);
if (from != address(0)) {
// Clear payment wallet on transfer — new owner must re-verify
delete _agentWallet[tokenId];
}
}
// ─── Reputation Registry ──────────────────────────────────────────────────
/**
* @notice Submit feedback for an agent after an interaction.
* @dev Caller must NOT be the agent owner or operator (prevents self-dealing).
* @param agentId The agent being rated.
* @param value Signed fixed-point score (e.g., 9500 for 95.00 with valueDecimals=2).
* @param valueDecimals Decimal places of the value (0–18).
* @param tag1 Optional tag (e.g., "trade-finance", "fast-delivery").
* @param tag2 Optional secondary tag.
* @param endpoint Optional endpoint URI the feedback applies to.
* @param feedbackURI Optional IPFS/HTTPS link to extended feedback JSON.
* @param feedbackHash KECCAK-256 of feedbackURI content (0x0 if IPFS or omitted).
*/
function giveFeedback(
uint256 agentId,
int128 value,
uint8 valueDecimals,
string calldata tag1,
string calldata tag2,
string calldata endpoint,
string calldata feedbackURI,
bytes32 feedbackHash
) external {
require(_exists(agentId), "Agent does not exist");
require(valueDecimals <= 18, "valueDecimals exceeds 18");
// Prevent owner/operator from self-rating
require(
!_isApprovedOrOwner(msg.sender, agentId),
"Agent owner/operator cannot give feedback"
);
_feedback[agentId].push(FeedbackEntry({
clientAddress: msg.sender,
value: value,
valueDecimals: valueDecimals,
tag1: tag1,
tag2: tag2,
endpoint: endpoint,
feedbackURI: feedbackURI,
feedbackHash: feedbackHash,
timestamp: block.timestamp
}));
emit FeedbackGiven(agentId, msg.sender, value, valueDecimals, tag1, tag2, endpoint, feedbackURI, feedbackHash);
}
/**
* @notice Get all feedback entries for an agent.
*/
function getFeedback(uint256 agentId)
external
view
returns (FeedbackEntry[] memory)
{
return _feedback[agentId];
}
/**
* @notice Get feedback count for an agent.
*/
function getFeedbackCount(uint256 agentId) external view returns (uint256) {
return _feedback[agentId].length;
}
/**
* @notice Compute a simple on-chain average score for an agent.
* @dev Returns scaled integer: divide by (10 ** valueDecimals) for the float equivalent.
* This is a naive average — production systems should use off-chain aggregation.
* @return avgValue Average score value
* @return decimals The common decimal precision used (from first feedback entry)
* @return count Number of feedback entries included
*/
function getAverageScore(uint256 agentId)
external
view
returns (int128 avgValue, uint8 decimals, uint256 count)
{
FeedbackEntry[] storage entries = _feedback[agentId];
count = entries.length;
if (count == 0) return (0, 0, 0);
decimals = entries[0].valueDecimals;
int256 sum = 0;
for (uint256 i = 0; i < count; i++) {
sum += int256(entries[i].value);
}
avgValue = int128(sum / int256(count));
}
/**
* @notice Get total registered agents.
*/
function totalAgents() external view returns (uint256) {
return _nextAgentId - 1;
}
}
Deployment on XDC Network (Hardhat)
hardhat.config.js
****require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: { enabled: true, runs: 200 },
viaIR: true // Required for complex contracts to avoid "Stack too deep"
}
},
apothem: {
url: "https://rpc.apothem.network",
accounts: [process.env.PRIVATE_KEY],
chainId: 51,
gasPrice: 12500000000
}
}
};
XDC-specific reminders:
Chain ID: 50 (mainnet), 51 (Apothem testnet)
Minimum gas price: 12.5 gwei (12500000000 wei)
Use Legacy Type 0 transactions — XDC does not support EIP-1559 Type 2
estimateGas can be unreliable — set explicit gas limits for critical txns
deploy.js
****const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying from:", deployer.address);
const Registry = await ethers.getContractFactory("XDCAgentRegistry");
const registry = await Registry.deploy({
gasPrice: ethers.parseUnits("12.5", "gwei"),
type: 0 // Legacy transaction (required on XDC)
});
await registry.waitForDeployment();
const address = await registry.getAddress();
console.log("XDCAgentRegistry deployed at:", address);
console.log("Agent Registry ID: eip155:50:" + address);
}
main().catch(console.error);
Live Deployment on XDC Apothem
The contract is deployed and live on XDC Apothem Testnet:
| Field | Value |
|---|---|
| Contract Address | 0x1B34Db6aCF3372db67B3E28abB10Bca54b966D91 |
| Network | XDC Apothem Testnet (Chain ID: 51) |
| Agent Registry ID | eip155:51:0x1B34Db6aCF3372db67B3E28abB10Bca54b966D91 |
| Explorer | testnet.xdcscan.com/address/0x1B34Db6aCF3372db67B3E28abB10Bca54b966D91 |
| Compiler | Solidity 0.8.24, viaIR: true, EVM: cancun |
| Deployer | 0x828dDD2139D59f7f324BDFE7E38FF74425DF1360 |
Interacting with the Registry
Register an Agent
****const agentRegistrationJSON = {
type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
name: "XDCTradeFinanceAgent",
description: "Autonomous agent for trade finance quote sourcing and settlement on XDC Network",
image: "https://example.com/agent.png",
services: [
{ name: "MCP", endpoint: "https://mcp.myagent.xdc/", version: "2025-06-18" },
{ name: "A2A", endpoint: "https://myagent.xdc/.well-known/agent-card.json" }
],
x402Support: true,
active: true,
supportedTrust: ["reputation"]
};
// Upload JSON to IPFS (or encode as base64 data URI for fully on-chain storage)
const agentURI = "ipfs://QmYourCIDHere";
const tx = await registry.register(agentURI, {
gasPrice: ethers.parseUnits("12.5", "gwei"),
type: 0
});
const receipt = await tx.wait();
// Parse the AgentRegistered event to get agentId
const event = receipt.logs.find(l => l.fragment?.name === "AgentRegistered");
const agentId = event.args.agentId;
console.log("Agent registered with ID:", agentId.toString());
Submit Feedback After an Interaction
****// Score: 9750 with 2 decimals = 97.50 out of 100
const value = 9750n;
const valueDecimals = 2;
await registry.giveFeedback(
agentId,
value,
valueDecimals,
"trade-finance", // tag1
"on-time-delivery", // tag2
"https://mcp.myagent.xdc/", // endpoint
"ipfs://QmFeedbackCID", // extended feedback JSON (optional)
ethers.ZeroHash, // feedbackHash (0x0 for IPFS URIs)
{
gasPrice: ethers.parseUnits("12.5", "gwei"),
type: 0
}
);
Read On-Chain Reputation
****const [avgValue, decimals, count] = await registry.getAverageScore(agentId);
const humanScore = Number(avgValue) / Math.pow(10, Number(decimals));
console.log(`Average score: ${humanScore.toFixed(2)} (from ${count} reviews)`);
// Get full feedback history
const feedbackEntries = await registry.getFeedback(agentId);
feedbackEntries.forEach((entry, i) => {
const score = Number(entry.value) / Math.pow(10, Number(entry.valueDecimals));
console.log(`Review #${i + 1}: ${score.toFixed(2)} | tags: ${entry.tag1}, ${entry.tag2}`);
});
Verify Agent Identity
****// Construct the global agent identifier
const chainId = 51;
const registryAddress = await registry.getAddress();
const globalAgentId = `eip155:${chainId}:${registryAddress}`;
console.log("Agent Registry:", globalAgentId);
console.log("Agent ID:", agentId.toString());
console.log("Agent URI:", await registry.tokenURI(agentId));
console.log("Payment Wallet:", await registry.getAgentWallet(agentId));
console.log("Total Agents:", await registry.totalAgents());
The Trust Model in Practice
ERC-8004's trust is tiered by stake, not binary:
Low-stakes task (e.g., summarize a document)
└── Reputation Registry suffices
└── Check average score, filter by tag
Medium-stakes task (e.g., route a DeFi swap)
└── Crypto-economic validation
└── Stakers re-execute and stake tokens on correctness
High-stakes task (e.g., release escrow funds, medical diagnosis)
└── zkML proof or TEE attestation
└── Cryptographic proof of correct model inference
For XDC's trade finance use case, this maps naturally:
Letter of Credit validation → Reputation (human reviewer feedback)
Invoice tokenization → Crypto-economic (staker-backed re-execution)
Cross-border settlement → TEE attestation (confidential computation)
Conclusion
ERC-8004 is to AI agents what ERC-20 was to tokens — foundational, composable, and deliberately minimal. By separating identity, reputation, and validation into lightweight on-chain primitives, it creates a neutral trust layer that any agent, on any EVM chain, can plug into.
For XDC Network, this is infrastructure that matters. As trade finance, RWA tokenization, and cross-border settlement increasingly involve autonomous agents — agents that hire other agents, negotiate terms, and execute transactions without human in the loop — the chain they anchor their identity on becomes their trust foundation.
The smart contract above is a starting point. Drop it on Apothem, register your first agent, and start building reputation from your very first x402 payment.
Discussion (0)