What is Adapter8004
Adapter8004 is a UUPS-upgradeable contract that binds an external NFT or token balance to an ERC-8004 agent identity. The adapter proxy permanently holds the agent NFT; control of the agent follows the bound external token.
This lets you use any existing NFT (PFP, membership pass, game item) as the controller for an on-chain AI agent identity.
Supported Chains & Proxies
| Chain | Adapter Proxy | Registry |
|---|---|---|
| Ethereum Mainnet | 0xde152AfB7db5373F34876E1499fbD893A82dD336 | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 |
| Base Mainnet | 0x270d25D2c59A8bcA1B0f40ad95fF7806c0025c27 | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 |
| Sepolia | 0x7621630cB63a73a194f45A3E6801B8C6A7eC2f92 | 0x8004A818BFB912233c491871b3d84c89A494BD9e |
TokenStandard Enum
Critical: Wrong enum = wrong control rule = agent can be effectively lost.
| Value | Standard | Control Rule |
|---|---|---|
0 | ERC-721 | Single owner of (contract, tokenId) |
1 | ERC-1155 | Any account with balanceOf(account, tokenId) > 0 |
2 | ERC-6909 | Any account with positive balance of (contract, tokenId) |
Do not use ownerOf for ERC-1155 or ERC-6909.
Decision Tree
1. No agent yet → register(...)
Mint new agent + bind in one transaction:
cast send $ADAPTER
"register(uint8,address,uint256,string)"
0
$NFT_CONTRACT
$TOKEN_ID
"ipfs://YOUR_AGENT_JSON"
--rpc-url $RPC
--private-key $PK 2. Already have agent NFT → bindExisting(...)
Two transactions:
# 1. Approve adapter to pull agent NFT
cast send $REGISTRY "approve(address,uint256)" $ADAPTER $AGENT_ID
--rpc-url $RPC --private-key $PK
# 2. Bind to external NFT
cast send $ADAPTER "bindExisting(uint256,uint8,address,uint256)"
$AGENT_ID 0 $NFT_CONTRACT $NFT_TOKEN_ID
--rpc-url $RPC --private-key $PK 3. Low-cost reservation → registerCounterfactual(...)
Emit-only flow. Emits events for indexers, no registry write. Promote later with real register(...) using same parameters.
Verifying Bindings & Controllers
# What is agent bound to?
cast call $ADAPTER "bindingOf(uint256)((uint8,address,uint256))" $AGENT_ID --rpc-url $RPC
# Is this account a controller?
cast call $ADAPTER "isController(uint256,address)(bool)" $AGENT_ID $ACCOUNT --rpc-url $RPC The binding returns (tokenStandard, tokenContract, tokenId).
Transferring Control
Control follows the bound token:
- ERC-721: Transfer the NFT
- ERC-1155: Transfer enough balance
- ERC-6909: Transfer positive balance
Delegation (ERC-721 Only)
For ERC-721 bindings, delegate.xyz v2 enables hot/cold wallet flows:
- Cold wallet holds NFT
- Cold wallet delegates NFT to hot wallet via delegate.xyz
- Hot wallet passes
isControllerchecks
Not supported for ERC-1155 or ERC-6909.
Controller-Gated Operations
Once controller is established, adapter forwards these registry calls:
setAgentURI(agentId, uri)setMetadata(agentId, key, value)setMetadataBatch(...)setAgentWallet(agentId, wallet)unsetAgentWallet(agentId)
Reserved keys (do not overwrite):
agent-bindingcf-registration
Common Pitfalls
- Wrong TokenStandard enum — verify standard before any bind
- ERC-1155/6909 with ownerOf — these standards don’t have ownerOf
- Unsupported chain — only Ethereum, Base, Sepolia have proxies
- Missing approval — bindExisting needs prior approve call
- Counterfactual confusion — cf registration is emit-only, not a real registration