I first started with openzeppelin base contract but I want to add into it lazy minting (I want to create some hundred tokens and I am not rich) and royalties (although they are not enforced as I have read). Thus I cheked many internet sources and finally came up with this piece of code working on remix. Compiler in remx tells me constructor and tokenuri functions costs infinite gas. I would thank any help:
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
pragma abicoder v2; // required to accept structs as function parameters
import "hardhat/console.sol";
import "@openzeppelin/contracts@5.0.1/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@5.0.1/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@5.0.1/token/ERC721/extensions/ERC721Royalty.sol";
import "@openzeppelin/contracts@5.0.1/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
contract CdelaMorena is ERC721URIStorage, AccessControl, EIP712, ERC721Royalty {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
string private constant SIGNING_DOMAIN = "LazyNFT-Voucher";
string private constant SIGNATURE_VERSION = "1";
mapping (address => uint256) pendingWithdrawals;
constructor(address payable minter)
ERC721("CdelaMorena", "CdlMo")
EIP712(SIGNING_DOMAIN, SIGNATURE_VERSION) {
grantRole(MINTER_ROLE, minter);
// Grant the contract deployer an author royalty of 5%
_setDefaultRoyalty(msg.sender, 500);
}
/// @notice Represents an un-minted NFT, which has not yet been recorded into the blockchain.
/// A signed voucher can be redeemed for a real NFT using the redeem function.
struct NFTVoucher {
/// @notice The id of the token to be redeemed. Must be unique -
/// if another token with this ID already exists, the redeem function will revert.
uint256 tokenId;
/// @notice The minimum price (in wei) that the NFT creator is willing to accept
/// for the initial sale of this NFT.
uint256 minPrice;
/// @notice The metadata URI to associate with this token.
string uri;
/// @notice the EIP-712 signature of all other fields in the NFTVoucher struct.
/// For a voucher to be valid, it must be signed by an account with the MINTER_ROLE.
bytes signature;
}
// @brief Returns the URI for a given token ID.
//
// This function overrides the tokenURI function in ERC721URIStorage.
//
// @param tokenId The ID of the token to query the URI for.
// @return A string representing the URI for the token.
function tokenURI(uint256 tokenId) public view virtual override(ERC721, ERC721URIStorage) returns (string memory) {
return ERC721URIStorage.tokenURI(tokenId);
}
/// @notice Redeems an NFTVoucher for an actual NFT, creating it in the process.
/// @param redeemer The address of the account which will receive the NFT upon success.
/// @param voucher A signed NFTVoucher that describes the NFT to be redeemed.
function redeem(address redeemer, NFTVoucher calldata voucher) public payable returns (uint256) {
// make sure signature is valid and get the address of the signer
address signer = _verify(voucher);
// make sure that the signer is authorized to mint NFTs
require(hasRole(MINTER_ROLE, signer), "Signature invalid or unauthorized");
// make sure that the redeemer is paying enough to cover the buyer's cost
require(msg.value >= voucher.minPrice, "Insufficient funds to redeem");
// first assign the token to the signer, to establish provenance on-chain
_safeMint(signer, voucher.tokenId);
_setTokenURI(voucher.tokenId, voucher.uri);
// transfer the token to the redeemer
_transfer(signer, redeemer, voucher.tokenId);
// record payment to signer's withdrawal balance
pendingWithdrawals[signer] += msg.value;
return voucher.tokenId;
}
/// @notice Transfers all pending withdrawal balance to the caller.
/// Reverts if the caller is not an authorized minter.
function withdraw() public {
require(hasRole(MINTER_ROLE, msg.sender), "Only authorized minters can withdraw");
// IMPORTANT: casting msg.sender to a payable address is only safe if ALL members of the minter role are payable addresses.
address payable receiver = payable(msg.sender);
uint amount = pendingWithdrawals[receiver];
// zero account before transfer to prevent re-entrancy attack
pendingWithdrawals[receiver] = 0;
receiver.transfer(amount);
}
/// @notice Retuns the amount of Ether available to the caller to withdraw.
function availableToWithdraw() public view returns (uint256) {
return pendingWithdrawals[msg.sender];
}
/// @notice Returns a hash of the given NFTVoucher, prepared using EIP712 typed data hashing rules.
/// @param voucher An NFTVoucher to hash.
function _hash(NFTVoucher calldata voucher) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(
keccak256("NFTVoucher(uint256 tokenId,uint256 minPrice,string uri)"),
voucher.tokenId,
voucher.minPrice,
keccak256(bytes(voucher.uri))
)));
}
/// @notice Returns the chain id of the current blockchain.
/// @dev This is used to workaround an issue with ganache returning different values from the on-chain chainid() function and
/// the eth_chainId RPC method. See https://github.com/protocol/nft-website/issues/121 for context.
function getChainID() external view returns (uint256) {
uint256 id;
assembly {
id := chainid()
}
return id;
}
/// @notice Verifies the signature for a given NFTVoucher, returning the address of the signer.
/// @dev Will revert if the signature is invalid. Does not verify that the signer is authorized to mint NFTs.
/// @param voucher An NFTVoucher describing an unminted NFT.
function _verify(NFTVoucher calldata voucher) internal view returns (address) {
bytes32 digest = _hash(voucher);
return ECDSA.recover(digest, voucher.signature);
}
// @notice Checks if the contract supports a given interface by returning a boolean value.
// @dev This function overrides the supportsInterface function from ERC721URIStorage, AccessControl, and ERC721Royalty.
// @param interfaceId The interface identifier to check.
// @return A boolean value indicating whether the contract supports the given interface.
function supportsInterface(bytes4 interfaceId) public view virtual override (ERC721URIStorage, AccessControl, ERC721Royalty) returns (bool) {
return ERC721.supportsInterface(interfaceId) || AccessControl.supportsInterface(interfaceId);
}
}