-
Notifications
You must be signed in to change notification settings - Fork 480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ERC: Limited Transfer Count NFT #274
Merged
Merged
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
32768be
Transit from EIP repo
OniReimu 4a5fcc8
`eip` changed to `erc` as required
OniReimu 33232b0
5521: revert erc refs back to eip
lightclient 966b5da
Updated
OniReimu 2fa4dda
Merge branch 'master' into master
OniReimu 24fe940
Update erc-5521.md
OniReimu b1d2ead
Create erc-xxxx.md
OniReimu 01fdf99
Merge branch 'master' into master
OniReimu fac5c8e
Update erc-xxxx.md
OniReimu d6b5bf8
Update erc-xxxx.md
OniReimu 55e8bcd
Assign an arbitrary eip number
OniReimu 06cbecc
Merge branch 'master' into master
OniReimu a5fabcc
Update erc-7633.md
OniReimu 040f4bf
Merge branch 'master' into master
OniReimu cc8a727
Update eip number
OniReimu 100ba73
Merge branch 'master' into master
OniReimu 6e05697
Update erc-7634.md
OniReimu ac0f735
Update erc-7634.md
OniReimu 79b3bee
Update erc-7634.md
OniReimu e66a3dc
Update erc-5521.md
OniReimu 9c93c73
Merge branch 'master' into master
OniReimu 3be1dad
Update erc-5521.md
OniReimu adc7b65
Merge branch 'master' of https://github.com/OniReimu/ERCs
OniReimu 0e2ffe9
Update erc-7634.md
OniReimu c4fb192
Merge branch 'master' into master
OniReimu defc655
Update erc-7634.md
OniReimu 0814e80
Update erc-7634.md
OniReimu e35e332
Merge branch 'master' into master
OniReimu 2beb755
Update erc-7634.md
SamWilsn 46aeec0
Update erc-7634.md
OniReimu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,223 @@ | ||||||
--- | ||||||
eip: 7634 | ||||||
title: Limited Transfer Count NFT | ||||||
description: An ERC-721 extension to limit transferability based on counts among NFTs | ||||||
author: Qin Wang (@qinwang-git), Saber Yu (@OniReimu), Shiping Chen <shiping.chen@data61.csiro.au> | ||||||
discussions-to: https://ethereum-magicians.org/t/erc-7634-limited-transferable-nft/18861 | ||||||
status: Draft | ||||||
type: Standards Track | ||||||
category: ERC | ||||||
created: 2024-02-22 | ||||||
requires: 165, 721 | ||||||
--- | ||||||
|
||||||
## Abstract | ||||||
|
||||||
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. | ||||||
|
||||||
|
||||||
## Motivation | ||||||
|
||||||
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. | ||||||
|
||||||
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. | ||||||
|
||||||
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. | ||||||
|
||||||
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. | ||||||
|
||||||
### Key Takeaways | ||||||
|
||||||
This standard provides several advantages: | ||||||
|
||||||
*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. | ||||||
|
||||||
*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. | ||||||
|
||||||
*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. | ||||||
|
||||||
*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. | ||||||
|
||||||
## Specification | ||||||
|
||||||
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. | ||||||
|
||||||
- `setTransferLimit`: a function establishes the transfer limit for a tokenId. | ||||||
- `transferLimitOf`: a function retrieves the transfer limit for a tokenId. | ||||||
- `transferCountOf`: a function returns the current transfer count for a tokenId. | ||||||
|
||||||
Implementers of this standard **MUST** have all of the following functions: | ||||||
|
||||||
```solidity | ||||||
|
||||||
pragma solidity ^0.8.4; | ||||||
|
||||||
/// @title IERC7634 Interface for Limited Transferable NFT | ||||||
/// @dev Interface for ERC7634 Limited Transferable NFT extension for ERC721 | ||||||
/// @author Saber Yu | ||||||
|
||||||
interface IERC7634 { | ||||||
|
||||||
/** | ||||||
* @dev Emitted when transfer count is set or updated | ||||||
*/ | ||||||
event TransferCount(uint256 indexed tokenId, address owner, uint256 counts); | ||||||
|
||||||
/** | ||||||
* @dev Returns the current transfer count for a tokenId | ||||||
*/ | ||||||
function transferCountOf(uint256 tokenId) external view returns (uint256); | ||||||
|
||||||
/** | ||||||
* @dev Sets the transfer limit for a tokenId. Can only be called by the token owner or an approved address. | ||||||
* @param tokenId The ID of the token for which to set the limit | ||||||
* @param limit The maximum number of transfers allowed for the token | ||||||
*/ | ||||||
function setTransferLimit(uint256 tokenId, uint256 limit) external; | ||||||
|
||||||
/** | ||||||
* @dev Returns the transfer limit for a tokenId | ||||||
*/ | ||||||
function transferLimitOf(uint256 tokenId) external view returns (uint256); | ||||||
} | ||||||
|
||||||
``` | ||||||
|
||||||
## Rationale | ||||||
|
||||||
### Does tracking the internal transfer count matter? | ||||||
|
||||||
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. | ||||||
|
||||||
### If opting for tracking, is that all we may want to track? | ||||||
|
||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar comment about "RECOMMENDED" and "OPTIONAL" here. |
||||||
|
||||||
|
||||||
## Backwards Compatibility | ||||||
|
||||||
This standard can be fully [ERC-721](./eip-721.md) compatible by adding an extension function set. | ||||||
|
||||||
### Extensions | ||||||
|
||||||
This standard can be enhanced with additional advanced functionalities alongside existing NFT protocols. For example: | ||||||
|
||||||
- Incorporating a burn function (e.g., [ERC-5679](./eip-5679.md)) would enable NFTs to automatically expire after reaching their transfer limits, akin to the ephemeral nature of Snapchat messages that disappear after multiple views. | ||||||
|
||||||
- Incorporating a non-transferring function, as defined in the SBT standards, would enable NFTs to settle and bond with a single owner after a predetermined number of transactions. This functionality mirrors the scenario where a bidder ultimately secures a treasury after participating in multiple bidding rounds. | ||||||
|
||||||
|
||||||
## Reference Implementation | ||||||
|
||||||
The **RECOMMENDED** implementation is demonstrated as follows: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
- `_incrementTransferCount`: an internal function facilitates incrementing the transfer count. | ||||||
- `_beforeTokenTransfer`: an overrided function defines the state before transfer. | ||||||
- `_afterTokenTransfe`: an overrided function outlines the state after transfer. | ||||||
|
||||||
```solidity | ||||||
|
||||||
pragma solidity ^0.8.4; | ||||||
|
||||||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | ||||||
import "./IERC7634.sol"; | ||||||
|
||||||
/// @title Limited Transferable NFT Extension for ERC721 | ||||||
/// @dev Implementation of the Limited Transferable NFT extension for ERC721 | ||||||
/// @author Saber Yu | ||||||
|
||||||
contract ERC7634 is ERC721, IERC7634 { | ||||||
|
||||||
// Mapping from tokenId to the transfer count | ||||||
mapping(uint256 => uint256) private _transferCounts; | ||||||
|
||||||
// Mapping from tokenId to its maximum transfer limit | ||||||
mapping(uint256 => uint256) private _transferLimits; | ||||||
|
||||||
/** | ||||||
* @dev See {IERC7634-transferCountOf}. | ||||||
*/ | ||||||
function transferCountOf(uint256 tokenId) public view override returns (uint256) { | ||||||
require(_exists(tokenId), "ERC7634: Nonexistent token"); | ||||||
return _transferCounts[tokenId]; | ||||||
} | ||||||
|
||||||
/** | ||||||
* @dev See {IERC7634-setTransferLimit}. | ||||||
*/ | ||||||
function setTransferLimit(uint256 tokenId, uint256 limit) public override { | ||||||
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC7634: caller is not owner nor approved"); | ||||||
_transferLimits[tokenId] = limit; | ||||||
} | ||||||
|
||||||
/** | ||||||
* @dev See {IERC7634-transferLimitOf}. | ||||||
*/ | ||||||
function transferLimitOf(uint256 tokenId) public view override returns (uint256) { | ||||||
require(_exists(tokenId), "ERC7634: Nonexistent token"); | ||||||
return _transferLimits[tokenId]; | ||||||
} | ||||||
|
||||||
/** | ||||||
* @dev Internal function to increment transfer count. | ||||||
*/ | ||||||
function _incrementTransferCount(uint256 tokenId) internal { | ||||||
_transferCounts[tokenId] += 1; | ||||||
emit TransferCount(tokenId, ownerOf(tokenId), _transferCounts[tokenId]); | ||||||
} | ||||||
|
||||||
/** | ||||||
* @dev Override {_beforeTokenTransfer} to enforce transfer limit. | ||||||
*/ | ||||||
function _beforeTokenTransfer( | ||||||
address from, | ||||||
address to, | ||||||
uint256 tokenId | ||||||
) internal override { | ||||||
require(_transferCounts[tokenId] < _transferLimits[tokenId], "ERC7634: Transfer limit reached"); | ||||||
super._beforeTokenTransfer(from, to, tokenId); | ||||||
} | ||||||
|
||||||
/** | ||||||
* @dev Override {_afterTokenTransfer} to handle post-transfer logic. | ||||||
*/ | ||||||
function _afterTokenTransfer( | ||||||
address from, | ||||||
address to, | ||||||
uint256 tokenId, | ||||||
uint256 quantity | ||||||
) internal virtual override { | ||||||
_incrementTransferCount(tokenId); | ||||||
|
||||||
if (_transferCounts[tokenId] == _transferLimits[tokenId]) { | ||||||
// Optional post-transfer operations once the limit is reached | ||||||
// Uncomment the following based on the desired behavior such as the `burn` opearation | ||||||
// --------------------------------------- | ||||||
// _burn(tokenId); // Burn the token | ||||||
// --------------------------------------- | ||||||
} | ||||||
|
||||||
super._afterTokenTransfer(from, to, tokenId, quantity); | ||||||
} | ||||||
|
||||||
|
||||||
/** | ||||||
* @dev Override {supportsInterface} to declare support for IERC7634. | ||||||
*/ | ||||||
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { | ||||||
return interfaceId == type(IERC7634).interfaceId || super.supportsInterface(interfaceId); | ||||||
} | ||||||
} | ||||||
|
||||||
``` | ||||||
|
||||||
## Security Considerations | ||||||
|
||||||
- Ensure that each NFT minter can call this function to set transfer limits. | ||||||
- Consider making transfer limits immutable once set to prevent tampering or unauthorized modifications. | ||||||
- Avoid performing resource-intensive operations when integration with advanced functions that could exceed the gas limit during execution. | ||||||
|
||||||
|
||||||
## Copyright | ||||||
|
||||||
Copyright and related rights waived via [CC0](../LICENSE.md). |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The uppercase keywords create requirements on implementations. These keywords should only appear in the Specification section.
If you intend to create a requirement, move the text to the specification section. Instead, if you're simply discussing something and did not intend to create a requirement, use lowercase instead ("optional".)