ERC-7432: NFT Roles
ERC-7432 standardises time-bounded role grants on NFTs. The owner of an NFT can grant a named role (e.g. keccak256("PILOT"), keccak256("EDITOR")) to a grantee for a bounded period; the grantee can then perform role-gated actions on contracts that recognise the NFT-as-credential. Generalises ERC-4907 (single "USER" role) to multiple named roles per NFT.
Used by:
- Game characters — multiple roles per character (pilot, gunner, navigator) granted to different scholars.
- DAO membership NFTs — roles encode permissions (proposer, voter, treasurer) with separate expiries.
- Smart-contract delegations — NFT holder grants a contract the "OPERATOR" role for a bounded window.
- Subscription credentials — roles encode tier (basic / pro / enterprise) with renewal-controlled expiries.
Required Interface
interface IERC7432 {
struct Role {
bytes32 roleId;
uint256 tokenId;
address tokenAddress;
address grantee;
uint64 expirationDate;
bool revocable;
bytes data;
}
event RoleGranted(bytes32 indexed roleId, uint256 indexed tokenId,
address indexed tokenAddress, address grantor, address grantee,
uint64 expirationDate, bool revocable, bytes data);
event RoleRevoked(bytes32 indexed roleId, uint256 indexed tokenId, address indexed tokenAddress);
function grantRole(Role calldata role) external;
function revokeRole(bytes32 roleId, uint256 tokenId, address tokenAddress) external;
function hasRole(bytes32 roleId, uint256 tokenId, address tokenAddress, address grantee)
external view returns (bool);
function roleData(bytes32 roleId, uint256 tokenId, address tokenAddress)
external view returns (bytes memory);
function roleExpirationDate(bytes32 roleId, uint256 tokenId, address tokenAddress)
external view returns (uint64);
}The roleId is keccak256 of the role's name — this gives off-chain tools a deterministic way to address roles without per-collection role enumerations.
Neo Equivalent: NEP-11 + (tokenId, role) Grant Storage
Neo port stores grants per (tokenId, roleId) with grantee + expiry + data. NEP-11 base unchanged; add the role storage and the grant / revoke methods.
public static void GrantRole(
ByteString roleId, ByteString tokenId,
UInt160 grantee, BigInteger expirationDate,
bool revocable, ByteString data)
{
var owner = OwnerOf(tokenId);
if (!Runtime.CheckWitness(owner)) throw new Exception("NEP11:NotOwner");
if (expirationDate <= Runtime.Time) throw new Exception("NEP11:ExpiryInPast");
var grant = new Map<string, object>
{
["grantee"] = grantee,
["expirationDate"] = expirationDate,
["revocable"] = revocable,
["data"] = data,
};
Storage.Put(Storage.CurrentContext, RoleKey(roleId, tokenId), StdLib.Serialize(grant));
OnRoleGranted(roleId, tokenId, owner, grantee, expirationDate, revocable, data);
}
public static bool HasRole(ByteString roleId, ByteString tokenId, UInt160 grantee)
{
var raw = Storage.Get(Storage.CurrentContext, RoleKey(roleId, tokenId));
if (raw is null) return false;
var grant = (Map<string, object>)StdLib.Deserialize((ByteString)raw);
if ((BigInteger)grant["expirationDate"] < Runtime.Time) return false;
return ((UInt160)grant["grantee"]).Equals(grantee);
}| ERC-7432 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
grantRole(Role) | GrantRole(roleId, tokenId, grantee, expiry, revocable, data) | Owner-witness-checked |
revokeRole(roleId, tokenId, tokenAddress) | RevokeRole(roleId, tokenId) | Only owner if revocable=true; locked otherwise |
hasRole(roleId, tokenId, tokenAddress, grantee) | HasRole(roleId, tokenId, grantee) view honouring expiry | Direct port |
roleData(roleId, tokenId, tokenAddress) | RoleData(roleId, tokenId) view | Direct port |
expirationDate | expirationDate (Runtime.Time-comparable) | Same semantics |
revocable flag | revocable flag | Locks role until expiry if false |
| keccak256 roleId | Application-defined ByteString roleId (typically Sha256("PILOT")) | Same role-naming convention |
Composition
- ERC-4907 — single-role rental. ERC-7432 is the multi-role generalisation.
- ERC-5006 — multi-token rental (ERC-1155 variant). ERC-7432 covers ERC-721; combine for full coverage.
- ERC-6066 — NFT signature validation. The grantee's signature for a role-gated action validates iff
HasRole(roleId, tokenId, signer)is true. - ERC-7066 — lockable. Grants typically pair with a lock (NFT shouldn't transfer while a non-revocable role is active).
Migration Notes
For game / DAO contracts using ERC-7432:
- Replace per-role storage map with
(roleId, tokenId) → grantkeyed storage. - Use
Sha256("ROLE_NAME")(or any deterministic ByteString) as roleId — must match what off-chain tools use to address roles. - For the revocable flag: when false, the role is locked until expiry and cannot be revoked even by the owner. Consumers can rely on the role being live for the full duration.
For DAO permission systems, prefer ERC-7432 over per-DAO custom role mappings — using the standardised roleId hashing means any external tool can recognise role grants without per-DAO integration.
