Many proposals for lockable ERC721 contracts exist in different phases of development:
and many others.
Unfortunately, any of them misses something or is too complicated and add extra functions that do not need to be part of a standard.
I tried to influence ERC-5192 making many comments and a PR that was closed by @Pandapip1 who suggested I make a new proposal. So, here we are.
interface IERC721DefaultLockable {
// Must be emitted one time, when the contract is deployed,
// defining the default status of any token that will be minted
event DefaultLocked(bool locked);
// Must be emitted any time the status changes
event Locked(uint256 indexed tokenId, bool locked);
// Returns the status of the token.
// It should revert if the token does not exist.
function locked(uint256 tokenId) external view returns (bool);
}
The primary limit in EIP-5192 (which I liked and I used in a couple of projects) is that
-
it has 2 events for Locked and Unlocked, which is not optimal.
To make a comparison, it’s like in the ERC721 instead of Transfer(from, to, id) used for mints, transfers and burns, there were Transfer(from, to, id), Mint(to, id), Burn(from, id), etc. -
it forces you to emit an event even when the token is minted, causing a waste of gas when a token borns with a status and dies with it.
Take for example most soulbounds and non-transferable badges. They will be locked forever and it does not make sense to emit an extra event for all the tokens.
Using this interface, instead, the contract emits DefaultLocked(bool locked)
when deployed, and that event sets the initial status of every token.
The Locked
events define the new status of any tokenId.
The function locked
returns the current status, allowing other contracts to interact with the token.
IMO, this is a minimalistic solution that reduces gas consumption and covers most scenarios.
I think that functions to lock, unlock, lock approvals, etc. should be managed as extensions, and should be not included in a minimalistic interface about lockability.