ERC-4907 is a standard that has been proposed by renting protocols to allow NFTs to be rented and accepted by the ecosystem. This standard makes it possible to create non-fungible digital assets that allow you the possibility of using the rights of the NFT instead of having ownership over it. This new standards extendes the ERC721 standard and introduces a "User" role in addition to the "Owner" role that exists initially.


ERC-4907 Interface

interface IERC4907 {
// Logged when the user of a token assigns a new user or updates expires
/// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed
/// The zero address for user indicates that there is no user address
event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires);

/// @notice set the user and expires of a NFT
/// @dev The zero address indicates there is no user 
/// Throws if `tokenId` is not valid NFT
/// @param user  The new user of the NFT
/// @param expires  UNIX timestamp, The new user could use the NFT before expires
function setUser(uint256 tokenId, address user, uint64 expires) external ;

/// @notice Get the user address of an NFT
/// @dev The zero address indicates that there is no user or the user is expired
/// @param tokenId The NFT to get the user address for
/// @return The user address for this NFT
function userOf(uint256 tokenId) external view returns(address);

/// @notice Get the user expires of an NFT
/// @dev The zero value indicates that there is no user 
/// @param tokenId The NFT to get the user expires for
/// @return The user expires for this NFT
function userExpires(uint256 tokenId) external view returns(uint256);

Reference Implementation

/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4907.md
/// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./IERC4907.sol";
/// You can choose the deferenct implementation of ERC721
/// case 1: contract ERC4907 is ERC721, IERC4907
/// case 2: contract ERC4907 is ERC721Enumerable, IERC4907
/// case 3: contract ERC4907 is ERC721Burnable, IERC4907
contract ERC4907 is ERC721, IERC4907 {
    struct UserInfo 
        address user;   // address of user role
        uint64 expires; // unix timestamp, user expires
    mapping (uint256  => UserInfo) internal _users;
    constructor(string memory name_, string memory symbol_)
     ERC721(name_, symbol_)
     /// @dev See {IERC4907-setUser}
    function setUser(uint256 tokenId, address user, uint64 expires) public virtual {
        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC4907: transfer caller is not owner nor approved");
        UserInfo storage info =  _users[tokenId];
        info.user = user;
        info.expires = expires;
        emit UpdateUser(tokenId, user, expires);

    /// @dev See {IERC4907-userOf}
    function userOf(uint256 tokenId) public view virtual returns(address){
        if( uint256(_users[tokenId].expires) >=  block.timestamp){
            return  _users[tokenId].user;
            return address(0);

    /// @dev See {IERC4907-userExpires}
    function userExpires(uint256 tokenId) public view virtual returns(uint256){
        return _users[tokenId].expires;

    /// @dev See {IERC165-supportsInterface}
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC4907).interfaceId || super.supportsInterface(interfaceId);
    /// @dev delete UserInfo when burn
    function _burn(uint256 tokenId) internal virtual override {
        delete _users[tokenId];
        emit UpdateUser(tokenId, address(0), 0);

Last updated