Created
July 22, 2024 07:31
-
-
Save sagaratalatti/5e3a96baa59efef8253e1cbe824303ca to your computer and use it in GitHub Desktop.
Rentable NFTs implementing ERC4907
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
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 { | |
struct UserInfo { | |
address user; // address of user role | |
uint64 expires; // unix timestamp, user expires | |
} | |
mapping(uint256 => UserInfo) internal _users; | |
constructor(string memory tokenName, string memory tokenTicker) ERC721(tokenName, tokenTicker) {} | |
/// @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 override { | |
require(_isAuthorized(msg.sender, user, tokenId), "ERC721: transfer caller is not owner nor authorized"); | |
require(userOf(tokenId) == address(0), "User already assigned"); | |
require(expires > block.timestamp, "expires should be in future"); | |
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 override returns (address) { | |
if (uint256(_users[tokenId].expires) >= block.timestamp) { | |
return _users[tokenId].user; | |
} | |
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 override returns (uint256) { | |
return _users[tokenId].expires; | |
} | |
/// @dev See {IERC165-supportsInterface}. | |
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721) returns (bool) { | |
return | |
interfaceId == type(IERC4907).interfaceId || | |
super.supportsInterface(interfaceId); | |
} | |
function _update(address to, uint256 tokenId, address auth) internal virtual override returns(address) { | |
super._update(to, tokenId, auth); | |
if ( | |
auth != to && | |
_users[tokenId].user != address(0) && //user present | |
block.timestamp >= _users[tokenId].expires //user expired | |
) { | |
delete _users[tokenId]; | |
emit UpdateUser(tokenId, address(0), 0); | |
} | |
return _users[tokenId].user; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import "./ERC4907.sol"; | |
import "@openzeppelin/contracts/utils/Counters.sol"; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
contract Rentables is ERC4907 { | |
using Counters for Counters.Counter; | |
Counters.Counter private currentTokenId; | |
string public baseTokenURI; | |
uint256 public baseAmount = 20000000000000; //0.00002 ethers | |
address private owner; | |
struct RentableItem { | |
bool rentable; | |
uint256 amountPerMinute; | |
} | |
mapping(uint256 => RentableItem) public rentables; | |
constructor(string memory _name, string memory _symbol) ERC4907(_name, _symbol) { | |
baseTokenURI = "https://bafybeicrtqwqiupfregzoumecwg4bwqldm4j2pbpbrclkjiwko74ckeqzu.ipfs.dweb.link/metadata/"; | |
owner = msg.sender; | |
} | |
modifier onlyOwner(address _caller) { | |
require(owner == _caller, "Only owner can access this function ;)"); | |
_; | |
} | |
function mint() public onlyOwner(msg.sender) { | |
currentTokenId.increment(); | |
uint256 newItemId = currentTokenId.current(); | |
_safeMint(owner, newItemId); | |
rentables[newItemId] = RentableItem( | |
{ | |
rentable: false, | |
amountPerMinute: baseAmount | |
}); | |
} | |
function rent(uint256 _tokenId, uint64 _expires) public payable virtual { | |
uint256 dueAmount = rentables[_tokenId].amountPerMinute * _expires; | |
require(msg.value == dueAmount, "Uncorrect amount"); | |
require(userOf(_tokenId) == address(0), "Already rented"); | |
require(rentables[_tokenId].rentable, "Renting disabled for the NFT"); | |
payable(ownerOf(_tokenId)).transfer(dueAmount); | |
UserInfo storage info = _users[_tokenId]; | |
info.user = msg.sender; | |
info.expires = uint64(block.timestamp + (_expires * 60)); | |
emit UpdateUser(_tokenId, msg.sender, _expires); | |
} | |
function _baseURI() internal view virtual override returns (string memory) { | |
return baseTokenURI; | |
} | |
function setBaseTokenURI(string memory _baseTokenURI) public onlyOwner(msg.sender) { | |
baseTokenURI = _baseTokenURI; | |
} | |
function setRentFee(address user, uint256 _tokenId, uint256 _amountPerMinute) public { | |
require(_isAuthorized(_msgSender(), user, _tokenId), "Caller is not token owner nor approved"); | |
rentables[_tokenId].amountPerMinute = _amountPerMinute; | |
} | |
function setRentable(address user, uint256 _tokenId, bool _rentable) public { | |
require(_isAuthorized(_msgSender(), user, _tokenId), "Caller is not token owner nor approved"); | |
rentables[_tokenId].rentable = _rentable; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment