writing.
Create a fair NFT mint on Ethereum with Solidity and NextJS #2: Smart Contract setup



Papers, please
In the last post, we've setup a request to our back-end as a requirement of the mint process.
Indeed, we fetched the hash, signature and nonce generated from the back-end and we sent them to the smart contract through thebuy
method.
Let's see how we're going to process those informations.
Verify the signer identity
First of all let's add a field that contains the address of the transaction signer to our smart contract. As we saw in the previous post,this address is an account that we own and with which we sign the transaction in our NextJS backend.
addressprivate _signerAddress=0x5260818d61ff27B7d0db3A96310246C041F7191e;// So we can modify the signer address on each drop to enhance securityfunctionsetSignerAddress(address addr)external onlyOwner{ _signerAddress= addr;}
Then we create a function calledmatchAddresSigner
to check if the address that signed the hash is the same as the one we set in the contract.
functionmatchAddresSigner(bytes32 hash,bytesmemory signature)privateviewreturns(bool){return _signerAddress== hash.recover(signature);}
Finally, we can use this function as a require in ourbuy
method.
require(matchAddresSigner(hash, signature),"DIRECT_MINT_DISALLOWED");
Prevent double minting
Now that we verified the signer identity, we can prevent the same mint from being executed twice. We'll use a simple string-to-boolean map to store the used nonces.
mapping(string=>bool)private _usedNonces;
Then require the nonce to be unique inside ourbuy
function.
require(!_usedNonces[nonce],"HASH_USED");
Check hash validity
Now that we've verified the signer identity and the nonce, we can check if the hash is valid. To do so, we'll create a function that recreates the hash from the transaction and compare it to the one we received from the sender.
functionhashTransaction(address sender,uint256 qty,stringmemory nonce)privatepurereturns(bytes32){bytes32 hash=keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",keccak256(abi.encodePacked(sender, qty, nonce))));return hash;}
Once again, we'll add it to ourbuy
function as a require.
require(hashTransaction(msg.sender, tokenQuantity, nonce)== hash,"HASH_FAIL");
The finalbuy
method
Now that we've verified the signer identity, the nonce and the hash, thebuy
function is pretty much classic, here's the full function:
functionbuy(bytes32 hash,bytesmemory signature,stringmemory nonce,uint256 tokenQuantity)externalpayable{require(saleLive,"SALE_CLOSED");require(!presaleLive,"ONLY_PRESALE");// Check if the address signer is the same as the _signerAddress, preventing direct minting.require(matchAddresSigner(hash, signature),"DIRECT_MINT_DISALLOWED");// This is required to prevent someone from re-using the same signature and hash combination to mint again.require(!_usedNonces[nonce],"HASH_USED");// Verify if the keccak256 hash matches the hash generated by the hashTransaction functionrequire(hashTransaction(msg.sender, tokenQuantity, nonce)== hash,"HASH_FAIL");// Out of stock checkrequire(totalSupply()< TOTAL_SUPPLY,"OUT_OF_STOCK");require(publicAmountMinted+ tokenQuantity<= DISTRIBUTED_TO_PUBLIC,"EXCEED_PUBLIC");require(tokenQuantity<= MAX_PER_MINT,"EXCEED_MAX");require(PRICE* tokenQuantity<= msg.value,"INSUFFICIENT_ETH");for(uint256 i=0; i< tokenQuantity; i++){ publicAmountMinted++;_safeMint(msg.sender,totalSupply()+1);}// Add the nonce to the list of already used nonce _usedNonces[nonce]=true;}
In the final post, we'll see how to add some fair minting logic to our smart contract with the whitelisting method.
Check out theGitHub repository for the full source.