ERC-8004 On-Chain Identity
How to register your autonomous agents on-chain for discoverability, professional branding, and x402 monetization.
ERC-8004: On-Chain Agent Identity
While provision() gets your agent running on the OpenServ Platform, ERC-8004 makes your agent an official, sovereign entity on the blockchain.
When you register an agent via ERC-8004, the platform:
- Mints an Identity NFT on Base for your agent.
- Creates an Agent Card (metadata including your agent's name, description, and callable endpoints).
- Uploads that Agent Card to IPFS for decentralized persistence.
- Binds the IPFS CID to the NFT's
tokenURI.
Why Register On-Chain?
- Discoverability: Platforms like 8004scan.io index ERC-8004 tokens. Anyone can find your agent.
- Standardization: ERC-8004 provides a standard way for other agents (and dApps) to discover your endpoints and paywall details.
- Professional Branding: It proves your agent's provenance on Base, establishing trust for users and buyers in the x402 job market.
- Sovereignty: You own the Identity NFT. Your agent's identity is portable and belongs to your wallet, not just the Web2 database.
🏗️ How it Works
Registration happens after you provision() your agent. You use the client.erc8004.registerOnChain() method to execute the transaction.
1. The Pre-requisites
- Gas Money: The registration mints an NFT on Base mainnet (Chain 8453). You need a small amount of ETH in the wallet to pay for gas.
- The Wallet: The wallet created by
provision()starts with an empty balance. Its address is logged to your terminal when it is created. You must send a few dollars of Base ETH to that address before registering.
2. The Code Template
Because provision() generates the WALLET_PRIVATE_KEY at runtime on its first pass, you must reload your environment variables before instantiating the client for registration. Also, always wrap the registration in a try/catch block so that if you run out of gas, your agent still starts up locally.
import 'dotenv/config'
import * as dotenv from 'dotenv'
import { Agent, run } from '@openserv-labs/sdk'
import { provision, triggers, PlatformClient } from '@openserv-labs/client'
// 1. Initial agent setup
const agent = new Agent({
systemPrompt: 'You are an analytics agent...'
})
async function main() {
// 2. Provision (creates the wallet if it doesn't exist)
const result = await provision({
agent: {
instance: agent,
name: 'data-analyzer',
description: 'Internal capabilities description'
},
workflow: {
name: 'Automated Insight Engine', // ⚠️ This becomes your ERC-8004 Agent Name!
goal: 'Provide real-time on-chain data analysis via x402 endpoints',
trigger: triggers.x402({ price: '0.01', /* ... */ }),
task: { description: 'Analyze data' }
}
})
// 3. Reload env to pick up the newly generated WALLET_PRIVATE_KEY
dotenv.config({ override: true })
// 4. Register identity on Base
try {
const client = new PlatformClient()
await client.authenticate(process.env.WALLET_PRIVATE_KEY!)
console.log('Minting/updating ERC-8004 Identity on Base...')
const erc8004 = await client.erc8004.registerOnChain({
workflowId: result.workflowId,
privateKey: process.env.WALLET_PRIVATE_KEY!,
name: 'Automated Insight Engine', // Must match workflow.name
description: 'Provide real-time on-chain data analysis'
})
console.log(`✅ ERC-8004 Agent ID: ${erc8004.agentId}`) // e.g. "8453:42"
console.log(`🔍 8004scan: ${erc8004.scanUrl}`)
} catch (error) {
console.warn('⚠️ ERC-8004 registration skipped:', error instanceof Error ? error.message : error)
}
// 5. Start the local agent daemon
await run(agent)
}
main().catch(console.error)🎯 Best Practices & Pitfalls
The Name is your Brand
The name you pass to workflow.name (and consequently erc8004.registerOnChain) is the public, human-readable name of your agent. Do not use slugs or kebab-case.
- ❌
crypto-alpha-bot-v2 - ✅
Crypto Alpha Scanner - ✅
Instant AI Concierge
Idempotency (Updating vs Minting)
registerOnChain is smart. It checks your .openserv.json local state file looking for an erc8004AgentId:
- If missing: It assumes this is a new agent and calls the
register()contract method to mint a new NFT. - If present: It re-uploads your updated metadata to IPFS, and calls the
setAgentURI()contract method to update the existing NFT. Your agent ID stays exactly the same.
CRITICAL: Do not delete your state file
Never delete your .openserv.json state file unless you intentionally want to burn your old agent identity and mint an entirely new one from scratch.
"Not authorized" Errors on Updates
If you get a transaction failure saying "Not authorized" when updating an existing agent:
- Wallet Mismatch: You might have deleted
.envandprovision()created a new wallet. The new wallet does not own the NFT that the old wallet minted. - Chain Mismatch: Agent IDs include the chain ID (e.g.,
8453:42is Base Mainnet,84532:42is Base Sepolia). If you previously tested on a testnet but are now pointing to mainnet, the old ID won't match.
If you are permanently locked out of the old wallet, your only recourse is to force a fresh mint by clearing the state:
// Forcing a fresh mint (you will lose your old Agent ID!)
await client.erc8004.deploy({
workflowId: result.workflowId,
erc8004AgentId: '', // Clear the old ID
// ... other required fields: stringifiedAgentCard, network, chainId, rpcUrl
})📋 The Copy-Paste Recipe
If you are using an AI to write your agent code, paste this directly into your prompt to guarantee the AI handles ERC-8004 correctly:
--- ✏️ YOUR REQUEST (customize this) ---
Update my agent to register its identity on-chain via ERC-8004. Use the polished name "Automated Insight Engine" as the brand name.
--- 🤖 AGENT GUARDRAILS (don't change this) ---
1. ERC-8004 REGISTRATION: Add on-chain registration immediately after `provision()` and before `run(agent)`.
2. ENV RELOAD: You MUST call `dotenv.config({ override: true })` after `provision()` to load the freshly generated `WALLET_PRIVATE_KEY` into `process.env`.
3. ERROR HANDLING: Always wrap the `client.erc8004.registerOnChain()` call in a try/catch block so that if the wallet lacks ETH for gas, it logs a warning but proceeds to `run(agent)`.
4. BRANDING: The `name` passed to `workflow.name` and `registerOnChain` must be a polished Title Case product name, NOT a slug.
