What is ERC-8004?
ERC-8004 is an open standard for AI agent identity and trust. It gives your agent:
- A portable identity — Your agent exists as an NFT on Ethereum/L2s. It’s yours, not tied to any platform.
- Discoverable endpoints — Other agents and applications can find your MCP server, A2A endpoint, wallet, and more.
- Composable reputation — Feedback from clients builds a track record that follows your agent everywhere.
The Three Registries
| Registry | Purpose |
|---|
| Identity | Stores who agents are and how to reach them |
| Reputation | Records feedback and ratings from clients |
| Validation | Provides independent verification for high-stakes tasks |
This guide focuses on the Identity Registry — registering your agent so it can be discovered.
Quick Start
For example agent cards and ready-to-run scripts, check out PinataCloud/erc8004. For an interactive experience, try the wizard:
npx @pinata/erc8004-wizard
Prerequisites
- Pinata account — Sign up at pinata.cloud, create an API key and a gateway
- Native tokens for gas — Get free testnet tokens from faucets (links in table below)
- Wallet private key — The wallet that will own your agent NFT
Supported Networks
ERC-8004 is deployed on multiple chains with consistent addresses:
| Network | Chain ID | Identity Registry | Faucet |
|---|
| Ethereum Mainnet | 1 | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 | — |
| Ethereum Sepolia | 11155111 | 0x8004A818BFB912233c491871b3d84c89A494BD9e | Alchemy |
| Base Mainnet | 8453 | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 | — |
| Base Sepolia | 84532 | 0x8004A818BFB912233c491871b3d84c89A494BD9e | Alchemy |
| Polygon Mainnet | 137 | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 | — |
| Polygon Amoy | 80002 | 0x8004A818BFB912233c491871b3d84c89A494BD9e | Polygon |
| Monad Mainnet | 143 | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 | — |
| Monad Testnet | 10143 | 0x8004A818BFB912233c491871b3d84c89A494BD9e | Monad |
| BSC Mainnet | 56 | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 | — |
| BSC Testnet | 97 | 0x8004A818BFB912233c491871b3d84c89A494BD9e | BNB Chain |
All mainnets share the same address: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432All testnets share the same address: 0x8004A818BFB912233c491871b3d84c89A494BD9e
Using the Scripts
Clone the repo and install dependencies:
git clone https://github.com/PinataCloud/erc8004
cd erc8004
bun install
cp .env.example .env # fill in your values
Available Scripts
bun run register <agent-card.json> # Register new agent
bun run register <agent-card.json> --update 3 # Update agent #3
bun run verify <agent-id> # Verify registration
bun run check-registration <agent-id> # Check registrations[] matches on-chain
bun run set-wallet <agent-id> # Set payment wallet
bun run unset-wallet <agent-id> # Clear payment wallet
Network Selection
By default, scripts use Ethereum Sepolia. To use a different network:
# Via environment variable
NETWORK=base-mainnet bun run register my-agent.json
# Via CLI flag
bun run register my-agent.json --network base-mainnet
bun run verify 1 --network polygon-mainnet
Registration Flow
Registration has four steps:
- Register on-chain (without URI) → Get your
agentId
- Build agent card with
registrations[] containing your new agentId
- Upload to IPFS via Pinata
- Set agent URI on-chain to point to your IPFS content
Code Example
import { PinataSDK } from "pinata"
import { createPublicClient, createWalletClient, http, parseAbi, parseEventLogs } from "viem"
import { privateKeyToAccount } from "viem/accounts"
import { sepolia } from "viem/chains"
const REGISTRY = "0x8004A818BFB912233c491871b3d84c89A494BD9e"
const AGENT_REGISTRY = `eip155:${sepolia.id}:${REGISTRY}`
const abi = parseAbi([
"function register() external returns (uint256 agentId)",
"function setAgentURI(uint256 agentId, string newURI) external",
"event Registered(uint256 indexed agentId, string agentURI, address indexed owner)",
])
async function registerAgent(name: string, description: string) {
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const publicClient = createPublicClient({ chain: sepolia, transport: http() })
const walletClient = createWalletClient({ account, chain: sepolia, transport: http() })
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: process.env.PINATA_GATEWAY!,
})
// Step 1: Register on-chain to get agent ID
const registerHash = await walletClient.writeContract({
address: REGISTRY,
abi,
functionName: "register",
args: [],
})
const receipt = await publicClient.waitForTransactionReceipt({ hash: registerHash })
const logs = parseEventLogs({ abi, logs: receipt.logs, eventName: "Registered" })
const agentId = logs[0].args.agentId
// Step 2: Build agent card with registrations
const agentCard = {
type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
name,
description,
image: "ipfs://bafkreiaims435hmzeg3l6ixlrlvnei7wept5kmfd6c2ncz3ucl466xhucu",
services: [],
registrations: [{ agentId: Number(agentId), agentRegistry: AGENT_REGISTRY }],
supportedTrust: ["reputation"],
}
// Step 3: Upload to Pinata
const blob = new Blob([JSON.stringify(agentCard, null, 2)], { type: "application/json" })
const file = new File([blob], `agent-${agentId}.json`, { type: "application/json" })
const uploadResult = await pinata.upload.public.file(file)
const tokenUri = `ipfs://${uploadResult.cid}`
// Step 4: Set agent URI on-chain
const setUriHash = await walletClient.writeContract({
address: REGISTRY,
abi,
functionName: "setAgentURI",
args: [agentId, tokenUri],
})
await publicClient.waitForTransactionReceipt({ hash: setUriHash })
return { agentId, tokenUri }
}
Agent Card Schema
| Field | Required | Description |
|---|
type | Yes | Always https://eips.ethereum.org/EIPS/eip-8004#registration-v1 |
name | Yes | Your agent’s display name |
description | Yes | What it does, capabilities, pricing |
image | Yes | Avatar URL (IPFS or HTTPS) |
services | No | Agent endpoints (see Services & IPFS) |
registrations | No | Registry references (populated after registration) |
x402Support | No | Accepts x402 payments (default: false) |
active | No | Currently running (default: true) |
supportedTrust | No | Trust models (see below) |
Trust Models
The supportedTrust array declares how clients can verify your agent:
| Model | Description |
|---|
reputation | Trust based on historical feedback from clients |
crypto-economic | Trust backed by staked tokens that can be slashed |
tee-attestation | Trust verified by Trusted Execution Environment |
Most agents use ["reputation"].
Example Agent Card
{
"type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
"name": "My Agent",
"description": "A helpful AI assistant.",
"image": "ipfs://bafkreiaims435hmzeg3l6ixlrlvnei7wept5kmfd6c2ncz3ucl466xhucu",
"services": [
{ "name": "web", "endpoint": "https://myagent.example.com" },
{ "name": "MCP", "endpoint": "https://mcp.myagent.example.com", "version": "2025-06-18" }
],
"registrations": [
{ "agentId": 5, "agentRegistry": "eip155:11155111:0x8004A818BFB912233c491871b3d84c89A494BD9e" }
],
"supportedTrust": ["reputation"]
}
Endpoint Verification
Agents can prove they control their advertised endpoints by serving a .well-known file.
How it works:
- Agent lists an HTTPS endpoint in their card
- Verifier fetches
https://{domain}/.well-known/agent-registration.json
- File must contain a
registrations[] entry matching the agent
Create /.well-known/agent-registration.json on your domain:
{
"registrations": [
{
"agentId": 5,
"agentRegistry": "eip155:11155111:0x8004A818BFB912233c491871b3d84c89A494BD9e"
}
]
}
Verification isn’t needed if your agent URI and endpoint share the same domain — domain control is already demonstrated.
Payment Wallet
ERC-8004 stores the agent’s payment wallet on-chain (not in the agent card). The wallet address must sign an EIP-712 message proving consent.
// Contract functions
function setAgentWallet(uint256 agentId, address newWallet, uint256 deadline, bytes signature)
function getAgentWallet(uint256 agentId) returns (address)
function unsetAgentWallet(uint256 agentId)
Read the payment wallet:
cast call 0x8004A818BFB912233c491871b3d84c89A494BD9e \
"getAgentWallet(uint256)(address)" <agent-id> \
--rpc-url https://ethereum-sepolia-rpc.publicnode.com
Or use the script:
bun run set-wallet <agent-id> # Set payment wallet
bun run unset-wallet <agent-id> # Clear payment wallet
Updating an Agent
To update metadata, re-upload the agent card and call setAgentURI:
const uploadResult = await pinata.upload.public.file(updatedAgentCardFile)
await walletClient.writeContract({
address: REGISTRY,
abi,
functionName: "setAgentURI",
args: [agentId, `ipfs://${uploadResult.cid}`],
})
Or use the script:
bun run register my-agent.json --update 3 # Update agent #3
Verifying Agents
The verify script fetches agent cards from IPFS via your Pinata gateway and validates them against the ERC-8004 spec:
bun run verify <agent-id>
bun run check-registration <agent-id> # Check registrations[] matches on-chain
To verify agents you didn’t register, set up Gateway Access Controls and add a gateway key to your .env or pass it via CLI: --gateway-key your_token
Resources