I was following this tutorial from openzeppelin on lazy minting
link to the video[youtube link]
by means of which a voucher is generated without modifying the state of the blockchain and hence making it gasless. What i don't understand is how is the tokenId of the voucher being kept track of(since making a counter and using that to make tokens would imply gas fees which is what i'm trying to avoid for the token creator)
const { voucher, signature } = await lazyMinter.createVoucher(
1,
"ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
minPrice
);
the number 1 is the tokenId that i have to provide in my tests which i'm not sure how to unload to the contract gas-lessly
This is the function used to create and redeem the voucher
const ethers = require("ethers");
const { TypedDataUtils } = require("ethers-eip712");
const SIGNING_DOMAIN_NAME = "LazyNFT-Voucher";
const SIGNING_DOMAIN_VERSION = "1";
class LazyMinter {
constructor({ contractAddress, signer }) {
this.contractAddress = contractAddress;
this.signer = signer;
this.types = {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
NFTVoucher: [
{ name: "tokenId", type: "uint256" },
{ name: "minPrice", type: "uint256" },
{ name: "uri", type: "string" },
],
};
}
async _signingDomain() {
if (this._domain != null) {
return this._domain;
}
const chainId = await this.signer.getChainId();
this._domain = {
name: SIGNING_DOMAIN_NAME,
version: SIGNING_DOMAIN_VERSION,
verifyingContract: this.contractAddress,
chainId,
};
return this._domain;
}
async _formatVoucher(voucher) {
const domain = await this._signingDomain();
return {
domain,
types: this.types,
primaryType: "NFTVoucher",
message: voucher,
};
}
async createVoucher(tokenId, uri, minPrice = 0) {
const voucher = { tokenId, uri, minPrice };
const typedData = await this._formatVoucher(voucher);
const digest = TypedDataUtils.encodeDigest(typedData);
const signature = await this.signer.signMessage(digest);
return {
voucher,
signature,
digest,
};
}
}
module.exports = {
LazyMinter,
};
The contract code to verify this is
function _hash(NFTVoucher calldata voucher) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(
keccak256("NFTVoucher(uint256 tokenId,uint256 minPrice,string uri)"),
//voucher.tokenId,
curr_tokenId,
voucher.minPrice,
keccak256(bytes(voucher.uri))
)));
}
/// @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.
/// @param signature An EIP712 signature of the given voucher.
function _verify(NFTVoucher calldata voucher, bytes memory signature) internal view returns (address) {
bytes32 digest = _hash(voucher);
return digest.toEthSignedMessageHash().recover(signature);
}
function supportsInterface(bytes4 interfaceId) public view virtual override (AccessControl, ERC721) returns (bool) {
return ERC721.supportsInterface(interfaceId) || AccessControl.supportsInterface(interfaceId);
}
If i understand correctly we have to store this voucher id in a centralised location off chain and retrieve as and when needed? I am new to web3 so pardon me if i missed out on any necessary details i'll be happy to provide them when asked for.