Create a smart contract to Mint NFTs and contribute to a good cause
As a developer, learning new technologies is a constant challenge. By itself, getting started on the new world of Web3 can be frightening, as it involves many new concepts. Beyond buzzwords like blockchain, NFT, and cryptocurrency, there are brand new programming languages such as Solidity, decentralized data storage methods like IPFS, new libraries, and tools to learn (ethers, Hardhat, OpenZeppelin), not to mention Web3 wallets to integrate with our apps.
However, here at 14Islands, we like to simplify things. In this tutorial, you will learn how to create a smart contract that mints NFTs for a noble cause. In that fashion, the set price for NFTs created by our smart contract will be a charity donation, similar to what we do in Blobmixer.
You are going to learn how to:
- Use Web3 development tools like Hardhat, ethers, and OpenZeppelin
- Create NFTs from your contract and visualize them on the Open sea marketplace
- Make charity donations from your smart contract
- Deploy your contract into the Goerli Ethereum testnet
Don't worry, in case, except for 'dotenv', you don't know what any of those extensions do. We will explain each of them as we go, even 'dotenv' ;).
Before diving deep into code, there are two things we need to sort out: getting a private key from a Web3 wallet and getting an Alchemy URL. Your private key can be retrieved from any wallet that supports Ethereum. We are going to use Metamask, as it is the most popular. On your Metamask, go to "account details" and then "export private key." Let's copy your private key and save it as an entry on the project .env file.
Important: when exporting your private key to be used on an application, you should never add any funds to this wallet, besides funds for the application itself, as they risk being compromised.
Furthermore, we still need an Alchemy URL. Alchemy acts as a bridge between our project and the blockchain network. Create your Alchemy account and then, from its dashboard, click on "create app." Pick a name and description for your app, then select Ethereum as the chain and Goerli for the network.
After the merge, Goerli is the recommended test network for the Ethereum main net. The test net allows us to try our code before deploying it into the real blockchain. Also, we won't need to spend real ether coins when interacting with the test net. Back to the Alchemy dashboard, select the app you just created, and click on the "view key" option. Let's copy the HTTPS key displayed and add that to our .env.
Back to coding, let's begin using those outlandish libraries we installed earlier. The first one we will be using is Hardhat, which provides an Ethereum building tool inside a Node.js environment. From your terminal, run the command 'npx hardhat'. from the options, choose "create an empty hardhat.config.js". This action should result in a new 'hardhat.config.js' file. Open this file and type the following code:
On the Hardhat config file, we are using 'dotenv' to load the environment variables we created earlier. Those will be used for us to interact with the Goerli test net later on. We are also importing @nomiclabs/hardhat-ethers — a plugin that adds the ethers library into the Hardhat environments we will use. Another important thing to note on the configuration file is that we are setting the version of solidity we will be compiling: "0.8.4".
Creating the smart contract
With the configuration part done, let's now start writing our smart contract on the solidity language. From the root of your project, create a contracts directory and a file called "Demo.sol." To make our lives easier while creating our contract we will use the OpenZepellin library. OpenZepellin provides us a suite of extensible contract solutions following Ethereum standards.
That looks nice, though it is a lot of code that may be confusing, let's understand the contract together. First, take a look at line 10, as it describes what our contract is.
At its core, our contract is supposed to mint non-fungible tokens. To accomplish that, we are inheriting ERC721. This OpenZeppelin class implements the non-fungible token standard defined on the Ethereum protocols. Nevertheless, ERC721 only implements the bare minimum for an NFT. There exist features that, while specified on the standard, are not required by it. In that fashion, we are adding two extra standard features to our smart-contract — ERC721Burnable and ERC721URIStorage. The first will add the possibility of burning tokens created with this contract, while the second specifies that tokens minted with this contract will point to an external URI that defines its metadata.
Beyond NFTs, we are also defining our contract as Ownable, which will add basic access control to the contract, and in this form, the original issuer of the contract will automatically be established as its owner.
Below the contract definition in lines 11 and 12 we are creating a counter for our contract. The counter will be useful to handle the ids for our NFTs and also control how many tokens can be minted.
Until now, our smart contract has only been using standard external library logic provided by OpenZeppelin. However, we begin to add custom logic to it at the constructor.
Like we do on Blobmixer, the requirement for minting tokens with this contract is to contribute with a donation to charity. Also, we define a maximum number of tokens that can be minted to make items on this collection unique. In that manner, the contract is responsible for defining the charity address and the max number of mintable tokens, and it also sets the minimum donation value for a donation. We prefer not to fix an exact value to allow people to contribute more if they want to.
We will be looking into how to pass those values into the contract soon, in the deployment section. Besides that, we are also defining a name and abbreviation for tokens minted with this contract. Feel free to change it to anything you may wish.
Finally, let's check how our contract actually mints its NFTs at the mintNFT function. Before jumping to logic, it is important to note that this function is payable, so it will receive some ether value as payment when it is called.
Before performing the mint action itself, our function has to validate if the request to mint is valid. To do that, first, we check if msg.value is equal to or greater than the minimum donation value and in case it's not we return an error message. Keep in mind that msg.value represents the amount of ether sent into the mintNFT function call. The second validation is related to the number of tokens minted. For that, we use the tokenId and check if it is less than maxNumberOfTokens, returning an error message on the negative case.
With our validation done we can call the _safeMint function provided by OpenZeppelin's ERC721. This function will effectively mint the NFT. It also applies some extra validations, like ensuring the tokenId is unique.
While in theory, we have effectively minted an NFT at this point, there is more we have to do. The _setTokenURI function is provided by ERC721URIStorage and is going to attach our NFT with an external URI that should return its metadata. We will understand more about how those URIs work in the final section. Before progressing toward the donation, we have to do some cleanup by incrementing the _tokenIdCounter, which prepares the upcoming NFT id next time the mintNFT function is called.
In the Solidity language, there are three methods for sending money from a smart contract:
The problem with send and transfer is that since the amount of gas they forward to the called address is limited, the contract could run out of gas, making the transaction fail. Thus the current recommended way to forward funds in solidity is to use the call method. For that reason, the call method is used at the end of mintNFT to forward all the funds submitted into the function to the defined charity address.
Okay, one extra step completed — we created our smart contract in solidity! Despite that, some things might still not be super clear.
- Where is the URI with my NFT metadata?
- Where does the charity address comes from?
- How do I mint an NFT with my contract and visualize it on a marketplace like Open Sea
Those are all excellent questions that will become clearer in the deploying and testing sections. Keep reading.
Now to deploy our contract. We need to make some decisions such as the minimum acceptable donation and how many tokens the contract will be allowed to mint. With those numbers in mind, we will define the value on .env for the application.
The third decision might be a little more complicated: picking a charity to give donations. On Blobmixer, for example, we donate to the Ukraine Emergency Response Fund.
One simple way to find institutions that accept crypto is by using The Giving Block. The Giving Block is a crypto donation solution, which provides an ecosystem for nonprofits and charities to fundraise ether and other cryptocurrencies. Via their website, you can choose a charity to help and get an address to forward crypto through the smart contract. We will then copy the donation address and add it to the .env file. Important: keep in mind that the donations will only work when the contract is deployed in the mainnet, using real ether. However, in this tutorial, we are using the Goerli testnet.
Finally, our .env configuration looks good, and everything is in place to deploy our contract except for a deploy script. Let's now create a scripts folder and place the deploy.js file inside it. Below is what our deployment script will look like. Remember when we created the smart contract and how it would receive information like the charity address and mint donation on the constructor? The deploy script reads this information we saved on the environment variables and forwards it to the contract when created.
One last step before being able to deploy is to get some fake ether to pay for the gas fees on the Goerli test network related to the contract issuance. This step will also fund our interactions with the contract after deployment. To receive funds, you can go into a Goerli faucet and request funds to your wallet address. We recommend the Alchemy faucet or Goerli Faucet.
Wonderful! With the ether funds in hand, it's deploying time! On your terminal, run these two commands — one to compile and the other to deploy the smart contract.
If everything is working, the deploy script will log a link to Goerli Etherscan, where you can confirm the creation of your contract on the blockchain. Also, it should log the deployed contract address. Make sure to save this address, as it will be necessary for minting tokens on the next section.
Minting NFTs with your contract
At this point, our contract is ready to mint NFTs. This tutorial won't cover how to integrate the smart contract with a front-end application and use browser wallets like Metamask or Coinbase wallet, as it would get too long. However, what we can do to interact with the contract and mint tokens is to create another script to run from the terminal. So, inside the scripts folder, create a mint.js file.
This function takes care of minting NFTs from our contract and sending the minimum donation value, as it is a requirement we specified on the smart contract earlier. Although, before running the script, there are some new environment variables to configure.
First, we have to set the address of the smart contract we deployed so that the scripts know which contract to interact with. In case you lost the address returned by the deploy script, you can make a new deployment and get a new address to use here.
Then we also have to set who will be the owner of the minted NFT. To keep it simple, you can add your wallet public address so that the token will belong to you. On Metamask, for example, you can get your public address by opening the extension and clicking on the address located at the top center.
Finally, for the last environment variable you will set in this tutorial, you need a token URI, which points to a JSON object representing the metadata of your NFT. Ideally, in the Web3 world, this URI should point to IPFS, a file-sharing peer-to-peer network for storing and sharing data in a distributed and decentralized manner.
You can learn more about the NFT metadata standard on Ethereum or OpenSea docs. However, so you don't have to worry too much about that, we prepared an IPFS metadata URI that you can use for this tutorial. Feel free to play around with that later.
Fantastic, we're ready to mint NFTs with our implemented contract by running our mint script with the following command:
Like the deployment, the mint script will return a link to Goerli Etherscan where you can verify that your NFT was minted. You could also visualize the created token on Open sea, as it should be in your profile shortly after the script run.
That's all! Let us know if you enjoyed this post and whether you wish to learn more about the Web3 world.