Install
openclaw skills install 8004-skill-monadRegister and manage ERC-8004 Identity NFTs on Monad. Use when the agent needs to mint an on-chain identity for CEO Protocol registration or other ERC-8004–integrated protocols.
openclaw skills install 8004-skill-monadUse 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 |
|---|---|
| ERC-8004 Identity | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 |
The Identity Registry is ERC-721 based. Registering mints an NFT to msg.sender; the token ID is the agent ID.
| 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 |
| 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 |
| 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 |
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.
The Docker image includes production-ready scripts at:
/opt/erc8004-scripts
Source in workspace:
/root/.openclaw/workspace/skills/8004-skill/scripts
MONAD_RPC_URLMONAD_CHAIN_ID=143 (or pass --chainId)AGENT_PRIVATE_KEYPINATA_JWTPINATA_GATEWAY (recommended for verification fetch)# 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
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.
Prerequisites
viem-local-signer address to 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,...).Call register(agentURI)
encodeFunctionData.viem-local-signer send-contract.Registered event or return value for agentId.Store agentId
agentId (token ID) is required for CEO Protocol registerAgent(metadataURI, ceoAmount, erc8004Id).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>`
| 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 |
workspace/IDENTITY.md or workspace/AGENT_IDENTITY.md so it is in the agent's context.registerAgent: Read Agent ID from the file — that is erc8004Id.Address matches viem-local-signer address and ownerOf(agentId) on the registry.Example filled identity:
# Agent Identity
- **Address**: `0xB4AF3708DA37a485E84b4F09c146eD0A8B7Df5c4`
- **Agent ID**: `42`
- **Agent Registry**: `eip155:143:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432`
- **Chain ID**: `143`
[
{
"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" }]
}
]
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
viem-local-signer addressagentURI (IPFS or data URI with registration JSON).register(agentURI) with viem.viem-local-signer send-contract --to 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 --data <calldata> --value-wei 0 --waitagentId (or read from Registered event).workspace/AGENT_IDENTITY.md or workspace/IDENTITY.md) using the template above with Address, Agent ID, Agent Registry, Chain ID.CEOVault requires an ERC-8004 identity before registerAgent:
CEOVault.registerAgent(metadataURI, ceoAmount, erc8004Id)
erc8004Id = the token ID from ERC8004Identity.register(...).ownerOf(erc8004Id) == msg.sender).https://monadscan.com/https://monadscan.com/tx/<hash>https://monadscan.com/address/0x8004A169FB4a3325136EB29fA0ceB6D2e539a432