Update 14/11/2024 Link to PR on github
Update 18/11/2024 Revised to make it more straightforward design, moving away from a library-based structure
Update 20/11/2024 Update in section Security Considerations
Simple Summary
An extended interface enables fungible tokens to possess expiration capabilities, allowing them to expire after a predetermined period.
Abstract
Introduces an extension for ERC20
tokens, which facilitates the implementation of an expiration mechanism. Through this extension, tokens have a predetermined validity period, after which they become invalid and can no longer be transferred or used. This functionality proves beneficial in scenarios such as time-limited bonds, loyalty rewards, or game tokens necessitating automatic invalidation after a specific duration. The extension is crafted to seamlessly align with the existing ERC20
standard, ensuring smooth integration with the prevailing token smart contract while introducing the capability to govern and enforce token expiration at the contract level.
Motivation
This extension facilitates the development of ERC20
standard compatible tokens featuring expiration dates. This capability broadens the scope of potential applications, particularly those involving time-sensitive assets. Expirable tokens are well-suited for scenarios necessitating temporary validity, including
- Bonds or financial instruments with defined maturity dates
- Time-constrained assets within gaming ecosystems
- Next-gen loyalty programs incorporating expiring rewards or points
- Prepaid credits for utilities or services (e.g., cashback, data packages, fuel, computing resources) that expire if not used within a specified time frame
- Postpaid telecom data package allocations that expire at the end of the billing cycle, motivating users to utilize their data before it resets
- Tokenized e-Money for a closed-loop ecosystem, such as transportation, food court, and retail payments
Specification
The keywords â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.
Implementers of this REQUIRED inheritance ERC20
interface and MUST have all of the following functions and all function behavior MUST meet the specification.
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0 <0.9.0;
/**
* @title ERC-7818: Expirable ERC20
* @dev Interface for creating expirable ERC20 tokens.
*/
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IERC7818 is IERC20 {
/**
* @dev Retrieves the balance of a specific `identifier` owned by an account.
* @param account The address of the account.
* @param identifier The Identifier "MAY" represents an epoch, round, period, or token identifier.
* @return uint256 The balance of the specified `identifier`.
* @notice `identifier` "MUST" represent a unique identifier, and its meaning "SHOULD"
* align with how contract maintain the `epoch` in the implementing contract.
*/
function balanceOf(
address account,
uint256 identifier
) 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 epoch() external view returns (uint256);
/**
* @dev Retrieves the duration a token remains valid.
* @return uint256 The validity duration.
* @notice `duration` "MUST" specify the token's validity period.
* The implementing contract "SHOULD" clearly document,
* whether the unit is blocks or time in seconds.
*/
function duration() external view returns (uint256);
/**
* @dev Checks whether a specific `identifier` is expired.
* @param identifier The Identifier "MAY" represents an epoch, round, period, or token identifier.
* @return bool True if the token is expired, false otherwise.
* @notice Implementing contracts "MUST" define the logic for determining expiration,
* typically by comparing the current `epoch()` with the given `identifier`.
*/
function expired(uint256 identifier) external view returns (bool);
/**
* @dev Transfers a specific `identifier` and value to a recipient.
* @param to The recipient address.
* @param identifier The Identifier "MAY" represents an epoch, round, period, or token identifier.
* @param value The amount to transfer.
* @return bool True if the transfer succeeded, false or reverted if give `identifier` it's expired.
* @notice The transfer "MUST" revert if the token `identifier` is expired.
*/
function transfer(
address to,
uint256 identifier,
uint256 value
) external returns (bool);
/**
* @dev Transfers a specific `identifier` and value from one account to another.
* @param from The sender's address.
* @param to The recipient's address.
* @param identifier The Identifier "MAY" represents an epoch, round, period, or token identifier.
* @param value The amount to transfer.
* @return bool True if the transfer succeeded, false or reverted if give `identifier` it's expired.
* @notice The transfer "MUST" revert if the token `identifier` is expired.
*/
function transferFrom(
address from,
address to,
uint256 identifier,
uint256 value
) external returns (bool);
}
Behavior specification
balanceOf
MUST return the total balance of tokens held by an account that are still valid (i.e., have not expired). This includes any tokens associated with specific periods, epochs, or other identifiers, provided they remain within their validity duration. Expired tokens MUST NOT be included in the returned balance, ensuring that only actively usable tokens are reflected in the result.transfer
andtransferFrom
MUST exclusively transfer tokens that remain non-expired at the time of the transaction. Attempting to transfer expired tokens MUST revert the transaction or return false. Additionally, implementations MAY include logic to prioritize the automatic transfer of tokens closest to expiration, ensuring that the earliest expiring tokens are used first, provided they meet the non-expired condition.totalSupply
SHOULD be set to0
ortype(uint256).max
due to the challenges of tracking only valid (non-expired) tokens.- The implementation MAY use a standardized revert message, such as
ERC7818TransferredExpiredToken
orERC7818TransferredExpiredToken(address sender, uint256 identifier)
, to clearly indicate that the operation failed due to attempting to transfer expired tokens.
Rationale
The rationale for developing an expirable ERC20
token extension is based on several key requirements that ensure its practicality and adaptability for various applications to
Compatibility with the existing ERC-20 standard.
The extension should integrate smoothly with the ERC20
interface, This ensures compatibility with existing token ecosystems and third-party tools like wallets and blockchain explorers.
Flexible interface for various implementations.
The smart contract should be extensible, allowing businesses to tailor the expiration functionality to their specific needs like expiry in bulk or each token independent expire, whether itâs dynamic reward systems or time-sensitive applications.
Backwards Compatibility
This standard is fully ERC-20 compatible.
Reference Implementation
For reference implementation can be found here
But in the reference implementation, we employ a sorted list to automatically select the token that nearest expires first with a First-In-First-Out (FIFO
) and sliding window algorithm that operates based on the block.number
as opposed to relying on block.timestamp
, which has been criticized for its lack of security and resilience, particularly given the increasing usage of Layer 2 (L2) networks over Layer 1 (L1) networks. Many L2 networks exhibit centralization and instability, which directly impacts asset integrity, rendering them potentially unusable during periods of network halting, as they are still reliant on the timestamp.
Security Considerations
SC06: Denial Of Service
Run out of gas problem due to the operation consuming high gas used if transferring multiple groups of small tokens.
SC09: Gas Limit Vulnerabilities
Exceeds block gas limit if the blockchain has a block gas limit lower than the gas used in the transaction.
SWC116: Block values as a proxy for time
if using block.timestamp
for calculating epoch()
Fairness Concerns
In a straightforward implementation, where all tokens within the same epoch share the same expiration (e.g., at epoch
:x
), bulk expiration occurs.
Risks in Liquidity Pools
When tokens with expiration dates are deposited into liquidity pools (e.g., in DEXs), they may expire while still in the pool.