Concepts

The binding model, 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;
    bool exists;
}

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

If you need single-owner semantics on a multi-token standard, pick a different binding model or wrap the token in an ERC-721 first — the adapter will not synthesize one for you.

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.