Abstract
This standard introduces role management for NFTs. Each role assignment is associated with a single NFT and expires automatically at a given timestamp. Inspired by ERC-5982, roles are defined as bytes32
and feature a custom _data
field of arbitrary size to allow customization.
Motivation
The NFT Roles interface aims to establish a standard for role management in NFTs. Tracking on-chain roles enables decentralized applications (dApps) to implement access control for privileged actions, e.g., minting tokens with a role (airdrop claim rights).
NFT roles can be deeply integrated with dApps to create a utility-sharing mechanism. A good example is in digital real estate. A user can create a digital property NFT and grant a keccak256("PROPERTY_MANAGER")
role to another user, allowing them to delegate specific utility without compromising ownership. The same user could also grant multiple keccak256("PROPERTY_TENANT")
roles, allowing the grantees to access and interact with the digital property.
There are also interesting use cases in decentralized finance (DeFi). Insurance policies could be issued as NFTs, and the beneficiaries, insured, and insurer could all be on-chain roles tracked using this standard.
Specification
The keywords âMUSTâ, âMUST NOTâ, âREQUIREDâ, âSHALLâ, âSHALL NOTâ, âSHOULDâ, âSHOULD NOTâ, âRECOMMENDEDâ, âNOT RECOMMENDEDâ, âMAYâ, and âOPTIONALâ in this document are to be interpreted as described in RFC 2119 and RFC 8174.
ERC-7432 compliant contracts MUST implement the following interface:
/// @title ERC-7432 Non-Fungible Token Roles
/// @dev See https://eips.ethereum.org/EIPS/eip-7432
/// Note: the ERC-165 identifier for this interface is 0x851f3b3f.
interface IERC7432 /* is ERC165 */ {
/// @notice Emitted when a role is granted.
/// @param _role The role identifier.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _grantee The user that receives the role assignment.
/// @param _expirationDate The expiration date of the role assignment.
/// @param _data Any additional data about the role assignment.
event RoleGranted(
bytes32 indexed _role,
address indexed _tokenAddress,
uint256 indexed _tokenId,
address _grantee,
uint64 _expirationDate,
bytes _data
);
/// @notice Emitted when a role is revoked.
/// @param _role The role identifier.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _grantee The user that receives the role revocation.
event RoleRevoked(
bytes32 indexed _role,
address indexed _tokenAddress,
uint256 indexed _tokenId,
address _grantee
);
/// @notice Grants a role to a user.
/// @param _role The role identifier.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _grantee The user that receives the role assignment.
/// @param _expirationDate The expiration date of the role assignment.
/// @param _data Any additional data about the role assignment.
function grantRole(
bytes32 _role,
address _tokenAddress,
uint256 _tokenId,
address _grantee,
uint64 _expirationDate,
bytes calldata _data
) external;
/// @notice Revokes a role from a user.
/// @param _role The role identifier.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _grantee The user that receives the role revocation.
function revokeRole(
bytes32 _role,
address _tokenAddress,
uint256 _tokenId,
address _grantee
) external;
/// @notice Checks if a user has a role.
/// @param _role The role identifier.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _grantor The role creator.
/// @param _grantee The user that receives the role.
/// @param _supportsMultipleAssignments if false, will return true only if account is the last role grantee.
function hasRole(
bytes32 _role,
address _tokenAddress,
uint256 _tokenId,
address _grantor,
address _grantee,
bool _supportsMultipleAssignments
) external view returns (bool);
/// @notice Returns the custom data of a role assignment.
/// @param _role The role identifier.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _grantor The role creator.
/// @param _grantee The user that receives the role.
function roleData(
bytes32 _role,
address _tokenAddress,
uint256 _tokenId,
address _grantor,
address _grantee
) external view returns (bytes memory data_);
/// @notice Returns the expiration date of a role assignment.
/// @param _role The role identifier.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _grantor The role creator.
/// @param _grantee The user that receives the role.
function roleExpirationDate(
bytes32 _role,
address _tokenAddress,
uint256 _tokenId,
address _grantor,
address _grantee
) external view returns (uint64 expirationDate_);
}
Caveats
- Compliant contracts MUST implement the
IERC7432
interface. - A role is represented by a
bytes32
, and itâs RECOMMENDED to use thekeccak256
of the roleâs name for this purpose:bytes32 role = keccak256("ROLE_NAME")
. grantRole
function MUST revert if the_expirationDate
is in the past, and MAY be implemented aspublic
orexternal
.revokeRole
function MAY be implemented aspublic
orexternal
.- The
hasRole
function MAY be implemented aspure
orview
. - The
hasRole
function SHOULD returnfalse
if_supportsMultipleAssignments
isfalse
and last role assignment is not to_grantee
(see Unique and Non-Unique Roles for more). - The
roleData
function MAY be implemented aspure
orview
. - Compliant contracts SHOULD support ERC-165.
Rationale
ERC-7432 IS NOT an extension of ERC-721 or ERC-1155. The main reason behind this decision is to keep the standard agnostic of any NFT implementation. This approach also enables the standard to be implemented externally or on the same contract as the NFT, and allow dApps to use roles with immutable NFTs.
Automatic Expiration
Automatic expiration is implemented via the grantRole
and hasRole
functions. grantRole
is responsible for setting the expiration date, and hasRole
checks if the role is expired by comparing with the current block timestamp (block.timestamp
). Since uint256
is not natively supported by most programming languages, dates are represented as uint64
on this standard. The maximum UNIX timestamp represented by a uint64
is about the year 584,942,417,355
, which should be enough to be considered âpermanentâ. For this reason, itâs RECOMMENDED using type(uint64).max
when calling the grantRole
function to support use cases that require an assignment never to expire.
Unique and Non-Unique Roles
The standard supports both unique and non-unique roles. Unique roles are roles that can be assigned to only one account, while non-unique roles can be granted to multiple accounts simultaneously. The parameter _supportsMultipleAssignments
was included in the hasRole
function to support both use cases. When _supportsMultipleAssignments
is true
, the function checks if the assignment exists and is not expired. However, when false
, the function also validates that no other role was granted afterward. In other words, for unique roles, each new assignment invalidates the previous one, and only the last one can be valid.
Assuming that the role was granted and is not expired, the following table shows the result the hasRole
function MUST return:
Role Type | _supportsMultipleAssignments |
Is last Role granted? | Result of hasRole |
---|---|---|---|
Non-Unique Role | false |
Irrelevant | true |
Unique Role | true |
true |
true |
Unique Role | true |
false |
false |
In conclusion, the _supportsMultipleAssignments
argument only affects the result when true
, and if the queried assignment is not the last one granted.
Custom Data
DApps can customize roles using the _data
parameter of the grantRole
function. _data
is implemented using the generic type bytes
to enable dApps to encode any role-specific information when creating a role assignment. The custom data is retrievable using the roleData
function and is emitted with the RoleGranted
event. With this approach, developers can integrate this information into their applications, both on-chain and off-chain.
Metadata Extension
The Roles Metadata extension extends the traditional JSON-based metadata schema of NFTs. Therefore, DApps supporting this feature MUST also implement the metadata extension of ERC-721 or ERC-1155. This extension is optional and allows developers to provide additional information for roles.
Updated Metadata Schema:
{
/** Existing NFT Metadata **/
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this NFT represents"
},
"description": {
"type": "string",
"description": "Describes the asset to which this NFT represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
}
},
/** Additional fields for Roles **/
"roles": [
{
"id": {
"type": "bytes32",
"description": "Identifies the role"
},
"name": {
"type": "string",
"description": "Human-readable name of the role"
},
"description": {
"type": "string",
"description": "Describes the role"
},
"supportsMultipleAssignments": {
"type": "boolean",
"description": "Whether the role supports simultaneous assignments or not"
},
"inputs": [
{
"name": {
"type": "string",
"description": "Human-readable name of the argument"
},
"type": {
"type": "string",
"description": "Solidity type, e.g., uint256 or address"
}
}
]
}
]
}
The following JSON is an example of ERC-7432 Metadata:
{
// ... Existing NFT Metadata
"roles": [
{
// keccak256("PROPERTY_MANAGER")
"id": "0x5cefc88e2d50f91b66109b6bb76803f11168ca3d1cee10cbafe864e4749970c7",
"name": "Property Manager",
"description": "The manager of the property is responsible for furnishing it and ensuring its good condition.",
"supportsMultipleAssignments": false,
"inputs": []
},
{
// keccak256("PROPERTY_TENANT")
"id": "0x06a3b33b0a800805559ee9c64f55afd8a43a05f8472feb6f6b77484ff5ac9c26",
"name": "Property Tenant",
"description": "The tenant of the property is responsible for paying the rent and keeping the property in good condition.",
"supportsMultipleAssignments": true,
"inputs": [
{
"name": "rent",
"type": "uint256"
}
]
}
]
}
The properties of the roles
array are SUGGESTED, and developers should add any other relevant information as necessary (e.g., an image for the role). However, itâs highly RECOMMENDED to include the supportsMultipleAssignments
field, as shown in the example. This field is used in the hasRole
function (refer back to Unique and Non-Unique Roles).
Backwards Compatibility
On all functions and events, the standard requires both the tokenAddress
and tokenId
to be provided. This requirement enables dApps to use a standalone ERC-7432 contract as the authoritative source for the roles of immutable NFTs. It also helps with backward compatibility as NFT-specific functions such as ownerOf
and balanceOf
arenât required. Consequently, this design ensures a more straightforward integration with different implementations of NFTs.
Security Considerations
Developers integrating the Non-Fungible Token Roles interface should consider the following on their implementations:
- Ensure proper access controls are in place to prevent unauthorized role assignments or revocations.
- Take into account potential attack vectors such as reentrancy and ensure appropriate safeguards are in place.
- Since this standard does not check NFT ownership, itâs the responsibility of the dApp to query for the NFT Owner and pass the correct
_grantor
to thehasRole
function. - Itâs the responsibility of the dApp to check if the role is unique or non-unique. To ensure the role was not assigned to another account when the role is unique,
hasRole
should be called with_supportsMultipleAssignments
set tofalse
.
Reference Implementation
NFT Roles - A reference implementation created by Orium Network.
- CC0 License.
- 100% test coverage.