ERC-4973 Account-bound Token (ABT) (Account-bound Tokens)
Review · NFTs & multi-token · created 2022-04-01
Defines an interface for issuing, accepting, releasing, and tracking Account-bound Tokens (ABT), which are NFTs that are bound to a single account and cannot be transferred. They are issued only with the signed consent of the recipient, and the recipient can release the binding at any time.
Why it is needed
Ordinary NFTs can be freely bought and sold, so holding one is not proof that the holder actually accomplished something themselves. The Ethereum community has expressed a need for tokens that, like the soulbound items in World of Warcraft, carry no market price and are permanently bound to an account, and in fact popular contracts already implicitly implement this kind of account-bound interaction right. But without a standard, if each project implements this by simply making the transfer function fail, wallets and marketplaces have no mechanical way to tell that the token is non-transferable, and on-chain data indexing ends up inconsistent as well. This standard aims to improve interoperability and indexing by unifying how account-bound tokens are created, assigned, revoked, and tracked, using a minimal interface that remains as compatible as possible with ERC-721.
What to implement
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);: An event emitted whenever ownership of an ABT changes. It fires when a token is given, or when it is equipped or unequipped (unequipping sets to to the zero address).function balanceOf(address owner) external view returns (uint256);: Counts the number of ABTs assigned to a given address. Querying the zero address fails.function ownerOf(uint256 tokenId) external view returns (address);: Returns the owner address that the given ABT is bound to. Querying a token assigned to the zero address fails.function unequip(uint256 tokenId) external;: Called by the recipient at any time to publicly break their link to an ABT. It must emit a Transfer event with to set to the zero address.function give(address to, bytes calldata metadata, bytes calldata signature) external returns (uint256);: msg.sender mints an ABT for to. The signature, which must be to's valid signature over the EIP-712 consent data, is required, and a new tokenId is returned.function take(address from, bytes calldata metadata, bytes calldata signature) external returns (uint256);: msg.sender mints and receives an ABT from from. A valid EIP-712 signature from from is required, and a new tokenId is returned.function decodeURI(bytes calldata metadata) external returns (string memory);: Interprets an opaque metadata byte string as the token URI string that will be linked when the token is created, and returns it.
In depth
ERC-721 compatibility strategy and interface detection
The ABT contract must implement ERC-165's ERC165(0x01ffc9a7) and ERC-721's ERC721Metadata(0x5b5e139f), and must not implement ERC-721's transfer and tracking interface (0x80ac58cd). This interface's own ERC-165 identifier is 0xeb72bb7c. When a wallet or marketplace calls supportsInterface and confirms 0x80ac58cd is absent, it should not offer transfer as a UI option, and because ERC721Metadata is still supported, it can display the token unchanged. Another implementation approach would be to revert all transfer functions, but the original text explains that this approach, which supports ERC-165 feature detection, is better. Names like ownerOf and balanceOf and the Transfer event signature were also reused from ERC-721 terminology to reduce the learning cost for existing implementers.
give and take: minting only by mutual agreement
An ABT can only be created through give or take. give is a function where msg.sender gives to to, and take is a function where msg.sender receives from from. Both functions must fail unless a signature is provided proving that the counterparty signed the EIP-712 structured data Agreement(address active,address passive,bytes metadata) and explicitly agreed to be publicly linked to msg.sender and metadata. If the signature is empty or the counterparty address is a contract, verification must go through an EIP-1271-compatible isValidSignatureNow(...) call, which allows contract accounts such as multisig wallets to participate as well. The tokenId is created by casting this EIP-712 hash (bytes32) to a uint256, and give/take must fail if the same tokenId already exists. A Transfer event is emitted on success.
unequip: the right to detach at any time
The recipient must be able to call unequip(tokenId) at any time to publicly sever the link between themselves and the ABT. Executing it must emit a Transfer event with to set to the zero address, and afterward the token must be able to be reattached using the same parameters as the original give or take call. The original text describes this requirement as a mechanism guaranteeing the owner's right to publicly disassociate themselves from something issued to their own account. It corresponds to how players in World of Warcraft could choose for themselves whether to equip, unequip, or discard a soulbound item in order to display an achievement.
Event-based indexing rules
ABTs can be tracked and indexed by monitoring Transfer(address indexed from, address indexed to, uint256 indexed tokenId) events. If neither from nor to is the zero address, it is a payment between accounts; if to is the zero address, it is an unequip. There is no mint operation where from is the zero address. To avoid being fooled by a contract maliciously mimicking Transfer events, an indexer must verify that the transaction's sender matches the event's from value.
Exception handling for key compromise and key rotation
Because ABTs cannot be transferred between accounts, if an account's or contract's keys are compromised or rotated, the user may lose the ability to link themselves to the token. In some cases, this may be the intended effect. Rather than deliberately standardizing how to handle this, the original text recommends that implementers create reissuance and recovery procedures as a remedy, and recommends a strictly decentralized, permissionless, and censorship-resistant reissuance procedure. To allow multiple accounts to share use, it suggests issuing the token to a contract account that implements multisig functionality.
Metadata handling and proposal status
decodeURI(bytes metadata) is a function that resolves the opaque metadata byte string passed to give/take into the token URI string that will be linked when the token is created on-chain. The metadata JSON schema follows ERC-721's definition as is. A Node.js reference implementation for signature generation (index.mjs, etc.) and a Solidity reference implementation (ERC4973-flat.sol) are attached to the original text. This proposal's current status is Review, meaning it is still under review and its content may change.