How to write a smart contract in solidity, deploy, and mint a simple NFT on the Polygon blockchain
A simple step-by-step guide on writing solidity smart contract for NFT and deploying it to polygon chain
Introduction
Blockchain is one of the most interesting and exciting technology in recent times. You can see people making millions of dollars by selling NFTs. Take for example an Indonesian guy Sultan Gustaf Al Ghozali, 22, who studies computer science at a college in Indonesia, he took a photo of himself sitting in front of his computer every day for five years.
Started selling them as NFT for $3 each but later it became famous and was sold out, as of today total volume traded is Ether 396 which is ~ $1.1 M.
ghozali-everyday Opensea account
With the rising interest in NFTs, developers are also slowly rising to the top. Ethereum is the biggest smart-contract blockchain and has a huge NFT community but the problem is its gas fees.
At the time of writing this article, a simple NFT smart contract would cost around $1000+ to deploy. So we would be using Polygon’s blockchain to deploy and mint. It is an EVM-compatible blockchain with many projects adopting it as a secondary option including OpenSea the dominating NFT marketplace. Gas fees in Polygon are relatively low compared to Ethereum and have the same functionalities.
⚙️ Project Setup
We will be using a tool called Hardhat, It is an Ethereum development environment, fortunately, it also works with Polygon and a few other chains. Hardhat will let us quickly compile smart contracts and test them locally. Assuming you already have node/npm in your system.
Next, let's head to the terminal. Go ahead and cd
to the directory you want to work in. then install hardhat following the below commands,
mkdir polygon-nft
cd polygon-nft
npm init -y
npm install --save-dev hardhat
After you execute the last command and install hardhat, You may see a message about vulnerabilities. This is a common warning when you install anything using NPM. You can google about it if you want to know more!
Now execute the following command which is basically initiating hardhat which will give us an option to start with basic/simple project setup. After you execute the below command it gives you a few options, for now, we will go ahead with default options, so just keep pressing enter.
npx hardhat
The sample project will ask you to install a few other dependencies which are helpful like hardhat-waffle
and hardhat-ethers
.
I would suggest going ahead and installing these other dependencies just in case it didn't do it automatically.
npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
We'll also want to install "OpenZeppelin" which is another library that's used a lot to develop secure smart contracts
npm install @openzeppelin/contracts
You should now have a directory filled with a few files and folders like contracts, scripts, and test.
So just to verify go ahead and run the following command
npx hardhat run scripts/sample-script.js
which should give us something like
💥 If you see this it means your local environment is set up and you also ran/deployed a smart contract to a local blockchain.
What happened here is that
- Hardhat compiled your smart contract from solidity to bytecode
- Hardhat created a "local blockchain" on your computer
- Hardhat then "deployed" your compiled contract to your local blockchain
✍Writing actual Smart Contract
Now there are multiple files and folders in your polygon-nft
folder.
Go ahead and delete the file sample-test.js under the test folder. Also, delete sample-script.js under scripts. Then, delete Greeter.sol under contracts. Don't delete the actual folders!
Now you can try going to docs.openzeppelin.com/contracts/4.x/wizard and get code is generated by their useful tool depending on selected features for making a simple contract.
OR
Let's write it!!
Create a file named polygonNFT.sol under the contracts directory and copy-paste the following code in that file and save it. I will explain the code below
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.1;
import "hardhat/console.sol";
contract polygonNFT {
constructor() {
console.log("This is my first ever Polygon NFT contract. I am Excited!");
}
}
let's go line by line :
// SPDX-License-Identifier: UNLICENSED
This is about the license of using your smart contract, you can fine more about them here.
pragma solidity ^0.8.1;
This is the version of the Solidity compiler we want our contract to use. When you say 0.8.1, it means that any Solidity compiler with version 0.8.1 or higher but less than 0.9.0 can be used to run our smart contract.
Note, be sure your compiler is set accordingly (0.8.1) in hardhat.config.js.
import "hardhat/console.sol";
Importing Hardhat provided a solidity file that will allow us to do some console logs in our contract which would be helpful while developing and testing.
contract polygonNFT {
constructor() {
console.log("This is my first ever Polygon NFT contract. I am Excited!");
}
}
It is basically like a class on other object-oriented programming languages. Once we initialize this contract for the first time, that constructor will run and print out that line to the console.
🏃 Let's run our smart contract
Go ahead into the scripts folder and create a file named run.js
. And copy-paste the following code in run.js
:
const main = async () => {
const polygonContractFactory = await hre.ethers.getContractFactory('polygonNFT');
const nftContract = await polygonContractFactory.deploy();
await nftContract.deployed();
console.log("Contract deployed to:", nftContract.address);
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};
runMain();
The above basically doing the following things:
- compile our smart contract
- deploy it to our local blockchain
let's go line by line again:
const polygonContractFactory = await hre.ethers.getContractFactory("polygonNFT");
This will compile our contract and generate a few necessary files in the artifacts
folder which are needed work with our contract.
const nftContract = await polygonContractFactory.deploy();
The above line will make Hardhat create a local Ethereum network for us, but just for this contract. Then, after the script completes it'll destroy that local network. So, every time you run the contract, it'll be a fresh blockchain.
await nftContract.deployed();
The above line will make sure to wait until our contract is officially mined and deployed to our local blockchain. Yeah, hardhat will actually create fake "miners" on your machine to try its best to imitate the actual blockchain.
Our constructor will also run when the smart contract is fully deployed!
console.log("Contract deployed to:", nftContract.address);
You know the console.log line, it will print the nftContract.address
which is the address of our smart contract on that blockchain onto the terminal.
The runMain() function is actually the starting function that will be executed once we run below. Function content is self-explanatory :)
Now let's go ahead and run the actual smart contract we created. Go back to your terminal where you executed "npx hardhat run scripts/sample-script.js" previously
npx hardhat run scripts/run.js
Once you run that command, on your terminal you would see "This is my first ever Polygon NFT contract. I am Excited!" the console.log text in your constructor then "Contract deployed to: .........." which displays your contract address.
It should look something like this:
👷 Now let's write minting functionality
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.1;
// We import some OpenZeppelin Contracts which are helpful.
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "hardhat/console.sol";
// We inherit the contract we imported. Which means we'll have access to the inherited contract's methods.
contract polygonNFT is ERC721URIStorage {
// provided by OpenZeppelin to help us keep track of tokenIds.
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
// We need to pass the name of our NFT token and its symbol.
constructor() ERC721 ("My First Polygon NFT Project", "0xViking") {
console.log("This is my first ever Polygon NFT contract. I am Excited!");
}
// A function our user will hit to get their NFT.
function mintMyPolygonNFT() public {
// Get the current tokenId, this starts at 0.
uint256 newItemId = _tokenIds.current();
//Main minting of the NFT to the caller using msg.sender.
_safeMint(msg.sender, newItemId);
// Set the NFTs data.
_setTokenURI(newItemId, "ThisNeedsToBeChanges");
// Increment the counter for when the next NFT is minted.
_tokenIds.increment();
}
}
I tried to explain each line using comments. If you still have any doubt please comment below, I am more than happy to explain you :)
Now let's update the TokenURI
which is the actual NFT data. It usually links to a JSON file called the metadata
and that looks something like this:
{
"name": "This is Viking",
"description": "Let's conquer the web3",
"image": "https://i.imgur.com/ZIbmiR6.png"
}
You can customize this, but, almost every NFT has a name, description, and a link to something like a video, image, etc.
Be careful with the structure of your metadata, if your structure does not match the OpenSea Requirements your NFT will appear broken on the website. This is all part of the ERC721 standards and it allows people to build websites on top of NFT data.
The image link mentioned above is an Imgur link which is a centralized image hosting service, I have created an account there and uploaded my image there, and copied the link from it.
Please check your copied Imgur link should be a Direct Imgur link that looks like - i.imgur.com/abc123.png NOT imgur.com/gallery/abc123. The easiest way to tell is to check if the URL ends in an image extension like .png or .jpg. You can right-click the Imgur image and "copy image address". This will give you the correct URL.
We can copy the This is Viking
JSON metadata above and paste it into this jsonkeeper site.
This website is just an easy place for people to host JSON data and we'll be using it to keep our NFT data for now. Once you click "Save" you'll get a link to the JSON file. (Something like jsonkeeper.com/b/56BW). You can test your link by opening a new tab and be sure it all looks good!
Now, let's go head to our smart contract and change that one line.
Following line of code
_setTokenURI(newItemId, "ThisNeedsToBeChanges")
to the below code where we are actually setting the URI as the link to our JSON file.
// mine looks like _setTokenURI(newItemId, "https://jsonkeeper.com/b/56BW");
_setTokenURI(newItemId, "INSERT_YOUR_JSON_URL_HERE");
Under that line, we will add a console.log
just to help us see when the NFT is minted and to whom!
console.log("An NFT with ID %s has been minted to %s", newItemId, msg.sender);
🎉 Let's Mint an NFT on our local chain
What we need to do is to change our run.js
file to call our mintMyPolygonNFT() function. add the following code below our console.log
line in the main
function
// Call the function.
let txn = await nftContract.mintMyPolygonNFT()
// Wait for it to be mined.
await txn.wait()
// Mint another NFT for fun.
txn = await nftContract.mintMyPolygonNFT()
// Wait for it to be mined.
await txn.wait()
that's it, let's run using the following command:
npx hardhat run scripts/run.js
It should mint 2 NFTs on your local blockchain. Here is how mine looks like
💻 Let's deploy to Polygon Mumbai testnet
When we deploy our contract, we need to tell all those miners, "hey, this is a new smart contract, please add my smart contract to the blockchain and then tell everyone else about it as well".
We will use Alchemy to do this.
Alchemy essentially helps us broadcast our contract creation transaction so that it can be picked up by miners as quickly as possible. Once the transaction is mined, it is then broadcasted to the blockchain as a legit transaction. From there, everyone updates their copy of the blockchain.
Go ahead and create an account at Alchemy
Then check out this video created by Farza below on how to get your Testnet API-key, in the video, he showed Rinkeby testent, instead, you can select Polygon and the Polygon-Mumbai in the dropdown.
BTW you also need a wallet to do so, I will suggest to go with Metamask and creating a wallet. Now add Polygon Mumbai to your metamask wallet. Check out this short video below. which also shows adding fake matic tokens which are required to deploy our smart contract
Once everything is ready let's set our deploy.js
file. Go ahead and create a file named deploy.js
under the scripts folder. Copy-paste all of run.js
into deploy.js
.
Then update your hardhat.config.js
require("@nomiclabs/hardhat-waffle");
// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.1",
networks: {
mumbai: {
url: 'YOUR_ALCHEMY_API_URL-COPY_IT-FROM-DASHBOARD',
accounts: ['YOUR-MUMBAI-ACCOUNT-PRIVATE-KEY'],
},
},
};
DON'T COMMIT THIS FILE TO GITHUB. IT HAS YOUR PRIVATE KEY. YOU WILL GET HACKED + ROBBED. THIS PRIVATE KEY IS THE SAME AS YOUR MAINNET PRIVATE KEY. In the meantime - open your .gitignore file and add a line for hardhat.config.js.
Once you've got your config setup we're set to deploy with the deploy script we wrote earlier.
execute the following command in the terminal:
npx hardhat run scripts/deploy.js --network mumbai
Executing the above command gives you similar output:
Copy that contract address and go to Mumbai Testnet explorer and paste the address there and verify whether it was successful or not.
Okay, BTW you have minted two NFTs while deploying your smart contract let's go to opensea testnetsand see in your account whether those minted NFTs are there are not, Opensea might take a few minutes to sync.
This is how my collection looks:
That brings an end to this article.
So while deploying to the Polygon mainnet, in the
deploy.js
file remove thatmintMyPolygonNFT()
triggering statements so that your collection starts from starting in the mainnet.
txn = await nftContract.mintMyPolygonNFT() // Wait for it to be mined. await txn.wait()
BONUS: If you want to deploy it to mainnet, add configurations to
networks
key in hardhat.config.js. Something like;
networks: { mumbai: { url: 'YOUR_ALCHEMY_API_URL-COPY_IT-FROM-DASHBOARD', accounts: ['YOUR-MUMBAI-ACCOUNT-PRIVATE-KEY'], }, rinkeby: { url: 'YOUR_ALCHEMY_RINKEBY-API_URL-COPY_IT-FROM-DASHBOARD', accounts: ['YOUR-ACCOUNT-PRIVATE-KEY'], }, polygon: { url: 'YOUR_ALCHEMY_POLYGON-MAINNET-API_URL-COPY_IT-FROM-DASHBOARD', accounts: ['YOUR-ACCOUNT-PRIVATE-KEY'], }, },
I hope this was helpful and all the best for your journey in web3.
Let me know how this went on Twitter @0xViking. My DMs are open feel free to text me :)
WAGMI🚀