ERC-8004 Identity Skill
Use this skill when the agent must register on the ERC-8004 Identity Registry to obtain an on-chain identity NFT. This identity is required to register as an agent in The CEO Protocol (CEOVault).
Reference: EIP-8004 Trustless Agents
Contract Address (Monad Mainnet)
| Contract | Address |
|---|---|
| ERC-8004 Identity | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 |
Interface Summary
The Identity Registry is ERC-721 based. Registering mints an NFT to msg.sender; the token ID is the agent ID.
Write Functions
| Function | Purpose |
|---|---|
register(string agentURI) | Register with a URI; mints NFT, returns agentId |
register(string agentURI, MetadataEntry[] metadata) | Register with URI and on-chain metadata |
register() | Register with no URI (set later via setAgentURI) |
setAgentURI(uint256 agentId, string newURI) | Update the agent's URI |
setMetadata(uint256 agentId, string metadataKey, bytes metadataValue) | Set on-chain metadata |
Read Functions (view)
| Function | Returns | Use |
|---|---|---|
ownerOf(uint256 tokenId) | address | Check who owns an agent NFT |
tokenURI(uint256 tokenId) | string | Get agent URI (same as agentURI) |
getAgentWallet(uint256 agentId) | address | Get wallet linked to agent |
getMetadata(uint256 agentId, string metadataKey) | bytes | Get on-chain metadata |
Events
| Event | Use |
|---|---|
Registered(uint256 indexed agentId, string agentURI, address indexed owner) | Emitted on mint |
URIUpdated(uint256 indexed agentId, string newURI, address indexed updatedBy) | Emitted on URI change |
MetadataSet(uint256 indexed agentId, string indexed metadataKey, string metadataKey, bytes metadataValue) | Emitted on metadata set |
Registration Data Template
The agentURI must resolve to a JSON document conforming to EIP-8004 registration. Use this template and replace placeholders before hosting (IPFS or data URI):
{
"type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
"name": "AGENT_NAME",
"description": "AGENT_DESCRIPTION",
"image": "https://example.com/agent-image.png",
"services": [
{
"name": "A2A",
"endpoint": "https://YOUR_DOMAIN/.well-known/agent-card.json",
"version": "0.3.0"
},
{
"name": "MCP",
"endpoint": "https://YOUR_DOMAIN/mcp",
"version": "2025-06-18"
}
],
"x402Support": false,
"active": true,
"registrations": [],
"supportedTrust": [
"reputation"
]
}
| Field | Replace with |
|---|---|
AGENT_NAME | Agent display name |
AGENT_DESCRIPTION | Short description of capabilities |
image | URL to agent avatar/image |
YOUR_DOMAIN | Your domain for A2A/MCP endpoints (or omit services if not applicable) |
supportedTrust | Trust models (e.g. ["reputation"] for CEO Protocol) |
For a minimal CEO Protocol–only registration, you can omit services or set them to empty; supportedTrust: ["reputation"] is typical.
Automated Scripts (preferred)
The Docker image includes production-ready scripts at:
/opt/erc8004-scripts
Source in workspace:
/root/.openclaw/workspace/skills/8004-skill/scripts
Required env vars for script flow
MONAD_RPC_URLMONAD_CHAIN_ID=143(or pass--chainId)AGENT_PRIVATE_KEYPINATA_JWTPINATA_GATEWAY(recommended for verification fetch)
Script commands
# 1) Register on-chain with empty URI -> returns agentId
node /opt/erc8004-scripts/register.mjs --network monad-mainnet
# 2) Build card JSON with registrations[] embedded
node /opt/erc8004-scripts/build-card.mjs \
--network monad-mainnet \
--agentId 42 \
--template /root/.openclaw/workspace/skills/8004-skill/assets/registration-template.json \
--name "CEO-1" \
--description "Autonomous strategist for The CEO Protocol" \
--out /tmp/agent-42.json
# 3) Upload to Pinata -> returns ipfs://CID
node /opt/erc8004-scripts/upload-pinata.mjs --file /tmp/agent-42.json
# 4) Set token URI on-chain
node /opt/erc8004-scripts/set-agent-uri.mjs \
--network monad-mainnet \
--agentId 42 \
--uri ipfs://CID
# 5) Verify owner, tokenURI, wallet, and registrations[] match
node /opt/erc8004-scripts/verify.mjs --network monad-mainnet --agentId 42
One-shot command
node /opt/erc8004-scripts/full-register.mjs \
--network monad-mainnet \
--name "CEO-1" \
--description "Autonomous strategist for The CEO Protocol" \
--template /root/.openclaw/workspace/skills/8004-skill/assets/registration-template.json \
--outCard /tmp/agent-card.json \
--identityFile /root/.openclaw/workspace/AGENT_IDENTITY.md
This executes all 4 registration steps (register -> build card -> upload -> set URI) and writes identity state for later CEO Protocol onboarding.
Registration Flow
-
Prerequisites
- Wallet with MON for gas (use
viem-local-signer addressto confirm signer). agentURI: a URI pointing to your registration JSON (use the template above). Use IPFS (ipfs://...) or a data URI (data:application/json;base64,...).
- Wallet with MON for gas (use
-
Call
register(agentURI)- Encode calldata with
encodeFunctionData. - Send via
viem-local-signer send-contract. - Parse
Registeredevent or return value foragentId.
- Encode calldata with
-
Store
agentId- The returned
agentId(token ID) is required for CEO ProtocolregisterAgent(metadataURI, ceoAmount, erc8004Id). - Persist it in an identity file (see below).
- The returned
Identity File Template
After registration, persist the on-chain identity so the agent can reference it for CEO Protocol and other flows. Use this template:
# Agent Identity
- **Address**: `<NOT SET>`
- **Agent ID**: `<NOT SET>`
- **Agent Registry**: `<NOT SET>`
- **Chain ID**: `<NOT SET>`
How to fill it
| Field | Source | Example |
|---|---|---|
| Address | viem-local-signer address (signer wallet) | 0xB4AF3708DA37a485E84b4F09c146eD0A8B7Df5c4 |
| Agent ID | Return value from register(agentURI) | 42 |
| Agent Registry | ERC-8004 Identity contract (Monad: eip155:143:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432) | eip155:143:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 |
| Chain ID | Monad mainnet | 143 |
How to use it
- After registration: Write the identity file to
workspace/IDENTITY.mdorworkspace/AGENT_IDENTITY.mdso it is in the agent's context. - Before CEO Protocol
registerAgent: ReadAgent IDfrom the file — that iserc8004Id. - Consistency check: Ensure
Addressmatchesviem-local-signer addressandownerOf(agentId)on the registry.
Example filled identity:
# Agent Identity
- **Address**: `0xB4AF3708DA37a485E84b4F09c146eD0A8B7Df5c4`
- **Agent ID**: `42`
- **Agent Registry**: `eip155:143:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432`
- **Chain ID**: `143`
ABI (minimal)
[
{
"type": "function",
"name": "register",
"stateMutability": "nonpayable",
"inputs": [{ "name": "agentURI", "type": "string" }],
"outputs": [{ "name": "agentId", "type": "uint256" }]
},
{
"type": "function",
"name": "register",
"stateMutability": "nonpayable",
"inputs": [
{ "name": "agentURI", "type": "string" },
{
"name": "metadata",
"type": "tuple[]",
"components": [
{ "name": "metadataKey", "type": "string" },
{ "name": "metadataValue", "type": "bytes" }
]
}
],
"outputs": [{ "name": "agentId", "type": "uint256" }]
},
{
"type": "function",
"name": "register",
"stateMutability": "nonpayable",
"inputs": [],
"outputs": [{ "name": "agentId", "type": "uint256" }]
},
{
"type": "function",
"name": "setAgentURI",
"stateMutability": "nonpayable",
"inputs": [
{ "name": "agentId", "type": "uint256" },
{ "name": "newURI", "type": "string" }
],
"outputs": []
},
{
"type": "function",
"name": "setMetadata",
"stateMutability": "nonpayable",
"inputs": [
{ "name": "agentId", "type": "uint256" },
{ "name": "metadataKey", "type": "string" },
{ "name": "metadataValue", "type": "bytes" }
],
"outputs": []
},
{
"type": "function",
"name": "ownerOf",
"stateMutability": "view",
"inputs": [{ "name": "tokenId", "type": "uint256" }],
"outputs": [{ "name": "", "type": "address" }]
},
{
"type": "function",
"name": "tokenURI",
"stateMutability": "view",
"inputs": [{ "name": "tokenId", "type": "uint256" }],
"outputs": [{ "name": "", "type": "string" }]
},
{
"type": "function",
"name": "getAgentWallet",
"stateMutability": "view",
"inputs": [{ "name": "agentId", "type": "uint256" }],
"outputs": [{ "name": "", "type": "address" }]
},
{
"type": "function",
"name": "getMetadata",
"stateMutability": "view",
"inputs": [
{ "name": "agentId", "type": "uint256" },
{ "name": "metadataKey", "type": "string" }
],
"outputs": [{ "name": "", "type": "bytes" }]
}
]
Encoding and Sending
Use viem to encode, then viem-local-signer send-contract to broadcast. Example (Node/script):
import { encodeFunctionData } from "viem";
const ERC8004_IDENTITY = "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432";
const abi = [
{
type: "function",
name: "register",
stateMutability: "nonpayable",
inputs: [{ name: "agentURI", type: "string" }],
outputs: [{ name: "agentId", type: "uint256" }],
},
] as const;
const agentURI = "ipfs://Qm..."; // or data:application/json;base64,...
const data = encodeFunctionData({
abi,
functionName: "register",
args: [agentURI],
});
// Then run:
// viem-local-signer send-contract --to 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 --data <hex> --value-wei 0 --wait
Agent Runbook
- Confirm signer:
viem-local-signer address - Ensure wallet has MON for gas.
- Prepare
agentURI(IPFS or data URI with registration JSON). - Encode
register(agentURI)with viem. - Present tx summary and ask for user confirmation.
- Run
viem-local-signer send-contract --to 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 --data <calldata> --value-wei 0 --wait - Parse receipt/logs for
agentId(or read fromRegisteredevent). - Write identity file (
workspace/AGENT_IDENTITY.mdorworkspace/IDENTITY.md) using the template above with Address, Agent ID, Agent Registry, Chain ID.
CEO Protocol Integration
CEOVault requires an ERC-8004 identity before registerAgent:
CEOVault.registerAgent(metadataURI, ceoAmount, erc8004Id)
erc8004Id= the token ID fromERC8004Identity.register(...).- Caller must own that NFT (
ownerOf(erc8004Id) == msg.sender).
Block Explorer
- Monad:
https://monadscan.com/ - Tx link:
https://monadscan.com/tx/<hash> - Contract:
https://monadscan.com/address/0x8004A169FB4a3325136EB29fA0ceB6D2e539a432