Skip to main content

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

RegistryPurpose
IdentityStores who agents are and how to reach them
ReputationRecords feedback and ratings from clients
ValidationProvides 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

  1. Pinata account — Sign up at pinata.cloud, create an API key and a gateway
  2. Native tokens for gas — Get free testnet tokens from faucets (links in table below)
  3. Wallet private key — The wallet that will own your agent NFT

Supported Networks

ERC-8004 is deployed on multiple chains with consistent addresses:
NetworkChain IDIdentity RegistryFaucet
Ethereum Mainnet10x8004A169FB4a3325136EB29fA0ceB6D2e539a432
Ethereum Sepolia111551110x8004A818BFB912233c491871b3d84c89A494BD9eAlchemy
Base Mainnet84530x8004A169FB4a3325136EB29fA0ceB6D2e539a432
Base Sepolia845320x8004A818BFB912233c491871b3d84c89A494BD9eAlchemy
Polygon Mainnet1370x8004A169FB4a3325136EB29fA0ceB6D2e539a432
Polygon Amoy800020x8004A818BFB912233c491871b3d84c89A494BD9ePolygon
Monad Mainnet1430x8004A169FB4a3325136EB29fA0ceB6D2e539a432
Monad Testnet101430x8004A818BFB912233c491871b3d84c89A494BD9eMonad
BSC Mainnet560x8004A169FB4a3325136EB29fA0ceB6D2e539a432
BSC Testnet970x8004A818BFB912233c491871b3d84c89A494BD9eBNB 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:
  1. Register on-chain (without URI) → Get your agentId
  2. Build agent card with registrations[] containing your new agentId
  3. Upload to IPFS via Pinata
  4. 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

FieldRequiredDescription
typeYesAlways https://eips.ethereum.org/EIPS/eip-8004#registration-v1
nameYesYour agent’s display name
descriptionYesWhat it does, capabilities, pricing
imageYesAvatar URL (IPFS or HTTPS)
servicesNoAgent endpoints (see Services & IPFS)
registrationsNoRegistry references (populated after registration)
x402SupportNoAccepts x402 payments (default: false)
activeNoCurrently running (default: true)
supportedTrustNoTrust models (see below)

Trust Models

The supportedTrust array declares how clients can verify your agent:
ModelDescription
reputationTrust based on historical feedback from clients
crypto-economicTrust backed by staked tokens that can be slashed
tee-attestationTrust 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:
  1. Agent lists an HTTPS endpoint in their card
  2. Verifier fetches https://{domain}/.well-known/agent-registration.json
  3. 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