Skip to content

Instantly share code, notes, and snippets.

@sagaratalatti
Created July 22, 2024 07:31
Show Gist options
  • Save sagaratalatti/5e3a96baa59efef8253e1cbe824303ca to your computer and use it in GitHub Desktop.
Save sagaratalatti/5e3a96baa59efef8253e1cbe824303ca to your computer and use it in GitHub Desktop.
Rentable NFTs implementing ERC4907
// 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;
}
}
// 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);
}
// 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