TokenId in lazy minting

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(

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";

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 = {
      verifyingContract: this.contractAddress,
    return this._domain;

  async _formatVoucher(voucher) {
    const domain = await this._signingDomain();
    return {
      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 {

module.exports = {

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)"),

  /// @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.

The token creator needs to generate a signed voucher, and the token buyer needs to receive both the voucher and the signature. The token buyer then has the tokenId (as it is part of the voucher) and they are the ones who have to submit it to the smart contract.

Does this answer the question?

1 Like

Hi Team, I have a follow-up question here. For instance, we need to increment the counter to obtain the token_id which will definitely change the state of the blockchain and it required gas fee..

In EIP712 implementation, when seller sign the voucher with the following metadata (tokenid, minprice, uri). We need tokenid to generate upfront and keep track of it. While doing that, a gas fee is required.

Can someone please provide direction here, how to tackle this and enable gasless transaction for signing the voucher with tokenid.. Also, how to handle this tokenid generation upfront?.

Thank you so much for your support..


If you have lazy minting using EIP712 signed vouchers then your contract would not really have a counter for token ids. It's the seller that has to keep a counter off chain and make sure to generate vouchers for new token ids.

Note also that the buyer will have no guarantees that a token id is unique and theirs until the mint is confirmed on chain.

Thank you so much, it helps!!.

I am also following the same tutorial and I have an issue. I added an extra unit 256 parameter to the NFTVoucher (in class LazyMinter). Adding this single value resulted in _verify function to return wrong address. It was returning the minter's address not it returns an arbitrary address. I don't understand why?!

1 Like

Please show some code so we can try to understand the error.

The code and the question is here Lazy minting with a voucher

i am stuck with the same thing I dont understand where to find the solution from. gone through roofs

please let me know of how you fixed this issue

hi guys, any news here?
wrong address after adding any new parameter to the NFTvoucher

1 Like

can we schedule a meeting for just five minutes?

For further questions please create a new post following the guidelines: