ERC-7634 Transfer-limited NFT (Limited Transfer Count NFT)
Final · NFTs & multi-token · created 2024-02-22
An ERC-721 extension standard that sets an upper bound on how many times each NFT can be transferred and blocks further transfers once that count is used up.
Why it is needed
Today, once an NFT is sold, it leaves the hands of the original issuer and can be resold endlessly to anyone. In some situations, however, this unlimited transferability is a problem. NFTs whose value rests on scarcity, such as premium auction items or limited editions, are exposed to frequent resale. Game items that should wear out after a few uses, and memberships and licenses that should be transferable only a set number of times, are hard to implement with standard methods. There is also no simple mechanism to curb adversarial arbitrage that repeats within a short time, such as ultra-short-term trading, or the practice of repeatedly restaking NFT positions to build leverage. This standard defines a common interface that sets a transfer limit per token, counts actual transfers, and blocks transfers that exceed the limit, enabling this kind of fine-grained transfer restriction while staying compatible with ERC-721.
What to implement
event TransferCountIncreased(uint256 indexed tokenId, uint256 newCount);: An event emitted after a transfer succeeds and the token's transfer count increases.event TransferLimitUpdated(uint256 indexed tokenId, uint256 previousLimit, uint256 newLimit);: An event that reports the previous limit and the new limit when a token's transfer limit is set or changed.function transferCountOf(uint256 tokenId) external view returns (uint256);: Returns the number of times the token has been transferred so far.function setTransferLimit(uint256 tokenId, uint256 limit) external;: Sets the transfer limit for the token. The owner or an approved address can call it.function transferLimitOf(uint256 tokenId) external view returns (uint256);: Returns the transfer limit set for the token.
In depth
Three core functions
setTransferLimit(tokenId, limit) sets the per-token transfer limit and can be called by the owner or an approved address. transferLimitOf(tokenId) returns the configured limit, and transferCountOf(tokenId) returns the number of transfers so far. Implementations MUST provide this IERC7634 interface. In the reference implementation, both query functions revert for a token that does not exist.
What is counted and what is not
The limit MUST be applied to native ERC-721 transfers of the underlying token, that is, transfers where neither from nor to is the zero address (address(0)). The count SHOULD be increased only after a native transfer succeeds, and minting and burning MUST NOT increase the count. Creating and destroying tokens therefore does not consume the transfer budget, and only actual moves between owners are counted.
Why there are two events
TransferLimitUpdated is emitted by the administrative act of setting or changing the limit, and TransferCountIncreased is emitted for state changes derived from actual transfers at runtime. Separating the two events lets you tell from the logs alone which changes were an administrator's configuration changes and which were the result of a transfer. The count must increase only after a successful native transfer, and it does not increase on minting or burning.
Reference implementation pattern
The recommended pattern is to override the hooks of OpenZeppelin ERC721. In _beforeTokenTransfer, when neither from nor to is the zero address, a require checks _transferCounts[tokenId] < _transferLimits[tokenId], blocking the transfer once the limit is reached. In _afterTokenTransfer, the count is increased by 1 and TransferCountIncreased is emitted, and there is a place to run optional actions such as _burn(tokenId) at the moment the count equals the limit. supportsInterface extends ERC-165 support to recognize type(IERC7634).interfaceId.
ERC-721 compatibility and combining extensions
This standard is fully compatible with ERC-721, and existing contracts can adopt it by adding only the interface and hooks, without changing ERC-721 semantics. It can also be combined with other standards. Combined with a burn feature such as ERC-5679, an NFT that reaches its transfer limit can disappear automatically, like a Snapchat message that vanishes after being viewed several times. Combined with the non-transferability feature of SBT standards, it can produce an NFT that is permanently bound to one owner after a set number of trades.
A design that acknowledges bypass is possible
The limit applies only to native transfers of the underlying token. An owner can bypass it, for example by putting the token into a wrapping contract and transferring a separate wrapper token. Preventing this would require breaking ERC-721 composability, so the standard does not prevent it. In other words, this standard does not offer an unbreakable guarantee but standardizes a basic unit, the native transfer budget, and where stronger guarantees are needed, mitigations such as a recipient allowlist or a compatible wrapper pattern that carries the count over unchanged MAY be added.
Things to watch out for
- It can be bypassed with a wrapper: The limit applies only to native ERC-721 transfers, so if an owner wraps the token and trades the wrapper token, the restriction does not reach it. There is no guarantee that a game item will necessarily wear out or that secondary trading will be restricted overall. Where stronger guarantees are needed, a recipient allowlist or a compatible wrapper pattern can be added.
- Possibility of changing the limit: To prevent the limit from being changed later to defeat the restriction, consider making a limit immutable once set, or allowing it only to be decreased.
- Gas cost of hooks: Do not put heavy logic in the transfer hook, and extension actions such as burning when the limit is reached must also be kept safe in terms of gas.