Skip to content

Commit 2cafaa1

Browse files
authored
Update ERC-7634: Move to Last Call
Merged by EIP-Bot.
1 parent f299295 commit 2cafaa1

File tree

1 file changed

+74
-95
lines changed

1 file changed

+74
-95
lines changed

ERCS/erc-7634.md

Lines changed: 74 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
---
22
eip: 7634
33
title: Limited Transfer Count NFT
4-
description: An ERC-721 extension to limit transferability based on counts among NFTs
4+
description: An ERC-721 extension that caps how many times an individual token can be transferred
55
author: Qin Wang (@qinwang-git), Saber Yu (@OniReimu), Shiping Chen <[email protected]>
66
discussions-to: https://ethereum-magicians.org/t/erc-7634-limited-transferable-nft/18861
7-
status: Review
7+
status: Last Call
8+
last-call-deadline: 2025-12-16
89
type: Standards Track
910
category: ERC
1011
created: 2024-02-22
@@ -13,70 +14,72 @@ requires: 165, 721
1314

1415
## Abstract
1516

16-
This standard extends [ERC-721](./eip-721.md) to introduce a mechanism that allows minters to customize the transferability of NFTs through a parameter called `TransferCount`. `TransferCount` sets a limit on how many times an NFT can be transferred. The standard specifies an interface that includes functions for setting and retrieving transfer limits, tracking transfer counts, and defining pre- and post-transfer states. The standard enables finer control over NFT ownership and transfer rights, ensuring that NFTs can be programmed to have specific, enforceable transfer restrictions.
17-
17+
This standard extends [ERC-721](./eip-721.md) with a mechanism that lets token owners/minters cap how many times a specific token can be transferred. It specifies functions to set and read a per-token transfer limit, to query a per-token transfer count, and defines transfer-time hooks to enforce the cap. The goal is fine-grained, enforceable transfer restrictions while preserving ERC-721 compatibility.
1818

1919
## Motivation
2020

21-
Once NFTs are sold, they detach from their minters (creators) and can be perpetually transferred thereafter. Yet, many circumstances demand precise control over NFT issuance. We outline their advantages across three dimensions.
21+
Once NFTs are sold, they detach from their minters and can be perpetually transferred. Yet many situations require tighter control over secondary movement.
2222

23-
Firstly, by imposing limitations on the frequency of NFT sales or trades, the worth of rare NFTs can be safeguarded. For example, in auctions, limiting the round of bids for a coveted item can uphold its premium price (especially in the Dutch Auction). Similarly, in the intellectual property sector, patents could be bounded by a finite number of transfers prior to becoming freely accessible (entering CC0). In the gaming sphere, items like weapons, apparel, and vehicles might possess a finite lifespan, with each usage or exchange contributing to wear and tear, culminating in automatic decommissioning (burn) upon reaching a predetermined threshold.
23+
First, limiting the number of transfers can help preserve value (e.g., premium auctions, IP that becomes CC0 after a finite number of transfers, or game items that “wear out” and burn at a threshold).
2424

25-
Secondly, enforcing restrictions on trading frequency can enhance network security and stability by mitigating the risks associated with malicious NFT arbitrage, including high-frequency trading (HFT). While this presents a common vulnerability, the lack of easily deployable and effective methods to address it has been notable, making our approach particularly valuable.
25+
Second, capping transfer frequency can reduce risks from adversarial arbitrage (e.g., HFT-like behavior) by offering an easy-to-deploy throttle.
2626

27-
Additionally, limiting the round of transfers can mitigate the economic risks associated with (re)staking NFTs, thereby curbing potential bubbles. With the rapid evolution of restaking mechanisms, it's foreseeable that users may soon engage in multiple rounds of NFT staking (e.g., NFT → stNFT → st^2NFT), akin to staking liquidity tokens with third-party platforms like EigenLayer (Ethereum), Babylon (Bitcoin), and Picasso (Solana). Notably, the current setup of EigenLayer employs an NFT as the restaking position (a type of proof-of-restake) for participants. Should this NFT be restaked repeatedly into the market, it could amplify leverage and exacerbate bubble dynamics. By imposing limits on the number of stake iterations, we can proactively prevent the emergence of Ponzi-like dynamics within staking ecosystems.
27+
Third, bounding re-staking cycles of NFT positions (e.g., proof-of-restake) can dampen recursive leverage and mitigate bubble dynamics.
2828

29-
### Key Takeaways
3029

31-
This standard provides several advantages:
30+
### Key Takeaways
3231

33-
*Controlled Value Preservation*: By allowing minters to set customized transfer limits for NFTs, this standard facilitates the preservation of value for digital assets Just as physical collectibles often gain or maintain value due to scarcity, limiting the number of transfers for an NFT can help ensure its continued value over time.
32+
*Controlled Value Preservation.* Scarcity via a transfer cap can help maintain value over time.
3433

35-
*Ensuring Intended Usage*: Setting transfer limits can ensure that NFTs are used in ways that align with their intended purpose. For example, if an NFT represents a limited-edition digital artwork, limiting transfers can prevent it from being excessively traded and potentially devalued.
34+
*Ensuring Intended Usage.* Limits keep usage aligned with intent (e.g., limited editions less prone to flip cycles).
3635

37-
*Expanding Use Cases*: These enhancements broaden the potential applications of NFTs by offering more control and flexibility to creators and owners. For instance, NFTs could be used to represent memberships or licenses with limited transferability, opening up new possibilities for digital ownership models.
36+
*Expanding Use Cases.* Memberships/licenses with bounded transferability become practical.
3837

39-
*Easy Integration*: To ensure broad adoption and ease of integration, this standard extends the existing [ERC-721](./eip-721.md) interface. By defining a separate interface (`IERC7634`) that includes the new functions, the standard allows existing [ERC-721](./eip-721.md) contracts to adopt the new features with minimal changes. This approach promotes backward compatibility and encourages the seamless incorporation of transfer limits into current NFT projects.
38+
*Easy Integration.* An extension interface (`IERC7634`) layers on top of [ERC-721](./eip-721.md), easing adoption without breaking compatibility.
4039

4140
## Specification
4241

4342
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
4443

45-
- `setTransferLimit`: a function establishes the transfer limit for a tokenId.
46-
- `transferLimitOf`: a function retrieves the transfer limit for a tokenId.
47-
- `transferCountOf`: a function returns the current transfer count for a tokenId.
44+
- `setTransferLimit`: establishes the transfer limit for a `tokenId`.
45+
- `transferLimitOf`: returns the transfer limit for a `tokenId`.
46+
- `transferCountOf`: returns the current transfer count for a `tokenId`.
4847

49-
Implementers of this standard **MUST** have all of the following functions:
48+
**Counting and enforcement scope.** Implementations **MUST** enforce the cap on native ERC-721 transfers of the underlying token (i.e., transfers where `from != address(0)` and `to != address(0)`). Incrementing the count **SHOULD** occur only after a successful native transfer. Mint and burn operations MUST NOT increment the count.
49+
50+
Implementers **MUST** provide the following interface:
5051

5152
```solidity
5253
5354
pragma solidity ^0.8.4;
5455
55-
/// @title IERC7634 Interface for Limited Transferable NFT
56-
/// @dev Interface for ERC7634 Limited Transferable NFT extension for ERC721
57-
/// @author Saber Yu
58-
56+
/// @title IERC7634 Interface for Transfer-Capped ERC-721 Tokens
57+
/// @dev ERC-7634 is an extension interface intended to be implemented alongside ERC-721
5958
interface IERC7634 {
59+
/**
60+
* @dev Emitted after a successful native transfer when the per-token count increases.
61+
*/
62+
event TransferCountIncreased(uint256 indexed tokenId, uint256 newCount);
6063
6164
/**
62-
* @dev Emitted when transfer count is set or updated
65+
* @dev Emitted when the per-token transfer limit is set or updated.
6366
*/
64-
event TransferCount(uint256 indexed tokenId, address owner, uint256 counts);
67+
event TransferLimitUpdated(uint256 indexed tokenId, uint256 previousLimit, uint256 newLimit);
6568
6669
/**
67-
* @dev Returns the current transfer count for a tokenId
70+
* @dev Returns the current transfer count for a tokenId.
6871
*/
6972
function transferCountOf(uint256 tokenId) external view returns (uint256);
7073
7174
/**
72-
* @dev Sets the transfer limit for a tokenId. Can only be called by the token owner or an approved address.
73-
* @param tokenId The ID of the token for which to set the limit
74-
* @param limit The maximum number of transfers allowed for the token
75+
* @dev Sets the transfer limit for a tokenId. Callable by owner or approved.
76+
* @param tokenId The token id to set the limit for.
77+
* @param limit The maximum number of native transfers allowed.
7578
*/
7679
function setTransferLimit(uint256 tokenId, uint256 limit) external;
7780
7881
/**
79-
* @dev Returns the transfer limit for a tokenId
82+
* @dev Returns the transfer limit for a tokenId.
8083
*/
8184
function transferLimitOf(uint256 tokenId) external view returns (uint256);
8285
}
@@ -85,18 +88,13 @@ interface IERC7634 {
8588

8689
## Rationale
8790

88-
### Does tracking the internal transfer count matter?
89-
90-
Yes and no. It is optional and quite depends on the actual requirements. The reference implementation given below is a recommended one if you opt for tracking. The `_incrementTransferCount` function and related retrieval functions (`transferLimitOf` and `transferCountOf`) are designed to keep track of the number of transfers an NFT has undergone. This internal tracking mechanism is crucial for enforcing the minter's transfer limits, ensuring that no further transfers can occur once the limit is reached.
91-
92-
### If opting for tracking, is that all we may want to track?
93-
94-
It is recommended to also track the before and after. The optional `_beforeTokenTransfer` and `_afterTokenTransfer` functions are overridden to define the state of the NFT before and after a transfer. These functions ensure that any necessary checks or updates are performed in line with the transfer limits and counts. By integrating these checks into the transfer process, the standard ensures that transfer limits are consistently enforced.
91+
### Tracking and hooks
9592

93+
`transferCountOf` and `transferLimitOf` expose state needed to enforce a cap. The count should only increase after a successful native transfer (not on mint/burn). Separating `TransferLimitUpdated` from `TransferCountIncreased` makes it clear that the former is an administrative change while the latter is derived from runtime transfers.
9694

9795
## Backwards Compatibility
9896

99-
This standard can be fully [ERC-721](./eip-721.md) compatible by adding an extension function set.
97+
This standard is fully compatible with [ERC-721](./eip-721.md). Existing contracts can adopt it by adding the new interface and hooks without changing ERC-721 semantics.
10098

10199
### Extensions
102100

@@ -109,11 +107,7 @@ This standard can be enhanced with additional advanced functionalities alongside
109107

110108
## Reference Implementation
111109

112-
A recommended implementation is demonstrated as follows:
113-
114-
- `_incrementTransferCount`: an internal function facilitates incrementing the transfer count.
115-
- `_beforeTokenTransfer`: an overrided function defines the state before transfer.
116-
- `_afterTokenTransfe`: an overrided function outlines the state after transfer.
110+
Below is a recommended pattern. It enforces the cap pre-transfer, increments the count post-transfer, ignores mint/burn for counting, and emits the clarified events. Implementations commonly override `_beforeTokenTransfer` to enforce `transferCount` < `transferLimit` and `_afterTokenTransfer` to increment the count and emit `TransferCountIncreased`.
117111

118112
```solidity
119113
@@ -122,89 +116,71 @@ pragma solidity ^0.8.4;
122116
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
123117
import "./IERC7634.sol";
124118
125-
/// @title Limited Transferable NFT Extension for ERC721
126-
/// @dev Implementation of the Limited Transferable NFT extension for ERC721
127-
/// @author Saber Yu
128-
119+
/// @title Transfer-Capped ERC-721 (ERC-7634)
120+
/// @dev Example implementation of ERC-7634 alongside ERC-721
129121
contract ERC7634 is ERC721, IERC7634 {
130-
131-
// Mapping from tokenId to the transfer count
122+
// tokenId => current transfer count
132123
mapping(uint256 => uint256) private _transferCounts;
133-
134-
// Mapping from tokenId to its maximum transfer limit
124+
// tokenId => max allowed native transfers
135125
mapping(uint256 => uint256) private _transferLimits;
136126
137-
/**
138-
* @dev See {IERC7634-transferCountOf}.
139-
*/
140127
function transferCountOf(uint256 tokenId) public view override returns (uint256) {
141-
require(_exists(tokenId), "ERC7634: Nonexistent token");
128+
require(_exists(tokenId), "ERC7634: nonexistent token");
142129
return _transferCounts[tokenId];
143130
}
144131
145-
/**
146-
* @dev See {IERC7634-setTransferLimit}.
147-
*/
148132
function setTransferLimit(uint256 tokenId, uint256 limit) public override {
149-
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC7634: caller is not owner nor approved");
133+
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC7634: not owner/approved");
134+
uint256 prev = _transferLimits[tokenId];
150135
_transferLimits[tokenId] = limit;
136+
emit TransferLimitUpdated(tokenId, prev, limit);
151137
}
152138
153-
/**
154-
* @dev See {IERC7634-transferLimitOf}.
155-
*/
156139
function transferLimitOf(uint256 tokenId) public view override returns (uint256) {
157-
require(_exists(tokenId), "ERC7634: Nonexistent token");
140+
require(_exists(tokenId), "ERC7634: nonexistent token");
158141
return _transferLimits[tokenId];
159142
}
160143
161-
/**
162-
* @dev Internal function to increment transfer count.
163-
*/
164-
function _incrementTransferCount(uint256 tokenId) internal {
165-
_transferCounts[tokenId] += 1;
166-
emit TransferCount(tokenId, ownerOf(tokenId), _transferCounts[tokenId]);
167-
}
168-
169-
/**
170-
* @dev Override {_beforeTokenTransfer} to enforce transfer limit.
171-
*/
144+
/// @dev Enforce transfer limit on native transfers (exclude mint/burn).
172145
function _beforeTokenTransfer(
173146
address from,
174147
address to,
175148
uint256 tokenId
176-
) internal override {
177-
require(_transferCounts[tokenId] < _transferLimits[tokenId], "ERC7634: Transfer limit reached");
149+
) internal virtual override {
150+
if (from != address(0) && to != address(0)) {
151+
require(
152+
_transferCounts[tokenId] < _transferLimits[tokenId],
153+
"ERC7634: transfer limit reached"
154+
);
155+
}
178156
super._beforeTokenTransfer(from, to, tokenId);
179157
}
180158
181-
/**
182-
* @dev Override {_afterTokenTransfer} to handle post-transfer logic.
183-
*/
159+
/// @dev Increment count only after successful native transfer and emit event.
184160
function _afterTokenTransfer(
185161
address from,
186162
address to,
187163
uint256 tokenId,
188164
uint256 quantity
189165
) internal virtual override {
190-
_incrementTransferCount(tokenId);
191-
192-
if (_transferCounts[tokenId] == _transferLimits[tokenId]) {
193-
// Optional post-transfer operations once the limit is reached
194-
// Uncomment the following based on the desired behavior such as the `burn` opearation
195-
// ---------------------------------------
196-
// _burn(tokenId); // Burn the token
197-
// ---------------------------------------
198-
}
166+
if (from != address(0) && to != address(0)) {
167+
unchecked { _transferCounts[tokenId] += 1; }
168+
emit TransferCountIncreased(tokenId, _transferCounts[tokenId]);
199169
170+
if (_transferCounts[tokenId] == _transferLimits[tokenId]) {
171+
// Optional: perform action exactly when the cap is reached (e.g., _burn(tokenId))
172+
}
173+
}
200174
super._afterTokenTransfer(from, to, tokenId, quantity);
201175
}
202176
203-
204-
/**
205-
* @dev Override {supportsInterface} to declare support for IERC7634.
206-
*/
207-
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
177+
function supportsInterface(bytes4 interfaceId)
178+
public
179+
view
180+
virtual
181+
override(ERC721)
182+
returns (bool)
183+
{
208184
return interfaceId == type(IERC7634).interfaceId || super.supportsInterface(interfaceId);
209185
}
210186
}
@@ -213,9 +189,12 @@ contract ERC7634 is ERC721, IERC7634 {
213189

214190
## Security Considerations
215191

216-
- Ensure that each NFT minter can call this function to set transfer limits.
217-
- Consider making transfer limits immutable once set to prevent tampering or unauthorized modifications.
218-
- Avoid performing resource-intensive operations when integration with advanced functions that could exceed the gas limit during execution.
192+
- Scope with wrappers. The cap applies only to native [ERC-721](./eip-721.md) transfers of the underlying token. Any owner can cheaply wrap a token and transfer a separate wrapper token; such downstream transfers cannot be prevented without breaking [ERC-721](./eip-721.md) composability. As a result, this standard does not provide an unbypassable guarantee that, for example, a game item will always wear out or that secondary trading is globally capped; it standardizes a primitive for budgeting native transfers that ecosystems may choose to coordinate around. Deployments that want stronger effective guarantees **MAY** add optional mitigations such as recipient allowlists/registries or "compliant wrapper" patterns that mirror counts/limits, trading off openness for enforcement.
193+
194+
- Limit mutability. Consider making limits immutable once set (or only decreasing), to prevent tampering.
195+
196+
- Gas. Avoid heavy logic in hooks; extensions (e.g., burn on cap) should remain gas-safe.
197+
219198

220199

221200
## Copyright

0 commit comments

Comments
 (0)