Concepts

The binding model, binding metadata format, shared control, and the EIP-712 wallet-proof subtlety.

The binding model#

Every ERC-8004 agentId issued through the adapter is permanently tied to one external token, captured as a Binding:

struct Binding {
    TokenStandard standard;  // ERC721, ERC1155, or ERC6909
    address tokenContract;
    uint256 tokenId;
}

The adapter registers the ERC-8004 identity itself, so the ERC-8004 registry records the adapter as the token owner. The adapter then records the binding locally and forwards all writes on behalf of the current controller.

Controller rules#

The adapter determines "who controls this agentId?" by calling into the bound token contract:

StandardControl rule
ERC-721IERC721(tokenContract).ownerOf(tokenId) == msg.sender
ERC-1155IERC1155(tokenContract).balanceOf(msg.sender, tokenId) > 0
ERC-6909IERC6909(tokenContract).balanceOf(msg.sender, tokenId) > 0

Every controller-gated call re-reads the token contract, so control tracks the current state of the underlying token in real time. Transferring the token moves control atomically, there is no separate "claim" step.

Shared control#

ERC-1155 and ERC-6909 can legitimately have multiple holders of the same id. The adapter preserves that: every current holder is a controller.

ERC-1155 token #7 held by alice (balance 1) and bob (balance 1)
  └─▶ both alice and bob can call setAgentURI / setMetadata / ...
  └─▶ transferring bob's balance to eve makes eve a controller too

For an NFT setup on ERC-1155 or ERC-6909, mint exactly one token per id. The single holder is then the only controller, which is the intended way to represent an NFT on either standard.

The wallet-proof subtlety#

The adapter forwards setAgentWallet to the ERC-8004 registry unchanged, so the wallet-proof rule still applies: newWallet must produce a valid EIP-712 or ERC-1271 signature.

The subtlety is that the ERC-8004 typed-data owner field is the current ERC-8004 token owner. In this architecture that owner is the adapter contract, not the external NFT holder.

That means wallet-binding signatures must be produced against typed data whose owner is the adapter address. A signature produced against the external NFT holder's address will fail verification.

// Correct
domain: {
  name: "ERC8004IdentityRegistry",
  version: "1",
  chainId: ...,
  verifyingContract: <ERC-8004 registry>
}
message: {
  agentId,
  newWallet,
  owner: <adapter address>,   // ← critical
  deadline
}

Clearing the initial agentWallet#

When the adapter calls register on the ERC-8004 registry, the registry follows its default behavior of setting agentWallet to msg.sender, which is the adapter itself. The adapter is only a custodian, not the intended runtime wallet, so it immediately calls unsetAgentWallet(agentId) to clear that slot as part of the same transaction.

You should then set the real runtime wallet with setAgentWallet once you have the EIP-712 / ERC-1271 signature prepared.

Binding metadata format#

On every successful register, the adapter writes canonical ERC-8004 metadata under the reserved key agent-binding. The value is exactly 20 bytes — the address of the binding contract:

abi.encodePacked(bindingContract)
FieldSizeDescription
bindingContract20 bytesThe adapter proxy address that serves bindingOf(agentId)

The token standard, token contract, and token id are not stored in the metadata. They live in bindingOf(agentId) on the binding contract and are the single source of truth. Duplicating them in metadata would risk drift if the binding logic ever changes.

The adapter reserves this key and rejects user attempts to set or batch-set it.

This format tracks ERC-8217 (Agent NFT Identity Bindings). The bindingContract and tokenContract may be the same address if a token contract implements the binding interface directly, or different addresses when a separate adapter contract is used (as in this repo).

Binding verification#

The interoperable verification flow is:

  1. Read the agent-binding metadata from the ERC-8004 record
  2. Require length == 20 bytes; decode as the binding contract address
  3. Call bindingOf(agentId) on that address
  4. Use the returned Binding { standard, tokenContract, tokenId } as the canonical record

If any step fails, treat the binding as unverified.

The adapter also exposes isController(agentId, account) as a convenience view, but that function is adapter-specific and is not part of the ERC draft.

Multiple agents per token#

A single external token can back multiple ERC-8004 agents. Each register call mints a new agent, the binding is immutable per agent, but there is no global uniqueness constraint on (standard, tokenContract, tokenId) across agents.

Counterfactual registration#

Counterfactual registration lets the current controller of an external token publish ERC-8004-style identity events without minting an ERC-8004 token. It is an indexable, low-gas alternative when an agent needs a stable identity before it needs a full onchain registration, and anyone who controls the same external token can later promote that path by calling normal onchain register with the same token.

See the Counterfactual page for the full reference.