Create a whitelist for your NFT project
Tags: solidity, nft, blockchain, ethereum
Published: 2022-01-05 18:13:32

Many NFT projects have been using whitelists/allowlists to reward their most active community members. The members in this list are allowed to mint their NFTs before the rest of the public. This saves them from competing in a gas war with others.

> wen whitelist??

Well, I don't know anything about that but I can show how you can implement it in your smart contracts.



## Prerequisites
We're going to examine the smart contract of the Doodles NFT project, and see how they stored a list of their members on the blockchain. We're also going to learn about different **function types**, **modifiers**, and **data locations** in `Solidity`.

To understand this tutorial, you need to know about NFTs, crypto wallets, and the Solidity programming language. We do go through some Solidity concepts as mentioned above, but you need to know how smart contracts work in general. 



## But why Doodles?
[Doodles](https://doodles.app/) is one of the best 10k NFT collectible projects out there. At the time of writing (5th Jan 2022), they've already traded around **46.3k** ETH on Opensea and have a floor price of **9.35** ETH. Their minting process went relatively smooth, so they are a good project to learn from.

**Doodles OpenSea link**: https://opensea.io/collection/doodles-official



## The process
The process is simple. We just need to store all the whitelisted addresses in a list. Doodles have gone one step ahead and stored the amount of NFTs the members can mint as well. They've used a data structure called `mapping` to do that.


### What is mapping?
Mapping in Solidity acts like a hash table or dictionary in any other language. It is used to store the data in the form of key-value pairs. Maps are created with the syntax `mapping(keyType => valueType)`.

- keyType could be a type such as `uint`, `address`, or `bytes`
- valueType could be all types including another mapping or an array.

Maps are not iterable, which means you cannot loop through them. You can only access a value through its key.

This is where Doodles are storing all the members and the number of NFTs they can mint:

```solidity
mapping(address => uint8) private _allowList;
```

You can access data from a mapping similar to how you'd do it from an array. Instead of an index, you'll just give it a key.

```solidity
_allowList[someAddress] = someNumber
``` 


### Adding members to the whitelist
Let's look at the `setAllowList` function where everything happens.

```solidity
function setAllowList(address[] calldata addresses, uint8 numAllowedToMint) external onlyOwner {
    for (uint256 i = 0; i < addresses.length; i++) {
        _allowList[addresses[i]] = numAllowedToMint;
    }
}
```

We're passing in an array of addresses and the number of tokens to the function. Inside the function, we loop through the addresses and store them in the `_allowList`. Pretty straightforward, eh?

The most interesting keywords here are `calldata`, `external`, and `onlyOwner`.

#### 1. The "OnlyOwner" modifier
This keyword is imported by the [OpenZeppelin](https://openzeppelin.com/) library. OpenZeppelin provides [Ownable](https://docs.openzeppelin.com/contracts/2.x/api/ownership#Ownable) for implementing ownership in your contracts. By adding this modifier to your function, you're only allowing it to be called by a specific address. By default, `onlyOwner` refers to the account that deployed the contract.

```solidity
import "@openzeppelin/contracts/access/Ownable.sol";

// ...

function setAllowList() external {
    // anyone can call this setAllowList()
}

function setAllowList() external onlyOwner {
    // only the owner can call setAllowList()!
}
```

#### 2. Different types of functions
There are four types of Solidity functions: `external`, `internal`, `public`, and `private`. 

- *private* functions can be only called from inside the contract.
- *internal* functions can be called from inside the contract as well other contracts inheriting from it.
- *external* functions can only be invoked from the outside.
- *public* functions can be called from anywhere.

Why are we using an `external` function here instead of, maybe, `public`? Well, because external functions are sometimes more efficient when they receive large arrays of data.

> The difference is because in public functions, Solidity immediately copies array arguments to memory, while external functions can read directly from calldata. Memory allocation is expensive, whereas reading from calldata is cheap.

This was taken from a [StackoverExchange answer](https://ethereum.stackexchange.com/questions/19380/external-vs-public-best-practices) on this topic.

#### 3. Data locations
Variables in Solidity can be stored in three different locations: `storage`, `memory`, and `calldata`.

- *storage* variables are stored directly on the blockchain.
- *memory* variables are stored in memory and only exist while a function is being called.
- *calldata* variables are special (more efficient) data locations that contain function arguments. They are only available for `external` functions.

Since our list of whitelisted members can be large, we're using `calldata` to store our array of addresses. 



## How to mint

Let's look at the mint function they've used for the members in the whitelist:

```solidity
function mintAllowList(uint8 numberOfTokens) external payable {
    uint256 ts = totalSupply();
    
    require(isAllowListActive, "Allow list is not active");
    require(numberOfTokens <= _allowList[msg.sender], "Exceeded max available to purchase");
    require(ts + numberOfTokens <= MAX_SUPPLY, "Purchase would exceed max tokens");
    require(PRICE_PER_TOKEN * numberOfTokens <= msg.value, "Ether value sent is not correct");

    _allowList[msg.sender] -= numberOfTokens;
    for (uint256 i = 0; i < numberOfTokens; i++) {
        _safeMint(msg.sender, ts + i);
    }
 }
```

The following line is relevant for this tutorial:

```solidity
require(numberOfTokens <= _allowList[msg.sender], "Exceeded max available to purchase");
```

if the wallet minting the token i.e. `msg.sender` is not available in the `_allowList`, this line will throw an exception.

You can take a look at the complete [source code here](https://etherscan.io/address/0x8a90cab2b38dba80c64b7734e58ee1db38b8992e#code).


<hr />
## Don't leave me, take me with you

Like what you read? Follow me on social media to know more about NFTs, Web development, and shit-posting.

Twitter: [@lilcoderman](https://twitter.com/lilcoderman)

Instagram: [@lilcoderman](https://www.instagram.com/lilcoderman/)