ERC-7432 NFT role assignment standard (Non-Fungible Token Roles)
Final · NFTs & multi-token · created 2023-07-14
Defines a common way to grant another account a role that expires automatically at a set time, while leaving ownership of the NFT unchanged. It can be used to split the utility of an NFT, as in digital real estate rentals or insurance policies.
Why it is needed
An NFT has only one owner, so it is hard to share its utility with others. For example, to hand someone the management or tenancy rights of a digital real estate NFT, the token itself must be transferred, and ownership goes with it. Also, without a common rule for recording on-chain who holds which permission and until when, every decentralized application (dApp) must implement access control for special actions, such as airdrop eligibility, in its own way. ERC-7432 defines a common interface that attaches multiple roles to a single NFT and makes each role expire automatically at a set time. Roles are identified by a bytes32 value and can carry additional data of arbitrary size, so they can be adapted to many uses that lend out only the utility while preserving ownership.
What to implement
event TokenLocked(address indexed _owner, address indexed _tokenAddress, uint256 _tokenId);: An event recorded when an NFT is locked (frozen or placed in escrow).event RoleRevoked(address indexed _tokenAddress, uint256 indexed _tokenId, bytes32 indexed _roleId);: An event recorded when a role is revoked.event TokenUnlocked(address indexed _owner, address indexed _tokenAddress, uint256 indexed _tokenId);: An event recorded when a locked NFT is released (reclaimed or unfrozen).event RoleApprovalForAll(address indexed _tokenAddress, address indexed _operator, bool indexed _isApproved);: Recorded when an account is approved, or has its approval withdrawn, to manage roles on behalf of another account.function grantRole(Role calldata _role) external;: Grants a role as specified in the Role struct. Fails if the caller is not the NFT owner or an approved account.function revokeRole(address _tokenAddress, uint256 _tokenId, bytes32 _roleId) external;: Revokes a role. Fails if the caller is not the original owner or an approved account.function unlockToken(address _tokenAddress, uint256 _tokenId) external;: Returns a locked NFT to its original owner or lifts the freeze. Fails if the caller is not approved.function setRoleApprovalForAll(address _tokenAddress, address _operator, bool _approved) external;: Approves or withdraws approval for an operator to grant and revoke roles on one's behalf for a specific token address.function ownerOf(address _tokenAddress, uint256 _tokenId) external view returns (address owner_);: Returns the address of the NFT's original owner.
In depth
A standalone standard, not an extension of ERC-721
This standard does not inherit from ERC-721. This lets it be implemented in the same contract as the NFT or as a separate external contract. For that reason, every function and event takes both tokenAddress and tokenId. This structure lets a standalone ERC-7432 contract serve as the authoritative source of roles, so role functionality can be added even to NFTs that are already deployed and immutable. A compliant contract must implement the IERC7432 interface, implementing ERC-165 is recommended, and the ERC-165 identifier of this interface is 0xd00ca5cf.
Role identifiers and the Role struct
Roles are identified by a bytes32 value. It is recommended to use the hash of the role name and input type with keccak256, that is, the form keccak256("RoleName(input_type)"). The Role struct passed to grantRole consists of roleId, tokenAddress, tokenId, recipient (the account receiving the role), expirationDate (a uint64 expiration time), revocable (whether early revocation is allowed), and data (additional data of arbitrary size). Because data is of type bytes, an app can freely encode whatever information the role needs. It is returned as is by the roleData function and is also recorded in the RoleGranted event, so it can be used both on-chain and off-chain.
grantRole and token locking
grantRole must revert if expirationDate is in the past or if msg.sender is not an account approved to grant roles on behalf of the NFT owner. When a role is granted, it emits a RoleGranted event, and if the token is frozen or moved to an escrow account, it must also emit a TokenLocked event. The source uses the neutral term lock so as not to prescribe an implementation. It may be a freeze that blocks transfers until the role expires, or it may be a deposit into an escrow contract, and the implementer chooses whichever fits the use case.
Automatic expiration and uint64 dates
Expiration is implemented by the expirationDate that grantRole sets and the roleExpirationDate function that reads it. Dates are represented as uint64 instead of uint256 because most programming languages do not natively support uint256. The maximum Unix timestamp representable in uint64 corresponds to about 584,942,417,355 years, which is effectively permanent. So for uses where a role must never expire, it is recommended to use type(uint64).max.
Revocability and unlockToken
If revocable is true, both the original owner and the recipient can revoke the role with revokeRole before it expires; if false, only the recipient can. So that a recipient can remove an unwanted role on their own, it is recommended to always allow revocation by the recipient regardless of the revocable value. revokeRole must revert if msg.sender is not authorized to act on behalf of the original owner or the recipient. unlockToken returns a locked NFT to its original owner or lifts the freeze, and it must revert if msg.sender is not approved or if any unexpired non-revocable role remains. This rule protects a recipient who was given a non-revocable role.
Role Approval
Similar to ERC-721 approvals, another account can manage roles on behalf of the owner. With setRoleApprovalForAll, an operator can be given or stripped of the authority to grant and revoke roles for a specific token address, and a RoleApprovalForAll event is recorded when the approval state changes. isRoleApprovedForAll must return true only if the operator is approved to grant and revoke roles on behalf of the original NFT owner. This feature was introduced so that third parties who do not own the NFT can interact with ERC-7432, and a compliant contract must implement both functions.
Metadata extension
The optional metadata extension adds a roles array to an existing NFT's JSON metadata. An app that supports this feature must also implement ERC-721's metadata extension. Each role entry contains an id (bytes32) that identifies the role, a human-readable name, a description, and inputs. inputs is an important field that describes the parameters that must be encoded and passed to grantRole, and it is recommended to use the type and components properties defined in the Solidity ABI specification. The property layout of the roles array is only a suggestion, so developers can add other information, such as a role image, as needed.
Things to watch out for
- Access control checks: Proper access control must be in place so that unauthorized accounts cannot grant or revoke roles.
- Guarding against attack vectors such as reentrancy: Potential attack paths such as reentrancy must be considered, and appropriate safeguards put in place.
- Restricting NFT transfer paths: Even though an approved account can manage roles on behalf of the owner, the NFT itself must be transferable only to the escrow contract and the original owner, and must not be transferred to an approved account.
- Check the expiration date: Always check a role's expiration time before allowing a user to access the NFT's utility.