I'm creating a version 2 of a NFT collection with 1025 unique nfts, collection 1 also had 1025 unique nfts. The minting is split up in three phases.

  1. Mint for holders, where they get their unique corresponding version 2 NFT. The idea here is that if you for instance had tokenID 26 in version 1, you will mint version 2 tokenID 26 as well. If there are any NFTs left, i.e not all holders of version 1 minted their version 2. Then we move on to phase 2.
  2. Whitelist, get to mint the potential remainding nfts that have not been minted by the holders of v1 for a reduced price.
  3. Public, get to mint the potential remainding nfts for full price.

I know the setup is a bit weird, but don't worry about that. The problem:

How to know which tokenIDs are valid for whitelist and public sale i.e which tokenIDs were not minted by the holders. We have tried having an uint256[] public unmintedTokens array, that we initilize with all 1025, and then remove when NFTs are minted. This required to many for loops both in the constructor:

constructor(address version1NFTAddress) ERC721("Version2NFT", "V2NFT") {
    version1NFT = IVersion1NFT(version1NFTAddress);

    for (uint256 i = 1; i <= MAX_NFT_SUPPLY; i++) {

And in the mint function to remove the minted token id:

 for (uint256 i = 0; i < unmintedTokens.length; i++) {
        if (unmintedTokens[i] == version1TokenId) {
            unmintedTokens[i] = unmintedTokens[unmintedTokens.length - 1];

We did some calculations and this required way too much gas.

Solution 2: Have a mapping uint256 => bool to see if tokenID has been minted or not. And then in the public/whitelist mint function finding a tokenID that has not been minted:

mapping(uint256 => bool) public mintedTokens;
uint256 public nextPublicTokenId = 1;

function publicMint() public {
    require(nextPublicTokenId <= MAX_NFT_SUPPLY, "All tokens have been minted");

    while (mintedTokens[nextPublicTokenId]) {
        require(nextPublicTokenId <= MAX_NFT_SUPPLY, "All tokens have been minted");

    _safeMint(msg.sender, nextPublicTokenId);
    mintedTokens[nextPublicTokenId] = true;

This also required way too much gas. Does anyone have any ideas that are still onchain or do we have to go offchain? We hope to keep the holders minting setup, that if you have tokenID 4 in version 1 you get tokenID 4 in version 2. We want it this way to easily publish the corresponding metadata and unique art. All help appreciated :)

1 Answer 1


I think the method below should work. Basically it saves a mapping _nextId that connects every id to the next "mintable" id. For example at the start nextId(10) = 11, but if 11 gets minted then nextId(10) = 12.

It should use less gas since there's no for/while loop.

    mapping(uint256 => uint256) private _nextId;
    uint256 private _lastPublicId;
    function holderMint(uint256 _id) public {
        _checkValid(_id);   // checks
        _nextId[_id-1] = nextId(_id);  // effect
        _safeMint(msg.sender, _id);   // interaction
    function publicMint() public {
        uint256 _id = nextPublicTokenId();
        require(_id <= MAX_NFT_SUPPLY, "All tokens have been minted");
        _lastPublicId = _id;
        _safeMint(msg.sender, _id);
    function nextPublicTokenId() public view returns(uint256) {
        return nextId(_lastPublicId);
    function nextId(uint256 _id) public view returns(uint256) {
        uint256 _next = _nextId[_id];
        if (_next == 0) {
            return _id+1;  // dafult is next number
        } else {
            return _next;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.