<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Rounak Banik</title>
    <description>The latest articles on Forem by Rounak Banik (@rounakbanik).</description>
    <link>https://forem.com/rounakbanik</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F691392%2Fde4376bc-e714-4d3c-a971-febf4ca50171.jpeg</url>
      <title>Forem: Rounak Banik</title>
      <link>https://forem.com/rounakbanik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rounakbanik"/>
    <language>en</language>
    <item>
      <title>Tutorial: Digital Signatures &amp; NFT Whitelists</title>
      <dc:creator>Rounak Banik</dc:creator>
      <pubDate>Fri, 11 Feb 2022 11:17:12 +0000</pubDate>
      <link>https://forem.com/rounakbanik/tutorial-digital-signatures-nft-allowlists-eeb</link>
      <guid>https://forem.com/rounakbanik/tutorial-digital-signatures-nft-allowlists-eeb</guid>
      <description>&lt;h3&gt;
  
  
  A Note on Terminology
&lt;/h3&gt;

&lt;p&gt;A previous version of this article used the term whitelist instead of allowlist. Although they refer to the same thing, we have decided to update this article to use the latter in the interest of being more inclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Creating NFT allowlists has been, by far, the most requested topic in our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;developer community&lt;/a&gt;. Therefore, in this article, we will cover the following topics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implementing allowlists on-chain and their cost implications&lt;/li&gt;
&lt;li&gt;Implementing allowlists off-chain using digital signatures&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the end of this tutorial, you should have an extremely good idea as to how to go about implementing allowlists in a secure and cost-efficient way, and in the process preventing unpleasant scenarios like gas wars.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;This article assumes that you have an intermediate knowledge of Solidity, Hardhat, and OpenZeppelin Contracts. If some of these terms sound alien to you, we strongly suggest you start &lt;a href="https://medium.com/scrappy-squirrels/tutorial-writing-an-nft-collectible-smart-contract-9c7e235e96da" rel="noopener noreferrer"&gt;here&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;We also wanted to point out that not every NFT project requires an allowlist. We recommend you think about implementing one only if you have an active and vibrant community, and your projected demand for your NFTs far exceeds supply. For 99.9% of the projects out there, this simply isn’t true. Therefore, trying to implement allowlists will not only result in wastage of resources that could be spent elsewhere but could also backfire by repelling the few backers that your project has should you not be able to fill all slots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Allowlists On-Chain
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe53oqle33qrtwcdl25bz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe53oqle33qrtwcdl25bz.png" alt="On chain"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On-chain allowlists are secure and fairly easy to implement. We will be using the &lt;a href="https://github.com/rounakbanik/nft-collectible-contract/blob/master/contracts/NFTCollectible.sol" rel="noopener noreferrer"&gt;NFT Collectible Contract&lt;/a&gt; from a &lt;a href="https://medium.com/scrappy-squirrels/tutorial-writing-an-nft-collectible-smart-contract-9c7e235e96da" rel="noopener noreferrer"&gt;previous tutorial&lt;/a&gt; as our base.&lt;/p&gt;

&lt;p&gt;These are the following additions that we need to make to our contract.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A global mapping variable &lt;code&gt;isAllowlistAddress&lt;/code&gt; that keeps track of all the addresses that have been allowlisted.&lt;/li&gt;
&lt;li&gt;A function &lt;code&gt;allowlistAddress&lt;/code&gt; that is callable only by the contract’s owner and that can add one or more addresses to &lt;code&gt;isAllowlistAddress&lt;/code&gt; mapping.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;preSale&lt;/code&gt; function that is very similar to the &lt;code&gt;mintNfts&lt;/code&gt; function except that it only allows allowlisted addresses to mint at a pre-sale price.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can define the mapping variable as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(address =&amp;gt; bool) public isAllowlistAddress;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s write a allowlisting function that allows the contract’s owner to add a list of addresses to the aforementioned mapping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Allowlist addresses
function allowlistAddresses(address[] calldata wAddresses) public onlyOwner {
    for (uint i = 0; i &amp;lt; wAddresses.length; i++) {
        isAllowlistAddress[wAddresses[i]] = true;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let’s write a &lt;code&gt;preSale&lt;/code&gt; function that allows only allowlisted addresses to mint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Presale mints
function preSale(uint _count) public payable {
    uint totalMinted = _tokenIds.current();
    uint preSalePrice = 0.005 ether;
    uint preSaleMaxMint = 2;

    require(totalMinted.add(_count) &amp;lt;= MAX_SUPPLY, 
            "Not enough NFTs left!");
    require(_count &amp;gt;0 &amp;amp;&amp;amp; _count &amp;lt;= preSaleMaxMint, 
            "Cannot mint specified number of NFTs.");
    require(msg.value &amp;gt;= preSalePrice.mul(_count), 
            "Not enough ether to purchase NFTs.");
    require(isAllowlistAddress[msg.sender], 
            "Address is not allowlisted");
    for (uint i = 0; i &amp;lt; _count; i++) {
        _mintSingleNFT();
    }

    isAllowlistAddress[msg.sender] = false;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that this function is very similar to the &lt;code&gt;mintNfts&lt;/code&gt; function that we already have in our contract. We use a different price and mint limit for presale. We also place an additional check to ensure only allowlisted addresses can mint. Finally, we remove the address from the &lt;code&gt;allowlist&lt;/code&gt; to ensure that the wallet does not mint more than once.&lt;/p&gt;

&lt;p&gt;Your final contract should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract NFTCollectible is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIds;

    mapping(address =&amp;gt; bool) public isAllowlistAddress;

    uint public constant MAX_SUPPLY = 100;
    uint public constant PRICE = 0.01 ether;
    uint public constant MAX_PER_MINT = 5;

    string public baseTokenURI;

    constructor(string memory baseURI) ERC721("NFT Collectible", "NFTC") {
        setBaseURI(baseURI);
    }

    // Allowlist addresses
    function allowlistAddresses(address[] calldata wAddresses) public onlyOwner {
        for (uint i = 0; i &amp;lt; wAddresses.length; i++) {
            isAllowlistAddress[wAddresses[i]] = true;
        }
    }

    function reserveNFTs() public onlyOwner {
        uint totalMinted = _tokenIds.current();

        require(totalMinted.add(10) &amp;lt; MAX_SUPPLY, "Not enough NFTs left to reserve");

        for (uint i = 0; i &amp;lt; 10; i++) {
            _mintSingleNFT();
        }
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    function setBaseURI(string memory _baseTokenURI) public onlyOwner {
        baseTokenURI = _baseTokenURI;
    }

    // Presale mints
    function preSale(uint _count) public payable {
        uint totalMinted = _tokenIds.current();
        uint preSalePrice = 0.005 ether;
        uint preSaleMaxMint = 2;

        require(totalMinted.add(_count) &amp;lt;= MAX_SUPPLY, "Not enough NFTs left!");
        require(_count &amp;gt;0 &amp;amp;&amp;amp; _count &amp;lt;= preSaleMaxMint, "Cannot mint specified number of NFTs.");
        require(msg.value &amp;gt;= preSalePrice.mul(_count), "Not enough ether to purchase NFTs.");
        require(isAllowlistAddress[msg.sender], "Address is not allowlisted");

        for (uint i = 0; i &amp;lt; _count; i++) {
            _mintSingleNFT();
        }

        isAllowlistAddress[msg.sender] = false;        
    }

    function mintNFTs(uint _count) public payable {
        uint totalMinted = _tokenIds.current();

        require(totalMinted.add(_count) &amp;lt;= MAX_SUPPLY, "Not enough NFTs left!");
        require(_count &amp;gt;0 &amp;amp;&amp;amp; _count &amp;lt;= MAX_PER_MINT, "Cannot mint specified number of NFTs.");
        require(msg.value &amp;gt;= PRICE.mul(_count), "Not enough ether to purchase NFTs.");

        for (uint i = 0; i &amp;lt; _count; i++) {
            _mintSingleNFT();
        }
    }

    function _mintSingleNFT() private {
        uint newTokenID = _tokenIds.current();
        _safeMint(msg.sender, newTokenID);
        _tokenIds.increment();
    }

    function tokensOfOwner(address _owner) external view returns (uint[] memory) {

        uint tokenCount = balanceOf(_owner);
        uint[] memory tokensId = new uint256[](tokenCount);

        for (uint i = 0; i &amp;lt; tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }

    function withdraw() public payable onlyOwner {
        uint balance = address(this).balance;
        require(balance &amp;gt; 0, "No ether left to withdraw");

        (bool success, ) = (msg.sender).call{value: balance}("");
        require(success, "Transfer failed.");
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The problem with on-chain allowlists
&lt;/h3&gt;

&lt;p&gt;The implementation we’ve used so far is secure and does exactly what it needs to do.&lt;/p&gt;

&lt;p&gt;However, this implementation is wildly inefficient. The root cause of this is the &lt;code&gt;allowlistAddresses&lt;/code&gt; function that can only be called by the contract’s owner. By its very design, this contract expects the owner to populate the mapping with all possible allowlisted addresses.&lt;/p&gt;

&lt;p&gt;Depending on the size of your allowlist, this process could prove to be computationally intensive and extremely expensive. You may be able to get away with this if you’re operating on a sidechain like Polygon or Binance Smart chain but on Ethereum, even modest-sized allowlists will set you back by several thousands of dollars.&lt;/p&gt;

&lt;p&gt;Fortunately, it is possible to implement allowlists securely off-chain without having to deal with extortionate gas fees. We can achieve this using digital signatures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Digital Signatures
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajtk3a9g03v8bqjtyvtz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajtk3a9g03v8bqjtyvtz.png" alt="Digital Signatures by Yos Riady"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Digital Signatures and public key cryptography are central to virtually everything that happens on a blockchains like Bitcoin and Ethereum. We won’t be covering how signatures work in this article (we have a series on cryptography coming very soon!). Instead, we will just acquire a black-box understanding of how it works.&lt;/p&gt;

&lt;p&gt;As most of you already know, we interact with Ethereum using a wallet which is associated with two keys: a public key (or wallet address) and a private key.&lt;/p&gt;

&lt;p&gt;Using cryptography, it is possible for a person to prove that s/he holds the private key of a particular wallet address without revealing the key itself. It should be obvious why this is very important. If we couldn’t initiate transactions using our private key without revealing said key, the system would break down completely as there would be no way to authenticate yourself securely and trustlessly.&lt;/p&gt;

&lt;p&gt;Digital cryptographic signatures allow us to accomplish the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The signer is able to sign a message using a private key and broadcast the signed message.&lt;/li&gt;
&lt;li&gt;It is impossible to recover the private key by simply looking at the message and/or the public key.&lt;/li&gt;
&lt;li&gt;It is however possible to verify that the signer holds the correct private key using the public key (or wallet address).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If this sounds a little magical, it’s because it is. The feats possible by public key cryptography are nothing short of miraculous. However, as stated earlier, we will cover this in detail in a future series.&lt;/p&gt;

&lt;p&gt;With this basic understanding of how digital signatures work, we can now propose the following system of implementing allowlists.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a centralized server and database that holds all the addresses that are allowlisted.&lt;/li&gt;
&lt;li&gt;When a wallet tries to initiate a mint on your website, send the wallet address to your server.&lt;/li&gt;
&lt;li&gt;The server checks if the address has been allowlisted and if it has, it signs the wallet address with a private key that is known only to the project’s creator.&lt;/li&gt;
&lt;li&gt;The server returns the signed message to the frontend client (or website) and this in turn, is sent to the smart contract.&lt;/li&gt;
&lt;li&gt;The contract’s mint function verifies that the message sent was indeed signed by the wallet controlled by the owner. If the verification succeeds, minting is allowed.&lt;/li&gt;
&lt;li&gt;The signed message is stored in a mapping to prevent it from being used more than once or by multiple wallets.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(We will not be implementing a real server or using a real database in this article. If this is something that you’ve never done before, taking a look at Express and Mongo tutorials would be a good place to start.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Signing Messages
&lt;/h3&gt;

&lt;p&gt;In your Hardhat project, create a new file called &lt;code&gt;allowlist.js&lt;/code&gt; in the scripts folder.&lt;/p&gt;

&lt;p&gt;We will be using the ethers library to sign our messages. Let’s allowlist Hardhat’s default accounts 1 to 5 for this example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const ethers = require('ethers');
const main = async () =&amp;gt; {
    const allowlistedAddresses = [
        '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
        '0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc',
        '0x90f79bf6eb2c4f870365e785982e1f101e93b906',
        '0x15d34aaf54267db7d7c367839aaf71a00a2c6a65',
        '0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc',
    ];
}

const runMain = async () =&amp;gt; {
    try {
        await main(); 
        process.exit(0);
    }
    catch (error) {
        console.log(error);
        process.exit(1);
    }
};

runMain();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the only addresses that will be allowed to mint in the presale. Let’s use Account 0 as the owner’s wallet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const owner = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266';

const privateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';

const signer = new ethers.Wallet(privateKey);
console.log(signer.address)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this script by running node &lt;code&gt;scripts/allowlist.js&lt;/code&gt; in the terminal. If all goes well, the wallet address printed to the console should be the same as that assigned to &lt;code&gt;owner&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s now sign a simple message and see how that works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let message = 'Hello World!'

let signature = await signer.signMessage(message)
console.log(signature);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this script will output a signed message &lt;code&gt;0xdd4...61c&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In our case, we will not be signing a message written in English. Instead, we will be signing the hash of a allowlisted wallet address (which is nothing but a hash itself). &lt;a href="https://docs.ethers.io/v5/getting-started/#getting-started--signing" rel="noopener noreferrer"&gt;Ethers documentation&lt;/a&gt; recommends that we convert binary hash data into an array before signing it.&lt;/p&gt;

&lt;p&gt;Let’s sign the hash of the first allowlisted address from above. Replace the code snippet above with the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Get first allowlisted address
let message = allowlistedAddresses[0];

// Compute hash of the address
let messageHash = ethers.utils.id(message);
console.log("Message Hash: ", messageHash);

// Sign the hashed address
let messageBytes = ethers.utils.arrayify(messageHash);
let signature = await signer.signMessage(messageBytes);
console.log("Signature: ", signature);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this snippet will output &lt;code&gt;0xee...c1b&lt;/code&gt; as signature.&lt;/p&gt;

&lt;p&gt;Therefore, when a wallet issues a request to the server, you server will need to do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if the wallet is a part of allowlistedAddresses&lt;/li&gt;
&lt;li&gt;If yes, sign the hashed wallet address with the supplied private key and return the signature and the hashed wallet address.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Verifying Signatures
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjn6hmgd0qt6b9lrr0l3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjn6hmgd0qt6b9lrr0l3k.png" alt="Verify signatures"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Verifying signatures is extremely simple using OpenZeppelin’s ECDSA library.&lt;/p&gt;

&lt;p&gt;Let’s start with our base NFTCollectible.sol &lt;a href="https://github.com/rounakbanik/nft-collectible-contract/blob/master/contracts/NFTCollectible.sol" rel="noopener noreferrer"&gt;contract&lt;/a&gt; again. As a first step, we will write a recoverSigner function that will take the hashed allowlisted wallet address and the signature as arguments and output the address of the signer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function recoverSigner(bytes32 hash, bytes memory signature) public pure returns (address) {
    bytes32 messageDigest = keccak256(
        abi.encodePacked(
            "\x19Ethereum Signed Message:\n32", 
            hash
        )
    );
    return ECDSA.recover(messageDigest, signature);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s open up a new Terminal and spin up a local instance of Ethereum using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s write code in &lt;code&gt;allowlist.js&lt;/code&gt; that compiles and deploys the contract to our local blockchain and calls the &lt;code&gt;recoverSigner&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const nftContractFactory = await hre.ethers.getContractFactory('NFTCollectible');
const nftContract = await nftContractFactory.deploy(
    "ipfs://your-cide-code"
);

await nftContract.deployed();

console.log("Contract deployed by: ", signer.address);
recover = await nftContract.recoverSigner(messageHash, signature);
console.log("Message was signed by: ", recover.toString());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s run this script using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat run scripts/allowlist.js --network localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all goes well, you should see your console telling you that the message was signed by the same wallet that deployed the contract.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmvgujo8imo1acp360ss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmvgujo8imo1acp360ss.png" alt="Terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Amazing work! We now have all the pieces we need to implement our &lt;code&gt;preSale&lt;/code&gt; function and by extension, allowlisting.&lt;/p&gt;

&lt;p&gt;Let’s define a mapping that will track if a particular signature has already been used to mint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(bytes =&amp;gt; bool) public signatureUsed;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let’s write our &lt;code&gt;preSale&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function preSale(uint _count, bytes32 hash, bytes memory signature) public payable {
    uint totalMinted = _tokenIds.current();
    uint preSalePrice = 0.005 ether;
    uint preSaleMaxMint = 2;

    require(totalMinted.add(_count) &amp;lt;= MAX_SUPPLY, 
            "Not enough NFTs left!");
    require(_count &amp;gt;0 &amp;amp;&amp;amp; _count &amp;lt;= preSaleMaxMint, 
            "Cannot mint specified number of NFTs.");
    require(msg.value &amp;gt;= preSalePrice.mul(_count), 
           "Not enough ether to purchase NFTs.");
    require(recoverSigner(hash, signature) == owner(), 
            "Address is not allowlisted");
    require(!signatureUsed[signature], 
            "Signature has already been used.");

    for (uint i = 0; i &amp;lt; _count; i++) {
        _mintSingleNFT();
    }
    signatureUsed[signature] = true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations! You have successfully implemented an allowlisting mechanism that works off-chain but is just as secure as its on-chain counterpart.&lt;/p&gt;

&lt;p&gt;Here is the final contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

contract NFTCollectible is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIds;

    mapping(bytes =&amp;gt; bool) public signatureUsed;

    uint public constant MAX_SUPPLY = 100;
    uint public constant PRICE = 0.01 ether;
    uint public constant MAX_PER_MINT = 5;

    string public baseTokenURI;

    constructor(string memory baseURI) ERC721("NFT Collectible", "NFTC") {
        setBaseURI(baseURI);
    }

    function reserveNFTs() public onlyOwner {
        uint totalMinted = _tokenIds.current();

        require(totalMinted.add(10) &amp;lt; MAX_SUPPLY, "Not enough NFTs left to reserve");

        for (uint i = 0; i &amp;lt; 10; i++) {
            _mintSingleNFT();
        }
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    function setBaseURI(string memory _baseTokenURI) public onlyOwner {
        baseTokenURI = _baseTokenURI;
    }

    function recoverSigner(bytes32 hash, bytes memory signature) public pure returns (address) {
        bytes32 messageDigest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
        return ECDSA.recover(messageDigest, signature);
    }

    function mintNFTs(uint _count) public payable {
        uint totalMinted = _tokenIds.current();

        require(totalMinted.add(_count) &amp;lt;= MAX_SUPPLY, "Not enough NFTs left!");
        require(_count &amp;gt;0 &amp;amp;&amp;amp; _count &amp;lt;= MAX_PER_MINT, "Cannot mint specified number of NFTs.");
        require(msg.value &amp;gt;= PRICE.mul(_count), "Not enough ether to purchase NFTs.");

        for (uint i = 0; i &amp;lt; _count; i++) {
            _mintSingleNFT();
        }
    }

    function preSale(uint _count, bytes32 hash, bytes memory signature) public payable {
        uint totalMinted = _tokenIds.current();
        uint preSalePrice = 0.005 ether;
        uint preSaleMaxMint = 2;

        require(totalMinted.add(_count) &amp;lt;= MAX_SUPPLY, "Not enough NFTs left!");
        require(_count &amp;gt;0 &amp;amp;&amp;amp; _count &amp;lt;= preSaleMaxMint, "Cannot mint specified number of NFTs.");
        require(msg.value &amp;gt;= preSalePrice.mul(_count), "Not enough ether to purchase NFTs.");
        require(recoverSigner(hash, signature) == owner(), "Address is not allowlisted");
        require(!signatureUsed[signature], "Signature has already been used.");

        for (uint i = 0; i &amp;lt; _count; i++) {
            _mintSingleNFT();
        }

        signatureUsed[signature] = true;
    }

    function _mintSingleNFT() private {
        uint newTokenID = _tokenIds.current();
        _safeMint(msg.sender, newTokenID);
        _tokenIds.increment();
    }

    function tokensOfOwner(address _owner) external view returns (uint[] memory) {

        uint tokenCount = balanceOf(_owner);
        uint[] memory tokensId = new uint256[](tokenCount);

        for (uint i = 0; i &amp;lt; tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }

    function withdraw() public payable onlyOwner {
        uint balance = address(this).balance;
        require(balance &amp;gt; 0, "No ether left to withdraw");

        (bool success, ) = (msg.sender).call{value: balance}("");
        require(success, "Transfer failed.");
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To summarize once again, this is how pre-sale minting would work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A buyer visits your website, connects wallet, specifies the number of NFTs s/he wants to mint, and clicks on the Mint NFT button.&lt;/li&gt;
&lt;li&gt;This initiates a request to your centralized server which checks if the address has been allowlisted. If yes, it sends back the hashed wallet address and the signature. If no, it returns an error.&lt;/li&gt;
&lt;li&gt;Your website takes the aforementioned values and initiates a transaction to your smart contract on behalf of the user.&lt;/li&gt;
&lt;li&gt;In the smart contract, the &lt;code&gt;preSale&lt;/code&gt; function verifies that the signature was indeed signed by you and allows minting to take place.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is by far the most technical article we’ve published so far. If you’ve understood major portions of what’s going on, then congratulations! You are well on your way to becoming an expert Solidity developer.&lt;/p&gt;

&lt;p&gt;If you find yourself struggling, don’t worry about it. It may be a little hard to digest this in one go. We would suggest you complement this article with alternate resources and tutorials on the topic.&lt;/p&gt;

&lt;p&gt;We should also mention that digital signatures aren’t the only way to achieve off-chain allowlists. It is possible to use Merkle trees to arrive at the same result. We will be releasing an article on that sometime in the future.&lt;/p&gt;

&lt;p&gt;If you have any questions or are stuck, reach out to us on our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you don’t have questions, come say hi to us on our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; anyway! Also, if you liked our content, we would be super grateful if you tweet about us, follow us(&lt;a href="https://twitter.com/ScrappyNFTs" rel="noopener noreferrer"&gt;@ScrappyNFTs&lt;/a&gt; and &lt;a href="https://twitter.com/Rounak_Banik" rel="noopener noreferrer"&gt;@Rounak_Banik&lt;/a&gt;), and invite your circle to our Discord. Thank you for your support!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;About Scrappy Squirrels&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.scrappysquirrels.co/" rel="noopener noreferrer"&gt;Scrappy Squirrels&lt;/a&gt; is a collection of 10,000+ randomly generated NFTs. Scrappy Squirrels are meant for buyers, creators, and developers who are completely new to the NFT ecosystem.&lt;/p&gt;

&lt;p&gt;The community is built around learning about the NFT revolution, exploring its current use cases, discovering new applications, and finding members to collaborate on exciting projects with.&lt;/p&gt;

&lt;p&gt;Join our community here: &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;https://discord.gg/8UqJXTX7Kd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>blockchain</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Deploying a Dapp to Mainnet</title>
      <dc:creator>Rounak Banik</dc:creator>
      <pubDate>Fri, 14 Jan 2022 16:16:48 +0000</pubDate>
      <link>https://forem.com/rounakbanik/deploying-a-dapp-to-mainnet-3cob</link>
      <guid>https://forem.com/rounakbanik/deploying-a-dapp-to-mainnet-3cob</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ry5q0kaegljb70hs745.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ry5q0kaegljb70hs745.png" alt="Banner Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;2021 saw a major boom in web3 education and developer content. There a plethora of excellent tutorials available online that teach you everything you need to get started in web3, from building simple &lt;a href="https://docs.alchemy.com/tutorials/hello-world-smart-contract" rel="noopener noreferrer"&gt;Hello World contracts&lt;/a&gt; to creating full-fledged &lt;a href="https://medium.com/@austin_48503/%EF%B8%8F-minimum-viable-exchange-d84f30bd0c90" rel="noopener noreferrer"&gt;decentralized exchanges&lt;/a&gt; and &lt;a href="https://dev.to/dabit3/building-scalable-full-stack-apps-on-ethereum-with-polygon-2cfb"&gt;NFT marketplaces&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, almost every tutorial (including the ones published by us) show you how to launch apps on testnets with fake money. There is extremely little coverage on how to launch on a mainnet, and the considerations and challenges involved with the process.&lt;/p&gt;

&lt;p&gt;This article, therefore, is on one of the most-requested topics on our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;: how to develop and deploy a smart contract in the real world with real money.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;We make the assumption that you are already familiar with developing smart contracts on EVM-compatible chains using tools like Solidity, Ethers, and Hardhat. If some of these terms sound alien to you, we strongly suggest going through &lt;a href="https://medium.com/scrappy-squirrels/tutorial-writing-an-nft-collectible-smart-contract-9c7e235e96da" rel="noopener noreferrer"&gt;this article&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Developing Contract on Testnet
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh96fbsrplnxf01eaxyno.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh96fbsrplnxf01eaxyno.png" alt="Mumbai"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first step remains the same as with every other tutorial. You develop and test your contract on a testnet of the blockchain (or sidechain) that you wish to launch on. For example, you would use Rinkeby or Ropsten if your network of choice was Ethereum and Mumbai if you preferred Polygon.&lt;/p&gt;

&lt;p&gt;Most testnets do a very good job of mimicking their corresponding mainnet and also provide free fake tokens to work with through faucets.&lt;/p&gt;

&lt;p&gt;You can reasonably expect your contract’s behaviour on a testnet to be almost identical to that on the mainnet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Auditing and Optimizing Contract
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fer6vhaovsgerzc9x8oqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fer6vhaovsgerzc9x8oqz.png" alt="Solidity Finance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deploying a contract on a testnet costs the same amount of gas as deploying to a mainnet (note that I’m referring to gas units and not gas price).&lt;/p&gt;

&lt;p&gt;Checking how much gas contract deployment consumes should give you a good early indication of how expensive deployment is going to be. In some cases, it may be possible to significantly reduce gas consumption (for example, using an ERC-1155 implementation in place of ERC-721 for an NFT dapp).&lt;/p&gt;

&lt;p&gt;Do take the time out to evaluate your choices and ensure that reduction of gas consumption does not come at the expense of security.&lt;/p&gt;

&lt;p&gt;Once you’re satisfied with the final version of the contract, you should get it audited.&lt;/p&gt;

&lt;p&gt;There are excellent services like &lt;a href="https://solidity.finance/" rel="noopener noreferrer"&gt;solidity.finance&lt;/a&gt; that will audit your contract for a fee. Do note that the fee may be steep for a lot of projects. But if your dapp is going to be handling assets of other people worth millions of dollars, then I believe that an audit is mandatory and definitely worth the price.&lt;/p&gt;

&lt;p&gt;On the other hand, if your project is of a significantly smaller scale (for example, a generative NFT project), then a professional audit may be overkill. In such cases, just ensure that the contract has been tested and walked through by at least two smart contract developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Estimating Cost of Deployment
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvoq9r3sa6npf8m8064d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvoq9r3sa6npf8m8064d.png" alt="Gas"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you’re satisfied with the way your dapp behaves and are convinced that there are no glaring security loopholes, you can proceed to computing the total cost of deployment.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, the amount of gas consumed across testnets and the mainnet is the same. To arrive at an estimate of deployment cost on a mainnet, all you need to do is multiply gas consumed with the gas price.&lt;/p&gt;

&lt;p&gt;Typically, deployments on Ethereum cost thousands of dollars whereas deployment to sidechains like Polygon and Binance can be done in under five dollars.&lt;/p&gt;

&lt;p&gt;We have a detailed tutorial on how to estimate costs and consider your chain options &lt;a href="https://medium.com/scrappy-squirrels/estimating-smart-contract-costs-f65acf818c26?source=collection_home---6------6-----------------------" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Acquiring tokens
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3k840dqny4njvg7x98a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3k840dqny4njvg7x98a.png" alt="Moonpay"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This step may seem way too obvious to warrant an entire section but we have seen a few of our community members trip up in this step, especially when working with sidechains.&lt;/p&gt;

&lt;p&gt;Remember that you need to acquire a particular token in the chain that you’re working with. This means you need ETH on the Ethereum Mainnet, MATIC on the Polygon network, and BNB on the Binance Smart Chain.&lt;/p&gt;

&lt;p&gt;The easiest way to acquire these tokens on their respective chains is by using a ramp service like &lt;a href="https://www.moonpay.com/" rel="noopener noreferrer"&gt;Moonpay&lt;/a&gt; that allows you to purchase crypto using just a credit card.&lt;/p&gt;

&lt;p&gt;However, these services don’t work in all countries (India, for example). In such cases, you will unfortunately have to deal with steps like buying on centralized exchanges, KYCs, and withdrawal to Metamask.&lt;/p&gt;

&lt;p&gt;During withdrawal, make sure that your tokens are being transferred to the correct network. By default, most exchanges will send your MATIC and BNB to the Ethereum network. &lt;strong&gt;They are useless there and bridging them to the correct network is complicated and expensive&lt;/strong&gt;. Make sure you only use exchanges that have a direct ramp to the network you want to use.&lt;/p&gt;

&lt;p&gt;We will be releasing an article on the options you have while purchasing MATIC, BNB, FTM, and other sidechain cryptocurrencies soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Configuring Hardhat and Alchemy
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqx1zd3kv7z38brvhsuft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqx1zd3kv7z38brvhsuft.png" alt="Hardhat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is now time for deployment!&lt;/p&gt;

&lt;p&gt;In order to deploy to a particular chain, we will need an RPC URL. We’ve already discussed how to acquire this using &lt;a href="https://alchemy.com/?a=d7d81950c0" rel="noopener noreferrer"&gt;Alchemy&lt;/a&gt; for the &lt;a href="https://medium.com/scrappy-squirrels/tutorial-writing-an-nft-collectible-smart-contract-9c7e235e96da" rel="noopener noreferrer"&gt;Rinkeby&lt;/a&gt; and &lt;a href="https://medium.com/scrappy-squirrels/tutorial-developing-for-polygon-and-sidechains-66bef9ec80ef" rel="noopener noreferrer"&gt;Polygon Mumbai&lt;/a&gt; testnets.&lt;/p&gt;

&lt;p&gt;For the corresponding mainnets, the process is identical: create an Alchemy app, set the network to the chain of your choice, and copy the HTTP RPC URL. Below is an app created for the Ethereum mainnet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkajlaa2xqzup34nor1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkajlaa2xqzup34nor1n.png" alt="Alchemy App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do note that Alchemy, at the time of writing, does not provide RPC URLs for every chain that you could potentially want to work with.&lt;/p&gt;

&lt;p&gt;For chains not supported by Alchemy (for example, Fantom Opera), you can use the public RPC URLs available. For instance, &lt;a href="https://rpc.ftm.tools/" rel="noopener noreferrer"&gt;https://rpc.ftm.tools/&lt;/a&gt; for Fantom.&lt;/p&gt;

&lt;p&gt;We now have everything to configure &lt;code&gt;hardhat.config.js&lt;/code&gt;. Add the mainnet network of your choice to &lt;code&gt;module.exports&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = {  
    solidity: "0.8.4",  
    networks: {    
        rinkeby: {      
            url: RINKEBY_RPC_URL,      
            accounts: [`0x${PRIVATE_KEY}`],   
        },
        mainnet: {      
            url: ETHEREUM_RPC_URL,      
            accounts: [`0x${PRIVATE_KEY}`],   
        },
        polygon: {      
            url: POLYGON_RPC_URL,      
            accounts: [`0x${PRIVATE_KEY}`],   
        },          
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As is good practice, we have defined our RPC URLs and our wallet’s private key in a &lt;code&gt;.env&lt;/code&gt; file that will not be committed to our git repository.&lt;/p&gt;

&lt;p&gt;Now, running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat run scripts/deploy.js --network mainnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will deploy your contract to the Ethereum mainnet. Similarly, running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat run scripts/deploy.js --network polygon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will deploy your contract to the Polygon mainnet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Gas Price
&lt;/h3&gt;

&lt;p&gt;Do note that if you deploy your contract using our &lt;code&gt;deploy.js&lt;/code&gt; script from the previous tutorials, ethers will automatically set a gas price and deploy using that price.&lt;/p&gt;

&lt;p&gt;In testnets and sidechains like Polygon and Binance, this may not really be an issue. However, lower gas fees could result in savings worth thousands of dollars on Ethereum. Which is why it is prudent to set a gas price yourself.&lt;/p&gt;

&lt;p&gt;This is very easy to with ethers. In &lt;code&gt;deploy.js&lt;/code&gt;, add an argument to the &lt;code&gt;deploy()&lt;/code&gt; method to set a gas price as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const factory = await hre.ethers.getContractFactory('MyContract');    
const contract = await factory.deploy(arg1, 
                                      arg2, 
                                      {gasPrice:50000000000});    
await contract.deployed();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can probably deduce, we have deployed this contract by setting a gas fee of 50 Gwei.&lt;/p&gt;

&lt;h3&gt;
  
  
  (Optional) Deploying using Metamask &amp;amp; Remix
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgi2ihgpzlfb1k1zn3880.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgi2ihgpzlfb1k1zn3880.png" alt="Remix"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you deploy using hardhat, it automatically drains your wallet of the funds that it needs to perform the request. In other words, there is no confirmation step in between. You issue a command to run the deployment script and boom! your funds are gone and your contract is deployed.&lt;/p&gt;

&lt;p&gt;If this is somewhat anxiety-inducing to you, you can consider using &lt;a href="https://remix.ethereum.org/" rel="noopener noreferrer"&gt;Remix&lt;/a&gt; as a viable alternative. Remix is world class IDE for developing and deploying contracts on Ethereum and EVM-based chains.&lt;/p&gt;

&lt;p&gt;Remix allows you to deploy your contracts using Metamask. By doing so, it places an important confirmation step in between where you can evaluate and approve the total amount you’re spending, and modify gas fees using Metamask’s interface.&lt;/p&gt;

&lt;p&gt;You also have the option of getting popup notifications from Metamask as and when your transaction is complete.&lt;/p&gt;

&lt;p&gt;We will be doing a tutorial on Remix very soon. There are plenty of great tutorials online that use Remix by default though so you shouldn’t find it too hard to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We hope this article has given you a good idea on the things you need to consider before you take the big step of launching your dapp to a mainnet and have real people use real money on it.&lt;/p&gt;

&lt;p&gt;If you have any questions, please feel free to drop them on the &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;#suggestions-and-qna channel&lt;/a&gt; of our Discord.&lt;/p&gt;

&lt;p&gt;If you don’t have questions, come say hi to us on our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; anyway! Also, if you liked our content, we would be super grateful if you tweet about us, follow us(@ScrappyNFTs and @Rounak_Banik), and invite your circle to our Discord. Thank you for your support!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;About Scrappy Squirrels&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Scrappy Squirrels is a collection of 10,000+ randomly generated NFTs. Scrappy Squirrels are meant for buyers, creators, and developers who are completely new to the NFT ecosystem.&lt;/p&gt;

&lt;p&gt;The community is built around learning about the NFT revolution, exploring its current use cases, discovering new applications, and finding members to collaborate on exciting projects with.&lt;/p&gt;

&lt;p&gt;Join our community here: &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;https://discord.gg/8UqJXTX7Kd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building an Ethereum Gas Tracker</title>
      <dc:creator>Rounak Banik</dc:creator>
      <pubDate>Tue, 14 Dec 2021 15:07:00 +0000</pubDate>
      <link>https://forem.com/rounakbanik/building-an-eip-1559-gas-tracker-4p7k</link>
      <guid>https://forem.com/rounakbanik/building-an-eip-1559-gas-tracker-4p7k</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi67qt8spaxe6rpwoq0ij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi67qt8spaxe6rpwoq0ij.png" alt="EIP-1559 Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The London Hard Fork in August 2021 brought about one of the biggest upgrades that the Ethereum network has ever witnessed. The fork implemented EIP-1559; a gas pricing mechanism that is touted to be superior to the blind auction model. It also introduced fundamental changes in the monetary policy of Ether (ETH), making it a deflationary currency at least in the short term.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a gas tracker that tracks the two new components of gas fees in EIP-1559 as well as other statistics (such as block volume) for the latest 20 blocks. By doing so, we will achieve two important goals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A deeper understanding of how EIP-1559 works under the hood and what improvements it brings to the table.&lt;/li&gt;
&lt;li&gt;A fully functional gas tracker app that retrieves the latest block volumes and gas fees, broken down by base and priority fee.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In order to do this, we will use Alchemy, the Alchemy web3.js library, Node, and React. Don't worry if some of these words sound alien to you, we will cover them in detail!&lt;/p&gt;

&lt;p&gt;This tutorial does assume that you have a basic understanding of how gas and gas prices work on Ethereum though. A preliminary understanding of EIP-1559 is also helpful but not required. In case you need a primer, I strongly suggest going through this &lt;a href="https://blog.alchemy.com/blog/eip-1559" rel="noopener noreferrer"&gt;excellent article on Alchemy's blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Recap of EIP-1559
&lt;/h2&gt;

&lt;p&gt;EIP-1559 brought about the following changes in the gas pricing mechanism of Ethereum.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The blind auction gas fee has now been replaced by two component fees: a base free and a priority fee (or miner's tip).&lt;/li&gt;
&lt;li&gt;The base fee is determined automatically by the network. It can increases up to 12.5% if the previous block was full and decrease by up to 12.5% if the previous block was empty.&lt;/li&gt;
&lt;li&gt;The miner's tip is determined by the user and can be tuned based on the urgency of the transaction.&lt;/li&gt;
&lt;li&gt;The base fee is burned by the network to prevent miners from artificially flooding blocks. Miners, however, get to pocket the tip.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apart from improving gas pricing, EIP-1559 also proposed an improvement to better equip the network in handling sudden spikes in traffic. As you may know, transactions in Ethereum are grouped into blocks. Prior to the fork, a block could hold only 15 million gas worth of transactions regardless of the volume of traffic. &lt;/p&gt;

&lt;p&gt;With the upgrade, the upper limit of &lt;strong&gt;block size&lt;/strong&gt; has been doubled to 30 million gas. This has been done so that periods of increased demand can be handled better. The expectation, however, is that the base fee will adjust in such a way that &lt;strong&gt;block volumes&lt;/strong&gt; (or &lt;strong&gt;gas used&lt;/strong&gt; by a block) averages at around 50% or 15 million gas. &lt;/p&gt;

&lt;p&gt;You will be able to see how all of this works in real time with the gas tracker that we build. We will be building this project in two parts: in the first part, we will write a node script that will track transaction fee history in real time. In the second part, we will create a React app leveraging this script to build our final tracker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: The Transaction Fee History script
&lt;/h2&gt;

&lt;p&gt;In this section, we will write a script (in node) that will allow us to get the gas fee history of the latest 20 blocks on the Ethereum network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 0: Install node and npm
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7x90udj6afkoznubg4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7x90udj6afkoznubg4g.png" alt="Node and npm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure you have node and npm installed on your local computer (at least v14 or higher). You can download it &lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create an Alchemy account
&lt;/h3&gt;

&lt;p&gt;In order to get the latest gas fee history of blocks, we will have to connect to and communicate with the Ethereum network. Alchemy is a blockchain developer platform that allows us to do this without having to spin up our own nodes.&lt;/p&gt;

&lt;p&gt;You can create an Alchemy account for free &lt;a href="https://alchemy.com/?a=d7d81950c0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create an Alchemy App (and API key)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkti0yseb1wp47ypwkfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkti0yseb1wp47ypwkfj.png" alt="Alchemy Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create an app on the &lt;a href="https://dashboard.alchemyapi.io/" rel="noopener noreferrer"&gt;Alchemy dashboard&lt;/a&gt;. Set the chain to &lt;strong&gt;Ethereum&lt;/strong&gt; and the network to &lt;strong&gt;Mainnet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vi9cxwhj1rv58un9d0z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vi9cxwhj1rv58un9d0z.png" alt="App Key Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, visit your app's page and click on &lt;strong&gt;View Key&lt;/strong&gt;. This will open a popup with the HTTP and Websocket URLs of your app. For this tutorial, we will be using the websocket URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create node project and install dependencies
&lt;/h3&gt;

&lt;p&gt;We are now in a good position to start writing our node script. Let's create an empty repository and install dependencies. For this script, we will be requiring the &lt;a href="https://docs.alchemy.com/alchemy/documentation/alchemy-web3" rel="noopener noreferrer"&gt;Alchemy web3.js library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On your Terminal (or Command Prompt), run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; mkdir gas-tracker-script &amp;amp;&amp;amp; cd gas-tracker-script
&amp;gt; npm init -y
&amp;gt; npm install --save @alch/alchemy-web3
&amp;gt; touch main.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should create a repository named &lt;code&gt;gas-tracker-script&lt;/code&gt; that holds all the files and dependencies we need. Open this repo in you favorite code editor. We will be writing all our code in the &lt;code&gt;main.js&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Create a web3 client instance using Alchemy
&lt;/h3&gt;

&lt;p&gt;Creating a client instance with Alchemy web3 is incredibly simple. &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;main.js&lt;/code&gt; file, add the following lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { createAlchemyWeb3 } = require("@alch/alchemy-web3");

// Using WebSockets
const web3 = createAlchemyWeb3(
    "wss://eth-mainnet.alchemyapi.io/v2/&amp;lt;--API KEY--&amp;gt;",
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to replace the placeholder above with the websocket URL of your app. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Get fee history of the last 20 blocks
&lt;/h3&gt;

&lt;p&gt;We want to get the gas fees history of the last 10 blocks. Data we're interested in includes the base fee, range of priority fees, block volume, and block number.&lt;/p&gt;

&lt;p&gt;Fortunately for us, Alchemy has a very convenient &lt;a href="https://docs.alchemy.com/alchemy/apis/ethereum/eth_feehistory" rel="noopener noreferrer"&gt;eth_feeHistory&lt;/a&gt; that returns all the aforementioned data automatically.&lt;/p&gt;

&lt;p&gt;All we need to specify is the newest block we want data for, the total number of blocks to look at, and the percentile ranges for priority fees.&lt;/p&gt;

&lt;p&gt;We are interested in the latest 20 blocks and the 25th, 50th, and 75th percentile of priority fees.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web3.eth.getFeeHistory(20, "latest", [25, 50, 75]).then(console.log)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this script (using &lt;code&gt;node main.js&lt;/code&gt;) should fetch you the data you're looking for. Here is some data I received after requesting for 5 blocks worth of data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecjl22k207rxyglsva6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecjl22k207rxyglsva6c.png" alt="Terminal output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Format output
&lt;/h3&gt;

&lt;p&gt;The output we received in step 5 is correct but is not very readable. The fees are expressed in hexadecimals and the data structure makes it difficult to figure out which data corresponds to which block.&lt;/p&gt;

&lt;p&gt;Let's write a small function that transforms the raw data into a list of dictionaries where each dictionary will contain data on a particular block. The function also converts all hexadecimal gas values denominated in wei to decimals denominated in Gwei.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const formatOutput = (data, numBlocks) =&amp;gt; {

    let blocks = []
    for (let i = 0; i &amp;lt; numBlocks; i++) {
        blocks.push({
            blockNumber: Number(data.oldestBlock) + i,
            reward: data.reward[i].map(r =&amp;gt; Math.round(Number(r) / 10 ** 9)),
            baseFeePerGas: Math.round(Number(data.baseFeePerGas[i]) / 10 ** 9),
            gasUsedRatio: data.gasUsedRatio[i],
        })
    }
    return blocks;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's use this function is callback of &lt;code&gt;feeHistory&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const numBlocks = 5;

web3.eth.getFeeHistory(numBlocks, "latest", [25, 50, 75]).then((data) =&amp;gt; {
    const blocks = formatOutput(data, numBlocks);
    console.log(blocks);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this version of the script should yield output in the following format:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73nbc2qlku9kwfresskp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73nbc2qlku9kwfresskp.png" alt="Terminal output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Subscribe to latest block headers
&lt;/h3&gt;

&lt;p&gt;A new block gets added to the Ethereum blockchain approximately every 15 seconds. Therefore, we would ideally want to subscribe to the event of blocks being added and update our transaction history such that it always shows data for the latest 20 blocks.&lt;/p&gt;

&lt;p&gt;Let's nest the &lt;code&gt;getFeeHistory&lt;/code&gt; functionality within a subscription event callback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let subscription = web3.eth.subscribe('newBlockHeaders');

subscription.on("data", () =&amp;gt; {
    web3.eth.getFeeHistory(numBlocks, "latest", [25, 50, 75]).then((data) =&amp;gt; {
        const blocks = formatOutput(data, numBlocks);
        console.log(blocks);
    });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the &lt;code&gt;main.js&lt;/code&gt; script now will output the freshest batch of data every 15 seconds or so. If you've come this far, congratulations! You now have a fully functional gas tracker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: The Gas Tracker React App
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobgf0x9giq33nam1x4ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobgf0x9giq33nam1x4ui.png" alt="React"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the previous section, we wrote a script that retrieved the fee history of the last 20 blocks every time a new block was added to the Ethereum mainnet.&lt;/p&gt;

&lt;p&gt;In this section, we will build a small React app that transports this data from our terminal to the browser. In addition to fee transaction history, we will also display the average gas fees and block volumes over the last 20 blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Initialize React project and install dependencies
&lt;/h3&gt;

&lt;p&gt;Run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; npx create-react-app gas-tracker-frontend
&amp;gt; cd gas-tracker-frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should create a sample React project. Apart from the react dependencies, we will also need to install the Alchemy web3 library from the previous section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; npm install --save @alch/alchemy-web3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Populate the App.js file
&lt;/h3&gt;

&lt;p&gt;All our logic will reside in the &lt;code&gt;App.js&lt;/code&gt; file. Copy the following contents into the aforementioned file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import './App.css';
import { useEffect, useState } from 'react';
import { createAlchemyWeb3 } from '@alch/alchemy-web3';

const NUM_BLOCKS = 20;

function App() {

  const [blockHistory, setBlockHistory] = useState(null);
  const [avgGas, setAvgGas] = useState(null);
  const [avgBlockVolume, setAvgBlockVolume] = useState(null);

  const formatOutput = (data) =&amp;gt; {

    let avgGasFee = 0;
    let avgFill = 0;
    let blocks = [];

    for (let i = 0; i &amp;lt; NUM_BLOCKS; i++) {

      avgGasFee = avgGasFee + Number(data.reward[i][1]) + Number(data.baseFeePerGas[i])
      avgFill = avgFill + Math.round(data.gasUsedRatio[i] * 100);

      blocks.push({
        blockNumber: Number(data.oldestBlock) + i,
        reward: data.reward[i].map(r =&amp;gt; Math.round(Number(r) / 10 ** 9)),
        baseFeePerGas: Math.round(Number(data.baseFeePerGas[i]) / 10 ** 9),
        gasUsedRatio: Math.round(data.gasUsedRatio[i] * 100),
      })
    }

    avgGasFee = avgGasFee / NUM_BLOCKS;
    avgGasFee = Math.round(avgGasFee / 10 ** 9)

    avgFill = avgFill / NUM_BLOCKS;
    return [blocks, avgGasFee, avgFill];
  }

  useEffect(() =&amp;gt; {

    const web3 = createAlchemyWeb3(
      "wss://eth-mainnet.alchemyapi.io/v2/&amp;lt;--API KEY--&amp;gt;",
    );

    let subscription = web3.eth.subscribe('newBlockHeaders');

    subscription.on('data', () =&amp;gt; {
      web3.eth.getFeeHistory(NUM_BLOCKS, "latest", [25, 50, 75]).then((feeHistory) =&amp;gt; {
        const [blocks, avgGasFee, avgFill] = formatOutput(feeHistory, NUM_BLOCKS);
        setBlockHistory(blocks);
        setAvgGas(avgGasFee);
        setAvgBlockVolume(avgFill);
      });
    });

    return () =&amp;gt; {
      web3.eth.clearSubscriptions();
    }
  }, [])


  return (
    &amp;lt;div className='main-container'&amp;gt;
      &amp;lt;h1&amp;gt;EIP-1559 Gas Tracker&amp;lt;/h1&amp;gt;
      {!blockHistory &amp;amp;&amp;amp; &amp;lt;p&amp;gt;Data is loading...&amp;lt;/p&amp;gt;}
      {avgGas &amp;amp;&amp;amp; avgBlockVolume &amp;amp;&amp;amp; &amp;lt;h3&amp;gt;
        &amp;lt;span className='gas'&amp;gt;{avgGas} Gwei&amp;lt;/span&amp;gt; | &amp;lt;span className='vol'&amp;gt;{avgBlockVolume}% Volume&amp;lt;/span&amp;gt;
      &amp;lt;/h3&amp;gt;}
      {blockHistory &amp;amp;&amp;amp; &amp;lt;table&amp;gt;
        &amp;lt;thead&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;Block Number&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Base Fee&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Reward (25%)&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Reward (50%)&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Reward (75%)&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Gas Used&amp;lt;/th&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
          {blockHistory.map(block =&amp;gt; {
            return (
              &amp;lt;tr key={block.blockNumber}&amp;gt;
                &amp;lt;td&amp;gt;{block.blockNumber}&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;{block.baseFeePerGas}&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;{block.reward[0]}&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;{block.reward[1]}&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;{block.reward[2]}&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;{block.gasUsedRatio}%&amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
            )
          })}
        &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this isn't a React course, we are not doing to dive deep into the React-specific bits. But you should be able to observe that all that we're doing is retrieving fee history like we did in our script and outputting it in the form of an HTML table.&lt;/p&gt;

&lt;p&gt;The only additional logic we employ is computing average gas price and average block volumes over 20 blocks which is a trivial task to perform.&lt;/p&gt;

&lt;h3&gt;
  
  
  (Optional) Step 3: Add some styles
&lt;/h3&gt;

&lt;p&gt;You can add some basic styles in the &lt;code&gt;App.css&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.main-container {
    text-align: center;
}

table {
    border-collapse: collapse;
    margin: 20px auto;
    box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}

thead {
    background: linear-gradient(267.45deg,#05d5ff -34.23%,#53f 99.39%);
    color: white;
    padding: 10px;
}

th {
    font-size: 18px;
    padding: 15px;

}

tbody &amp;gt; tr {
    border-top: 1px solid #ccc; 
    border-bottom: 1px solid #ccc;
    margin: 0px;
    padding: 15px;
}

td {
    padding: 6px;
}

.gas {
    color: #4299E1;
}

.vol {
    color: #4C51BF;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Deploy app to localhost
&lt;/h3&gt;

&lt;p&gt;We're all done. Watch your app in all its glory by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what the app should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby05a6u5lyeozgyertje.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby05a6u5lyeozgyertje.png" alt="Gas Tracker"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations! You've built a fully functional gas tracker app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analysis
&lt;/h2&gt;

&lt;p&gt;Let's take a step back and analyze the data above. Here are a few things we can observe which are a direct result of the EIP-1559 implementation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The base fee does not fluctuate wildly from block to block. In fact, the maximum it increases or decreases is by 12.5%.&lt;/li&gt;
&lt;li&gt;The priority fee, in most cases, is a small percentage of the total fee.&lt;/li&gt;
&lt;li&gt;Block volumes tend to fluctuate but the average block volumes hover around 50%.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The data does seem to suggest that gas fees in this model is much more predictable. Since everyone pays the same base fee and the priority fee, in most cases, is a small percentage of total fee, most transactions don't end up overpaying for gas. Therefore, this small sample of data suggests that EIP-1559 has succeeded in what it set out to achieve: more predictable gas prices, and less overpayment in gas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We've covered a lot of ground in this article. By building an EIP-1559 gas tracker from scratch, I hope you were able to grasp and appreciate the improvement it brings to transacting on Ethereum.&lt;/p&gt;

&lt;p&gt;I also hope that you've gotten a decent grasp on how to use Alchemy, its APIs, and the web3.js library. We've barely scratched the surface with respect to its capabilities and offerings. I strongly suggest you dig more into &lt;a href="https://docs.alchemy.com/alchemy/" rel="noopener noreferrer"&gt;their documentation&lt;/a&gt; if and when you set out to build your next great dapp.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>javascript</category>
      <category>react</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a web3 frontend with React</title>
      <dc:creator>Rounak Banik</dc:creator>
      <pubDate>Sun, 28 Nov 2021 10:46:46 +0000</pubDate>
      <link>https://forem.com/rounakbanik/building-a-web3-frontend-with-react-340c</link>
      <guid>https://forem.com/rounakbanik/building-a-web3-frontend-with-react-340c</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2atk7k2oyoew8gpifj28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2atk7k2oyoew8gpifj28.png" alt="React + Scrappy Squirrels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In a &lt;a href="https://dev.to/rounakbanik/writing-an-nft-collectible-smart-contract-2nh8"&gt;previous tutorial&lt;/a&gt;, we covered how to create and deploy an NFT collectible smart contract from scratch. We also explored how to verify our contract on etherscan and enable yourself as well as your users to call functions directly from the contract’s etherscan page.&lt;/p&gt;

&lt;p&gt;However, most serious projects tend to deploy their own websites and allow users to mint directly from the website.&lt;/p&gt;

&lt;p&gt;This is exactly what we will be covering in this tutorial. More specifically, this tutorial will show you how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let users connect their Metamask wallet to your website&lt;/li&gt;
&lt;li&gt;Allow users to call a contract function, make a payment, and mint an NFT from your collection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the end of this tutorial, you’ll have a fully functioning web3 frontend built with React. You will have also gained the foundational knowledge required to build any general-purpose web3 frontend (beyond an NFT minter).&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5n2m87tq72ncq2zi2ny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5n2m87tq72ncq2zi2ny.png" alt="React Tutorial"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tutorial assumes you have already developed and deployed your smart contract to the Rinkeby test network. If you haven’t, we strongly suggest you go through &lt;a href="https://dev.to/rounakbanik/writing-an-nft-collectible-smart-contract-2nh8"&gt;this tutorial&lt;/a&gt;. In order to follow along with this tutorial, you will need the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The ABI file for your smart contract (which is available in the &lt;em&gt;artifacts&lt;/em&gt; folder of your smart contract project).&lt;/li&gt;
&lt;li&gt;The address of your smart contract.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also assume that you have experience working with React and Javascript. If not, we strongly suggest you go through the &lt;a href="https://reactjs.org/tutorial/tutorial.html" rel="noopener noreferrer"&gt;official tutorial on React’s website&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the project
&lt;/h2&gt;

&lt;p&gt;Let’s start off by creating a React project using &lt;code&gt;create-react-app&lt;/code&gt;. Open your terminal and run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx create-react-app nft-collectible-frontend


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The installation process will take anywhere between 2–10 minutes. Once its done, check that everything is working by running the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

cd nft-collectible-frontend
npm start


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If all goes well, you should see your browser open a new tab at &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;localhost://3000&lt;/a&gt; with the following screen. Pretty standard React stuff.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyzvf3q49ybu6awir1f2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyzvf3q49ybu6awir1f2.png" alt="React starter website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s do a little cleanup now.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;public/index.html&lt;/code&gt; and change the title and meta description of your website. This step is optional.&lt;/p&gt;

&lt;p&gt;Next, go to the src folder and delete the &lt;code&gt;App.test.js&lt;/code&gt;, &lt;code&gt;logo.svg&lt;/code&gt;, and &lt;code&gt;setupTests.js&lt;/code&gt; files. We will not be needing these files for this tutorial.&lt;/p&gt;

&lt;p&gt;Go to the &lt;code&gt;App.js&lt;/code&gt; file and replace its contents with the following boilerplate.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import './App.css';

function App() {
    return (
        &amp;lt;h1&amp;gt;Hello World&amp;lt;/h1&amp;gt;
    );
}

export default App;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Remove all the contents of &lt;code&gt;App.css&lt;/code&gt; as well. Do not, however, delete this file. In a later section, we will provide you with some basic styling that should be good enough for this demo project.&lt;/p&gt;

&lt;p&gt;If you go back to localhost, you should see a screen that says &lt;strong&gt;Hello World&lt;/strong&gt;. We now have a basic react project set up and good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting contract ABI and address
&lt;/h2&gt;

&lt;p&gt;For our React frontend to be able to connect and communicate with our smart contract, it needs the contract’s ABI and address.&lt;/p&gt;

&lt;p&gt;ABI (or Application Binary Interface) is a JSON file that is automatically generated during contract compilation. The blockchain we deploy to stores our smart contract in the form of bytecode. In order to invoke functions on it, pass the correct parameters, and parse return values using a high-level language, we need to specify details about the functions and the contract (such as name, arguments, types, etc.) to our frontend. This is exactly what the ABI file does. In order to learn more about the ABI, we suggest you go through &lt;a href="https://www.quicknode.com/guides/solidity/what-is-an-abi" rel="noopener noreferrer"&gt;this excellent post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To find your ABI file, go to your hardhat project and navigate to &lt;code&gt;artifacts/contracts/NFTCollectible.sol/NFTCollectible.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymm08q5ufrxk368ayon3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymm08q5ufrxk368ayon3.png" alt="Folder tree"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to now copy the JSON file to our React project. Create a new folder called &lt;code&gt;contracts&lt;/code&gt; in the &lt;code&gt;src&lt;/code&gt; folder and paste the &lt;code&gt;NFTCollectible.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;You should already have the address of your deployed smart contract. (If you don’t just deploy it to Rinkeby again, and get the latest address and ABI file).&lt;/p&gt;

&lt;p&gt;Our contract address from the previous tutorial is 0x355638a4eCcb777794257f22f50c289d4189F245. We will be using this contract in this tutorial too.&lt;/p&gt;

&lt;p&gt;Let us now import the contract ABI and define the contract address in the &lt;code&gt;App.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqetfa7sb4rkslu3df5y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqetfa7sb4rkslu3df5y.png" alt="App.js"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up boilerplate HTML, CSS, and JS
&lt;/h2&gt;

&lt;p&gt;Our website is going to be incredibly simple. All it will have is a heading and a &lt;em&gt;Connect Wallet&lt;/em&gt; button. Once the wallet is connected, the &lt;em&gt;Connect Wallet&lt;/em&gt; button will be replaced by a &lt;em&gt;Mint NFT&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;We’re not going to bother with creating separate component files. Instead, we will write all our HTML and logic in &lt;code&gt;App.js&lt;/code&gt; and all our CSS in &lt;code&gt;App.css&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Copy the contents of the following Github gist into your &lt;code&gt;App.js&lt;/code&gt; file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import { useEffect } from 'react';
import './App.css';
import contract from './contracts/NFTCollectible.json';

const contractAddress = "0x355638a4eCcb777794257f22f50c289d4189F245";
const abi = contract.abi;

function App() {

  const checkWalletIsConnected = () =&amp;gt; { }

  const connectWalletHandler = () =&amp;gt; { }

  const mintNftHandler = () =&amp;gt; { }

  const connectWalletButton = () =&amp;gt; {
    return (
      &amp;lt;button onClick={connectWalletHandler} className='cta-button connect-wallet-button'&amp;gt;
        Connect Wallet
      &amp;lt;/button&amp;gt;
    )
  }

  const mintNftButton = () =&amp;gt; {
    return (
      &amp;lt;button onClick={mintNftHandler} className='cta-button mint-nft-button'&amp;gt;
        Mint NFT
      &amp;lt;/button&amp;gt;
    )
  }

  useEffect(() =&amp;gt; {
    checkWalletIsConnected();
  }, [])

  return (
    &amp;lt;div className='main-app'&amp;gt;
      &amp;lt;h1&amp;gt;Scrappy Squirrels Tutorial&amp;lt;/h1&amp;gt;
      &amp;lt;div&amp;gt;
        {connectWalletButton()}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default App;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;(Remember to set the correct contract address on line 5)&lt;/p&gt;

&lt;p&gt;Notice that we have defined a few functions for you which do not do a lot at the moment. We will be explaining their purpose and populating them with logic as we proceed with this tutorial.&lt;/p&gt;

&lt;p&gt;We have a small amount of CSS for you to use too. Copy the following into your &lt;code&gt;App.css&lt;/code&gt; file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

.main-app {
    text-align: center;
    margin: 100px;
}

.cta-button {
    padding: 15px;
    border: none;
    border-radius: 12px;
    min-width: 250px;
    color: white;
    font-size: 18px;
    cursor: pointer;
}

.connect-wallet-button {
    background: rgb(32, 129, 226);
}

.mint-nft-button {
    background: orange;
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Your website should now look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzmmz65rhjqngyjk1tpx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzmmz65rhjqngyjk1tpx.png" alt="Frontend"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to customize the look of the website by adding more styles and static elements (images, header, footer, social media links, etc.).&lt;/p&gt;

&lt;p&gt;We’ve put together most of the foundational blocks of the project. We are now in a good position to tackle one of the first major objectives of this tutorial: allowing a user to connect their wallet to our website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Metamask Wallet
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foamoap1p2sblkrlbqtoe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foamoap1p2sblkrlbqtoe.png" alt="Metamask"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a user to call functions from our contract, they need to be able to connect their wallet to our website. The wallet will enable the user to pay gas and the sale price in order to mint an NFT from our collection.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will be working exclusively with the Metamask wallet and its suite of APIs. Off-the-shelf solutions like &lt;a href="https://moralis.io/" rel="noopener noreferrer"&gt;Moralis&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/web3modal" rel="noopener noreferrer"&gt;web3modal&lt;/a&gt; exist that allow you to add support for multiple wallets with very few lines of code. But for this project, we will focus on implementing connect wallet functionality from scratch. We will cover solutions like Moralis in a later tutorial.&lt;/p&gt;

&lt;p&gt;We assume you already have the Metamask wallet extension installed in your browser. If you do, Metamask injects an &lt;code&gt;ethereum&lt;/code&gt; object into your browser’s global &lt;code&gt;window&lt;/code&gt; object. We will be accessing &lt;code&gt;window.ethereum&lt;/code&gt; to perform the bulk of our functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking if Metamask Wallet Exists
&lt;/h3&gt;

&lt;p&gt;A user cannot mint NFTs on our website unless they have a Metamask wallet. Let’s populate the &lt;code&gt;checkWalletIsConnected&lt;/code&gt; function within the &lt;code&gt;App&lt;/code&gt; component that checks if the Metamask wallet exists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9x3wluw6nofif4bu3z4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9x3wluw6nofif4bu3z4o.png" alt="Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that we have also defined the &lt;code&gt;useEffect&lt;/code&gt; hook that checks Metamask’s existence when the App component loads.&lt;/p&gt;

&lt;p&gt;Open the console on your app’s localhost page. If you have Metamask installed, you should see a message that says &lt;em&gt;Wallet exists! We’re ready to go!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjglm085ox31okd3gn7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjglm085ox31okd3gn7r.png" alt="Browser console output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting Metamask Programmatically
&lt;/h3&gt;

&lt;p&gt;Just because we have the Metamask extension installed doesn’t mean that Metamask will automatically connect to every website we visit. We need to prompt Metamask to ask the user to do so.&lt;/p&gt;

&lt;p&gt;This is where the &lt;em&gt;Connect Wallet&lt;/em&gt; functionality comes in. It is the web3 equivalent of a login button. It allows the user to connect and send contract function call requests through the website frontend.&lt;/p&gt;

&lt;p&gt;Metamask makes this process remarkably simple with the &lt;code&gt;window.ethereum.request&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Let’s first define a variable in &lt;code&gt;App()&lt;/code&gt; with the useState hook that will keep track of the user’s wallet address. (Don’t forget to import &lt;code&gt;useState&lt;/code&gt; from React!)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const [currentAccount, setCurrentAccount] = useState(null);


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, let’s define the &lt;code&gt;connectWalletHandler&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtg2cpe30pfp7fqc14a5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtg2cpe30pfp7fqc14a5.png" alt="Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s briefly go through what this function does.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It checks if you have Metamask installed. If not, the website displays a pop-up asking you to install Metamask.&lt;/li&gt;
&lt;li&gt;It requests Metamask for the user’s wallet addresses.&lt;/li&gt;
&lt;li&gt;Once the user has consented to connect with the website, it takes the first wallet address that is available and sets it as the value of the currentAccount variable.&lt;/li&gt;
&lt;li&gt;If something goes wrong (such as the user refusing to connect), it fails and prints an error message to the console.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the moment, if you open the Metamask extension on your website, it will tell you that you’re not connected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6icgk9022dyqtu0pqel5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6icgk9022dyqtu0pqel5.png" alt="Metamask wallet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is now time for the moment of truth. Click on the &lt;em&gt;Connect Wallet&lt;/em&gt; button on your website. Metamask will prompt you to connect with the website. Once you agree to do so, your extension screen will look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3o1csu71wxdsv9rt01u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3o1csu71wxdsv9rt01u.png" alt="Metamask wallet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations! We have successfully connected our wallet to our website.&lt;/p&gt;

&lt;p&gt;Once the wallet is connected, we should ideally replace the &lt;em&gt;Connect Wallet&lt;/em&gt; button with a &lt;em&gt;Mint NFT&lt;/em&gt; button. In the return value of App , let’s replace the render of a &lt;em&gt;Connect Wallet&lt;/em&gt; button with a conditional render.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{currentAccount ? mintNftButton() : connectWalletButton()}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Our website should now look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqp00nwj98isz0u59w1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqp00nwj98isz0u59w1m.png" alt="Mint NFT"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s refresh our page and check our extension. You will see that Metamask tells us that we are still connected to the website but our website still displays a &lt;em&gt;Connect Wallet&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;If you’re familiar with React, it should be obvious why this is happening. After all, we are setting the &lt;code&gt;currentAccount&lt;/code&gt; state only within the &lt;code&gt;connectWallet&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Ideally what should happen is that the website should check if the wallet is connected every time the &lt;code&gt;App&lt;/code&gt; component is loaded (i.e every time we refresh).&lt;/p&gt;

&lt;p&gt;Let us extend the &lt;code&gt;checkWalletIsConnected&lt;/code&gt; function to check for accounts as soon as the website is loaded and set currentAccount if the wallet has already been connected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhy8d4qtxpt6o2v12nmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhy8d4qtxpt6o2v12nmk.png" alt="Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Note that we have marked this function async ). Let’s briefly touch upon what this function does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It checks if Metamask is installed and outputs result to the console.&lt;/li&gt;
&lt;li&gt;It attempts to request Metamask for accounts that are connected.&lt;/li&gt;
&lt;li&gt;If Metamask is already connected, it obliges by giving the function a list of accounts. If not, an empty list is returned.&lt;/li&gt;
&lt;li&gt;If the list is not empty, the function picks the first account sent over by Metamask and sets it as the current account.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you now refresh the page, you will see that the website indeed displays the &lt;em&gt;Mint NFT&lt;/em&gt; button as it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mint NFTs from the website
&lt;/h2&gt;

&lt;p&gt;Let us now implement the core functionality of our website. When a user clicks on the &lt;em&gt;Mint NFT&lt;/em&gt; button, we expect the following to happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Metamask prompts the user to pay the NFT’s price + gas.&lt;/li&gt;
&lt;li&gt;Once the user accepts, Metamask calls the mintNFT function of our contract on behalf of the user.&lt;/li&gt;
&lt;li&gt;It notifies the user about the success/failure of the transaction once it is complete.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To do this, we will require the &lt;code&gt;ethers&lt;/code&gt; library from our smart contract project. In your terminal, run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npm install ethers


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let us import this library in &lt;code&gt;App.js&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import { ethers } from 'ethers';


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, let’s populate the &lt;code&gt;mintNftHandler&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fso14774uc0dxqqxyiu53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fso14774uc0dxqqxyiu53.png" alt="Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Don’t forget to mark this function as &lt;code&gt;async&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;As usual, let’s touch upon what this function does.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It tries to access the ethereum object injected by Metamask.&lt;/li&gt;
&lt;li&gt;If ethereum exists, it sets Metamask as the RPC provider. This means that you will be issuing requests to the miners using your Metamask wallet.&lt;/li&gt;
&lt;li&gt;To issue requests, the user will need to sign transactions using their private key. We access signer for this purpose.&lt;/li&gt;
&lt;li&gt;We then initiate an ethers Contract instance using the deployed contract’s address, the contract ABI, and the signer.&lt;/li&gt;
&lt;li&gt;We can now call functions on our contract through the aforementioned contract object. We call the mintNFT function and ask Metamask to send 0.01 ETH (which is the price we set for our NFT).&lt;/li&gt;
&lt;li&gt;We wait for the transaction to be processed and once it’s done, we output the transaction hash to the console.&lt;/li&gt;
&lt;li&gt;If anything fails (the wrong function called, wrong parameters passed, &amp;lt; 0.01 ETH sent, user rejected transaction, etc.), an error is printed to the console.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On your website, open your browser’s console so that you are able to view the mining status in real-time.&lt;/p&gt;

&lt;p&gt;Now, click on the &lt;em&gt;Mint NFT&lt;/em&gt; button. Metamask will prompt you to pay 0.01 ETH + gas. The transaction will take approximately 15–20 seconds to process. Once it’s done, the transaction will be confirmed both by a Metamask popup and the console output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmm6i8q7wbxfe8gu23272.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmm6i8q7wbxfe8gu23272.png" alt="Website + Console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now view the NFT on Opensea too. &lt;a href="https://testnets.opensea.io/account" rel="noopener noreferrer"&gt;Navigate to your account on testnets.opensea.io&lt;/a&gt; and you should be able to see your latest NFT.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffesdcf1u9h7rg96zj06u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffesdcf1u9h7rg96zj06u.png" alt="Scrappy Squirrel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  UX Improvements &amp;amp; Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations! You now have a fully functioning web3 frontend that users can mint NFTs from.&lt;/p&gt;

&lt;p&gt;However, as you may have noticed, the UX of the website leaves a lot to be desired. Here are a few improvements that you should consider doing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ensure the user is connected to the right network
&lt;/h3&gt;

&lt;p&gt;Our website assumes that the user is connected to the Rinkeby Network when interacting with our website. This may not always be the case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3u7n2dz3oclm6bmfzkx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3u7n2dz3oclm6bmfzkx.png" alt="Change network banner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you implement functionality that gently alerts the user if s/he is not connected to Rinkeby (like OpenSea does)? Also, ensure that the user is not able to see the &lt;em&gt;Mint NFT&lt;/em&gt; button when connected to the wrong network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Show transaction status
&lt;/h3&gt;

&lt;p&gt;Currently, our website prints the transaction status onto the console. In a real project, you cannot really expect your users to open their console while interacting with the website.&lt;/p&gt;

&lt;p&gt;Can you implement state which tracks the transaction status and gives feedback to the user in real-time? It should show a loader when the transaction is processing, notify the user if the transaction has failed, and display the transaction hash/Opensea link if the transaction has succeeded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt Metamask even if funds are non-existent
&lt;/h3&gt;

&lt;p&gt;If you do not have any ETH in your Metamask wallet, clicking on Mint NFT will not prompt Metamask at all. In fact, the user will receive no feedback.&lt;/p&gt;

&lt;p&gt;Can you ensure that Metamask is prompted even when the user has insufficient funds? It should ideally be Metamask that informs the user how much ETH is required and how much s/he is short by.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Quality of life changes
&lt;/h3&gt;

&lt;p&gt;Here are a few other quality of life changes that you can consider.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Allow users to mint more than 1 NFT at a time.&lt;/li&gt;
&lt;li&gt;Add a few sample artworks from your NFT collection.&lt;/li&gt;
&lt;li&gt;Add a link to your collection on Opensea.&lt;/li&gt;
&lt;li&gt;Add the verified smart contract address so people can double-check what’s really happening behind the scenes.&lt;/li&gt;
&lt;li&gt;Add links to your Twitter, IG, and Discord.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F825l21hl7cy0ms8x8zs7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F825l21hl7cy0ms8x8zs7.png" alt="Rinkeby Squirrels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our NFT sandbox project, &lt;a href="https://medium.com/scrappy-squirrels/getting-started-with-nft-collectibles-communities-for-free-24bab021a97" rel="noopener noreferrer"&gt;Rinkeby Squirrels&lt;/a&gt;, implements a majority of the UX upgrades mentioned here. &lt;a href="https://rsq-frontend.vercel.app/" rel="noopener noreferrer"&gt;Try and mint one here&lt;/a&gt; and see if you can notice the difference between it and the website we built.&lt;/p&gt;

&lt;p&gt;We will be launching future tutorials showing you how to implement a few of these upgrades. But we really suggest you try doing this yourself. You’ll be one step closer to becoming a web3 frontend master.&lt;/p&gt;

&lt;p&gt;If you have any questions or are stuck, reach out to us on our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you don’t have questions, come say hi to us on our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; anyway! Also, if you liked our content, we would be super grateful if you tweet about us, follow us(@ScrappyNFTs and @Rounak_Banik), and invite your circle to our Discord. Thank you for your support!&lt;/p&gt;

&lt;p&gt;Final code repository: &lt;a href="https://github.com/rounakbanik/nft-collectible-frontend" rel="noopener noreferrer"&gt;https://github.com/rounakbanik/nft-collectible-frontend&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;About Scrappy Squirrels&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.scrappysquirrels.co/" rel="noopener noreferrer"&gt;Scrappy Squirrels&lt;/a&gt; is a collection of 10,000+ randomly generated NFTs. Scrappy Squirrels are meant for buyers, creators, and developers who are completely new to the NFT ecosystem.&lt;/p&gt;

&lt;p&gt;The community is built around learning about the NFT revolution, exploring its current use cases, discovering new applications, and finding members to collaborate on exciting projects with.&lt;/p&gt;

&lt;p&gt;Join our community here: &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;https://discord.gg/8UqJXTX7Kd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Developing for Polygon and Sidechains</title>
      <dc:creator>Rounak Banik</dc:creator>
      <pubDate>Thu, 25 Nov 2021 10:23:26 +0000</pubDate>
      <link>https://forem.com/rounakbanik/developing-for-polygon-and-sidechains-4h6a</link>
      <guid>https://forem.com/rounakbanik/developing-for-polygon-and-sidechains-4h6a</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In our last tutorial, we covered how to develop and deploy an &lt;a href="https://dev.to/rounakbanik/writing-an-nft-collectible-smart-contract-2nh8"&gt;NFT Collectible Smart Contract&lt;/a&gt; from scratch. Last week, we published another article showing you how to &lt;a href="https://medium.com/scrappy-squirrels/estimating-smart-contract-costs-f65acf818c26" rel="noopener noreferrer"&gt;estimate your costs&lt;/a&gt; while operating on the Ethereum mainnet and why it would be a good idea to consider a scalability solution like Polygon.&lt;/p&gt;

&lt;p&gt;In this article, we will show you how to deploy your project to the Polygon network and in the process, end up saving potentially thousands of dollars.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of developing on sidechains
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghj3xbyqu75tu82x1shp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghj3xbyqu75tu82x1shp.png" alt="Polygon, Binance, and Fantom"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you followed our tutorial on how to create an NFT Collectible Smart Contract, then congratulations! You are already a Polygon developer. You’re also a developer on the Binance Smart Chain, the Fantom Opera Network, and any sidechain or L2 scalability solutions that are EVM compatible.&lt;/p&gt;

&lt;p&gt;This tutorial will demonstrate deployment to Polygon but the steps are almost identical for any other Ethereum sidechain and (to an extent) L2 chains like Arbitrum.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Write a smart contract like you would for the Ethereum mainnet.&lt;/li&gt;
&lt;li&gt;Recalibrate payable currency to reflect the chain’s token value.&lt;/li&gt;
&lt;li&gt;Add the sidechain network to Metamask and Hardhat configuration file.&lt;/li&gt;
&lt;li&gt;Acquire the chain’s token directly or by bridging from the Ethereum mainnet.&lt;/li&gt;
&lt;li&gt;Deploy to the sidechain by paying fees using the chain’s token.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Writing a Smart Contract
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Note
&lt;/h3&gt;

&lt;p&gt;You can skip this section if you completed our smart contract tutorial.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g44d1wee1ntqs9ram9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g44d1wee1ntqs9ram9f.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have already covered how to develop a smart contract for Ethereum in detail (and I have a feeling I’m mentioning this a little too often). I hope you already have your custom project ready to go. If not, you can clone a starter repository that we created.&lt;/p&gt;

&lt;p&gt;Make sure you have &lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; and run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/rounakbanik/nft-collectible-contract polygon-nft

cd polygon-nft

npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new file called &lt;code&gt;.env&lt;/code&gt; and input the following details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API_URL = "&amp;lt;--Rinkeby RPC URL--&amp;gt;"

PRIVATE_KEY = "&amp;lt;-- Metamask wallet private key --&amp;gt;"

ETHERSCAN_API = ""

POLYGON_URL = ""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You won’t need &lt;code&gt;API_URL&lt;/code&gt; for this tutorial so feel free to set this to a blank string (don’t delete it though, the configuration file will break).&lt;/p&gt;

&lt;p&gt;You should already know how to get your Metamask wallet’s private key. Let &lt;code&gt;ETHERSCAN_API&lt;/code&gt; and &lt;code&gt;POLYGON_URL&lt;/code&gt; stay blank for the time being.&lt;/p&gt;

&lt;p&gt;Now, go to the &lt;code&gt;hardhat.config.js&lt;/code&gt; file and remove line 25 (the one with the &lt;code&gt;defaultNetwork&lt;/code&gt; configuration. We won’t be needing this either.)&lt;/p&gt;

&lt;p&gt;Finally, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat run scripts/run.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this runs without any errors, congratulations! You are up to speed, and we can finally concentrate on the Polygon aspects of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recalibrating price
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadpemyggerdb8b8kp6m0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadpemyggerdb8b8kp6m0.png" alt="Polygon Matic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We set the base price of our NFT at 0.01 ETH. In other words, users would have to pay 0.01 ETH for each NFT that they minted (plus gas, of course). We encapsulate this information in line 16 of &lt;code&gt;NFTCollectible.sol&lt;/code&gt; in the &lt;code&gt;contracts&lt;/code&gt; folder of our project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3z4ca1wpxm8xm66qvho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3z4ca1wpxm8xm66qvho.png" alt="VS Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Transactions on the Polygon sidechain aren’t conducted in ETH though. The Polygon chain has its own ERC20 token called MATIC. We, therefore, need to set our price in MATIC.&lt;/p&gt;

&lt;p&gt;At the time of writing, ETH is touching $5000 whereas MATIC is touching $2. Therefore, if we wanted our NFT to be priced the same (in terms of USD), we would price it at 25 MATIC.&lt;/p&gt;

&lt;p&gt;Let’s make a change in our contract to reflect this change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint public constant PRICE = 25 ether;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait, what? Why does this say &lt;code&gt;25 ether&lt;/code&gt; and not something like &lt;code&gt;25 matic&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Solidity does not really understand what ETH is. In Solidity, the keyword ether is just a shorthand for 10¹⁸. To Solidity, the line above is the same as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint public constant PRICE = 25000000000000000000;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To put it another way, you can specify payable amounts in Solidity in terms of Wei. On the mainnet, 1 ETH is 10¹⁸ Wei. On Polygon, 10¹⁸ Wei is 1 MATIC. This is a huge difference considering the difference in the price of ETH and MATIC. Always make sure you calibrate your prices correctly if you are moving to a different network!&lt;/p&gt;

&lt;p&gt;In this tutorial, we are going to be working with the Polygon Mumbai testnet and I’m going to price the NFT at 0.01 MATIC (for reasons you’ll see soon). So, I’m going to reset the line back to what it originally was.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint public constant PRICE = 0.01 ether;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please remember. On Polygon, this is 0.01 MATIC. &lt;strong&gt;Not 0.01 ETH.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Polygon Network to Metamask and Hardhat
&lt;/h2&gt;

&lt;p&gt;Let’s add the Polygon and Polygon MUMBAI networks to our Metamask wallet. This is really simple to do and &lt;a href="https://docs.polygon.technology/docs/develop/metamask/config-polygon-on-metamask" rel="noopener noreferrer"&gt;Polygon has a short, excellent tutorial&lt;/a&gt; on this. Here is a snapshot of my wallet connected to the Mumbai network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6w12ifn8jk8dx9iqoi8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6w12ifn8jk8dx9iqoi8s.png" alt="Metamask"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Hardhat, we will use a custom RPC URL from Alchemy. Go ahead and &lt;a href="https://alchemy.com/?a=d7d81950c0" rel="noopener noreferrer"&gt;create an Alchemy account&lt;/a&gt; if you haven’t already. Next, create an App by setting the chain to Polygon and the network to Mumbai.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t0mfhx4l06t8pmlk08n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t0mfhx4l06t8pmlk08n.png" alt="Alchemy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, click on &lt;strong&gt;VIEW KEY&lt;/strong&gt; for your app and get the HTTP URL. Go back to your .env file and fill in the value for &lt;code&gt;POLYGON_URL&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POLYGON_URL = "&amp;lt;---Alchemy Polygon URL --&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, our &lt;code&gt;hardhat.config.js&lt;/code&gt; file should add Mumbai as one of our deployment networks. I have done this already for you in lines 30–33.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get fake MATIC
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6d3ld191b71ckdm2cfl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6d3ld191b71ckdm2cfl.png" alt="Fake Matic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have configured our network on both Metamask and Hardhat, let’s proceed to get some fake MATIC.&lt;/p&gt;

&lt;p&gt;Go to &lt;a href="https://faucet.polygon.technology/" rel="noopener noreferrer"&gt;https://faucet.polygon.technology/&lt;/a&gt; and request for test tokens for the Mumbai network. Unlike Rinkeby, you shouldn’t face too many issues acquiring these tokens. You will almost always get 1 MATIC almost near instantaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Note about Polygon and sidechain main networks
&lt;/h3&gt;

&lt;p&gt;When you’re ready to deploy to the main Polygon network (or the sidechain of your choice), you will need to acquire real MATIC.&lt;/p&gt;

&lt;p&gt;There are two ways to do this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Buy MATIC on the Ethereum mainnet and bridge it to the Polygon network.&lt;/li&gt;
&lt;li&gt;Buy MATIC on a centralized exchange (like Wazirx or Coinbase) and transfer it directly to Metamask.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the case of sidechains, it is almost always easier and cheaper to do (2).&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy to the Polygon Mumbai network
&lt;/h2&gt;

&lt;p&gt;We’re ready to go! Run the following command on your terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat run scripts/run.js --network mumbai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q5ajuqf56nn2k01udk2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q5ajuqf56nn2k01udk2.png" alt="Terminal output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can confirm that our contract was deployed and our NFTs were minted by visiting &lt;a href="https://mumbai.polygonscan.com/" rel="noopener noreferrer"&gt;https://mumbai.polygonscan.com/&lt;/a&gt; and searching for our contract address. As you can see above, our contract was deployed to 0xe4ad3e1d2553eCbe4Ab64cd717564dbD36d520cc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fif5vjc1wd40ri1yn8ndg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fif5vjc1wd40ri1yn8ndg.png" alt="Polygonscan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the biggest advantages that Polygon has over other sidechains is that it is supported by OpenSea, the largest NFT marketplace in the world and the defacto platform for secondary sales for almost every popular NFT project.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://testnets.opensea.io/" rel="noopener noreferrer"&gt;https://testnets.opensea.io/&lt;/a&gt; and search for your contract address. You will see that your collection has already been uploaded to OpenSea almost magically.&lt;/p&gt;

&lt;p&gt;Check out our collection &lt;a href="https://testnets.opensea.io/collection/nft-collectible-2mnw3epg2h" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecphe7svoex8ygyy0n2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecphe7svoex8ygyy0n2c.png" alt="Scrappy Squirrels on Opensea"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying our contract
&lt;/h2&gt;

&lt;p&gt;As a bonus, let’s verify our contract on Polygonscan so our users are able to mint from Polygonscan directly.&lt;/p&gt;

&lt;p&gt;To do this, you will need to sign up for a &lt;a href="https://polygonscan.com/" rel="noopener noreferrer"&gt;Polygonscan account&lt;/a&gt;. Next, proceed to &lt;a href="https://polygonscan.com/myapikey" rel="noopener noreferrer"&gt;create an API key&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Go back to the &lt;code&gt;.env&lt;/code&gt; file one last time and fill in the value for &lt;code&gt;ETHERSCAN_API&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ETHERSCAN_API = "&amp;lt;--Polygonscan API key--&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have kept the name &lt;code&gt;ETHERSCAN_API&lt;/code&gt; from the previous tutorial because Polygonscan is powered by Etherscan and we still use the &lt;code&gt;hardhat-etherscan&lt;/code&gt; library to verify our contract. Feel free to change the naming if you wish.&lt;/p&gt;

&lt;p&gt;Now, run the following command on your Terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat verify --network mumbai DEPLOYED_CONTRACT_ADDRESS "ipfs://QmZbWNKJPAjxXuNFSEaksCJVd1M6DaKQViJBYPK2BdpDEP/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, this was the exact command I ran.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat verify --network mumbai 0xe4ad3e1d2553eCbe4Ab64cd717564dbD36d520cc "ipfs://QmZbWNKJPAjxXuNFSEaksCJVd1M6DaKQViJBYPK2BdpDEP/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see a small green checkmark on your contract’s Polygonscan page. More importantly, your users will be able to read your contract and call functions from it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqbq94647pevn2zyiu94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqbq94647pevn2zyiu94.png" alt="Polygonscan contract page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations! You have a good understanding of how to build for Polygon or migrate existing projects into Polygon. The great news, as I’ve stated already, is that this knowledge converts really well to any EVM-compatible network (Binance, Fantom, Arbitrum, Optimism, etc.)&lt;/p&gt;

&lt;p&gt;If you have any questions, please feel free to drop them on the &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;#suggestions-and-qna channel of our Discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you don’t have questions, come say hi to us on our &lt;a href="//If%20you%20don%E2%80%99t%20have%20questions,%20come%20say%20hi%20to%20us%20on%20our%20Discord%20anyway!%20Also,%20if%20you%20liked%20our%20content,%20we%20would%20be%20super%20grateful%20if%20you%20tweet%20about%20us,%20follow%20us(@ScrappyNFTs%20and%20@Rounak_Banik),%20and%20invite%20your%20circle%20to%20our%20Discord.%20Thank%20you%20for%20your%20support!"&gt;Discord&lt;/a&gt; anyway! Also, if you liked our content, we would be super grateful if you tweet about us, follow us(@ScrappyNFTs and &lt;a href="https://twitter.com/Rounak_Banik" rel="noopener noreferrer"&gt;@Rounak_Banik&lt;/a&gt;), and invite your circle to our Discord. Thank you for your support!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;About Scrappy Squirrels&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.scrappysquirrels.co/" rel="noopener noreferrer"&gt;Scrappy Squirrels&lt;/a&gt; is a collection of 10,000+ randomly generated NFTs on the Ethereum Blockchain. Scrappy Squirrels are meant for buyers, creators, and developers who are completely new to the NFT ecosystem.&lt;/p&gt;

&lt;p&gt;The community is built around learning about the NFT revolution, exploring its current use cases, discovering new applications, and finding members to collaborate on exciting projects with.&lt;/p&gt;

&lt;p&gt;Join our community here: &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;https://discord.gg/8UqJXTX7Kd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>solidity</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Writing an NFT Collectible Smart Contract</title>
      <dc:creator>Rounak Banik</dc:creator>
      <pubDate>Mon, 22 Nov 2021 11:04:18 +0000</pubDate>
      <link>https://forem.com/rounakbanik/writing-an-nft-collectible-smart-contract-2nh8</link>
      <guid>https://forem.com/rounakbanik/writing-an-nft-collectible-smart-contract-2nh8</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2veoc5fjkk34jo2x0q7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2veoc5fjkk34jo2x0q7i.png" alt="Scrappy Squirrels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In my previous tutorials, we showed you how to use our &lt;a href="https://github.com/rounakbanik/generative-art-nft" rel="noopener noreferrer"&gt;generative art library&lt;/a&gt; to create a &lt;a href="https://dev.to/rounakbanik/create-generative-nft-art-with-rarities-1n6f"&gt;collection of avatars&lt;/a&gt;, generate compliant NFT metadata, and &lt;a href="https://dev.to/rounakbanik/working-with-nft-metadata-ipfs-and-pinata-3ieh"&gt;upload the metadata JSON and media files to IPFS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, we haven’t minted any of our avatars as NFTs yet. Therefore, in this tutorial, we will write a smart contract that will allow anyone to mint an NFT from our collection by paying gas and a price that we’ve set for each NFT piece.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzt3qvp6rvjdoiwcqjkf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzt3qvp6rvjdoiwcqjkf6.png" alt="node and npm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intermediate knowledge of Javascript. (In case you need a refresher, I’d suggest this &lt;a href="https://www.youtube.com/watch?v=NCwa_xi0Uuc" rel="noopener noreferrer"&gt;YouTube tutorial&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Intermediate knowledge of Solidity and OpenZeppelin Contracts. (I will be releasing tutorials on this very soon! For the time being, I strongly recommend &lt;a href="https://cryptozombies.io/en/course/" rel="noopener noreferrer"&gt;CryptoZombies&lt;/a&gt; and &lt;a href="https://buildspace.so/" rel="noopener noreferrer"&gt;Buildspace&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;node and npm installed on your local computer&lt;/li&gt;
&lt;li&gt;A collection of media files and NFT metadata JSON uploaded to IPFS. (In case you don’t have this, we have created a toy collection for you to experiment with. You can find the media files &lt;a href="https://ipfs.io/ipfs/QmUygfragP8UmCa7aq19AHLttxiLw1ELnqcsQQpM5crgTF" rel="noopener noreferrer"&gt;here&lt;/a&gt; and the JSON metadata files &lt;a href="https://ipfs.io/ipfs/QmZbWNKJPAjxXuNFSEaksCJVd1M6DaKQViJBYPK2BdpDEP" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While it may be possible for readers who do not satisfy the prerequisites to follow along and even deploy a smart contract, we strongly recommend getting a developer who knows what s/he is doing if you’re serious about your project. Smart contract development and deployment can be incredibly expensive and unforgiving w.r.t security flaws and bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up our local development environment
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffx2r3az50dg8wiklyex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffx2r3az50dg8wiklyex.png" alt="Hardhat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will be using Hardhat, an industry-standard ethereum development environment, to develop, deploy, and verify our smart contracts. Create an empty folder for our project and initialize an empty package.json file by running the following command in your Terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir nft-collectible &amp;amp;&amp;amp; cd nft-collectible &amp;amp;&amp;amp; npm init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now be inside the &lt;code&gt;nft-collectible&lt;/code&gt; folder and have a file named &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, let’s install Hardhat. Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev hardhat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now create a sample Hardhat project by running the following command and choosing &lt;code&gt;Create a basic sample project&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agree to all the defaults (project root, adding a &lt;code&gt;.gitignore&lt;/code&gt;, and installing all sample project dependencies).&lt;/p&gt;

&lt;p&gt;Let’s check that our sample project has been installed properly. Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat run scripts/sample-script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all goes well, you should see output that looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxo3khuckms2mivj3os2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxo3khuckms2mivj3os2w.png" alt="Terminal output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We now have our hardhat development environment successfully configured. Let us now install the OpenZeppelin contracts package. This will give us access to the ERC721 contracts (the standard for NFTs) as well as a few helper libraries that we will encounter later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @openzeppelin/contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we want to share our project’s code publicly (on a website like GitHub), we wouldn’t want to share sensitive information like our private key, our Etherscan API key, or our Alchemy URL (don’t worry if some of these words don’t make sense to you yet). Therefore, let us install another library called dotenv.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations! We are now in a good place to start developing our smart contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the Smart Contract
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqqc80ajsxmmuruou4d9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqqc80ajsxmmuruou4d9.png" alt="Solidity"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this section, we are going to write a smart contract in Solidity that allows anyone to mint a certain number of NFTs by paying the required amount of ether + gas.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;contracts&lt;/code&gt; folder of your project, create a new file called &lt;code&gt;NFTCollectible.sol&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We will be using Solidity v8.0. Our contract will inherit from OpenZeppelin’s &lt;code&gt;ERC721Enumerable&lt;/code&gt; and &lt;code&gt;Ownable&lt;/code&gt; contracts. The former has a default implementation of the ERC721 (NFT) standard in addition to a few helper functions that are useful when dealing with NFT collections. The latter allows us to add administrative privileges to certain aspects of our contract.&lt;/p&gt;

&lt;p&gt;In addition to the above, we will also use OpenZeppelin’s &lt;code&gt;SafeMath&lt;/code&gt; and &lt;code&gt;Counters&lt;/code&gt; libraries to safely deal with unsigned integer arithmetic (by preventing overflows) and token IDs respectively.&lt;/p&gt;

&lt;p&gt;This is what the skeleton of our contract looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
contract NFTCollectible is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIds;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Storage constants and variables
&lt;/h3&gt;

&lt;p&gt;Our contract needs to keep track of certain variables and constants. For this tutorial, we will be defining the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Supply&lt;/strong&gt;: The maximum number of NFTs that can be minted in your collection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price&lt;/strong&gt;: The amount of ether required to buy 1 NFT.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum number of mints per transaction&lt;/strong&gt;: The upper limit of NFTs that you can mint at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Base Token URI&lt;/strong&gt;: The IPFS URL of the folder containing the JSON metadata.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this tutorial, we will set 1–3 as constants. In other words, we won’t be able to modify them once the contract has been deployed. We will write a setter function for &lt;code&gt;baseTokenURI&lt;/code&gt; that will allow the contract’s owner (or deployer) to change the base URI as and when required.&lt;/p&gt;

&lt;p&gt;Right under the &lt;code&gt;_tokenIds&lt;/code&gt; declaration, add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint public constant MAX_SUPPLY = 100;
uint public constant PRICE = 0.01 ether;
uint public constant MAX_PER_MINT = 5;

string public baseTokenURI;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that I’ve used all caps for the constants. Feel free to change the values for the constants based on your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Constructor
&lt;/h3&gt;

&lt;p&gt;We will set the &lt;code&gt;baseTokenURI&lt;/code&gt; in our constructor call. We will also call the parent constructor and set the name and symbol for our NFT collection.&lt;/p&gt;

&lt;p&gt;Our constructor, therefore, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor(string memory baseURI) ERC721("NFT Collectible", "NFTC") {
     setBaseURI(baseURI);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reserve NFTs function
&lt;/h3&gt;

&lt;p&gt;As the creator of the project, you probably want to reserve a few NFTs of the collection for yourself, your team, and for events like giveaways.&lt;/p&gt;

&lt;p&gt;Let’s write a function that allows us to mint a certain number of NFTs (in this case, ten) for free. Since anyone calling this function only has to pay gas, we will obviously mark it as &lt;code&gt;onlyOwner&lt;/code&gt; so that only the owner of the contract will be able to call it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function reserveNFTs() public onlyOwner {
     uint totalMinted = _tokenIds.current();
     require(
        totalMinted.add(10) &amp;lt; MAX_SUPPLY, "Not enough NFTs"
     );
     for (uint i = 0; i &amp;lt; 10; i++) {
          _mintSingleNFT();
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We check the total number of NFTs minted so far by calling &lt;code&gt;tokenIds.current()&lt;/code&gt;. We then check if there are enough NFTs left in the collection for us to reserve. If yes, we proceed to mint 10 NFTs by calling &lt;code&gt;_mintSingleNFT&lt;/code&gt; ten times.&lt;/p&gt;

&lt;p&gt;It is in the &lt;code&gt;_mintSingleNFT&lt;/code&gt; function that the real magic happens. We will look into this a little later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Base Token URI
&lt;/h3&gt;

&lt;p&gt;Our NFT JSON metadata is available at this IPFS URL: &lt;code&gt;ipfs://QmZbWNKJPAjxXuNFSEaksCJVd1M6DaKQViJBYPK2BdpDEP/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When we set this as the base URI, OpenZeppelin’s implementation automatically deduces the URI for each token. It assumes that token 1’s metadata will be available at &lt;code&gt;ipfs://QmZbWNKJPAjxXuNFSEaksCJVd1M6DaKQViJBYPK2BdpDEP/1&lt;/code&gt;, token 2’s metadata will be available at &lt;code&gt;ipfs://QmZbWNKJPAjxXuNFSEaksCJVd1M6DaKQViJBYPK2BdpDEP/2&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;(Please note that there is no &lt;code&gt;.json&lt;/code&gt; extension to these files)&lt;/p&gt;

&lt;p&gt;However, we need to tell our contract that the &lt;code&gt;baseTokenURI&lt;/code&gt; variable that we defined is the base URI that the contract must use. To do this, we override an empty function called &lt;code&gt;_baseURI()&lt;/code&gt; and make it return baseTokenURI.&lt;/p&gt;

&lt;p&gt;We also write an only owner function that allows us to change the &lt;code&gt;baseTokenURI&lt;/code&gt; even after the contract has been deployed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function _baseURI() internal 
                    view 
                    virtual 
                    override 
                    returns (string memory) {
     return baseTokenURI;
}

function setBaseURI(string memory _baseTokenURI) public onlyOwner {
     baseTokenURI = _baseTokenURI;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mint NFTs function
&lt;/h3&gt;

&lt;p&gt;Let us now turn our attention to the main mint NFTs function. Our users and customers will call this function when they want to purchase and mint NFTs from our collection.&lt;/p&gt;

&lt;p&gt;Since they’re sending ether to this function, we have to mark it as &lt;code&gt;payable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We need to make three checks before we allow the mint to take place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There are enough NFTs left in the collection for the caller to mint the requested amount.&lt;/li&gt;
&lt;li&gt;The caller has requested to mint more than 0 and less than the maximum number of NFTs allowed per transaction.&lt;/li&gt;
&lt;li&gt;The caller has sent enough ether to mint the requested number of NFTs.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function mintNFTs(uint _count) public payable {
     uint totalMinted = _tokenIds.current();
     require(
       totalMinted.add(_count) &amp;lt;= MAX_SUPPLY, "Not enough NFTs!"
     );
     require(
       _count &amp;gt; 0 &amp;amp;&amp;amp; _count &amp;lt;= MAX_PER_MINT, 
       "Cannot mint specified number of NFTs."
     );
     require(
       msg.value &amp;gt;= PRICE.mul(_count), 
       "Not enough ether to purchase NFTs."
     );
     for (uint i = 0; i &amp;lt; _count; i++) {
            _mintSingleNFT();
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mint Single NFT function
&lt;/h3&gt;

&lt;p&gt;Let’s finally take a look at the private &lt;code&gt;_mintSingleNFT()&lt;/code&gt; function that’s being called whenever we (or a third party) want to mint an NFT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function _mintSingleNFT() private {
      uint newTokenID = _tokenIds.current();
      _safeMint(msg.sender, newTokenID);
      _tokenIds.increment();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what is happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We get the current ID that hasn’t been minted yet.&lt;/li&gt;
&lt;li&gt;We use the &lt;code&gt;_safeMint()&lt;/code&gt; function already defined by OpenZeppelin to assign the NFT ID to the account that called the function.&lt;/li&gt;
&lt;li&gt;We increment the token IDs counter by 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The token ID is 0 before any mint has taken place.&lt;/p&gt;

&lt;p&gt;When this function is called for the first time, &lt;code&gt;newTokenID&lt;/code&gt; is 0. Calling &lt;code&gt;safeMint()&lt;/code&gt; assigns NFT with ID 0 to the person who called the contract function. The counter is then incremented to 1.&lt;/p&gt;

&lt;p&gt;The next time this function is called, &lt;code&gt;_newTokenID&lt;/code&gt; has value 1. Calling &lt;code&gt;safeMint()&lt;/code&gt; assigns NFT with ID 1 to the person who… I think you get the gist.&lt;/p&gt;

&lt;p&gt;Note that we don’t need to explicitly set the metadata for each NFT. Setting the base URI ensures that each NFT gets the correct metadata (stored in IPFS) assigned automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting all tokens owned by a particular account
&lt;/h3&gt;

&lt;p&gt;If you plan on giving any sort of utility to your NFT holders, you would want to know which NFTs from your collection each user holds.&lt;/p&gt;

&lt;p&gt;Let’s write a simple function that returns all IDs owned by a particular holder. This is made super simple by ERC721Enumerable‘s &lt;code&gt;balanceOf&lt;/code&gt; and &lt;code&gt;tokenOfOwnerByIndex&lt;/code&gt; functions. The former tells us how many tokens a particular owner holds, and the latter can be used to get all the IDs that an owner owns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function tokensOfOwner(address _owner) 
         external 
         view 
         returns (uint[] memory) {
     uint tokenCount = balanceOf(_owner);
     uint[] memory tokensId = new uint256[](tokenCount);
     for (uint i = 0; i &amp;lt; tokenCount; i++) {
          tokensId[i] = tokenOfOwnerByIndex(_owner, i);
     }

     return tokensId;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Withdraw balance function
&lt;/h3&gt;

&lt;p&gt;All the effort we’ve put in so far would go to waste if we are not able to withdraw the ether that has been sent to the contract.&lt;/p&gt;

&lt;p&gt;Let us write a function that allows us to withdraw the contract’s entire balance. This will obviously be marked as &lt;code&gt;onlyOwner&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function withdraw() public payable onlyOwner {
     uint balance = address(this).balance;
     require(balance &amp;gt; 0, "No ether left to withdraw");
     (bool success, ) = (msg.sender).call{value: balance}("");
     require(success, "Transfer failed.");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final Contract
&lt;/h3&gt;

&lt;p&gt;We’re done with the smart contract. This is what it looks like. (By the way, if you haven’t already, delete the &lt;code&gt;Greeter.sol&lt;/code&gt; file.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract NFTCollectible is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIds;

    uint public constant MAX_SUPPLY = 100;
    uint public constant PRICE = 0.01 ether;
    uint public constant MAX_PER_MINT = 5;

    string public baseTokenURI;

    constructor(string memory baseURI) ERC721("NFT Collectible", "NFTC") {
        setBaseURI(baseURI);
    }

    function reserveNFTs() public onlyOwner {
        uint totalMinted = _tokenIds.current();

        require(totalMinted.add(10) &amp;lt; MAX_SUPPLY, "Not enough NFTs left to reserve");

        for (uint i = 0; i &amp;lt; 10; i++) {
            _mintSingleNFT();
        }
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    function setBaseURI(string memory _baseTokenURI) public onlyOwner {
        baseTokenURI = _baseTokenURI;
    }

    function mintNFTs(uint _count) public payable {
        uint totalMinted = _tokenIds.current();

        require(totalMinted.add(_count) &amp;lt;= MAX_SUPPLY, "Not enough NFTs left!");
        require(_count &amp;gt;0 &amp;amp;&amp;amp; _count &amp;lt;= MAX_PER_MINT, "Cannot mint specified number of NFTs.");
        require(msg.value &amp;gt;= PRICE.mul(_count), "Not enough ether to purchase NFTs.");

        for (uint i = 0; i &amp;lt; _count; i++) {
            _mintSingleNFT();
        }
    }

    function _mintSingleNFT() private {
        uint newTokenID = _tokenIds.current();
        _safeMint(msg.sender, newTokenID);
        _tokenIds.increment();
    }

    function tokensOfOwner(address _owner) external view returns (uint[] memory) {

        uint tokenCount = balanceOf(_owner);
        uint[] memory tokensId = new uint256[](tokenCount);

        for (uint i = 0; i &amp;lt; tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }

    function withdraw() public payable onlyOwner {
        uint balance = address(this).balance;
        require(balance &amp;gt; 0, "No ether left to withdraw");

        (bool success, ) = (msg.sender).call{value: balance}("");
        require(success, "Transfer failed.");
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying the contract locally
&lt;/h2&gt;

&lt;p&gt;Let us now make preparations to deploy our contract to the Rinkeby test network by simulating it in a local environment.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;scripts&lt;/code&gt; folder, create a new file called &lt;code&gt;run.js&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { utils } = require("ethers");

async function main() {
    const baseTokenURI = "ipfs://QmZbWNKJPAjxXuNFSEaksCJVd1M6DaKQViJBYPK2BdpDEP/";

    // Get owner/deployer's wallet address
    const [owner] = await hre.ethers.getSigners();

    // Get contract that we want to deploy
    const contractFactory = await hre.ethers.getContractFactory("NFTCollectible");

    // Deploy contract with the correct constructor arguments
    const contract = await contractFactory.deploy(baseTokenURI);

    // Wait for this transaction to be mined
    await contract.deployed();

    // Get contract address
    console.log("Contract deployed to:", contract.address);

    // Reserve NFTs
    let txn = await contract.reserveNFTs();
    await txn.wait();
    console.log("10 NFTs have been reserved");

    // Mint 3 NFTs by sending 0.03 ether
    txn = await contract.mintNFTs(3, { value: utils.parseEther('0.03') });
    await txn.wait()

    // Get all token IDs of the owner
    let tokens = await contract.tokensOfOwner(owner.address)
    console.log("Owner has tokens: ", tokens);

}

main()
    .then(() =&amp;gt; process.exit(0))
    .catch((error) =&amp;gt; {
        console.error(error);
        process.exit(1);
    });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is some Javascript code that utilizes the &lt;code&gt;ethers.js&lt;/code&gt; library to deploy our contract, and then call functions of the contract once it has been deployed.&lt;/p&gt;

&lt;p&gt;Here is the series of what’s going on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We get the address of the deployer/owner (us)&lt;/li&gt;
&lt;li&gt;We get the contract that we want to deploy.&lt;/li&gt;
&lt;li&gt;We send a request for the contract to be deployed and wait for a miner to pick this request and add it to the blockchain.&lt;/li&gt;
&lt;li&gt;Once mined, we get the contract address.&lt;/li&gt;
&lt;li&gt;We then call public functions of our contract. We reserve 10 NFTs, mint 3 NFTs by sending 0.03 ETH to the contract, and check the NFTs owned by us. Note that the first two calls require gas (because they’re writing to the blockchain) whereas the third simply reads from the blockchain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s give this a run locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat run scripts/run.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all goes well, you should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhzzt1fuie5kazs7keth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhzzt1fuie5kazs7keth.png" alt="Terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the contract to Rinkeby
&lt;/h2&gt;

&lt;p&gt;To deploy our contract to Rinkeby, we will need to set up a few things.&lt;/p&gt;

&lt;p&gt;First, we will need an RPC URL that will allow us to broadcast our contract creation transaction. We will use Alchemy for this. &lt;a href="https://alchemy.com/?a=d7d81950c0" rel="noopener noreferrer"&gt;Create an Alchemy account here and then proceed to create a free app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuo67wyts22m72tg0jca0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuo67wyts22m72tg0jca0.png" alt="Alchemy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure that the network is set to &lt;em&gt;Rinkeby&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Once you’ve created an app, go to your &lt;a href="https://dashboard.alchemyapi.io/" rel="noopener noreferrer"&gt;Alchemy dashboard&lt;/a&gt; and select your app. This will open a new window with a View Key button on the top right. Click on that and select the HTTP URL.&lt;/p&gt;

&lt;p&gt;Acquire some fake Rinkeby ETH from the faucet &lt;a href="https://faucet.rinkeby.io/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. For our use case, 0.5 ETH should be more than enough. Once you’ve acquired this ETH, open your Metamask extension and get the private key for the wallet containing the fake ETH (you can do this by going into &lt;em&gt;Account Details&lt;/em&gt; in the 3-dots menu near the top-right).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not share your URL and private key publicly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will use the &lt;code&gt;dotenv&lt;/code&gt; library to store the aforementioned variables as environment variables and will not commit them to our repository.&lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;.env&lt;/code&gt; and store your URL and private key in the following format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API_URL = "&amp;lt;--YOUR ALCHEMY URL HERE--&amp;gt;"
PRIVATE_KEY = "&amp;lt;--YOUR PRIVATE KEY HERE--&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, replace your &lt;code&gt;hardhat.config.js&lt;/code&gt; file with the following contents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require("@nomiclabs/hardhat-waffle");
require('dotenv').config();

const { API_URL, PRIVATE_KEY } = process.env;

// 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) =&amp;gt; {
  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.4",
  defaultNetwork: "rinkeby",
  networks: {
    rinkeby: {
      url: API_URL,
      accounts: [PRIVATE_KEY]
    }
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re almost there! Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat run scripts/run.js --network rinkeby
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should give you output very similar to what you got earlier, except that this has been deployed to the real blockchain.&lt;/p&gt;

&lt;p&gt;Make a note of the contract address. Ours was 0x355638a4eCcb777794257f22f50c289d4189F245.&lt;/p&gt;

&lt;p&gt;You can check this contract out on Etherscan. Go to Etherscan and type in the contract address. You should see something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepkue9thxys1en06js0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepkue9thxys1en06js0a.png" alt="Etherscan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing our NFTs on OpenSea
&lt;/h2&gt;

&lt;p&gt;Believe it or not, our NFT collection is now already available on OpenSea without us having to upload it explicitly. Go to &lt;a href="https://testnets.opensea.io/" rel="noopener noreferrer"&gt;testnets.opensea.io&lt;/a&gt; and search for your contract address.&lt;/p&gt;

&lt;p&gt;This is what our collection looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt2is6221squm6ysokec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt2is6221squm6ysokec.png" alt="Scrappy Squirrels on Opensea"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying our contract on Etherscan
&lt;/h2&gt;

&lt;p&gt;We have come a LONG way in this article but there is one final thing we’d like to do before we go.&lt;/p&gt;

&lt;p&gt;Let’s verify our contract on etherscan. This will allow your users to see your contract’s code and ensure that there is no funny business going on. More importantly, verifying your code will allow your users to connect their Metamask wallet to etherscan and mint your NFTs from etherscan itself!&lt;/p&gt;

&lt;p&gt;Before we can do this, we will need an Etherscan API key. Sign up for a free account &lt;a href="https://etherscan.io/apis" rel="noopener noreferrer"&gt;here&lt;/a&gt; and access your API keys &lt;a href="https://etherscan.io/myapikey" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s add this API key to our &lt;code&gt;.env&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ETHERSCAN_API = "&amp;lt;--YOUR ETHERSCAN API KEY--&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hardhat makes it really simple to verify our contract on Etherscan. Let’s install the following package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @nomiclabs/hardhat-etherscan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, make adjustments to &lt;code&gt;hardhat.config.js&lt;/code&gt; so it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-etherscan");
require('dotenv').config();

const { API_URL, PRIVATE_KEY, ETHERSCAN_API } = process.env;

// 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) =&amp;gt; {
  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.4",
  defaultNetwork: "rinkeby",
  networks: {
    rinkeby: {
      url: API_URL,
      accounts: [PRIVATE_KEY]
    }
  },
  etherscan: {
    apiKey: ETHERSCAN_API
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, run the following two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat clean

npx hardhat verify --network rinkeby DEPLOYED_CONTRACT_ADDRESS "BASE_TOKEN_URI"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case, the second command looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hardhat verify --network rinkeby 0x355638a4eCcb777794257f22f50c289d4189F245 "ipfs://QmZbWNKJPAjxXuNFSEaksCJVd1M6DaKQViJBYPK2BdpDEP/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnjhqrzpkkmckxkn3fe3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnjhqrzpkkmckxkn3fe3.png" alt="Terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if you visit your contract’s Rinkeby Etherscan page, you should see a small green tick next to the Contract tab. More importantly, your users will now be able to connect to web3 using Metamask and call your contract’s functions from Etherscan itself!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdcygoamhlno0zhlvtnf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdcygoamhlno0zhlvtnf.png" alt="Etherscan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try this out yourself.&lt;/p&gt;

&lt;p&gt;Connect the account that you used to deploy the contract and call the &lt;code&gt;withdraw&lt;/code&gt; function from etherscan. You should be able to transfer the 0.03 ETH in the contract to your wallet. Also, ask one of your friends to connect their wallet and mint a few NFTs by calling the &lt;code&gt;mintNFTs&lt;/code&gt; function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We now have a deployed smart contract that lets users mint NFTs from our collection. An obvious next step would be to build a web3 app that allows our users to mint NFTs directly from our website. This will be the subject of a future tutorial.&lt;/p&gt;

&lt;p&gt;If you’ve reached this far, congratulations! You are on your way to becoming a master Solidity and blockchain developer. We’ve covered some complex concepts in this article and coming this far is truly incredible. We’re proud. :)&lt;/p&gt;

&lt;p&gt;We would love to take a look at your collection. Come say hi to us on our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;. Also, if you liked our content, we would be super grateful if you tweet about us, follow us(@ScrappyNFTs and @Rounak_Banik), and invite your circle to our Discord. Thank you for your support!&lt;/p&gt;

&lt;p&gt;Final code repository: &lt;a href="https://github.com/rounakbanik/nft-collectible-contract" rel="noopener noreferrer"&gt;https://github.com/rounakbanik/nft-collectible-contract&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Scrappy Squirrels&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.scrappysquirrels.co/" rel="noopener noreferrer"&gt;Scrappy Squirrels&lt;/a&gt; is a collection of 10,000+ randomly generated NFTs on the Ethereum Blockchain. Scrappy Squirrels are meant for buyers, creators, and developers who are completely new to the NFT ecosystem.&lt;/p&gt;

&lt;p&gt;The community is built around learning about the NFT revolution, exploring its current use cases, discovering new applications, and finding members to collaborate on exciting projects with.&lt;/p&gt;

&lt;p&gt;Join our community here: &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;https://discord.gg/8UqJXTX7Kd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>blockchain</category>
      <category>tutorial</category>
      <category>solidity</category>
    </item>
    <item>
      <title>Working with NFT Metadata, IPFS, and Pinata</title>
      <dc:creator>Rounak Banik</dc:creator>
      <pubDate>Thu, 18 Nov 2021 16:50:54 +0000</pubDate>
      <link>https://forem.com/rounakbanik/working-with-nft-metadata-ipfs-and-pinata-3ieh</link>
      <guid>https://forem.com/rounakbanik/working-with-nft-metadata-ipfs-and-pinata-3ieh</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1378sjgjblut105ikbpm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1378sjgjblut105ikbpm.png" alt="Pinata"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article serves as a sequel to my tutorial on &lt;a href="https://dev.to/rounakbanik/create-generative-nft-art-with-rarities-1n6f"&gt;creating generative NFT art&lt;/a&gt;. If you haven’t read it, I suggest you do so. I assume your system is already set up (with Python and required packages), and you have used the &lt;a href="https://github.com/rounakbanik/generative-art-nft" rel="noopener noreferrer"&gt;generative-art-nft&lt;/a&gt; library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxohh1lzo9bgbm7nj4b4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxohh1lzo9bgbm7nj4b4l.png" alt="Mekaverse NFTs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the previous tutorial, you learned how to create a generative art collection with custom rarities. If you followed along with your custom artwork (or used my samples), you should now have a collection of PNG images, and a metadata CSV file that contains information on traits for each image. &lt;/p&gt;

&lt;p&gt;This is a great first step towards creating an NFT collection but we still have a long way to go. The images that we generated exist on our local systems and are not really accessible to anyone. The metadata we generated is ideal for analyzing on Excel but is not in a format that adheres to standards (and by extension, cannot be used by an NFT marketplace platform like OpenSea).&lt;/p&gt;

&lt;p&gt;I will be addressing these issues in this article. More specifically, we will show you how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload Images to IPFS&lt;/li&gt;
&lt;li&gt;Generate compliant JSON NFT metadata&lt;/li&gt;
&lt;li&gt;Upload metadata files to IPFS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don’t worry if some of these words don’t make sense right now. I will be explaining them as and when required.&lt;/p&gt;

&lt;h2&gt;
  
  
  How NFT Minting Works
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo2jufo45pbfcn96agssz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo2jufo45pbfcn96agssz.png" alt="Mekaverse NFTs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To understand why we’re doing what we’re doing, we need to understand how NFT minting works. If you know this already, feel free to skip this section.&lt;/p&gt;

&lt;p&gt;Let’s say you want to mint a collection of 10,000 NFTs. What does that really mean?&lt;/p&gt;

&lt;p&gt;This means that you’re writing some code (called a smart contract) that tells the blockchain to initialize a table for you. This table stores ownership and metadata information about your NFTs. More specifically, each row of the table consists of the following information:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Token Identifier (or ID)&lt;/li&gt;
&lt;li&gt;The Owner of the Token&lt;/li&gt;
&lt;li&gt;The Metadata associated with the token &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is an example table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firah15b0q4na6os3h1au.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firah15b0q4na6os3h1au.png" alt="Table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see here that the &lt;code&gt;ID&lt;/code&gt; is nothing but a unique positive integer that identifies a particular NFT. The &lt;code&gt;Owner&lt;/code&gt; column stores the addresses associated with each NFT’s holder. Finally, the &lt;code&gt;Metadata&lt;/code&gt; is a column that may contain data &lt;em&gt;of&lt;/em&gt; the NFT or &lt;em&gt;about&lt;/em&gt; the NFT. &lt;/p&gt;

&lt;p&gt;It is possible to store the entire image in the &lt;code&gt;Metadata&lt;/code&gt; column of the table. However, &lt;strong&gt;storing data on a blockchain is expensive&lt;/strong&gt;. To give you some context, our collection of 10,000 squirrels occupies a disk space of 600 MB. If we wanted to store 600 MB worth of data on the Ethereum blockchain, it would cost us &lt;strong&gt;$1 million dollars.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is clearly not a great option. Therefore, in most cases, instead of storing data &lt;em&gt;of&lt;/em&gt; the NFT, we instead simply store data &lt;em&gt;about&lt;/em&gt; the NFT. This data (or metadata) is stored in a format called JSON. If you don’t know what JSON is, don’t worry about it. For our purposes, think of them like Python dictionaries (encapsulated in {}) that you encountered in the previous article to define layers.&lt;/p&gt;

&lt;p&gt;This JSON file needs to have information about the NFT such as its name, description, image URL, attributes, etc. In order to make sure that everyone in the ecosystem (including NFT marketplaces like OpenSea) understands what’s in our JSON files, we need to format them in a way that is compliant with the standards. In our case, we will use the &lt;a href="https://docs.opensea.io/docs/metadata-standards" rel="noopener noreferrer"&gt;standards recommended by OpenSea&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a JSON metadata file for a sample NFT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-nft.json

{   
   "description": "Friendly OpenSea Creature",      
   "image": "https://opensea-prod.appspot.com/puffs/3.png",  
   "name": "Dave Starbelly",   
   "attributes": [
       { "trait_type": "Base", "value": "Starfish" },      
       { "trait_type": "Eyes", "value": "Big" },      
       { "trait_type": "Mouth","value": "Surprised" },
   ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Storing metadata in this format on the blockchain is still very expensive. Hence, we add an additional layer of abstraction, and upload this JSON to the cloud as well and simply store a URL pointing to the JSON file.&lt;/p&gt;

&lt;p&gt;Therefore, at the end of the data, all you’re storing on the blockchain is &lt;em&gt;&lt;a href="https://mywebsite.com/my-nft.json" rel="noopener noreferrer"&gt;https://mywebsite.com/my-nft.json&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To summarize, here is what we need to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload all our images online and get a URL associated with each image. (This URL will go into our metadata).&lt;/li&gt;
&lt;li&gt;Generate a separate JSON file for each image containing metadata in the standard shown above (Image URL, attributes/traits, name, etc.)&lt;/li&gt;
&lt;li&gt;Upload all the JSON files to the cloud and get a URL associated with each JSON file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Uploading Images to IPFS
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qlnglbcvo70cugw0ukx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qlnglbcvo70cugw0ukx.png" alt="Uploads"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Uploading images to the internet is pretty simple. We’re sure you must have used a service like Google Drive, GitHub, or AWS to upload folders to the cloud.&lt;/p&gt;

&lt;p&gt;While uploading images to such centralized services (AWS, Google Drive, your own server, etc.) would work, &lt;strong&gt;it would not be a very good idea.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why not? For two reasons, mainly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Centralized Storage tends to be location based
&lt;/h3&gt;

&lt;p&gt;Imagine you upload an image of a dog (called dog.jpeg) to a centralized storage service. Your dog image would then be available by accessing a URL (something like &lt;em&gt;&lt;a href="https://mystorage.com/dog.jpeg" rel="noopener noreferrer"&gt;https://mystorage.com/dog.jpeg&lt;/a&gt;&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;However, it is very easy to swap this image for another. I could upload another image with the same name (dog.jpeg) that replaces the original image.&lt;/p&gt;

&lt;p&gt;Now, if I visited the same URL as before (&lt;em&gt;&lt;a href="https://mystorage.com/dog.jpeg" rel="noopener noreferrer"&gt;https://mystorage.com/dog.jpeg&lt;/a&gt;&lt;/em&gt;), I will see a different image. You can see why this is not ideal in NFT world. People spend thousands of dollars on NFTs and they would be pissed if you simply replaced an avatar with extremely rare traits with something else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Centralized Storage can be taken down
&lt;/h3&gt;

&lt;p&gt;Let’s say you upload an image to a Google Drive or AWS. If you removed the image from these services or the services themselves shut down, the URL pointing to the image would break. Therefore, it is very easy to pull the rug if your images and data exist on a centralized storage service.&lt;/p&gt;

&lt;p&gt;For these reasons, almost every serious NFT project uses a service called &lt;strong&gt;IPFS (or Interplanetary File System).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;IPFS is a peer-to-peer filesharing system that is decentralized, uses content-based addressing, and is secure.&lt;/p&gt;

&lt;p&gt;If none of the words above make sense, don’t worry. All you need to know is this:&lt;/p&gt;

&lt;h3&gt;
  
  
  IPFS used content-based addressing
&lt;/h3&gt;

&lt;p&gt;On the IPFS network, the address (URL) of a file will be dependent on the content of the file. If you change the contents of a file, then the address of the file on the IPFS will also change.&lt;/p&gt;

&lt;p&gt;Therefore, on the IPFS network, it is impossible to make one URL point to two different images.&lt;/p&gt;

&lt;h3&gt;
  
  
  IPFS never goes down
&lt;/h3&gt;

&lt;p&gt;Like most decentralized systems (like blockchains), IPFS never goes down. This means that once you’ve uploaded a file (or image) to IPFS, it will always be available as long as at least one node in the network has the file. This means that you cannot pull the rug at will. Nor is there a threat that the system will be shut down.&lt;/p&gt;

&lt;p&gt;We’re not going to go into the nitty-gritties of how IPFS works. If you’re interested, we suggest you give the following two articles a read:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://hackernoon.com/a-beginners-guide-to-ipfs-20673fedd3f" rel="noopener noreferrer"&gt;https://hackernoon.com/a-beginners-guide-to-ipfs-20673fedd3f&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ipfs.io/how-to/mint-nfts-with-ipfs/#a-short-introduction-to-nfts" rel="noopener noreferrer"&gt;https://docs.ipfs.io/how-to/mint-nfts-with-ipfs/#a-short-introduction-to-nfts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Uploading to IPFS is as easy as uploading to Google Drive, thanks to a service called &lt;a href="https://www.pinata.cloud/" rel="noopener noreferrer"&gt;Pinata&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.pinata.cloud/signin" rel="noopener noreferrer"&gt;Go to the Pinata website and create an account&lt;/a&gt;. It’s free if you’re uploading up to 1 GB of data.&lt;/p&gt;

&lt;p&gt;Once you have signed up, you will be taken to the Pin Manager window. Upload your folder using the interface. Once you’ve uploaded your folder, you will get a CID associated with it. It should look something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1lodkdedmqpiuqthu4f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1lodkdedmqpiuqthu4f.png" alt="Pinata screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This CID was generated based on the contents of the folder. If the contents of the folder change (an image removed, an image swapped with another of the same name, etc.), the CID will also change.&lt;/p&gt;

&lt;p&gt;For my folder, the CID is QmRvSoppQ5MKfsT4p5Snheae1DG3Af2NhYXWpKNZBvz2Eo.&lt;/p&gt;

&lt;p&gt;Therefore, the IPFS URL for this folder is ipfs://QmRvSoppQ5MKfsT4p5Snheae1DG3Af2NhYXWpKNZBvz2Eo.&lt;/p&gt;

&lt;p&gt;This URL will not open in a browser. In order to do that, you can use a HTTP URL of an IPFS gateway. Try visiting this link: &lt;a href="https://ipfs.io/ipfs/QmRvSoppQ5MKfsT4p5Snheae1DG3Af2NhYXWpKNZBvz2Eo/00001.png" rel="noopener noreferrer"&gt;https://ipfs.io/ipfs/QmRvSoppQ5MKfsT4p5Snheae1DG3Af2NhYXWpKNZBvz2Eo/00001.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will display an image that I named 00001.png and uploaded to my folder.&lt;/p&gt;

&lt;p&gt;Congratulations! That is all there is to uploading images on IPFS using Pinata. For the next step, you will need the CID. Keep that handy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate compliant NFT JSON metadata
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89zlcoejff2kcjb3dis0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89zlcoejff2kcjb3dis0.png" alt="Opensea"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we uploaded our images to IPFS, we now have IPFS URLs for each and every image.&lt;/p&gt;

&lt;p&gt;Our next task is to create a JSON file for each image and populate it with data (including the image URL) in a format that is compliant and understandable by platforms like NFT marketplaces.&lt;/p&gt;

&lt;p&gt;Fortunately, the &lt;a href="https://github.com/rounakbanik/generative-art-nft" rel="noopener noreferrer"&gt;generative-art-nft library&lt;/a&gt; does all the heavy lifting for you.&lt;/p&gt;

&lt;p&gt;Check that the &lt;code&gt;metadata.py&lt;/code&gt; file exists in the repository. If not, clone the latest version of the repository and transfer the &lt;em&gt;assets&lt;/em&gt; and &lt;em&gt;output&lt;/em&gt; folders into the new repo.&lt;/p&gt;

&lt;p&gt;Open the metadata.py file in a text editor. Don’t worry if you don’t understand the code here. The only things you need to fill are &lt;code&gt;BASE_NAME&lt;/code&gt;, &lt;code&gt;BASE_URL&lt;/code&gt;, and &lt;code&gt;BASE_JSON&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In line 17, replace &lt;code&gt;←Your CID Code →&lt;/code&gt; with the CID code of the image folder you uploaded to Pinata.&lt;/p&gt;

&lt;p&gt;In line 18, add a base name for your NFTs. This is strictly optional. If you do not add a base name, your NFTs will be named 0, 1, 2, and so on. If you put a base name like &lt;em&gt;“Scrappy Squirrel #”&lt;/em&gt;, your NFTs will be named &lt;em&gt;Scrappy Squirrel #0&lt;/em&gt;, &lt;em&gt;Scrappy Squirrel #1&lt;/em&gt;, etc.&lt;/p&gt;

&lt;p&gt;Finally, in line 22, add a description for your collection. Like the base name, this is optional.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F64qehqbu8v11zfi5hdcp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F64qehqbu8v11zfi5hdcp.png" alt="Sample inputs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, open a Terminal in this folder and run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python metadata.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program will ask you the edition to generate metadata for. In our case, it was &lt;strong&gt;v2&lt;/strong&gt;, so that’s what we enter.&lt;/p&gt;

&lt;p&gt;It should take less than 15 seconds to generate 10,000+ JSON files. All these files will be conveniently available in a json folder within your edition folder.&lt;/p&gt;

&lt;p&gt;That’s it for step 2!&lt;/p&gt;

&lt;h2&gt;
  
  
  Upload JSON metadata files to IPFS
&lt;/h2&gt;

&lt;p&gt;The third step is probably the simplest. Just like you did with the images, upload your &lt;code&gt;json&lt;/code&gt; folder to Pinata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations! You now have a very good setup for your NFT metadata. The last and most important step is to write a smart contract that can use this metadata and assign ownership to various holders. That is, however, a topic of a future article. Stay tuned!&lt;/p&gt;

&lt;p&gt;If you have any questions or would like us to add additional features to this library, please reach out to us on our Discord server, or drop them in the comments below. We will try to address as many of them as possible.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;About Scrappy Squirrels&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Scrappy Squirrels is a collection of 10,000+ randomly generated NFTs. Scrappy Squirrels are meant for buyers, creators, and developers who are completely new to the NFT ecosystem.&lt;/p&gt;

&lt;p&gt;The community is built around learning about the NFT revolution, exploring its current use cases, discovering new applications, and finding members to collaborate on exciting projects with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Join our community here:&lt;/strong&gt; &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;https://discord.gg/8UqJXTX7Kd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>blockchain</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Create Generative NFT Art with Rarities</title>
      <dc:creator>Rounak Banik</dc:creator>
      <pubDate>Sun, 14 Nov 2021 12:10:22 +0000</pubDate>
      <link>https://forem.com/rounakbanik/create-generative-nft-art-with-rarities-1n6f</link>
      <guid>https://forem.com/rounakbanik/create-generative-nft-art-with-rarities-1n6f</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclaimer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article was originally published on the &lt;a href="https://medium.com/scrappy-squirrels" rel="noopener noreferrer"&gt;Scrappy Squirrels Medium Publication&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflaypyvklwocyzusvc1s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflaypyvklwocyzusvc1s.png" alt="Cryptopunks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Marquee NFT projects like &lt;a href="https://www.larvalabs.com/cryptopunks" rel="noopener noreferrer"&gt;Cryptopunks&lt;/a&gt; and &lt;a href="https://boredapeyachtclub.com/#/" rel="noopener noreferrer"&gt;Bored Ape Yacht Club&lt;/a&gt; have generated hundreds of millions of dollars in revenue, and have made several of their owners millionaires.&lt;/p&gt;

&lt;p&gt;What the aforementioned projects (and most other successful NFT projects today) have been in common is that they are PFP projects. This means that they usually are a collection of 10,000+ avatars where each avatar is unique and has a set of traits.&lt;/p&gt;

&lt;p&gt;In this tutorial, I will show you how to generate a collection like this with custom rarities. I will be using a library created by the Scrappy Squirrels team to accomplish this. At the end of this tutorial, you will have generated your own custom avatar collection with associated metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Python and pip installed on your computer
&lt;/h3&gt;

&lt;p&gt;Our library is written in Python so you will need to have this installed on your computer. You will also need pip which will install important packages for us. Go to &lt;a href="https://www.python.org/downloads/" rel="noopener noreferrer"&gt;this website&lt;/a&gt; and download the latest version of Python&lt;/p&gt;

&lt;h3&gt;
  
  
  An artist (preferred but not required)
&lt;/h3&gt;

&lt;p&gt;You will need an artist who knows their way around digital art to create your own custom collection. However, this is not required to follow this tutorial. I will be providing you with test images to play around with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scrappy Squirrels Collection
&lt;/h2&gt;

&lt;p&gt;As part of this tutorial, I will walk you through the process of creating the &lt;a href="https://www.scrappysquirrels.co/" rel="noopener noreferrer"&gt;Scrappy Squirrels&lt;/a&gt; NFTs, a real project that my team has launched. This tutorial (and every subsequent one) has been created as part of our roadmap goals to make NFTs and blockchains more accessible to people. Do check out our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; for more details. (Go on, I will wait :))&lt;/p&gt;

&lt;p&gt;The squirrels have been generated using over 90 traits. Here are a few samples.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom6no8grc7mgug1g8q4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom6no8grc7mgug1g8q4h.png" alt="Scrappy Squirrels Sample"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Generation Process
&lt;/h2&gt;

&lt;p&gt;The squirrels that you see above were generated by stacking PNG images on top of one another. Although no blue-chip NFT projects describe how they generate their art, we are certain that this is what they do too. Almost every NFT avatar that you see is a set of stacked PNG images (which makes the claims that they are just JPEGs false. Checkmate, NFT critics).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuek6k6ne1tow6u9ftxm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuek6k6ne1tow6u9ftxm.png" alt="Stacking PNG images to create generative art"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Starting from the top right, if you stack every trait image clockwise, one after the other, you will end up with the image in the center. Here are few things to note:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each trait image (and the final squirrel avatar) is of exactly the same dimension.&lt;/li&gt;
&lt;li&gt;Apart from the background trait (which is the first trait), every other trait image has a transparent background.&lt;/li&gt;
&lt;li&gt;The trait images must be stacked in order to get the correct squirrel avatar (i.e clockwise from top-right).&lt;/li&gt;
&lt;li&gt;The trait images are drawn in such a way that their positioning makes sense with respect to all other traits.&lt;/li&gt;
&lt;li&gt;We can swap any trait with another trait of the same category (for instance, a red shirt for a blue shirt). Therefore, in this case, if we had 10 traits for each category of trait, we could theoretically produce 100 million distinct squirrels.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Therefore, the artist’s job is to create multiple images of various trait categories. You can have as many or as few trait categories as you want. Do keep in mind though that the number of possible combinations increases exponentially with the number of traits categories.&lt;/p&gt;

&lt;p&gt;In the Scrappy Squirrels project, we created 8 trait categories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8g9eiprekd8i55g0978.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8g9eiprekd8i55g0978.png" alt="Trait Folders"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each trait category had a varying number of trait images. For instance, we had 11 different shirts to work with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fplg3a78px7kog0rgyvni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fplg3a78px7kog0rgyvni.png" alt="Folder containing clothes artwork"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, it’s your turn. You will need to decide on trait categories that you want to work with and generate trait images for each category. Make sure they satisfy the conditions mentioned above (should be of the same dimension, should be correctly positioned, etc). Also, make sure you name the trait images appropriately. What you name your image is what will appear in the metadata file.&lt;/p&gt;

&lt;p&gt;Once you are done with this, we are now ready to use the library to generate our collection automatically! If you are not an artist (or do not have access to one), don’t worry! We have some sample images that you can play around with.&lt;/p&gt;

&lt;h4&gt;
  
  
  NOTE:
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;At present, the library is only capable of handling PNG images. We will be adding support for other media types soon.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Download repository and install required packages
&lt;/h2&gt;

&lt;p&gt;Our &lt;a href="https://github.com/rounakbanik/generative-art-nft" rel="noopener noreferrer"&gt;generative art library&lt;/a&gt; is available for free on GitHub. Go ahead and clone it.&lt;/p&gt;

&lt;p&gt;Once you’ve cloned the repository, open your Terminal or Command Prompt, and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install Pillow pandas progressbar2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this command will install three important Python packages that our library depends on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pillow&lt;/strong&gt;: An image-processing library that will help us stack trait images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pandas&lt;/strong&gt;: A data analysis library that will help us in generating and saving our image metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressbar&lt;/strong&gt;: A library that will tell us about progress and ETA when the image generation takes place.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Add your custom assets
&lt;/h2&gt;

&lt;p&gt;In the &lt;em&gt;generative-art-nft&lt;/em&gt; repository that you downloaded, you will see that there is an assets folder. If you have your custom trait artwork available with you, go ahead and replace the contents of this folder with your assets. In our case, our assets folder had 8 subfolders representing categories named appropriately (see above), and each subfolder had trait images of that particular category.&lt;/p&gt;

&lt;p&gt;If you do not have custom artwork, leave the default assets folder as is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure the config.py file
&lt;/h2&gt;

&lt;p&gt;This is the last (and perhaps, the most important step) before we can generate our avatar collection. Open the &lt;em&gt;config.py&lt;/em&gt; file and fill it up according to the instructions below.&lt;/p&gt;

&lt;p&gt;The config file consists of a single Python variable called CONFIG. CONFIG is a Python list (encapsulated by []). It contains a list of trait categories in the order that they need to be stacked. &lt;strong&gt;The order here is extremely important.&lt;/strong&gt; Here is a sample configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONFIG = [
    {
        'id': 1,
        'name': 'background',
        'directory': 'Background',
        'required': True,
        'rarity_weights': None,
    },
    {
        'id': 2,
        'name': 'body',
        'directory': 'Body',
        'required': True,
        'rarity_weights': 'random'
    },
    {
        'id': 3,
        'name': 'eyes',
        'directory': 'Expressions',
        'required': True,
        'rarity_weights': None
    },
    {
        'id': 4,
        'name': 'head_gear',
        'directory': 'Head Gear',
        'required': False,
        'rarity_weights': None
    },
    {
        'id': 5,
        'name': 'clothes',
        'directory': 'Shirt',
        'required': False,
        'rarity_weights': None
    },
    {
        'id': 6,
        'name': 'held_item',
        'directory': 'Misc',
        'required': True,
        'rarity_weights': None,
    },
    {
        'id': 7,
        'name': 'hands',
        'directory': 'Hands',
        'required': True,
        'rarity_weights': None,
    },
    {
        'id': 8,
        'name': 'wristband',
        'directory': 'Wristband',
        'required': False,
        'rarity_weights': [100, 5, 5, 15, 5, 5, 15, 15, 5, 1]
    },
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each trait category is represented as a Python dictionary (encapsulated by {}). All that needs to be done is define these trait category dictionaries in order in the CONFIG list.&lt;/p&gt;

&lt;p&gt;A trait category dictionary has 5 keys that it needs. These are &lt;em&gt;id&lt;/em&gt;, &lt;em&gt;name&lt;/em&gt;, &lt;em&gt;directory&lt;/em&gt;, &lt;em&gt;required&lt;/em&gt;, and &lt;em&gt;rarity_weights&lt;/em&gt;. When creating a new layer (or replacing an existing one), make sure all these keys are defined.&lt;/p&gt;

&lt;p&gt;This is how you go about assigning value to each key.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;id:&lt;/strong&gt; The layer number. For instance, if the body is the second trait category (or layer) that needs to be stacked, it will have an id of 2. Please note that layers must still be defined in the correct order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;name:&lt;/strong&gt; The name of the trait category. This can be anything you choose it to be. It will appear in the metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;directory:&lt;/strong&gt; The name of the folder inside assets that contain images of that particular trait category.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;required:&lt;/strong&gt; If this category is required for every image. Certain trait categories (like background, body, and eyes) must appear in every avatar whereas certain other categories (like headgear, wrist band, or clothes) can be optional. We strongly recommend that you set the first layer’s &lt;em&gt;required&lt;/em&gt; value to true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rarity_weights:&lt;/strong&gt; This category will determine how common (or rare) your traits are going to be. Check the next section for more details.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Configuring rarity weights
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;rarity_weights&lt;/em&gt; key can take three values: None, ‘random’, or a Python list. Let’s explore each value one by one.&lt;/p&gt;

&lt;h4&gt;
  
  
  None
&lt;/h4&gt;

&lt;p&gt;If you set the rarity_weights value to &lt;em&gt;None&lt;/em&gt;, each trait will be assigned an equal weight. Therefore, if you have 5 traits, each trait will appear in roughly 20% of the avatars.&lt;/p&gt;

&lt;p&gt;In case &lt;em&gt;required&lt;/em&gt; is False, it will be equally likely to not get that particular trait at all. In the previous case, if the &lt;em&gt;required&lt;/em&gt; property was set to false, then each trait would appear in roughly 16.6% of the avatars. Another 16.6% of avatars would not have that particular trait at all.&lt;/p&gt;

&lt;h4&gt;
  
  
  ‘random’
&lt;/h4&gt;

&lt;p&gt;Setting &lt;em&gt;rarity_weights&lt;/em&gt; to ‘random’ (note the parenthesis) would randomly assign weights to each category. &lt;strong&gt;We strongly recommend you do not use this feature. Always resort to either equal or custom user-defined rarity.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  List
&lt;/h4&gt;

&lt;p&gt;This is probably the most common way of assigning rarity weights.&lt;/p&gt;

&lt;p&gt;The first thing to do is to go to your trait category folders and sort the trait images by Name. For instance, sorting the Wristbands folder will yield this for us:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw6c1bbc5fz1919psgk5u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw6c1bbc5fz1919psgk5u.png" alt="Wristbands folder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that we have 9 different kinds of wristbands. Now, we need to define a Python list (encapsulated by []) where each number represents a weight assigned to a particular trait in ascending order.&lt;/p&gt;

&lt;p&gt;If &lt;em&gt;required&lt;/em&gt; is set to True, then the number of weights should be equal to the number of traits for that category. If &lt;em&gt;required&lt;/em&gt; is set to False, then the number of weights should be equal to the number of traits plus one.&lt;/p&gt;

&lt;p&gt;In our case, if wristbands were required, we would define nine weights in the list and if it wasn’t required, we would define ten weights. In the latter case, the first weight would be the weight associated with not having the wristband at all.&lt;/p&gt;

&lt;p&gt;Let’s take a look at the &lt;em&gt;rarity_weights&lt;/em&gt; we defined for Wristbands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[100, 5, 5, 15, 5, 5, 15, 15, 5, 1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since wristbands aren’t required, we have set ten weights (nine plus one). The first weight is the weight associated with not having a wristband at all. The second weight is associated with the &lt;em&gt;Cheetah&lt;/em&gt; band, the third weight is associated with the &lt;em&gt;Giraffe band&lt;/em&gt;, and so on. Note the alphabetical order here.&lt;/p&gt;

&lt;p&gt;The higher the weight, the more common a particular trait is. For instance, &lt;em&gt;Cheetah&lt;/em&gt; has a weight of 5, and not having a band has a weight of 100. This means that having a &lt;em&gt;Cheetah&lt;/em&gt; band is 20 times rarer than not having a band at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating the collection
&lt;/h2&gt;

&lt;p&gt;Once you’ve configured the &lt;em&gt;config.py&lt;/em&gt; file, it is now time to generate your collection. Open up your Terminal (or Command Prompt) and navigate to the &lt;em&gt;generative-art-nft&lt;/em&gt; folder (using the cd command).&lt;/p&gt;

&lt;p&gt;Now, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python nft.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this command will initiate the image generation program. It will first check that the config.py file is valid. Next, it will tell you about the total number of distinct possible combinations.&lt;/p&gt;

&lt;p&gt;It will then ask you how many avatars you’d like to create. I suggest creating 20% more than what you want to create so you have plenty left over even after the removal of duplicates. In our case, we chose to create 12,000 avatars although we wanted 10,000. It will then ask you to name the collection, and will then begin the generation process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5bz8gtdpx7i6eb4unue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5bz8gtdpx7i6eb4unue.png" alt="Terminal output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It took me approximately 30 minutes to generate 11,957 avatars (after removing duplicates). The images and their related metadata will be available in the output folder.&lt;/p&gt;

&lt;p&gt;The images folder will look something like this (note that this is only a sample and not the final squirrels that we generated).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frh89tldv30bdofs36e48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frh89tldv30bdofs36e48.png" alt="Generated Scrappy Squirrels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The metadata file is a CSV file that you can import into Excel and analyze (for things like which trait is the rarest, which trait combination is the most common, avatar rarity ranking, etc.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And there you have it! You have generated your very own avatar collection.&lt;/p&gt;

&lt;p&gt;So are we now ready to launch the next big NFT project? Not quite. You will need to upload these images to IPFS, allow your users to mint them into NFTs, and create community and buzz around your project.&lt;/p&gt;

&lt;p&gt;Some of these words sounding alien? Don’t worry. We will be launching tutorials on the aforementioned topics very soon. Do join our &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; for the latest updates.&lt;/p&gt;

&lt;p&gt;If you have any questions or would like us to add additional features to this library, please reach out to us on our Discord server, or drop them in the comments below. We will try to address as many of them as possible.&lt;br&gt;
Until next time!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;About Scrappy Squirrels&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Scrappy Squirrels is a collection of 10,000+ randomly generated NFTs. Scrappy Squirrels are meant for buyers, creators, and developers who are completely new to the NFT ecosystem.&lt;/p&gt;

&lt;p&gt;The community is built around learning about the NFT revolution, exploring its current use cases, discovering new applications, and finding members to collaborate on exciting projects with.&lt;/p&gt;

&lt;p&gt;Join our community here: &lt;a href="https://discord.gg/8UqJXTX7Kd" rel="noopener noreferrer"&gt;https://discord.gg/8UqJXTX7Kd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
