ERC-6551 Token bound accounts owned by NFTs (Non-fungible Token Bound Accounts)
Review · NFTs & multi-token · created 2023-02-23
Gives every NFT a unique smart contract account, allowing the NFT to directly hold assets and interact with apps. There is no need to modify existing NFT contracts.
Why it is needed
NFTs (non-fungible tokens) can represent a unique asset, but cannot themselves own or act on anything. This means an NFT is merely an item that sits inside a wallet and cannot be used like a wallet. As a result, it is difficult to represent with a single NFT real-world assets such as a game character that accumulates equipment and abilities over time, a car composed of multiple parts, an investment portfolio holding multiple assets, or a membership card that accumulates usage history. Attaching item tokens to a character NFT requires building a separate contract mechanism each time, and transferring the attached assets along with the NFT when it is sold is also cumbersome. This standard aims to give every NFT the same rights as an Ethereum user. It does this by assigning each NFT a dedicated account address computed deterministically, and giving control of that account to the NFT's holder.
What to implement
error AccountCreationFailed();: If create2 deployment fails, the registry must revert with this error.receive() external payable;: Allows the account to receive ether. Logic restricting the conditions for receiving it can also be included.function token() external view returns (uint256 chainId, address tokenContract, uint256 tokenId);: Returns the chain ID, contract address, and token ID of the NFT that owns this account. The return value must never change.function state() external view returns (uint256);: Returns a value that must change whenever the account's state changes. It can be used to invalidate stale orders or signatures.function isValidSigner(address signer, bytes calldata context) external view returns (bytes4 magicValue);: Checks whether a given address is authorized to act on behalf of this account. If valid, it returns the magic value 0x523e3260.function execute(address to, uint256 value, bytes calldata data, uint8 operation) external payable returns (bytes memory);: A valid signer executes a low-level operation on behalf of the account. If it fails, the error is propagated and the call reverts.
In depth
Singleton registry and fixed address
The registry is a singleton contract that serves as the single entry point for looking up every token bound account address. It has only two functions, createAccount and account, and is permissionless, immutable, and ownerless. The registry must be deployed to address 0x000000006551c19487814612e58FE06813775758 using Nick's Factory (0x4e59b44847b379578588920cA78FbF26c0B4956C) and salt 0x0000000000000000000000000000000000000000fd8eb4e1dca713016c518e31. The single transaction given in the original text can deploy the registry to the same address on any EVM compatible chain. Because there is a single ownerless registry, accounts only need to trust one implementation contract, and events and address lookups are consolidated in one place, simplifying indexing.
ERC-1167 proxies and NFT information embedded in bytecode
The registry deploys each account as an ERC-1167 minimal proxy, but appends immutable constant data after the bytecode. The deployed bytecode must follow this structure: the ERC-1167 header (10 bytes), the implementation address (20 bytes), the ERC-1167 footer (15 bytes), followed by salt (bytes32), chainId (uint256), tokenContract (address), and tokenId (uint256), each 32 bytes. The implementation contract reads this data with extcodecopy to know which NFT it is bound to, and because the data is embedded in the code, it cannot be changed. Every account must be deployed with create2, and its address is deterministically derived from a combination of the implementation address, token contract address, token ID, chain ID, and salt.
Account interface: determining the owning NFT and the signer
Every account implementation must implement ERC-165 interface detection and ERC-1271 signature verification, and must implement the IERC6551Account interface (identifier 0x6faff5f1). token() returns the chain ID, token contract, and token ID of the NFT that owns the account, and this value must never change. state() returns a value that must change whenever the account's state changes. isValidSigner must return the magic value 0x523e3260 if the given address is authorized to act on behalf of the account. By default the NFT holder must be treated as a valid signer, but an implementation may add extra logic to invalidate the holder or grant signing authority to other addresses.
The execution interface is left open, but must be announced via ERC-165
Every account must implement an execution interface through which a valid signer can perform arbitrary operations, and must announce that support via ERC-165. However, no specific interface is mandated. As one option, the suggested IERC6551Executable (identifier 0x51945447) execute function must accept operation values of 0 (CALL), 1 (DELEGATECALL), 2 (CREATE), and 3 (CREATE2), and may support additional operations or restrict execution to specific signers. If execution fails, the error is propagated and the call reverts. The execution interface is left unfixed so as to accommodate the many execution interfaces that already exist and to remain compatible with interfaces that may be standardized in the future.
Counterfactual accounts and cross-chain addresses
Because every account is created with create2, an account can exist in a counterfactual state, with its address fixed before actual deployment, and can receive assets in advance at that address. In addition, thanks to the singleton registry at a known fixed address, the same account can exist on multiple chains. Because createAccount takes chainId as a parameter, the account contract can be deployed at the same address on any supported chain, and it is possible to implement cross-chain execution in which an NFT on one chain controls its own account on another chain. Since token identifiers are unique only within a single chain, the chain ID is used together with the contract address and token ID when identifying an NFT.
Why multiple accounts per NFT are allowed
This standard does not limit each NFT to a single account. By varying the salt and implementation address, one NFT can have multiple accounts. An alternative design limiting each NFT to a single account was considered but not adopted. First, since smart contracts are permissionless, anyone can deploy an additional registry, so a single-account limit could not be enforced in practice anyway. Second, enforcing a single account would require including a fixed, trusted implementation in the standard, which would constrain the account's capabilities and was judged premature. Third, entities active on-chain commonly use multiple accounts, such as a hot account for daily use and a cold account for storage, and the view taken was that NFTs should have the same capability.
Backward compatibility and proposal status
This standard does not extend ERC-721 and requires no changes to existing NFT contracts. The registry does not check via ERC-165 whether a token is ERC-721 before creating an account. This makes it compatible with contracts that predate ERC-721, such as CryptoKitties, and with things like ENS NameWrapper names that implement only part of the ERC-721 interface, and it can also be used with non-fungible and fungible tokens alike (though that use case is outside the scope of this proposal). If needed, an account implementation can add its own ERC-721 interface check. This proposal is currently in Review status, meaning its contents may still change.
Things to watch out for
- Asset-stripping scam just before a sale: If someone offers 11 ether for an NFT whose account holds 10 ether, the seller could drain the ether first and then accept the offer, leaving the buyer with an empty account. Marketplaces are advised to protect against this by attaching the account's state value or asset list to the order and invalidating it if there is any change after the order is placed, by verifying this through an external contract, or by placing a lock on the account. Preventing this kind of fraud is itself outside the scope of this proposal.
- Asset freezing due to ownership cycles: If an ERC-721 token is sent to its own token bound account, that account cannot execute a transaction to send the token back out, so the token and all assets inside the account become permanently inaccessible. Cycles with a depth greater than 1 are difficult to prevent on-chain, so clients and account implementations are advised to include safeguards that reduce the possibility of such cycles.