Best way to, limit mint to 1 per wallet, and, reserve X token for owner?

Hello guys, I'm creating my first smart-contract (ERC721 free mint) and I'm trying to figure out some stuff, I already managed to do some customization like, starting the mint at ID 1 and not 0, or adding a max supply.

But I would also like to add two things ;

  • Only 1 mint per address
  • Reserve X tokens for owner (owner avoiding the 1 mint limit)

Here is my current smart contract :

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

import "@openzeppelin/contracts@4.7.2/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.7.2/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts@4.7.2/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@4.7.2/access/Ownable.sol";
import "@openzeppelin/contracts@4.7.2/utils/Counters.sol";

contract AmongCats is ERC721, ERC721Enumerable, ERC721URIStorage {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;
    uint256 MAX_SUPPLY = 9999;

    constructor() ERC721("My Token Name", "TICKER") {
        _tokenIdCounter.increment(); // Start mint at ID 1
    }

    function _baseURI() internal pure override returns (string memory) {
        return "placeURIhere";
    }

    function safeMint(address to, string memory uri) public {
        require(_tokenIdCounter.current() <= MAX_SUPPLY, "I'm sorry we reached the cap");
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    // The following functions are overrides required by Solidity.

    function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

I found some code examples, but they are from topics a bit old and I'm not sure if these examples are still the best ways. :slight_smile: I added Ownable.sol in my imports, because I guess I will need it for the 2 functions I want.

And totally optional but I want to know if there is an easy way to manage a basic whitelist, for example, I have a list of 10000 addresses in a file. And only addresses in the file are allowed to mint. Maybe by uploading a json file with all adresses to IPFS and telling the contract to check it ? I'm trying to keep the code as simple and short as possible, as I'm still learning.

Thank you :slight_smile:

Hi,

Let's start from the top.

To make sure only one mint is done per address, you can use the balanceOf method built in ERC721 which returns the user's balance of NFTs that address holds.
Therefore in the mint function, you can write

require(balanceOf(msg.sender) == 0, "Max Mint per wallet reached");

In the above example, msg.sender is the address of the caller, and the mint only happens if the caller does not have a NFT, so therefore the caller cannot call the function again (assuming the safeMint function only mints one NFT).

To reserve X tokens for the owner, you can only another function using the onlyOwner modifier like so :

function mintOwner() public onlyOwner {}

And then change the public mint function max cap to :

uint256 ownerAmount = 50 // Saving 50 for the owner, put this as a state variable.
 require(_tokenIdCounter.current() <= (MAX_SUPPLY - ownerAmount), "I'm sorry we reached the cap");

The best approach for Whitelisting in your case would be merkle trees, but that's a complicated topic. So I suggest you have a mapping of mapping (address => bool) whitelisters; and creating a function

function addToWhitelist(address _ad) external onlyOwner {
   whitelisters[_ad] = true;
}

Note that this can only be called by the owner and that takes gas fees
Alternatively, you can have the people themselves join the whitelist.

I hope that answers all.

1 Like

Thank you for the answer, I'm taking the time to read all this and try everything, and I'll edit my post if I managed to make it work :smiley:

edit : mint limit & owner reserve is working perfectly on testnet! Tank you very much. Here is the new code, if you ever see something weird in it don't hesitate to tell me.

Now I'll do some research on the whitelist. :slight_smile:

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

import "@openzeppelin/contracts@4.7.2/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.7.2/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts@4.7.2/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@4.7.2/access/Ownable.sol";
import "@openzeppelin/contracts@4.7.2/utils/Counters.sol";

contract AmongCats is ERC721, Ownable, ERC721Enumerable, ERC721URIStorage {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;
    uint256 MAX_SUPPLY = 5; // Max supply is 5
    uint256 ownerAmount = 2; // Saving 2 for the owner

    constructor() ERC721("SUPPLY TEST", "TEST") {
        _tokenIdCounter.increment(); // Start mint at ID 1
    }

    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://placehere/";
    }

    function safeMint(address to, string memory uri) public {
        require(_tokenIdCounter.current() <= (MAX_SUPPLY - ownerAmount), "I'm sorry we reached the cap");
        require(balanceOf(msg.sender) == 0, "Max Mint per wallet reached");
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    function mintOwner(address to, string memory uri) public onlyOwner{
        require(_tokenIdCounter.current() <= MAX_SUPPLY, "I'm sorry we reached the cap");
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    // The following functions are overrides required by Solidity.

    function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}
3 Likes

FYI, limiting minting to 1 nft per wallet doesn't really work if you are trying to stop bots. A private key can generate an almost infinite amount of wallet addresses and you can also have a contract deploy clones (generating new addresses) and buying them that way. There are workarounds to make it harder, just so you know. I've seen major brands using that assumption and bots bought all the Nfts :slight_smile:

1 Like