EIP4907: ERC-721 User And Expires Extension

But if so, the owner sets the renter to one of his own addresses and also sets the expiration to 100 years later. At this point the owner sells the NFT to someone else, and the buyer will suffer a loss without paying attention to it.
If we do not allow the owner to be transferred in the case of the user is valid, it will be different from the common transfer logic and easy to cause confusion.

THX

1 Like

I think this is prevented with your implementation here, right?
https://github.com/ethereum/EIPs/blob/master/assets/eip-4907/contracts/ERC4907.sol#L67

Anyway, I get this point. It may be something to work out in another way.

Thanks.

Great idea, I had a similar one recently in an effort to develop a rental protocol. There is 2 things I’d like to mention:

  1. Maybe the scope can include erc1155, which would benefit from this proposal just as much as erc721.
  2. The security issues regarding the end date until which a user is set can be handled at the lease contract level, which has the added benefit of making a lease more trustless.

Can’t wait to see this standard broadly accepted, it will open a whole new segment of decentralised finance.

Hi there!

Early this year I built a proof of concept for a lendable/rentable token and the core idea is close to what is being discussed in this EIP. You can find the source code on etherscan

I just wanted to chime in and share some suggestions.

I would rename user to holder - it is more self descriptive. I would also rename userExpire to expiresOf for consistency (since they both take a tokenId).

expires could be 0 for lending and > 0 for rentals etc - this way you can check against block.timestamp and use that as a way to allow the owner to update the holder. Specifically when expires is 0 they can update the holder at any time. Otherwise block.timestamp must be > expires.

It would be great to think of a mechanism that would enable sub-lending etc. (holder lends to another holder within expires timeframe). There are plenty of real life applications for this behavior.

Finally it would be great if we could figure out:

  1. A way to have the ownership check in the token contract - transferring the NFT to the marketplace is sub optimal
  2. A method to lookup the ownership status of a token i.e. a method that returns (address owner, address holder, uint expires) - this is not super important as you can call holderOf and expiresOf
4 Likes

Hey,

Great EIP! Like 0xG, I have a similar suggestion regarding the user.

In my opinion user is too general, because the owner may use an NFT and then he is also the user. Therefore I’d suggest to rename it to possessor.
Here’s a good explanation: “For example, an owner of a car could lend it to someone else to drive. That driver would then possess the car. However, the owner does not give up ownership simply by lending the car to someone else.” (source: Possession versus Ownership legal definition of Possession versus Ownership)

2 Likes

Hi Team,

I liked the proposal, but I have a suggestion:

Add a new function to renew the loan (NewExpiresOf) and keep the ExpiresOf to check how long the first date lasted for the new date, this could be a new condition to allow interest in the future.

2 Likes

This standard caught my attention as separating users and owners could help me make progress on implementing Harberger taxes and self-assessed pricing for NFTs. I’ve started implementing it for libharberger here: Attempt ERC4907 integration by TimDaub · Pull Request #38 · rugpullindex/libharberger · GitHub any help integrating or feedback is welcome!

1 Like

It’s a shame that the authors of this standard didn’t wait a bit longer before flagging it as final as I’m noticing slight inconsistencies with the surrounding ecosystem.

E.g. EIP-721 which this contract aspires to be compatible with has a similar function to function userOf(uint256 tokenId) but in case the tokenId doesn’t exist, it throws. See OZ’s implementation. So it is inconsistent.

EIP-4907, however, made the odd choice of returning the zero address as a signal, which I judge as a bad decision given that I conceptually see function ownerOf to be very similar to function userOf.

And before someone misunderstands: 4907 mandates returning the zero address in the standard and 721 mandates throwing so these are NOT implementation details, they are interface inconsistencies!

eip-721.md

    /// @notice Find the owner of an NFT
    /// @dev NFTs assigned to zero address are considered invalid, and queries
    ///  about them do throw.
    /// @param _tokenId The identifier for an NFT
    /// @return The address of the owner of the NFT
    function ownerOf(uint256 _tokenId) external view returns (address);

eip-4907.md

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

Cleaned up trailing white space, formatting and repackaged ERC4907 as a foundry package here: GitHub - rugpullindex/ERC4907: Reference Implementation of EIP-4907 "Rental NFT, ERC-721 User And Expires Extension"

1 Like

Thanks for your feedback. Because the status of ERC-4907 is Final, you could propose a new EIP as extension of ERC-4907 to add this new function.

Hi, thanks for your work on this EIP to allow the rental of NFT while keeping ownership flexibility

This is something I have been working on in the past and I actually got my own proposal which in my opinion offers significant benefits over the one proposed here. I already have a repo exploring the idea in great detail, see here: GitHub - wighawag/erc721-lease: A contract to manage lease of NFT

I do not want to hijack the discussion here and so I also created a topic to discuss the proposal on its own here: ERC721 Lease: allowing owner to rent NFT to other

To recapitulate, the main difference with EIP-4907 is that my proposal

  • work with all ERC721, past, present and future
  • does not need to get implemented in the token contract
  • is completely generic: can implement any kind of contract between the user and owner.
  • 's rental representation is itself an ERC721, so no extra work to get it working with existing infrastructure or tooling
  • puts users (lease owners) and NFT owners on the same footing. EIP-4907 on the other end does not protect the “users” in any way as the owner can reclaim at any time and thus does not establish a fair ground for owner/user agreement

The main pain point I see for EIP-4907 is that “usership” expiry is not enforced in any way and NFT owner has full power. This takes a political stance on the matter of ownership vs rental rights while such a system should be as fair as possible to allow user and owner to set their own term.

4 Likes

From a business and legal point of view, handling the ownership of any asset is a problem solved by ancient romans lawyers. Basically, you need to handle 3 properties:

  • Who is the owner of an assets, basically who is able to transfer the ownership of the asset, and by default setting is also the owner of any other rigths.
  • Who can use the asset. For instance, the one who lives on a rented house
  • Who can take the benefits from the asset (romans call this ius fruendi). Using the same rented house example, the landlord of the house is the one who owns this right.
    This way of modeling this has worked over 2.000 years… honestly I doubt any one in the crypto world can make something better.
2 Likes

I will be happy to chat more. Please check this out: → EIP-tbd Rental & Delegation NFT - ERC-721 Extension

Great, i would like to chat more. :slight_smile: Please, check this out: → EIP-tbd Rental & Delegation NFT - ERC-721 Extension

Great point, please check this out: → EIP-tbd Rental & Delegation NFT - ERC-721 Extension And let us know, what you think. :slight_smile:

1 Like
1 Like

What is the consensus on this EIP? What projects are using it?

This seems like a pretty serious issue that I have not seen addressed:

1 Like

When I was testing ERC-4907 contract, I found out something. Let’s say an NFT with token id:1 was minted on user(1) and rented to user(2) for 3 days. Now during these 3 days, whenever user(1) transfers that NFT to let’s assume user(3), user(2) which should have access to rented NFT for 3 days, now no longer have access to that NFT. userOf(1) sets to address(0). This can be overcome by adding modifiers and overriding transfer and safeTransfer functions of ERC-721.

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

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

contract ERC4907 is ERC721, IERC4907 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;


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

    mapping (uint256  => UserInfo) internal _users;

    modifier notRented(uint256 tokenId){
        require(userOf(tokenId)==address(0),"can not transfer rented NFT");
        _;
    }

    constructor(string memory name_, string memory symbol_)
     ERC721(name_,symbol_)
     {
     }



    function safeMint(address to) public {
        _tokenIdCounter.increment();
        uint256 tokenId = _tokenIdCounter.current();
        _safeMint(to, tokenId);
    }

    /// @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) public virtual{
        require(_isApprovedOrOwner(msg.sender, tokenId),"ERC721: transfer caller is not owner nor approved");
        UserInfo storage info =  _users[tokenId];
        info.user = user;
        info.expires = expires;
        emit UpdateUser(tokenId,user,expires);
    }
    function getTimestamp()public view returns(uint256){
        return block.timestamp +300;
    }

    /// @notice Get the user1664725514 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);
        }
    }

    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override notRented(tokenId){
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override notRented(tokenId){
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) public virtual override notRented(tokenId){
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
        _safeTransfer(from, to, tokenId, data);
    }

}
1 Like

Hey mate, great documentation. Are these features approved and ready to be implemented into any dApp ? or do you need to get it approved first.

thank you

For rental protocol, to protect the use rights, the ERC-4907 NFT can be staked to a contract.

Double Protocol Lending Example:
Glossary:

  • oNFT: original NFT, which is an ERC-4907 NFT
  • doNFT: DOUBLE NFT. doNFT complies with the ERC-721 standard and represents a certificate for the right to use the NFT within a specific time.
  • vNFT: A special doNFT, as a voucher to redeem the original NFT.

Steps:
1 Alice calls setApprovalForAll() function of oNFT contract, approves doNFT contract can transfer the oNFT token.

2 Alice calls mintVNft() function of the doNFT contract to mint a vNFT token:

2.1 doNFT contract calls transferFrom() function of oNFT contract.

2.2 doNFT contract transfers the oNFT token to the doNFT contract. After that, the owner of the oNFT will become the doNFT contract.

2.3 doNFT contract mints a vNFT token and transfers it to Alice.

2.4 doNFT contract calls setUser() function of oNFT contract.

2.5 Set user to Alice so that Alice can use the oNFT when the oNFT is not rented.

3 Alice calls createLendOrder() function of the market contract, sets the rental price, period, and creates a lending order in the market.

More details: ERC-4907 Model Timing Diagram - Double