1

New to solidity here. I am learning how to code ERC-721 contracts for NFTs. I have the following code so far:

pragma solidity ^0.7.0;

contract TestNFTContract is ERC721, Ownable {
    using SafeMath for uint256;


    uint256 public salePrice = 50000000000000000; // 0.050 ETH

    uint256 public constant maxNFTPurchase = 20;

    uint256 public constant MAX_TEST_NFT = 10000;

    bool public saleIsActive = false;

    uint256 public testNFTReserve = 100; // Reserve 100 Test for giveaway as example

   

    constructor() ERC721("Test Nft", "TESTNFT") {}

    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        msg.sender.transfer(balance);
    }

    function reserveNFTs(address _to, uint256 _reserveAmount)
        public
        onlyOwner
    {
        uint256 supply = totalSupply();
        require(
            _reserveAmount > 0 && _reserveAmount <= testNFTReserve,
            "Not enough reserve left for team"
        );
        for (uint256 i = 0; i < _reserveAmount; i++) {
            _safeMint(_to, supply + i);
        }
        testNFTReserve = testNFTReserve.sub(_reserveAmount);
    }


    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }

    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }



    function mintTestNFT(uint256 numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint NFT");
        require(
            numberOfTokens > 0 && numberOfTokens <= testNFTReserve,
            "Can only mint 20 tokens at a time"
        );
        require(
            totalSupply().add(numberOfTokens) <= MAX_TEST_NFT,
            "Purchase would exceed max supply of TEST NFTs"
        );
        require(
            msg.value >= salePrice.mul(numberOfTokens),
            "Ether value sent is not correct"
        );

        for (uint256 i = 0; i < numberOfTokens; i++) {
            uint256 mintIndex = totalSupply();
            if (totalSupply() < MAX_TEST_NFT) {
                _safeMint(msg.sender, mintIndex);
            }
        }
    }

    function setTestNftPrice(uint256 newPrice) public onlyOwner {
        salePrice = newPrice;
    }
}

Questions that I have:

  1. How I do set different prices for pre-sale and general-sale? Is that something I have to code into the contract or would the owner change that via the setTestNftPrice function? If the owner sets it, do they do it via etherscan?
  2. If the owner indeed sets the prices, do I only set the pre-sale price in code and then the owner can change it for public sale?
  3. I want to close the pre-sale and start the public sale once the total no of mints reaches 4000 for example. Is that possible?

Thanks!

1 Answer 1

2
  1. you can add a second state variable: uint public presalePrice and use it in a second function presaleMint() to mint on the presale. The presaleMint() function could then include a check to see if the minter is on an allowed presale list, or you could just cap the presale mints as you said. (One idea with presales is to limit who can mint during them though, to minimise gas spikes, so consider using a whitelist/allowlist instead of just a cap on numbers).

  2. If you wanted to use one price variable and one mint function, then change the price between presale and public sale, yes, you could do it that way! In which case yes, you could do it via etherscan or via cmd/terminal (e.g. using hardhat)

    It may be better practice to use a presalePrice variable though, as it is transparent (and can't be messed up by you accidentally using 16 decimal places instead of 18 lol).

  3. You can definitely put a cap on the presale, just like you've ensured you don't mint over the 10k maximum:

    in presaleMint() use: require(supply + numberOfTokens <= 4000, "Presale full");

    It might be cleaner to add a state variable: uint public constant MAX_PRESALE = 4000; then use: require(supply + numberOfTokens <= MAX_PRESALE, "Presale full");

3b. Contracts need to be prodded to work (unless you use things like Chainlink Keepers, but that's another topic). If you want a general sale to start, either:

  • you have to start it: flipSaleState(). You can do that via etherscan, or via a terminal/cmd line console. or:
  • you have a condition in the general sale mintTestNFT() function that does not allow minting until a condition has been met, i.e. 4000 sales, or a certain block number is reached. IMO it is better to manually control this, because you get some flexibility should anything go wrong, there is a delay, or you don't manage to make 4000 presale mints etc etc.

extra points: in your reserveNFTs() function you set a temporary variable: uint256 supply = totalSupply(); Then you do some checks and go on to mint:

for (uint256 i = 0; i < _reserveAmount; i++) {
    _safeMint(_to, supply + i);

This is a more efficient method than in your mintTestNFT() function, where you call totalSupply() in every loop:

for (uint256 i = 0; i < numberOfTokens; i++) {
    uint256 mintIndex = totalSupply(); //<-- here totalSupply() called every loop
    if (totalSupply() < MAX_TEST_NFT) {  //<-- and here
        _safeMint(msg.sender, mintIndex);
    }

every time totalSupply() is called (or you check for a storage variable) there is a significant gas cost. Much better to declare a temporary variable and use that in the minting loop, just like you did in reserveNFTs(). Even better in mintTestNFTs(), you could declare uint supply = totalSupply(); higher up and save another call to totalSupply(), marked at ** below:

function mintTestNFT(uint256 numberOfTokens) public payable {
    require(saleIsActive, "Sale must be active to mint NFT");
    require(
        numberOfTokens > 0 && numberOfTokens <= maxNftPurchase, //<-- you had a typo here
        "Can only mint 20 tokens at a time"
    );

    uint supply = totalSupply(); //<--- declare temp variable

    require(
        supply.add(numberOfTokens) <= MAX_TEST_NFT, // <--- use it here! **
        "Purchase would exceed max supply of TEST NFTs"
    );
    require(
        msg.value >= salePrice.mul(numberOfTokens),
        "Ether value sent is not correct"
    );

    for (uint256 i = 0; i < numberOfTokens; i++) {
        if (supply < MAX_TEST_NFT) { //  <-- use your local variable ***
            _safeMint(msg.sender, mintIndex);
        }
        supply++;  // <-- increment local variable (now it's the next id to mint)
    }
}

in fact, you could go one step further too! In the line marked ** you have already checked you don't exceed the max tokens, so you don't need to check that again in your minting loop at ***. You can omit that if (supply < MAX_TEST_NFT) check and save a bit of gas!

2
  • Thanks for all the info! Follow up question: 1. If I create another function called presaleMint of type payable, how do I manage both on the front end? Would I need to update the site at the end of the pre-sale? 2. How do I flip the state after presale? Like I suppose what would happen if the mint hits 4000 and its not flipped over? I presume the owner needs to check the total no of mints in etherscan first and then flip the state? Commented Feb 23, 2022 at 22:45
  • When I've used separate mint() and presaleMint() functions I've had an allowlist of approved addresses. On the front end I've checked if the connected wallet address was in that allowlist. If so: render the presaleMint() function component. If not: render the mint() function component. (so yes, two separate function components on the front end. Flipping the state at the end of presale - you could have a set date for the public sale to start, then flip it live at that date. Otherwise you tie yourself into needing 4000 people to mint in presale...
    – panda
    Commented Feb 23, 2022 at 23:45

Your Answer

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

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