Discussion topic for ERC-7858: Expirable NFTs and SBTs
Update Log
- 2025-01-04: Initiate Idea, received from @Arvolear #16, #18
- 2025-01-06: Update section Specification
- 2025-01-09: Update section Specification
- 2025-02-25: Update section Specification commit fix comment from @alexroan
b10c573 - 2025-02-26: Update section Security Considerations commit a3a27bb
- 2025-02-26: Update section Specification commit 91bdd1b
- 2025-04-21: Update resolve some comments from EIP Editor commit 450f579 and 816f06e
- 2025-05-14: Update Status Move to Review #1023
External Reviews
- Receive comment from @alexroan about verb/grammar on function name.
Outstanding Issue
- 2025-02-26: Nonexistence token can return
startTime
andendTime
fix in commit 91bdd1b
All changes will be managed through pull requests. This topic provides an initial draft version to help those who are not familiar with using GitHub.
Author Opinion
In the past, plenty of ERCs have tried adding an âexpirationâ feature to NFTs and SBTs as part of their use case. But honestly, most smart contracts or protocols probably just need to know if the token (or contract) can expireâdoesnât matter if itâs for subscriptions, rentals, or synthetic use cases. credit to the original idea ERC-5007 but this proposal focuses more on âexpirableâ behavior. allowing other use cases to be built on top of this foundational feature.
Simple Summary
An extended interface enables Non-Fungible Tokens (NFTs) and Soulbound Tokens (SBTs) to possess expiration capabilities, allowing them to expire after a predetermined time.
Abstract
Introduces an extension for ERC-721 Non-Fungible Tokens (NFTs) and Soulbound Tokens (SBTs) that adds an expiration mechanism, allowing tokens to become invalid after a predefined period. This additional layer of functionality ensures that the expiration mechanism does not interfere with existing NFTs or SBTs, preserving transferability for NFTs and compatibility with current DApps such as NFT Marketplace. Expiration can be defined using either block height or timestamp, offering flexibility for various use cases.
Motivation
Introduces an extension for ERC-721 Non-Fungible Tokens (NFTs) and Soulbound Tokens (SBTs), which facilitates the implementation of an expiration mechanism.
Use cases include:
- Access and Authentication
- Digital Certifications, Contracts, Copyrights, Documents, Licenses, Policies, etc.
- Loyalty Program voucher or coupon
- Governance and Voting Rights
- Financial Product
- Bonds, Loans, Hedge, and Options Contract
Specification
The key words âMUSTâ, âMUST NOTâ, âREQUIREDâ, âSHALLâ, âSHALL NOTâ, âSHOULDâ, âSHOULD NOTâ, âRECOMMENDEDâ, âNOT RECOMMENDEDâ, âMAYâ, and âOPTIONALâ in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Interface
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0 <0.9.0;
/**
* @title ERC-7858: Expirable NFTs and SBTs
* @notice unique/granular expiry
*/
// import "./IERC721.sol";
// The EIP-165 identifier of this interface is `0x3ebdfa31`.
interface IERC7858 /**is IERC721 */ {
enum EXPIRY_TYPE {
BLOCKS_BASED, // block.number
TIME_BASED // block.timestamp
}
/**
* @dev Emitted when the expiration date of a token is set or updated.
* @param tokenId The identifier of the token ERC721 `tokenId`.
* @param startTime The start time of the token (block number or timestamp based on `expiryType`).
* @param endTime The end time of the token (block number or timestamp based on `expiryType`).
*/
event TokenExpiryUpdated(
uint256 indexed tokenId,
uint256 indexed startTime,
uint256 indexed endTime
);
/**
* @dev Returns the type of the expiry.
* @return EXPIRY_TYPE Enum value indicating the unit of an expiry.
*/
function expiryType() external view returns (EXPIRY_TYPE);
/**
* @dev Checks whether a specific token is expired.
* @param tokenId The identifier representing the `tokenId` (ERC721).
* @return bool True if the token is expired, false otherwise.
*/
function isTokenExpired(uint256 tokenId) external view returns (bool);
// return depends on the type `block.timestamp` or `block.number`
// {ERC-5007} return in uint64 MAY not suitable for `block.number` based.
function startTime(uint256 tokenId) external view returns (uint256);
function endTime(uint256 tokenId) external view returns (uint256);
}
Behavior Specification
balanceOf
that inherited from ERC-721 MUST return all tokens even if expired; they still exist but are unusable due to the limitation of tracking expired token on-chain.- For NFTs
transferFrom
, andsafeTransferFrom
MUST allow transferring tokens even if they expired. This ensures that expired tokens remain transferable and tradable, preserving compatibility with existing applications already deployed. However, expired tokens MUST be considered invalid and unusable in contracts that check for token validity. expiryType
MUST return the type of expiry used by the contract, which can be eitherBLOCK
orTIME
.startTime
andendTime
oftokenId
, can beblock.number
orblock.timestamp
depending onexpiryType
. ThestartTime
MUST less thanendTime
and SHOULD except when both are set to 0. AstartTime
andendTime
of 0 indicates that thetokenId
ortokenType
has no time-limited.isTokenExpired
is used for retrieving the status of the giventokenId
ortokenType
the function MUST returntrue
if the token is still valid; otherwise,false
.supportInterface
forIERC7858
is0x3ebdfa31
forIERC7858Epoch
is0xaaf87b24
TokenExpiryUpdated
MUST be emitted when the token is minted or when its expiration details (startTime
orendTime
) are updated.
Extension Interface
Epochs represent a specific period or block range during which certain tokens are valid borrowing concepts from ERC-7818, tokens are grouped under an epoch
and share the same validityDuration
.
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0 <0.9.0;
/**
* @title ERC-7858: Expirable NFTs and SBTs
* @notice epoch expiry extension
*/
// import "./IERC7858.sol";
// The EIP-165 identifier of this interface is `0xaaf87b24`.
interface IERC7858Epoch /** is IERC7858 */ {
/**
* @dev Retrieves the balance of a specific `epoch` owned by an account.
* @param epoch The `epoch for which the balance is checked.
* @param account The address of the account.
* @return uint256 The balance of the specified `epoch`.
* @notice "MUST" return 0 if the specified `epoch` is expired.
*/
function balanceOfAtEpoch(uint256 epoch, address account) external view returns (uint256);
/**
* @dev Retrieves the current epoch of the contract.
* @return uint256 The current epoch of the token contract,
* often used for determining active/expired states.
*/
function currentEpoch() external view returns (uint256);
/**
* @dev Retrieves the duration of a single epoch.
* @return uint256 The duration of a single epoch.
* @notice The unit of the epoch length is determined by the `validityPeriodType` function.
*/
function epochLength() external view returns (uint256);
/**
* @dev Returns the type of the epoch.
* @return EXPIRY_TYPE Enum value indicating the unit of an epoch.
*/
function epochType() external view returns (EXPIRY_TYPE);
/**
* @dev Checks whether a specific `epoch` is expired.
* @param epoch The `epoch` to check.
* @return bool True if the token is expired, false otherwise.
* @notice Implementing contracts "MUST" define and document the logic for determining expiration,
* typically by comparing the latest epoch with the given `epoch` value,
* based on the `EXPIRY_TYPE` measurement (e.g., block count or time duration).
*/
function isEpochExpired(uint256 epoch) external view returns (bool);
/**
* @dev Retrieves the balance of unexpired tokens owned by an account.
* @param account The address of the account.
* @return uint256 The amount of unexpired tokens owned by an account.
*/
function unexpiredBalanceOf(address account) external view returns (uint256);
/**
* @dev Retrieves the validity duration of each token.
* @return uint256 The validity duration of each token in `epoch` unit.
*/
function validityDuration() external view returns (uint256);
}
balanceOfAtEpoch
MUST return the balance of tokens held by an account at the specifiedepoch
,If the specifiedepoch
is expired, this function MUST return0
. For example, if epoch5
has expired, callingbalanceOfByEpoch(5, address)
returns0
even if there were tokens previously held in that epoch.unexpiredBalanceOf
MUST return only unexpired or usable tokens.currentEpoch
MUST return the currentepoch
of the contract.epochLength
MUST return duration betweenepoch
in blocks or time in seconds.epochType
MUST return the type of epoch used by the contract, which can be eitherBLOCKS_BASED
orTIME_BASED
.validityDuration
MUST return the validity duration of tokens in terms ofepoch
counts.isEpochExpired
MUST return true if the givenepoch
is expired, otherwisefalse
.
Additional Potential Useful Function
These OPTIONAL functions provide additional functionality that might be useful depending on the specific use case.
getEpochBalance
returns the amount of tokens stored in a given epoch, even if the epoch has expired.getEpochInfo
returns both the start and end of the specifiedepoch
.getNearestExpiryOf
returns the list oftokenId
closest to expiration, along with an estimated expiration block number or timestamp based onepochType
.getRemainingDurationBeforeEpochChange
returns the remaining time or blocks before the epoch change happens, based on theepochType
.
Rationale
First, do no harm
Introducing expirability as an additional layer of functionality ensures it doesnât interfere with existing use cases or applications. For non-SBT tokens, transferability remains intact, maintaining compatibility with current systems. Expired tokens are simply flagged as unusable during validity checks, treating expiration as an enhancement rather than a fundamental change.
Expiry Types
Defining expiration by either block height (block.number
) or block timestamp (block.timestamp
) offers flexibility for various use cases. Block-based expiration suits applications that rely on network activity and require precise consistency, while time-based expiration is ideal for networks with variable block intervals.
Backwards Compatibility
This standard is fully compatible with ERC-721, ERC-5484 and other SBTs.
Security Considerations
No security considerations were found.
Historical links related to this standard
ERC that mentions âexpirationâ includes:
ERC-4907: ERC-721 User And Expires Extension (Final)
ERC-5007: ERC-721 Time Extension (Final)
ERC-5334: ERC-721 User And Expires And Level Extension (Draft)
ERC-5643: Subscription NFTs (Stagnant)
ERC-5727: Semi-Fungible Soulbound Token (Draft)
ERC-6036: Subscribable NFT Extension (Draft)
ERC-6785: ERC-721 Utilities extension (Draft)