ERC Draft: Permanent Locks for Lockable Tokens (ERC-6982 extension)

,

Hi all! I’m opening discussion for a new ERC: Permanent Locks for Lockable Tokens, an extension of ERC-6982.

PR: Add ERC: Permanent Locks for Lockable Tokens by ryanio · Pull Request #2084 · ethereum/ERCs · GitHub

Abstract

ERC-6982 lets a contract lock or unlock every token with one DefaultLocked event, with per-token Locked overrides. This extension keeps those events as they are and adds what ERC-6982 leaves open:

  1. Permanent locks, so marketplaces and wallets can tell a temporary trading pause from a collection that will never be transferable.
  2. ERC-1155 support, with tokenId read as the token type ID.
  3. Mint and burn rules: locking restricts holder transfers only, so minting continues while trading is paused.
interface IERCXXXX /* is IERC165, IERC6982 */ {
    event PermanentlyLocked();
    error TokenLocked(uint256 tokenId);
    function permanentlyLocked() external view returns (bool);
}

A trading pause during a mint is DefaultLocked(true) at deployment and DefaultLocked(false) when trading opens, which is plain ERC-6982. A soulbound collection emits DefaultLocked(true) and PermanentlyLocked() once at deployment, and the lock can never be lifted after that.

Why

Creators regularly turn off secondary transfers for a while: during a public mint, before a reveal, during a migration. Other collections are non-transferable for good, such as credentials and badges. Marketplaces need to stop offering trades in both cases, but should treat them differently: keep orders for when a pause lifts, and label a permanent lock as non-transferable.

ERC-6982 can’t express permanence, requires ERC-721, and doesn’t say whether minting continues while locked. ERC-5192 covers permanent locks, but needs one event per token.

Looking for feedback on

  • Permanence uses a separate event because ERC-6982 is Final. The lock has to be in place before it’s marked permanent, so a consumer that only knows ERC-6982 still sees every token as locked.
  • Permanence is contract-wide only. The motivating cases lock whole collections, and ERC-5192 already covers single soulbound tokens, so the interface stays at one event, one view, and one error.
  • Permanence is one way. Once marked, the contract can never emit an unlock for the default or for any token.
  • Strict reverts apply only to permanent locks. Temporary locks keep ERC-6982’s flexibility, where a protector wallet can still move a locked token.
  • There’s no range event. The default status covers whole collections and Locked covers single tokens.
  • There’s no expected end time field. Pauses often end early or late, and a stale end time would lead consumers to show orders that revert.

The draft’s reference implementation is an OpenZeppelin 5.x ERC-721 that locks at deployment and lets the owner unlock, lock again, or make the lock permanent.

If this would help your marketplace, wallet, or contract, a +1 here or on the PR helps get it merged as a Draft.

3 Likes

Much needed, thanks for finding an improvement for this

1 Like

Nice, telling a mint pause apart from a soulbound collection is exactly where marketplaces get it wrong today.

Three things from reading the PR:

  1. Rule 6.1 fixes the view, but not the event history. If a token got Locked(id, false) before permanence, an indexer that rebuilds status from ERC-6982 events, where the latest Locked overrides the default, still shows that token as unlocked after PermanentlyLocked. So the backwards compatibility line only holds for consumers that call locked(). Requiring Locked(id, true) for every outstanding unlock before PermanentlyLocked, or saying PermanentlyLocked clears all overrides, would close it.
  2. Permanent is only as permanent as the upgrade path. A credential collection behind a proxy can be made transferable by an upgrade without ever emitting an unlock. Worth a sentence in Security Considerations that wallets labeling tokens non-transferable should check whether the contract is upgradeable and who holds that key.
  3. For ERC-1155 an ID exists once a unit is minted, but what about after every unit is burned? If locked() starts reverting again for that ID it will surprise indexers, so it’s worth saying whether existence means ever minted or current supply above zero.

Thanks, all three were real gaps. I pushed a commit to the PR that addresses each one:

  1. New behavior rule 6 covers the event history: before emitting PermanentlyLocked, the contract MUST emit Locked(tokenId, true) for every existing token whose most recent Locked event unlocked it. An indexer that rebuilds status from ERC-6982 events now reaches the same answer as locked(), so the backwards compatibility line holds for both kinds of consumer. The cost is one event per outstanding unlock override, which is zero for collections that only ever use the default.
  2. Security Considerations now says a permanent lock is only as permanent as the code. An upgradeable proxy, or replaceable transfer-check logic, can make tokens transferable again without any unlock event, so wallets labeling tokens non-transferable should check upgradeability and who controls it.
  3. For ERC-1155, a token ID now exists while its total supply is greater than zero. That matches ERC-721, where a burned token stops existing and locked() reverts for it, and lines up with the usual supply-tracking extensions.