ERC-8004 Identity Registry (Preinstall)
The on-chain primitive for AI agent identity: an ERC-8004 Identity Registry preinstalled at a fixed address, with no contract owner.
Maroo treats AI agent identity as a chain-level primitive by installing an ERC-8004 Identity Registry as a preinstall at a fixed, well-known address (0x8004000000000000000000000000000000000001) instead of leaving each dapp to deploy its own. On Maroo Testnet it arrived with the v0.4.0 chain upgrade (block 6,372,905) rather than at genesis. Registration, the agent URI, per-agent metadata and the agent wallet use the ERC-8004 v1 Identity Registry ABI, so tools that speak that ABI work unchanged; the registry has no attestation or revocation function. The Maroo-specific agent precompile sits beside it: it returns the registry addresses and maps an agent wallet to its agent IDs (see agent-precompile-overview).
Why preinstall (not a dapp-deployed contract)?
x/agent indexing and the agent precompile track that one contract.Compare to OKRW or PCL, which are precompiles (Go-native code at fixed addresses). The IdentityRegistry is a regular Solidity contract — the explorer shows it as the verified
PreinstallIdentityRegistry: a non-upgradeable implementation of the ERC-8004 Identity interface on top of ERC-721, with no contract owner. Preinstall is the chain operator's way of saying "this is foundational; you don't need to deploy your own."What's covered in V1
- Identity Registry at
0x8004000000000000000000000000000000000001— the subject of this page. - Reputation Registry at
0x8004000000000000000000000000000000000002— deployed and wired to the Identity Registry (giveFeedback,readFeedback,revokeFeedback,appendResponse, …). It implements part of the ERC-8004 Reputation interface: there is nogetSummaryorreadAllFeedback. - No Validation Registry is installed.
IAgent.getParams() returns the Identity and Reputation addresses.What the Identity Registry does:
- Register an agent.
register(),register(agentURI)orregister(agentURI, metadata)mints an ERC-721agentId— auint256issued sequentially from 0 — to the caller. The caller becomes the owner and the default agent wallet; no new address is created. - Link the owner. The owner is the holder of the
agentIdNFT. The registry neither checks nor stores KYC/KYB attestations; those live in the EAS contracts and matter only where a PCL policy checks them. - Store metadata. Free-form key → bytes per agent (
setMetadata/getMetadata). Chain code reads two things from the registry: the agent wallet (setAgentWallet, which needs an EIP-712 signature from the new wallet), for theagentprecompile's index, and theTransferLimitkey — a 32-byteuint256— forAGENT_OKRW_TRANSFER_LIMIT_POLICY. Other keys, such as MAWS'sspendingLimitorstatus, are read off-chain only. - No revocation. There is no
revokeorburn. An owner can transfer the NFT or callunsetAgentWallet; a "revoked" status written as metadata is a convention the chain does not enforce.
Owner ↔ Agent linkage and KYA
1. Owner identity — a KYC/KYB EAS attestation held by the account that owns the agent. It lives in the EAS contracts (
0x1000000000000000000000000000000000000007, indexed at 0x1000000000000000000000000000000000000008), not in the registry.2. Delegation scope — spending caps and allowed targets. On Maroo these are agent metadata (
spendingLimit, allowedTargets, plus a status flag): MAWS writes and enforces them off-chain, and the one cap enforced on-chain is TransferLimit, read by AGENT_OKRW_TRANSFER_LIMIT_POLICY where that policy is configured.3. Action constraints — what the agent's wallet is technically permitted to do, enforced by PCL at execution time.
PCL evaluates EAS conditions through the chain's EAS integration and agent
TransferLimit metadata through x/agent, and only for policies that are actually configured. The stack composes KYA without any single registry encoding it monolithically.Calling the registry
onERC721Received). The full ABI ships as identityRegistryAbi in @maroo-chain/viem.// The IdentityRegistry's ERC-8004 Identity functions (it is also an ERC-721).
struct MetadataEntry {
string metadataKey;
bytes metadataValue;
}
interface IIdentityRegistry {
function register() external returns (uint256 agentId);
function register(string calldata agentURI) external returns (uint256 agentId);
function register(string calldata agentURI, MetadataEntry[] calldata metadata) external returns (uint256 agentId);
function setAgentURI(uint256 agentId, string calldata newURI) external;
function getMetadata(uint256 agentId, string calldata metadataKey) external view returns (bytes memory);
function setMetadata(uint256 agentId, string calldata metadataKey, bytes calldata metadataValue) external;
function getAgentWallet(uint256 agentId) external view returns (address);
function setAgentWallet(uint256 agentId, address newWallet, uint256 deadline, bytes calldata signature) external;
function unsetAgentWallet(uint256 agentId) external;
function ownerOf(uint256 tokenId) external view returns (address);
function tokenURI(uint256 tokenId) external view returns (string memory);
}
contract MyAgentDeployer {
IIdentityRegistry constant REGISTRY = IIdentityRegistry(0x8004000000000000000000000000000000000001); // Maroo preinstall
function registerSelf(string calldata agentURI) external returns (uint256 agentId) {
agentId = REGISTRY.register(agentURI); // agentId is a uint256, issued sequentially from 0
}
// register() mints with _safeMint, so the receiving contract must accept ERC-721 tokens.
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
} IAgent.getParams() also returns it — resolve it at runtime if you target more than one network. To list the agents an address owns, use the registry's ERC-721 surface: balanceOf(owner) for the count, and its Transfer events (or the explorer's NFT API) for the IDs. The agent precompile's getAgentIds is keyed by agent wallet, not by owner, and its entry is cleared when the agent NFT is transferred.