This standard is an extension of ERC-721. It proposes a new role user in addition to owner for a token. A token can have multiple users under separate expiration time. It allows the subscription model where an NFT can be subscribed non-exclusively by different users.
Motivation
Some NFTs represent IP assets, and IP assets have the need to be licensed for access without transferring ownership. The subscription model is a very common practice for IP licensing where multiple users can subscribe to an NFT to obtain access. Each subscription is usually time limited and will thus be recorded with an expiration time.
Existing ERC-4907 introduces a similar feature, but does not allow for more than one user. It is more suitable in the rental scenario where a user gains an exclusive right of use to an NFT before the next user. This rental model is common for NFTs representing physical assets like in games, but not very useful for shareable IP assets.
interfaceIERC7507{/// @notice Emitted when the expires of a user for an NFT is changed
eventUpdateUser(uint256indexedtokenId,addressindexeduser,uint64expires);/// @notice Get the user expires of an NFT
/// @param tokenId The NFT to get the user expires for
/// @param user The user to get the expires for
/// @return The user expires for this NFT
functionuserExpires(uint256tokenId,addressuser)externalviewreturns(uint256);/// @notice Set the user expires of an NFT
/// @param tokenId The NFT to set the user expires for
/// @param user The user to set the expires for
/// @param expires The user could use the NFT before expires in UNIX timestamp
functionsetUser(uint256tokenId,addressuser,uint64expires)external;}
Rationale
This standard complements ERC-4907 to support multi-user feature. Therefore the proposed interface tries to keep consistent using the same naming for functions and parameters.
However, we didn’t include the corresponding usersOf(uint256 tokenId) function as that would imply the implemention has to support enumerability over multiple users. It is not always necessary, for example, in the case of open subscription. So we decide not to add it to the interface and leave the choice up to the implementers.
import{loadFixture}from"@nomicfoundation/hardhat-toolbox/network-helpers";import{expect}from"chai";import{ethers}from"hardhat";constNAME="NAME";constSYMBOL="SYMBOL";constTOKEN_ID=1234;constEXPIRATION=2000000000;constYEAR=31536000;describe("ERC7507",function(){asyncfunctiondeployContractFixture(){const[deployer,owner,user1,user2]=awaitethers.getSigners();constcontract=awaitethers.deployContract("ERC7507",[NAME,SYMBOL],deployer);awaitcontract.mint(owner,TOKEN_ID);return{contract,owner,user1,user2};}describe("Functions",function(){it("Should not set user if not owner or approved",asyncfunction(){const{contract,user1}=awaitloadFixture(deployContractFixture);awaitexpect(contract.setUser(TOKEN_ID,user1,EXPIRATION)).to.be.revertedWith("ERC7507: caller is not owner or approved");});it("Should return zero expiration for nonexistent user",asyncfunction(){const{contract,user1}=awaitloadFixture(deployContractFixture);expect(awaitcontract.userExpires(TOKEN_ID,user1)).to.equal(0);});it("Should set users and then update",asyncfunction(){const{contract,owner,user1,user2}=awaitloadFixture(deployContractFixture);awaitcontract.connect(owner).setUser(TOKEN_ID,user1,EXPIRATION);awaitcontract.connect(owner).setUser(TOKEN_ID,user2,EXPIRATION);expect(awaitcontract.userExpires(TOKEN_ID,user1)).to.equal(EXPIRATION);expect(awaitcontract.userExpires(TOKEN_ID,user2)).to.equal(EXPIRATION);awaitcontract.connect(owner).setUser(TOKEN_ID,user1,EXPIRATION+YEAR);awaitcontract.connect(owner).setUser(TOKEN_ID,user2,0);expect(awaitcontract.userExpires(TOKEN_ID,user1)).to.equal(EXPIRATION+YEAR);expect(awaitcontract.userExpires(TOKEN_ID,user2)).to.equal(0);});});describe("Events",function(){it("Should emit event when set user",asyncfunction(){const{contract,owner,user1}=awaitloadFixture(deployContractFixture);awaitexpect(contract.connect(owner).setUser(TOKEN_ID,user1,EXPIRATION)).to.emit(contract,"UpdateUser").withArgs(TOKEN_ID,user1.address,EXPIRATION);});});});
Reference Implementation
Reference implementation available at: ERC7507.sol:
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.0;import"@openzeppelin/contracts/token/ERC721/ERC721.sol";import"./IERC7507.sol";contractERC7507isERC721,IERC7507{mapping(uint256=>mapping(address=>uint64))private_expires;constructor(stringmemoryname,stringmemorysymbol)ERC721(name,symbol){}functionsupportsInterface(bytes4interfaceId)publicviewvirtualoverridereturns(bool){returninterfaceId==type(IERC7507).interfaceId||super.supportsInterface(interfaceId);}functionuserExpires(uint256tokenId,addressuser)publicviewvirtualoverridereturns(uint256){require(_exists(tokenId),"ERC7507: query for nonexistent token");return_expires[tokenId][user];}functionsetUser(uint256tokenId,addressuser,uint64expires)publicvirtualoverride{require(_isApprovedOrOwner(_msgSender(),tokenId),"ERC7507: caller is not owner or approved");_expires[tokenId][user]=expires;emitUpdateUser(tokenId,user,expires);}}
Ming Jiang (@minkyn), Zheng Han (@hanbsd), Fan Yang (@fayang), "ERC-7507: Multi-User NFT Extension [DRAFT]," Ethereum Improvement Proposals, no. 7507, August 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7507.