ERC-7858: Expirable NFTs and SBTs

Non-fungible (NFT) and soulbound (SBT) tokens with expiration, supporting time-limited use cases.


Metadata
Status: FinalStandards Track: ERCCreated: 2024-01-04
Authors
sirawt (@MASDXI), ADISAKBOONMARK (@ADISAKBOONMARK), parametprame (@parametprame), Nacharoen (@najaroen)
Requires

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


Before this EIP, NFTs and SBTs implemented expiration mechanisms via custom mappings. However, this approach presents challenges, such as: inconsistent integration with other smart contracts, and the custom ERC-721 implementations might misbehave when used in NFT marketplaces. This EIP addresses these issues by providing a built-in expiration mechanism and interfaces that are compatible with existing infrastructure. It supports both per-token and epoch-based expiry, allowing developers to choose the appropriate expiry type for their use cases, including:

  • Access and Authentication
    • Authentication for Identity and Access Management (IAM)
    • Membership for Membership Management System (MMS)
    • Ticket and Press for Meetings, Incentive Travel, Conventions, and Exhibitions (MICE) when using with ERC-2135 or ERC-7578.
    • Subscription-based access for digital platforms.
  • 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
  • Rental
    • Real Estate Unit, Digital Access, DePIN, etc

Specification


The keywords “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.

Compatible implementations MUST implement the IERC7858 interface and MUST inherit from ERC-721's interface. All functions defined in the interface MUST be present and all function behavior MUST meet the interface specification requirements.

Interface


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, and safeTransferFrom 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 either BLOCK or TIME.
  • isTokenExpired is used for retrieving the status of the given tokenId; the function MUST return true if the token is expired and MUST revert if the tokenId does not exist. Implementations that use custom errors SHOULD revert with ERC721NonexistentToken following the relevant ERC-6093 and implementations using Solidity version below v0.8.4 or those preferring to use revert with string error SHOULD revert with NonexistToken. If the tokenId exists and is not expired, the function MUST return false.
  • startTime and endTime of tokenId, can be block.number or block.timestamp depending on expiryType. The startTime MUST be less than or equal to endTime and startTime SHOULD be strictly less than endTime except when both are set to 0. A startTime and endTime of 0 indicates that the tokenId has no expiration. Tokens with a non-zero startTime and a zero endTime are also considered to have no expiration. If the tokenId does not exist, this function MUST revert in the same way as isTokenExpired.
  • The interface ID for ERC-165's supportsInterface for IERC7858 is 0x3ebdfa31, and for IERC7858Epoch it is 0xec7ffd66
  • TokenExpiryUpdated MUST be emitted when the token is minted or when its expiration details (startTime or endTime) 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. For implementations that require epoch-based expiration management, the IERC7858Epoch extension interface MUST be implemented alongside the base IERC7858 interface, and all functions and behaviors MUST meet both IERC7858 and IERC7858Epoch interface specification requirements.


Extension Behavior Specification

  • unexpiredBalanceOfAtEpoch MUST return the count of usable (unexpired) tokens held by an account from the specified epoch. If the specified epoch is expired, this function MUST return 0. For example, if epoch 5 has expired, calling unexpiredBalanceOfAtEpoch(5, address) returns 0 even if there were tokens previously held in that epoch.
  • unexpiredBalanceOf MUST return total count of usable (unexpired) tokens owned by the account.
  • currentEpoch MUST return the current epoch of the contract.
  • epochLength MUST return duration between epoch in blocks or time in seconds.
  • validityDuration MUST return the validity duration of tokens in terms of epoch counts.
  • isEpochExpired MUST return true if the given epoch is expired, otherwise false.

Additional Potential Useful Function

These OPTIONAL functions provide additional functionality that developers MAY choose to implement based on their specific requirements and use cases.

Base (default)


  • getRemainingDurationBeforeTokenExpired returns the remaining time or blocks before the given tokenId is expired.

Epoch (extension)


  • 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 specified epoch.

  • getNearestExpiryOf returns the list of tokenId closest to expiration, along with an estimated expiration block number or timestamp based on epochType.

  • getRemainingDurationBeforeEpochChange returns the remaining time or blocks before the epoch change happens, based on the epochType.

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.

Reference Implementation


You can find our reference implementation here.

Security Considerations


Burn and Re-Mint

Implementation should ensure that burning token and re-minting it with the same tokenId will not introduce an unauthorized renewal.

Unauthorized Update

Implementation should ensure that only authorized can update startTime and endTime.

Copyright


Copyright and related rights waived via CC0.