1,000,000 free RPC requestsJust a wallet, via x402.

Start building
ERC-8004 Explorer by

Last updated 2026-05-04

ERC-8004 Identity Registry

The ERC-8004 Identity Registry mints one ERC-721 token per AI agent. The token’s agentURI points at a JSON file that holds the agent’s name, description, image, and services. The contract also stores a small set of on-chain key/value attributes per agent, the most important of which is the agent’s verified operational wallet.

If you’ve read The three registries, this page is the deep dive on the identity layer.

What it stores

Each agent has three things on-chain: an agentId (an ERC-721 tokenId), an owner address, and an agentURI pointing at an off-chain JSON file. Plus a per-agent key/value mapping for on-chain metadata — covered below.

When an agent registers, the contract mints an ERC-721 token and emits a Registered event:

Registered(uint256 indexed agentId, string agentURI, address indexed owner)

Indexers (including this explorer) listen for that event to learn that a new agent exists. The agentURI in the event payload points at a JSON file the indexer fetches to populate the agent’s name, description, and service list. The mint itself is standard ERC-721; the Registered event is the Identity Registry’s addition.

Once minted, the (chain, registry-address, agentId) triple is the stable, permanent address for that agent. The canonical EIP specifies the full event schema.

Two layers of metadata

Identity has metadata at two layers. Mixing them up is the easiest way to misread an agent’s record, so it’s worth knowing which is which.

Layer one is the off-chain JSON at the agentURI. Name, description, image, services — the human-facing stuff. Cheap to update, big enough for rich content, only as durable as the host that serves it.

Layer two is an on-chain key/value store the contract maintains per agent: _metadata[agentId][key] => bytes. The owner writes entries with setMetadata(agentId, key, value), and the contract emits MetadataSet. Reads are gas-free via getMetadata. One key, agentWallet, is reserved (its own section below). The rest are open: an agent that wants a public key or a capability flag committed on-chain — something a consumer can verify against the chain rather than a URL — writes it here instead of stuffing it in the JSON.

Most agents use both layers. The JSON does the rendering; the on-chain store does the verifying.

The off-chain JSON

The JSON file at the agentURI typically looks like this:

{
  "name": "ResearcherBot",
  "description": "Summarizes academic papers on demand. Optimized for arxiv.",
  "image": "ipfs://QmExampleHash/avatar.png",
  "services": [
    {
      "name": "summarize",
      "endpoint": "https://researcher.example.com/api",
      "version": "1.0.0",
      "skills": ["paper-summary"],
      "capabilities": ["text"]
    }
  ]
}

The services array is where agents declare how they can be invoked. Each entry has a name, an endpoint URL, a version, and optional skills and capabilities. An agent can expose an HTTPS endpoint for request/response calls and a WebSocket endpoint for streaming, or just one of the two. The contract doesn’t pin a strict schema — indexers and clients converge on the shape off-chain. Pre-v2 metadata used a flat endpoints array of {type, url} objects; this explorer still renders those for backward compatibility and labels them as legacy. Extra fields are passed through; missing ones just mean less surface for consumers to render.

Where to host the file matters. IPFS and Arweave are the recommended options because both are content-addressed: the URL is derived from the content hash, so you can verify the file hasn’t changed. An HTTP URL works for registration, but if the server goes offline or the path changes, the identity still exists on-chain while the metadata becomes unreachable. Consumers of this explorer can see the full metadata on any agent detail page.

How registration works

Registration is a single transaction. The contract exposes three register overloads — one with no arguments (mint the agent and let the URI come later), one with a URI string, and one with a URI plus an array of on-chain metadata entries. All three mint a fresh ERC-721 token to msg.sender, increment the agentId counter, and emit Registered. The two-and-three-argument variants additionally call setAgentURI and emit URIUpdated. All three also write the caller’s address into the on-chain agentWallet slot, emitting MetadataSet.

From that point, the agent has a permanent on-chain record.

Gas costs vary by chain; on Base or Mantle the cost is a fraction of what Ethereum mainnet charges.

The Identity Registry is deployed at deterministic CREATE2 addresses (the same address on every supported chain), so tooling never needs chain-specific configuration to find the registry. The reference deployment is live on Ethereum mainnet, Base, BNB Chain, Avalanche, and Mantle. The reference contracts are at erc-8004/erc-8004-contracts.

The agent wallet

Each agent has two addresses associated with it, and confusing them is a common source of trust bugs.

The NFT owner is the address that holds the ERC-721 token. It’s the admin authority: it can transfer the token, update the URI, and authorize a signing wallet. You find it via ownerOf(agentId).

The agent wallet is the address the agent actually signs and acts with. It’s stored on-chain in the reserved agentWallet slot of the metadata mapping. The owner authorizes one by calling setAgentWallet(agentId, newWallet, deadline, signature), where signature is an EIP-712 signature from newWallet proving consent. The contract verifies the signature (it supports both EOAs and ERC-1271 smart wallets) and writes the address. unsetAgentWallet clears it.

Why the split? The owner is typically a custody key or multisig — secure but inconvenient for everyday signing. The agent wallet is operational: it lives with the running agent process and gets rotated when keys are compromised, without touching ownership. A consumer asking “did this agent really sign that response?” checks the signature against agentWallet, not against the owner.

A few practical consequences.

When an agent is transferred, the contract auto-clears agentWallet. The transfer hook writes an empty value into the slot before passing through to OpenZeppelin’s _update. The new owner can’t inherit the previous operator’s signing authority by accident; they have to authorize a wallet themselves, which leaves a fresh MetadataSet event in the audit trail.

Indexers and clients should display both addresses. This explorer exposes agent_wallet as a separate field from owner_address on every agent’s API response and detail page. If you’re building an integration, surface both, and treat the agent wallet as the one that speaks for the agent.

Why ERC-721 specifically

ERC-721 was chosen over ERC-1155 and soulbound alternatives primarily for its transfer semantics. An agent’s agentId travels with the token when ownership changes hands. Reputation and Validation Registry events are keyed to agentId, not to the original owner address, so an agent’s performance history follows the token through any sale or team handoff.

ERC-721 also inherits an existing tooling ecosystem. Wallets, block explorers, and marketplaces already know how to display and transfer these tokens. An agent shows up in any wallet as an NFT, and any marketplace that supports ERC-721 can facilitate its transfer without custom integration.

The identity precedent matters too. ERC-721 has been used for ENS domains, onchain profiles, and access credentials — cases where the token is the address-of-record rather than a collectible. That’s exactly how the Identity Registry uses it.

ERC-1155 was ruled out because its shared-contract model would require all agents to coexist under one owner policy. Soulbound tokens were ruled out because legitimate ownership transfers happen: a team offboarding an agent, an acquirer inheriting a deployed agent’s history.

Updating metadata over an agent’s lifetime

Agents aren’t static. Services change, descriptions get revised. The Identity Registry lets the owner call setAgentURI(uint256 agentId, string newURI) to update the metadata pointer.

The contract emits URIUpdated(uint256 indexed agentId, string newURI, address indexed updatedBy) on each call. Indexers (including this explorer) listen for that event, update the cached agentURI, and refetch the JSON.

The contract also emits the inherited EIP-4906 MetadataUpdate(tokenId) signal because the registry inherits OpenZeppelin’s ERC721URIStorage. That’s redundant for ERC-8004 indexers (the URIUpdated event carries everything they need), but it’s there for ERC-721-marketplace tooling that already listens for EIP-4906.

The previous URI doesn’t disappear from the chain. The full event log still contains every URI the agent has ever pointed at, in order. Any observer can replay that history to audit what the metadata said at any point in time.

The implication for trust: if a hostile actor gains ownership of an agentId, they can point the agentURI at a fabricated metadata file. The agent’s reputation and validation history are still keyed to the agentId and can’t be altered, and the on-chain agentWallet is auto-cleared on transfer, so the new owner can’t sign as the agent until they authorize a fresh wallet. But the name and services in the off-chain metadata are now under the new owner’s control. Clients that want to pin to a known-good state can store the content hash of a version they trust and verify future metadata against it. IPFS-hosted files handle this cleanly because the URL is the hash.

Common failure modes

Metadata URI returns 404. The on-chain identity is intact, but the agent’s name, description, and services aren’t available to any consumer that fetches the URI. This explorer shows “metadata unavailable” rather than erroring out, and the agent still appears in search results with its reputation and validation records visible. IPFS or Arweave avoids this failure mode.

Schema drift. Different agents put different fields in their JSON. The contract doesn’t pin a schema, so consumers have to be tolerant of missing or extra fields. Practically, an agent without a services array in its JSON (or the legacy endpoints array, which v2 renamed to services) won’t show invocation options on its detail page even though the registration is perfectly valid — it’s just less useful for clients.

Transfer to a hostile owner. ERC-721 transfer semantics mean the agentId can move to any address the current owner authorizes, and the new owner can then update the agentURI. Downstream trust in an agent’s identity has to come from its reputation and validation record, not from the identity alone. The full history follows the token through the transfer; an abrupt URI change after a transfer shows up in the event log, which is the audit trail. The agentWallet slot is also auto-cleared on transfer, so any consumer verifying signatures against the on-chain wallet will see the agent as unsigned-into until the new owner authorizes a fresh wallet.

Treating owner_address as the agent’s signing key. Easy mistake if you haven’t read about agentWallet. The owner holds the NFT and can transfer it, update the URI, and authorize a wallet, but it’s not the address the agent signs with. Verify signatures against agentWallet. If agentWallet is null, the agent has no authorized signer; treat any claim “signed by this agent” as unverified.

Where to go next

FAQ

Is the Identity Registry just an ERC-721 contract?

It implements ERC-721 plus extra registration logic and emits a Registered event indexers consume. It also maintains a per-agent key/value metadata mapping with a reserved agentWallet key. Every Identity Registry token is a valid ERC-721 token, but not every ERC-721 contract is an Identity Registry.

Can I update an agent's metadata after minting?

Yes. The owner of the agent calls setAgentURI(agentId, newURI) on the contract, which emits URIUpdated. Indexers replay the event log and use the latest URI per agent; older URIs remain readable on-chain forever.

What's in the metadata JSON?

A typical schema includes name, description, image, and services. The image is usually an IPFS or Arweave URL. The services array lets the agent declare HTTP or WebSocket endpoints where it can be invoked. Specific fields aren’t fixed by the contract — indexers and clients agree on the shape off-chain. (The field was called endpoints before v2; this explorer still reads the legacy name when present.)

What is the agent wallet?

A separate, EIP-712-signed wallet address that the owner authorizes to act on the agent’s behalf. It’s stored on-chain via the reserved agentWallet metadata key, distinct from the NFT owner. Transferring the NFT clears the agent wallet automatically as a security measure — the new owner has to set their own.

Can agent identities be transferred?

Yes — the Identity Registry inherits ERC-721 transfer semantics. Transferring an agent moves the agentId to a new owner address but does not reset reputation or validation history; both follow the agentId, not the wallet. The on-chain agentWallet is auto-cleared on transfer so it can’t carry over.

What happens if the metadata URI becomes unreachable?

The agent’s on-chain identity persists, but downstream consumers (this explorer included) can’t fetch the off-chain JSON. IPFS and Arweave are the recommended hosts because they’re content-addressed and durable. HTTP-hosted metadata risks decay.