Skip to content

What is NFTfi? (ERC-4907)

“Since the emergence of NFTs in 2022, numerous NFT projects have appeared and a tremendous amount of NFTs have been traded. However, in contrast to this popularity, questions about the effectiveness of NFTs have been constantly raised. This is because, despite spending a lot of money to purchase and own an NFT, there was no way to use it. To solve this problem, NFT Service Providers have expanded the usability of NFTs by offering various benefits such as service discounts and event participation to users who own their NFTs. Now, they have begun to build an ecosystem where NFTs can be used financially. This ecosystem is called NFTfi. Let’s take a look at what NFTfi is and how one of the contracts related to it, ERC-4907, is written.”

NFTfi

NFTfi is a term that combines NFT and Decentralized Finance (DeFi), and it refers to an ecosystem where you can secure the value and liquidity of NFTs by lending tokens with NFT as collateral or renting NFTs so that buyers can use them as needed. In the case of traditional NFTs, there were not many ways for the owner to use the NFT, but with the activation of NFTfi, it expanded the usability of NFTs, increased their value, supplied liquidity based on this, and opened a new path in the NFT market. The ways to use NFT in NFTfi are 1. Loan with NFT, 2. NFT Staking, 3. NFT Rental. The representative projects and processes for these three methods are as follows.

NFT loan

  • The borrower presents an NFT to be used as collateral.
  • The lender evaluates the value of the NFT and proposes a loan amount, repayment amount, and repayment date. The loan amount is the amount that the borrower borrows from the lender, and the repayment amount is the amount that the borrower has to repay to the lender.
  • Once the contract is concluded, the NFT is locked and the borrower receives the loan amount. After this, there are two options.

  • Option 1) If the repayment amount is repaid within the due date, the NFT is unlocked and the ownership returns to the borrower.
  • Option 2) If not repaid within the due date, the NFT is unlocked and the lender becomes the owner of the NFT.

NFT Staking

  • The owner locks up their NFT using a staking service.
  • The NFT is deposited and the owner obtains the right to participate in staking. By participating in staking, the owner can earn profits.

NFT Rental

  • The owner proposes an amount and expiration time to rent out their NFT.
  • The user sends the amount proposed by the owner and receives the right to use the NFT. (Ownership does not change.)
  • When the expiration time passes, the user’s right to use expires and is reclaimed.

For such services, Ethereum supports several standard contracts. Among them, we will analyze the well-known ERC-4907, Rental NFT contract.

ERC4907 – Rental NFT, an Extension of EIP-721

As the name Rental NFT suggests, ERC-4907 is a representative ERC standard related to NFT rentals. It is an extension of ERC-721 and was proposed by Double Protocol, which provides an NFT Rental protocol, as EIP-4907. It became ERC-4907 when it was confirmed as a standard through community voting. They explained the reason for proposing ERC-4907 as follows.

Some utility NFTs may not always have the same owner and user depending on the situation. In such cases, it is necessary to identify the owner and user separately and manage the authority to perform operations accordingly. Some projects use roles differentiated by names such as ‘Operator’ and ‘Controller’, but as this configuration becomes increasingly common, a unified standard is needed to facilitate collaboration between all applications.

Two transactions are needed to grant NFT usage rights. (1. Entering the address for assigning a new user role, 2. Reclaiming the user role) Generating two transactions is inefficient and uneconomical, so we have made it so that the usage period is automatically ended using only one transaction.

They also anticipated the following effects from the introduction of ERC-4907:

💡 Effects of Introduction

Convenient Rights Assignment** – The authority (rights) of the owner and user can be easily managed due to role assignment.

Simple Rental Management – The Expires function can be used to automatically expire the user’s rights, allowing for the reclamation of rights without a separate transaction.

Easy Integration with Third-Party Platforms – Interaction with other projects is possible without the permission of the NFT issuer or application.

Compatibility with Previous Versions – It is fully compatible with ERC721.

</aside>

Then, let’s analyze how the contract code was written to achieve these effects.

IERC4907.sol

  • Looking at IERC4907.sol, you can see that there are three functions: setUser, userOf, userExpires, and there is an event called UpdateUser.
				
					interface IERC4907 {

    // Logged when the user of an NFT is changed or expires is changed
    /// @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 an 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);
				
			

The ERC4907.sol code, written by inheriting IERC4907, is as follows.

ERC4907.sol

				
					// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./IERC4907.sol";

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_)
     {
     }
    
    /// @notice set the user and expires of an 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) 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);
    }

    /// @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) public view virtual returns(address){
        if( uint256(_users[tokenId].expires) >=  block.timestamp){
            return  _users[tokenId].user;
        }
        else{
            return address(0);
        }
    }

    /// @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) 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);
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override{
        super._beforeTokenTransfer(from, to, tokenId);

        if (from != to && _users[tokenId].user != address(0)) {
            delete _users[tokenId];
            emit UpdateUser(tokenId, address(0), 0);
        }
    }
}
				
			

The analysis of this code is as follows.

Variables

  • *struct UserInfo**

Declare a structure called UserInfo. UserInfo has an address type user that stores the borrower’s address and a uint64 type expires that stores the NFT rental expiration period as variables. expires stores the expiration time as a unixTimestamp.

				
					   struct UserInfo
    {
        address user;   // address of user role
        uint64 expires; // unix timestamp, user expires
    }

				
			

mapping (uint256 => UserInfo) internal **_users;**

Declare _users that maps TokenId and UserInfo. You can access the mapped UserInfo structure using TokenId as the Key value.

TokenId(user, expires)
(uint256)({UserAddress} , {UnixTimestamp})

Method

function setUser(uint256 tokenId, address user, uint64 expires)

It takes the tokenId of the NFT, the address of the user (not the Owner), and the validity period as arguments, checks if msg.sender is the Owner of the NFT, declares a structure named info with the same structure as UserInfo, saves the user address and validity period, and then runs the UpdateUser event to record the data.

				
					 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);
    }
				
			

function userOf(uint256 tokenId)

It takes the tokenId of the NFT as an argument and loads the data of the structure mapped to that value. If the expires value stored in the structure is greater than block.timestamp, it returns the Address stored in the mapped structure, and if it is less than block.timestamp, it returns a zeroAddress.

				
					function userExpires(uint256 tokenId) public view virtual returns(uint256){
        return _users[tokenId].expires;
    }

				
			

Event

When the event is executed, the two values uint256 indexed tokenId and address indexed user specified as indexed are stored in the Log. The non-indexed uint64 expires is recorded in the Data Field.

				
					event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires);

				
			

Test & Result

To test ERC-4907, I deployed the contract to the Ethereum Sepolia Testnet and executed the Method.

  • Owner: 0x99b1CB2591578A5ceF5F7003CB6c5561B87A3122
  • User: 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC
  • The owner rented the NFT to the user and set expires to 5 minutes later. The actual executed transaction can be checked through the link below.

Check Sepolia Testnet Transaction

The owner sets the To address to the contract address and sends the transaction. When the transaction is executed, the UpdateUser Event is executed and the Log is recorded. In the Log, you can see that the TokenId and Address are indexed and recorded, and expires is stored in the Data Field.

The following were returned by executing userOf and userExpires, which can check the address and expiration period of the renter. The ownerOf Method was added to clearly compare the actual owner and the renter. You can see that the renter’s address changes to zeroAddress as expires elapses.

				
					//info.expires >= block.timestamp
userOf : 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC
user Expires : 1687514172
ownerOf : 0x99b1CB2591578A5ceF5F7003CB6c5561B87A3122

//info.expires < block.timestamp
userOf : 0x0000000000000000000000000000000000000000
user Expires : 1687514172
ownerOf : 0x99b1CB2591578A5ceF5F7003CB6c5561B87A3122
				
			

Based on these contents, EIP-4907 was selected as ERC-4907 through a vote of the Ethereum community. However, there are things to be aware of before actually using ERC-4907.

  1. setUser has no exception handling.

setUser is a function that simply specifies Address and Expires, but because there is no exception handling, you can rent an NFT that is currently being rented to another user without any restrictions. To prevent this, code that checks whether the NFT corresponding to the tokenId entered as a parameter is currently being rented and, if it is being rented, reverts the transaction should be added.

  1. Expires expiration is a zeroAddress return.

If you look at the userOf function, it is written to return zeroAddress when the Expires period has expired, without changing the Address and expires stored in the Struct. It is presumed to be for saving gas because a transaction occurs when Address and expires are initialized when expires expires. It is possible to check as zeroAddress for the information of the user whose usage period has expired using the userOf, userExpires Method, but there may be a misunderstanding because the user’s Address still remains when querying the data of the Struct.

  1. Usage rights are not ownership.

In this code, rental (granting usage rights) is just storing Address and Expires in Struct, not actually transferring NFT. Because the user cannot check the NFT in their wallet, consideration should also be given to implementing a page that can confirm that the user is a user who has rented the NFT.

There are several things to be aware of as above, but it is definitely meaningful that the usability of NFT increases as extended functions of NFT like ERC-4907 appear. It’s because it proves the actual need and value of NFT, breaking away from the level that focused only on ownership and inquiry. If you look at the contract that recently became an ERC standard, there is a lot of content related to NFT. Following this trend, watching and experiencing the development of NFT will also be fun.

Share your blockchain-related digital insights with your friends

Facebook
Twitter
LinkedIn

Get more insights

Say Goodbye to Goerli and Hello to Holesky and Sepolia!

Ending Support for Goerli Testnet on Ethereum, Arbitrum, and Optimism Testnets are cost-effective environments for testing services before their deployment on the mainnet. However, as these testnets essentially function as

02 Arbitrum ONE & NOVA – Why Arbitrum is L2 No.1

This series of posts is content created in collaboration with the Aslan Academy research team, ART: Aslan Research Team, affiliated with the blockchain research society Aslan Academy. This series of

Luniverse NOVA X Arbitrum 01 What is Arbitrum & Why ?

This series of posts is produced in collaboration with ART: Aslan Research Team, a research team within the Aslan Academy, a blockchain research organization. As part of Luniverse NOVA’s support

Luniverse NOVA X Polygon – 3. User Analysis

In the previous articles, we discussed a brief explanation of Polygon and explored various solutions. In this article, we will quantify Polygon into numbers to determine its strengths and weaknesses.