How to Create an NFT Allowlist Based on Ownership

Learn how to create on-chain allowlists based on previously owned NFTs

The most successful NFT PFP projects usually like to reward their community with continuing utility. One of the most common ways of doing this is by allowing existing holders to mint free NFTs from their subsequent collections.

1024

BAYC NFT holders were able to mint MAYC NFTs for free at the time of release

Another fairly common design paradigm is to allow NFT holders of a project you do not own to mint NFTs from your collection for free or at a discounted rate. This is usually done to incentivize some of the most active collectors in the NFT ecosystem to participate in your project.

Fortunately, building allowlists that query ownership data on already deployed contracts is fairly simple on Ethereum and other EVM-based blockchains. In this article, we will write an NFT collectible smart contract using Solidity and Hardhat that allows users to mint NFTs only if they own an NFT from another pre-determined collection.

Creating the Allowlist Contract

Step 1: Install Node and npm

In case you haven't already, install node and npm on your local machine.
Make sure that node is at least v14 or higher by typing the following in your terminal:

node -v

Step 2: Create a Hardhat project

We're going to set up our project using Hardhat, the industry-standard development environment for Ethereum smart contracts. Additionally, we'll also install OpenZeppelin contracts.

To set up Hardhat, run the following commands in your terminal:

mkdir nft-allowlist && cd nft-allowlist
npm init -y
npm install --save-dev hardhat
npx hardhat

Choose Create a Javascript project from the menu and accept all defaults. To ensure everything is installed correctly, run the following command in your terminal:

npx hardhat test

To install OpenZeppelin:

npm install @openzeppelin/contracts

Step 3: Write the original NFT smart contract

In order to create an NFT smart contract that allows mints based on ownership of another NFT collection, we need to either get the deployed address of the original NFT collection or write one from scratch.

Since we are working in a local development environment, we will do the latter. Create a file called OGCollection.sol in the contracts folder and add the following code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract OGCollection is ERC721 {

	using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() ERC721("Original Collection", "OGC") {}
		
		function mintNft() public {
		    uint256 tokenId = _tokenIds.current();
        _mint(msg.sender, tokenId);
        _tokenIds.increment();
		}
}

Step 4: Write the NFT Smart Contract with Allowlist

Let’s now write an NFT smart contract that allows mint only if a wallet holds an NFT from the OG collection above. In order to do this, we will have to invoke the balanceOf() function of OGCollection provided to it by OpenZeppelin’s ERC-721 implementation.

To make sure that our contract can actually call the aforementioned function, we need to provide it with the function’s signature. We will do this using Solidity interfaces.

In the contracts folder, create a new file called NftWithAllowlist.sol and add the following code:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "hardhat/console.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

interface OGCollectionInterface {
    // Define signature of balanceOf
    function balanceOf(address owner) external view returns (uint256);
}

contract NFTAllowlist is ERC721 {
    using Counters for Counters.Counter;
    
    Counters.Counter private _tokenIds;

    // Deployed address of original collection
    address public ogContractAddress;
    OGCollectionInterface ogContract;
    
    constructor(address _ogContractAddress) ERC721("NFT Allowlist Demo", "NAD") {
        ogContractAddress = _ogContractAddress;
        ogContract = OGCollectionInterface(ogContractAddress);
    }

    // Presale mint
    function preSale() public {
        require(ogContract.balanceOf(msg.sender) > 0, "Don't have NFT from original collection.");

        for (uint i = 0; i < 2; i++) {
            _mintSingleNFT();
        }

        console.log("2 NFTs minted using allowlist.");    
    }
    
    function _mintSingleNFT() private {
        uint newTokenID = _tokenIds.current();
        _safeMint(msg.sender, newTokenID);
        _tokenIds.increment();
    }
}

Compile the contracts and make sure everything is working by running:

npx hardhat compile

Step 5: Test the allowlist functionality locally

Next, write a script that allows us to test the allowlist locally. To do this, create a new file called run.jsin the scripts folder, then add the following code:

const hre = require("hardhat");

async function main() {

    // Get wallet addresses from hardhat
    const [owner, address1, address2] = await hre.ethers.getSigners();

    // Deploy OG collection and get deployed contract address
    const ogFactory = await hre.ethers.getContractFactory("OGCollection");
    const ogContract = await ogFactory.deploy();

    await ogContract.deployed();
    console.log("OG Collection deployed to: ", ogContract.address, "\n");

    ogContractAddress = ogContract.address

    // Mint OG NFTs to address1
    let txn;
    txn = await ogContract.connect(address1).mintNft();
    await txn.wait()
    console.log("1 OG NFT minted to ", address1.address);

    // Deploy contract with allowlist 
    const alFactory = await hre.ethers.getContractFactory("NftWithAllowlist");
    const alContract = await alFactory.deploy(ogContractAddress);

    await alContract.deployed();
    console.log("NFT with Allowlist deployed to: ", alContract.address, "\n");

    // Mint NFTs from address1 (has OG NFT)
    console.log("Minting NFTs from address with OG NFT...")
    txn = await alContract.connect(address1).preSale();
    await txn.wait();

    // Mint NFTs from address2 (does not have OG NFT)
    console.log("\nMinting NFTs from address without OG NFT...")
    txn = await alContract.connect(address2).preSale();
    await txn.wait();
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

Run this script by running the following command in your terminal:

npx hardhat run scripts/run.js

You should see output that looks like this:

OG Collection deployed to:  0x5FbDB2315678afecb367f032d93F642f64180aa3 

1 OG NFT minted to  0x70997970C51812dc3A010C7d01b50e0d17dc79C8
NFT with Allowlist deployed to:  0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 

Minting NFTs from address with OG NFT...
2 NFTs minted using allowlist.

Minting NFTs from address without OG NFT...
Error: VM Exception while processing transaction: reverted with reason string 'Don't have NFT from original collection.'
    at NftWithAllowlist.preSale (contracts/NftWithAllowlist.sol:29)
    at HardhatNode._mineBlockWithPendingTxs (/Users/rounakbanik/alchemy-tut/nft-allowlist-2/node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:1773:23)
    at HardhatNode.mineBlock (/Users/rounakbanik/alchemy-tut/nft-allowlist-2/node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:466:16)
    at EthModule._sendTransactionAndReturnHash (/Users/rounakbanik/alchemy-tut/nft-allowlist-2/node_modules/hardhat/src/internal/hardhat-network/provider/modules/eth.ts:1504:18)
    at HardhatNetworkProvider.request (/Users/rounakbanik/alchemy-tut/nft-allowlist-2/node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:118:18)
    at EthersProviderWrapper.send (/Users/rounakbanik/alchemy-tut/nft-allowlist-2/node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)

Above, you can see that an address with the OG NFT was able to mint but an address without the OG NFT was not.

Conclusion

Congratulations! You now know how to implement an on-chain NFT allowlist based on previously owned NFTs.

If you enjoyed this tutorial about creating on-chain allowlists, tweet us at @AlchemyPlatform and give us a shoutout!

Don't forget to join our Discord server to meet other blockchain devs, builders, and entrepreneurs!

Ready to start building your NFT collection?

Create a free Alchemy account and do share your project with us!


ReadMe