EIP-5501 Rental & Delegation NFT - ERC-721 Extension


eip:
title: Rental & Delegation NFT - ERC-721 Extension
description: Adds a conditional time-limited user role to ERC-721. This role can be delegated or borrowed.
author: Jan Smrža, David Rábel, Tomáš Janča, DOBBYLABS (hello@dobbylabs.com)
status: Draft
type: Standards Track
category: ERC
created: 2022-08-18
requires: EIP 165, 721

Abstract

The following standard proposes an additional user role for EIP-721. This role grants the permission to use the NFT with no ability to transfer or set users. It has an expiry and a flag if the token is borrowed or not. Owner can delegate the NFT for usage to hot wallets or lend the NFT. If the token is borrowed, not even the owner can change the user until the status expires or both parties agree to terminate. This way, it is possible to keep both roles active at the same time.

Motivation

Collectibles, gaming assets, metaverse, event tickets, music, video, domains, real item representation are several among many NFT use cases. With EIP-721 only the owner can reap the benefits. However, with most of the utilities it would be beneficial to distinguish between the token owner and its user. For instance music or movies could be rented. Metaverse lands could be delegated for usage.

The two reasons why to set the user are:

  • delegation - Assign user to your hot wallet to interact with applications securely. In this case, the owner can change the user at any time.
  • renting - This use case comes with additional requirements. It is needed to terminate the loan once the established lending period is over. This is provided by expires of the user. It is also necessary to protect the borrower against resetting their status by the owner. Thus, isBorrowed check must be implemented to disable the option to set the user before the contract expires.

The most common use cases for having an additional user role are:

  • delegation - For security reasons.
  • gaming - Would you like to try a game (or particular gaming assets) but are you unsure whether or not you will like it? Rent assets first.
  • guilds - Keep the owner of the NFTs as the multisig wallet and set the user to a hot wallet with shared private keys among your guild members.
  • events - Distinguish between ownerOf and userOf. Each role has a different access.
  • social - Differentiate between roles for different rooms. For example owner has read + write access while userOf has read access only.

This proposal is a follow up to EIP-4400 and EIP-4907 and introduces additional upgrades for lending and borrowing which include:

  • NFT stays in owner’s wallet during rental period
  • Listing and sale of NFT without termination of the rent
  • Claiming owner benefits during rental period

Building the standard with additional isBorrowed check now allows to create rental marketplaces which can set the user of NFT without the necessary staking mechanism. With current standards if a token is not staked during the rental period, the owner can simply terminate the loan by setting user the repeatedly. This is taken care of by disabling the function if the token is borrowed which in turn is providing the owner additional benefits. They can keep the token tied to their wallet, meaning they can still receive airdrops, claim free mints based on token ownership or otherwise use the NFT provided by third-party services for owners. They can also keep the NFT listed for sale. Receiving airdrops or free mints was previously possible but the owner was completely reliant on the implementation of rental marketplaces and their discretion.

Decentralized applications can now differentiate between ownerOf and userOf while both statuses can coexist.

Specification

Main interface

/**
 * @title IERCX: Rental & Delegation NFT - ERC-721 Extension
 * @notice the ERC-165 identifier for this interface is 0xf808ec37.
 */
interface IERCX /* is IERC721 */ {
    /**
     * @dev Emitted when the user of an NFT is modified.
     */
    event UpdateUser(uint256 indexed _tokenId, address indexed _user, uint64 _expires, bool _isBorrowed);

    /**
     * @notice Set the user info of an NFT.
     * @dev User address cannot be zero address.
     * Only approved operator or NFT owner can set the user.
     * If NFT is borrowed, the user info cannot be changed until user status expires.
     * @param _tokenId uint256 ID of the token to set user info for
     * @param _user address of the new user
     * @param _expires Unix timestamp when user info expires
     * @param _isBorrowed flag whether or not the NFT is borrowed
     */
    function setUser(uint256 _tokenId, address _user, uint64 _expires, bool _isBorrowed) external;

    /**
     * @notice Get the user address of an NFT.
     * @dev Reverts if user is not set.
     * @param _tokenId uint256 ID of the token to get the user address for
     * @return address user address for this NFT
     */
    function userOf(uint256 _tokenId) external view returns (address);

    /**
     * @notice Get the user expires of an NFT.
     * @param _tokenId uint256 ID of the token to get the user expires for
     * @return uint64 user expires for this NFT
     */
    function userExpires(uint256 _tokenId) external view returns (uint64);

    /**
     * @notice Get the user isBorrowed of an NFT.
     * @param _tokenId uint256 ID of the token to get the user isBorrowed for
     * @return uint64 user isBorrowed for this NFT
     */
    function userIsBorrowed(uint256 _tokenId) external view returns (bool);
}

Balance extension

/**
 * @title IERCXBalance
 * Extension for ERCX which adds userBalanceOf to query how many tokens address is userOf.
 * @notice the ERC-165 identifier for this interface is 0x0cb22289.
 */
interface IERCXBalance /* is IERCX */{
    /**
     * @notice Count of all NFTs assigned to a user.
     * @dev Reverts if user is zero address.
     * @param _user an address for which to query the balance
     * @return uint256 the number of NFTs the user has
     */
    function userBalanceOf(address _user) external view returns (uint256);
}

Enumerable extension

/**
 * @title IERCXEnumerable
 * This extension for ERCX adds the option to iterate over user tokens.
 * @notice the ERC-165 identifier for this interface is 0x1d350ef8.
 */
interface IERCXEnumerable /* is IERCXBalance, IERCX */ {
    /**
     * @notice Enumerate NFTs assigned to a user.
     * @dev Reverts if user is zero address or _index >= userBalanceOf(_owner).
     * @param _user an address to iterate over its tokens
     * @return uint256 the token ID for given index assigned to _user
     */
    function tokenOfUserByIndex(address _user, uint256 _index) external view returns (uint256);
}

Terminable extension

/**
 * @title IERCXTerminable
 * This extension for ERCX adds the option to terminate borrowing if both parties agree.
 * @notice the ERC-165 identifier for this interface is 0x6a26417e.
 */
interface IERCXTerminable /* is IERCX */ {
    /**
     * @dev Emitted when one party from borrowing contract approves termination of agreement.
     * @param _isLender true for lender, false for borrower
     */
    event AgreeToTerminateBorrow(uint256 indexed _tokenId, address indexed _party, bool _isLender);

    /**
     * @dev Emitted when agreements to terminate borrow are reset.
     */
    event ResetTerminationAgreements(uint256 indexed _tokenId);

    /**
     * @dev Emitted when borrow of token ID is terminated.
     */
    event TerminateBorrow(uint256 indexed _tokenId, address indexed _lender, address indexed _borrower, address _caller);

    /**
     * @notice Agree to terminate a borrowing.
     * @dev Lender must be ownerOf token ID. Borrower must be userOf token ID.
     * If lender and borrower are the same, set termination agreement for both at once.
     * @param _tokenId uint256 ID of the token to set termination info for
     */
    function setBorrowTermination(uint256 _tokenId) external;

    /**
     * @notice Get if it is possible to terminate a borrow agreement.
     * @param _tokenId uint256 ID of the token to get termination info for
     * @return bool, bool first indicates lender agrees, second indicates borrower agrees
     */
    function getBorrowTermination(uint256 _tokenId) external view returns (bool, bool);

    /**
     * @notice Terminate a borrow if both parties agreed.
     * @dev Both parties must have agreed, otherwise revert.
     * @param _tokenId uint256 ID of the token to terminate borrow of
     */
    function terminateBorrow(uint256 _tokenId) external;
}

Rationale

The main factors influencing this standard are:

  • EIP-4400 and EIP-4907
  • Allow lending and borrowing without the necessary stake or overcollateralization while owner retains ownership
  • Leave the delegation option available
  • Keep the number of functions in the interfaces to a minimum while achieving desired functionality
  • Modularize additional extensions to let developers choose what they need for their project

Name

The name for the additional role has been chosen to fit the purpose and to keep compatibility with EIP-4907.

Ownership retention

Many collections offer their owners airdrops or free minting of various tokens. This is essentially broken if the owner is lending a token by staking it into a contract (unless the contract is implementing a way to claim at least airdropped tokens). Applications can also provide different access and benefits to owner and user roles in their ecosystem.

Balance and Enumerable extensions

These have been chosen as OPTIONAL extensions due to the complexity of implementation based on the fact that balance is less once user status expires and there is no immediate on-chain transaction to evaluate that. In both userBalanceOf and tokenOfUserByIndex functions there must be a way to determine whether or not user status has expired.

Terminable extension

If the owner mistakenly sets a user with borrow status and expires to a large value they would essentially be blocked from setting the user ever again. The problem is addressed by this extension if both parties agree to terminate the user status.

Security

Once applications adopt the user role, it is possible to delegate ownership to hot wallet and interact with them with no fear of connecting to malicious websites.

Backwards Compatibility

This standard is compatible with current EIP-721 by adding an extension function set. The new functions introduced are similar to existing functions in EIP-721 which guarantees easy adoption by developers and applications. This standard also shares similarities to EIP-4907 considering user role and its expiry which means applications will be able to determine the user if either of the standards is used.

Test Cases And Reference Implementation

Security Considerations

Developers implementing EIP-X standard and applications must consider all the permissions they give to users and owners. Since owner and user are both active roles at the same time, double-spending problem must be avoided. Balance extension must be implemented in such a way which will not cause any gas problems. Marketplaces should let users know if a token listed for sale is borrowed or not.

Copyright

Copyright and related rights waived via CC0.