From c73a559d2d0ec171fc71ea40eaa61e6087ab4b9a Mon Sep 17 00:00:00 2001 From: Xamaitena Date: Mon, 25 May 2026 07:26:41 +0200 Subject: [PATCH] docs: add ERC-8004 agent identity registration guide Add a dedicated page under base-app/agents covering how to register a trustless agent identity in the ERC-8004 IdentityRegistry on Base Mainnet and Sepolia: registration file schema, IPFS/HTTPS/on-chain hosting, viem + cast examples, agentWallet EIP-712 binding, and read-back. --- .../agents/erc-8004-agent-registration.mdx | 187 ++++++++++++++++++ docs/docs.json | 1 + 2 files changed, 188 insertions(+) create mode 100644 docs/base-app/agents/erc-8004-agent-registration.mdx diff --git a/docs/base-app/agents/erc-8004-agent-registration.mdx b/docs/base-app/agents/erc-8004-agent-registration.mdx new file mode 100644 index 000000000..5b8d164b1 --- /dev/null +++ b/docs/base-app/agents/erc-8004-agent-registration.mdx @@ -0,0 +1,187 @@ +--- +title: 'Registering an Agent Identity (ERC-8004)' +description: 'How to register a trustless agent identity on Base using the ERC-8004 IdentityRegistry' +sidebarTitle: 'Agent Identity (ERC-8004)' +--- + +[ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) defines on-chain registries for autonomous agents. Registering gives your agent an ERC-721 `agentId`, an optional `agentURI` pointing to a machine-readable registration file, and an `agentWallet` for signing transactions on behalf of the agent. + +## Contract Addresses + +| Network | IdentityRegistry | Chain ID | +| :------ | :--------------- | :------- | +| Base Mainnet | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` | 8453 | +| Base Sepolia | `0x8004A818BFB912233c491871b3d84c89A494BD9e` | 84532 | + +## Registration File + +The `agentURI` must resolve to a JSON document with this structure: + +```json agent.json +{ + "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", + "name": "MyAgent", + "description": "What the agent does and how to interact with it", + "image": "https://example.com/agent.png", + "services": [ + { "name": "A2A", "endpoint": "https://agent.example/.well-known/agent-card.json", "version": "0.3.0" } + ], + "x402Support": false, + "active": true, + "registrations": [ + { + "agentId": 42, + "agentRegistry": "eip155:84532:0x8004A818BFB912233c491871b3d84c89A494BD9e" + } + ], + "supportedTrust": ["reputation"] +} +``` + +The `agentRegistry` field uses the format `eip155:{chainId}:{identityRegistryAddress}`. If the `agentId` is not known yet, use `0` as a placeholder and update it with `setAgentURI()` after minting. + +### Hosting Options + +| Option | URI Format | Notes | +| :----- | :--------- | :---- | +| IPFS | `ipfs://{cid}` | Recommended — immutable, content-addressed | +| HTTPS | `https://example.com/agent.json` | Simple; endpoint must stay live | +| On-chain | `data:application/json;base64,{base64}` | Maximum censorship-resistance; higher gas | + +## Register On-Chain + +### viem (TypeScript) + + +```typescript Register with URI +import { createWalletClient, createPublicClient, http, parseAbi } from "viem"; +import { baseSepolia } from "viem/chains"; // use `base` for Mainnet +import { privateKeyToAccount } from "viem/accounts"; + +const IDENTITY_REGISTRY = "0x8004A818BFB912233c491871b3d84c89A494BD9e"; + +const abi = parseAbi([ + "function register() returns (uint256 agentId)", + "function register(string agentURI) returns (uint256 agentId)", + "function setAgentURI(uint256 agentId, string newURI) external", + "function ownerOf(uint256 tokenId) view returns (address)", + "function tokenURI(uint256 tokenId) view returns (string)", + "function getAgentWallet(uint256 agentId) view returns (address)", +]); + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); +const client = createWalletClient({ account, chain: baseSepolia, transport: http() }); + +// Register and set URI in one step +const hash = await client.writeContract({ + address: IDENTITY_REGISTRY, + abi, + functionName: "register", + args: ["ipfs://your-cid"], +}); +``` + +```typescript Register then set URI +// Step 1 — mint the agentId +const hash = await client.writeContract({ + address: IDENTITY_REGISTRY, + abi, + functionName: "register", + args: [], +}); + +// Step 2 — get agentId from the Registered event, then: +await client.writeContract({ + address: IDENTITY_REGISTRY, + abi, + functionName: "setAgentURI", + args: [agentId, "ipfs://final-cid"], +}); +``` + + +### cast (Foundry) + + +```bash Sepolia +cast send 0x8004A818BFB912233c491871b3d84c89A494BD9e \ + "register(string)(uint256)" "ipfs://your-cid" \ + --rpc-url https://sepolia.base.org \ + --private-key $PRIVATE_KEY +``` + +```bash Mainnet +cast send 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + "register(string)(uint256)" "ipfs://your-cid" \ + --rpc-url https://mainnet.base.org \ + --private-key $PRIVATE_KEY +``` + + +The returned value is your `agentId` — store it, every subsequent call requires it. + +## Read Agent Data + +```typescript +const publicClient = createPublicClient({ chain: baseSepolia, transport: http() }); + +const owner = await publicClient.readContract({ address: IDENTITY_REGISTRY, abi, functionName: "ownerOf", args: [agentId] }); +const uri = await publicClient.readContract({ address: IDENTITY_REGISTRY, abi, functionName: "tokenURI", args: [agentId] }); +const wallet = await publicClient.readContract({ address: IDENTITY_REGISTRY, abi, functionName: "getAgentWallet", args: [agentId] }); +``` + +You can also look up any agent on [basescan.org](https://basescan.org) (Mainnet) or [sepolia.basescan.org](https://sepolia.basescan.org) (Sepolia). + +## Bind agentWallet (Optional) + +By default, `agentWallet` is set to the owner's address on registration. To change it, `newWallet` must sign an EIP-712 message: + +```typescript +import { signTypedData } from "viem/accounts"; + +const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); + +const signature = await signTypedData({ + privateKey: newWalletPrivateKey, + domain: { + name: "ERC8004IdentityRegistry", + version: "1", + chainId: 84532, // 8453 for Mainnet + verifyingContract: IDENTITY_REGISTRY, + }, + types: { + AgentWalletSet: [ + { name: "agentId", type: "uint256" }, + { name: "newWallet", type: "address" }, + { name: "owner", type: "address" }, + { name: "deadline", type: "uint256" }, + ], + }, + primaryType: "AgentWalletSet", + message: { agentId: BigInt(agentId), newWallet, owner: agentOwnerAddress, deadline }, +}); + +await client.writeContract({ + address: IDENTITY_REGISTRY, + abi: parseAbi(["function setAgentWallet(uint256,address,uint256,bytes) external"]), + functionName: "setAgentWallet", + args: [BigInt(agentId), newWallet, deadline, signature], +}); +``` + + +On transfer, `agentWallet` is automatically cleared and must be re-set by the new owner. + + +## Key Facts + +- `agentId` is an ERC-721 `tokenId` — an auto-incremented integer, not a wallet address. +- An agent can be registered on multiple chains simultaneously; include each chain in the `registrations` array of the registration file. +- Do not pass `agentWallet` in the metadata array of `register()` — that field is reserved and the call will revert. +- ERC-4337 smart wallets are supported for `agentWallet`: the contract calls `isValidSignature` on `newWallet` instead of ECDSA recovery. + +## Resources + +- [ERC-8004 Specification](https://eips.ethereum.org/EIPS/eip-8004) +- [ERC-8004 Contracts](https://github.com/erc-8004/erc-8004-contracts) +- [8004.org/build](https://www.8004.org/build) diff --git a/docs/docs.json b/docs/docs.json index dccc09947..12da17909 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -429,6 +429,7 @@ "base-app/agents/transaction-trays", "base-app/agents/deeplinks", "base-app/agents/x402-agents", + "base-app/agents/erc-8004-agent-registration", "base-app/agents/mini-apps-and-agents" ] }