{"id":"ERC-7858","name":"Expirable NFTs and SBTs","status":"Final","chain":"both","category":{"id":"nft","name":"NFT Capabilities","description":"What NFTs can do beyond basic ownership"},"journeyStages":[{"id":"management","name":"Asset Management","description":"Managing assets over time"}],"uxImpact":"ERC-7858 reports block-based or time-based expiration for NFTs and SBTs. Utility consumers must enforce the reported expiry; ownership and ordinary transfer behavior remain governed by the token implementation.","officialUrl":"https://ercs.ethereum.org/ERCS/erc-7858","lastReviewed":"2026-09-18","lastUpdated":"2026-09-18","kind":"proposal","hasDetailedContent":true,"content":{"id":"ERC-7858","specConstraints":[{"id":"expired-token-balance-visible","constraint":"An expired NFT remains in `balanceOf`, even though it is unusable under the expiration rules.","appliesTo":"application","normativity":"normative","specQuote":"`balanceOf` that inherited from [ERC-721](./eip-721.md) MUST return all tokens even if expired; they still exist but are unusable due to the limitation of tracking expired token on-chain.","specSection":"Specification — Behavior Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#behavior-specification","designImplication":"Separate owned, expired, and usable counts. Do not treat balance ownership as proof that the token can still be used."},{"id":"expired-token-transferable-but-unusable","constraint":"Expired tokens remain transferable for compatibility but are invalid and unusable in contracts that check token validity.","appliesTo":"application","normativity":"normative","specQuote":"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.","specSection":"Specification — Behavior Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#behavior-specification","designImplication":"Keep transfer and usability as separate states. Tell users when a token can move but cannot perform its intended action."},{"id":"expiry-type","constraint":"`expiryType` identifies whether expiration uses blocks or timestamps.","appliesTo":"application","normativity":"normative","specQuote":"`expiryType` MUST return the type of expiry used by the contract, which can be either `BLOCK` or `TIME`.","specSection":"Specification — Behavior Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#behavior-specification","designImplication":"Label the time basis in expiration copy and calculate the displayed deadline with the contract’s declared type."},{"id":"expiry-status","constraint":"`isTokenExpired` returns whether the token is expired and reverts for a nonexistent token.","appliesTo":"application","normativity":"normative","specQuote":"`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.","specSection":"Specification — Behavior Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#behavior-specification","designImplication":"Provide separate Not found, Active, and Expired states. Do not display a missing token as an expired one."},{"id":"expiry-window","constraint":"Token start and end values define the valid window, with zero values representing no expiration in the specified cases.","appliesTo":"application","normativity":"normative","specQuote":"`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.","specSection":"Specification — Behavior Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#behavior-specification","designImplication":"Show the start and end conditions, including the no-expiration cases, instead of inventing a universal expiry date."},{"id":"epoch-unexpired-balance","constraint":"An expired epoch contributes zero usable tokens even when the account previously held tokens in it.","appliesTo":"application","normativity":"normative","specQuote":"- `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.","specSection":"Specification — Extension Behavior Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#extension-behavior-specification","designImplication":"Show usable balance by epoch and make an expired epoch’s zero usable count explicit."},{"id":"epoch-status-queries","constraint":"Epoch extensions expose the current epoch, epoch length, validity duration, and expiration status used to explain token availability.","appliesTo":"application","normativity":"normative","specQuote":"- `unexpiredBalanceOf` MUST return total count of usable (unexpired) tokens owned by the account.\n- `currentEpoch` MUST return the current `epoch` of the contract.\n- `epochLength` MUST return duration between `epoch` in blocks or time in seconds.\n- `validityDuration` MUST return the validity duration of tokens in terms of `epoch` counts.\n- `isEpochExpired` MUST return true if the given `epoch` is expired, otherwise `false`.","specSection":"Specification — Extension Behavior Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#extension-behavior-specification","designImplication":"Use the contract’s epoch status as the source for time and availability labels; do not infer it from token count alone."},{"id":"erc721-expiry-compatibility","constraint":"The standard remains compatible with ERC-721 and other stated SBT integrations.","appliesTo":"application","normativity":"backwards-compatibility","specQuote":"## Backwards Compatibility\n\nThis standard is fully compatible with [ERC-721](./eip-721.md), [ERC-5484](./eip-5484.md) and other SBTs.","specSection":"Backwards Compatibility","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#backwards-compatibility","designImplication":"Retain ordinary ERC-721 ownership and transfer views while adding explicit expired and usable states."},{"id":"expiration-renewal-risk","constraint":"Implementations should prevent burning and re-minting the same token ID from creating an unauthorized renewal.","appliesTo":"application","normativity":"security-considerations","specQuote":"Implementation should ensure that burning token and re-minting it with the same `tokenId` will not introduce an unauthorized renewal.","specSection":"Security Considerations — Burn and Re-Mint","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#burn-and-re-mint","designImplication":"Show a new issuance or renewal as a distinct event and do not carry old validity forward without a fresh authorization."},{"id":"expiration-update-authority","constraint":"Only authorized actors should update a token’s expiration window.","appliesTo":"application","normativity":"security-considerations","specQuote":"Implementation should ensure that only authorized can update `startTime` and `endTime`.","specSection":"Security Considerations — Unauthorized Update","specUrl":"https://ercs.ethereum.org/ERCS/erc-7858#unauthorized-update","designImplication":"Show who can change validity and record the update in the token history."}],"sources":[{"label":"Official specification","url":"https://ercs.ethereum.org/ERCS/erc-7858","type":"official-spec"}],"lastReviewed":"2026-09-18","lastUpdated":"2026-09-18","summary":"ERC-7858 reports block-based or time-based expiration for NFTs and SBTs. Utility consumers must enforce the reported expiry; ownership and ordinary transfer behavior remain governed by the token implementation.","designerTakeaways":["You can show Expires in 14 days prominently on every expiring token card.","Your filters can split Active and Expired without hiding expired items by surprise.","You can block utility actions when the consuming implementation enforces expiry, with a Renew CTA only if supported."],"applicability":{"whenToUse":["Passes, licenses, subscriptions, or seasonal badges expire.","Expiry data is available on-chain and the consuming implementation applies it to validity or utility.","Renewal or re-mint extends expiry."],"whenToAvoid":["Permanent collectibles with no expiry concept.","The target token does not implement the expiry interface.","Expiry handled off-chain only."]},"prototypeFirst":[{"screen":"Expiring pass detail","why":"Holder checks validity before showing up at event.","covers":["Active countdown","Expires today","Expired"],"include":["Large expiry date","Countdown","Expired overlay"]},{"screen":"Active vs expired filter","why":"Wallets fill with old passes over time.","covers":["Filter active","Show expired archive"],"include":["Tab toggle","Expired gray treatment","Count badges"]},{"screen":"Renewal flow","why":"Extend expiry without losing identity of token if supported.","covers":["Renew available","Renew completed"],"include":["Renew CTA","New expiry preview","Payment step"]},{"screen":"Batch epoch expiry","why":"Many tokens expire together — communicate scale.","covers":["Epoch warning","Post-epoch expired set"],"include":["Batch expiry notice","Affected count","Bulk renew if offered"]}],"mentalModel":[{"label":"Expiry timestamp","description":"On-chain expiry data that a consuming integration may apply to utility."},{"label":"Active vs expired","description":"Active or expired state after applying the implementation's validity rule."},{"label":"Still owned but expired","description":"The NFT may remain owned after expiry; utility depends on whether a consumer checks the field."},{"label":"Renewal","description":"Optional path to extend expiry without new mint."},{"label":"Epoch batch","description":"Group expiry — many tokens reach their configured expiry together at season end."}],"statesToDesign":[{"state":"Active — far from expiry","trigger":"The consuming implementation reports the token as valid with long runway.","userNeed":"Glance validity.","designResponse":"Subtle Expires [date] on card."},{"state":"Expiring soon","trigger":"Within warning window.","userNeed":"Renew or use before deadline.","designResponse":"Amber countdown and notification."},{"state":"Expired — still in wallet","trigger":"The consuming implementation reports the token as past expiry.","userNeed":"Know the consuming app's utility state; optional archive.","designResponse":"Expired badge and disabled utility actions only where the consuming app enforces expiry."},{"state":"Renewal in progress","trigger":"User extending expiry.","userNeed":"Track new date.","designResponse":"Pending renewal with new expiry preview."},{"state":"Epoch batch expired","trigger":"Season ended.","userNeed":"Understand all related tokens expired.","designResponse":"Banner on collection: Season ended; passes expired."}],"designDecisions":[{"question":"Hide expired tokens?","recommendation":"Default Active filter; Expired tab for archive.","rationale":"Hiding surprises users who expect history."},{"question":"How alarming is expiry UI?","recommendation":"Amber at 30 days; red only day-of and after.","rationale":"Constant red creates alert fatigue."},{"question":"Show block-based expiry to users?","recommendation":"Convert to approximate time with Block-based note in advanced.","rationale":"Block numbers are meaningless to most users."}],"problemsSolved":[{"problem":"Expired passes still look valid","oldWay":"Same artwork with no date","newWay":"Expired badge and disabled utility where the consuming app enforces expiry","impact":"high"},{"problem":"Missed renewals","oldWay":"No countdown on subscription NFTs","newWay":"Prominent expiry and notifications","impact":"high"},{"problem":"Wallet clutter from dead tokens","oldWay":"All tokens mixed together","newWay":"Active/Expired filters","impact":"medium"}],"uxPatterns":[{"name":"Expiry Countdown Chip","description":"Prominent deadline on expiring tokens.","components":["ExpiryChip","CountdownTimer","ExpiredOverlay"],"userFlow":["Load token","Read expiry","Show countdown","Switch to Expired after date"]},{"name":"Active Expired Filter","description":"Split wallet views by validity.","components":["FilterTabs","ExpiredArchive","RenewCTA"],"userFlow":["Open wallet","Default Active","Switch to Expired","Optional renew"]}],"antiPatterns":[{"pattern":"No expiry visible on pass NFT","why":"Users denied entry at door","instead":"Expiry date on thumbnail and detail header","severity":"critical"},{"pattern":"Deleting expired tokens from wallet UI","why":"Users lose proof they held pass","instead":"Expired archive tab","severity":"medium"},{"pattern":"Showing block number as expiry","why":"Meaningless to users","instead":"Human datetime with advanced block note","severity":"high"}],"vocabulary":[{"use":"Valid until [date]","avoid":"Expiry block 18293482","why":"Human deadlines."},{"use":"Expired","avoid":"Validity epoch ended","why":"Plain status."},{"use":"Renew pass","avoid":"Extend expiration timestamp","why":"Subscription-familiar action."}],"technicalNotes":"ERC-7858 reports expiry data; re-read it on every view, but do not imply that the standard itself disables utility or transfers. Consumers must decide whether and how to enforce block- or time-based expiry; renewal or re-mint remains implementation-specific.","relatedStandards":[{"id":"ERC-5192","relationship":"Soulbound tokens often expire"},{"id":"ERC-4907","relationship":"Rental expiry separate from token expiry"}]},"sources":[{"label":"Official specification","url":"https://ercs.ethereum.org/ERCS/erc-7858","type":"official-spec"},{"label":"Discussion search","url":"https://ethereum-magicians.org/search?q=ERC-7858","type":"discussion"}],"urls":{"canonical":"https://www.eipsfordesigners.com/standards/ERC-7858","markdown":"https://www.eipsfordesigners.com/standards/ERC-7858/content.md","agent":"https://www.eipsfordesigners.com/standards/ERC-7858/agent.md","api":"https://www.eipsfordesigners.com/api/standards/ERC-7858","official":"https://ercs.ethereum.org/ERCS/erc-7858","discussion":"https://ethereum-magicians.org/search?q=ERC-7858"},"freshness":{"lastReviewed":"2026-09-18","lastUpdated":"2026-09-18"}}