2. Minting from your new contract and improvements

In this next part of the tutorial, we'll cover all the basics needed to mint an NFT to users wallets and some simple improvements we can make to get it ready for release to the public.

Let's start with minting.

📘

Section Prerequisites

If you haven't already, go back to the previous step in this tutorial and follow along to set up and deploy your own NFT contract.

Minting from your new NFT contract using Hardhat

Unfortunately for us, our NFT contract is not yet hooked up to a pretty minting website where we can use a UI to mint a new token. Fortunately for us, we can write a script that utilizes ethers.js and Hardhat to mimic the same behavior programmatically.

Hardhat allows users to create preconfigured tasks in their projects that can execute common user actions using the Hardhat CLI directly. We can actually move our earlier deploy.js script to be a task (since we will want to deploy new versions of the contract as we continue improving it) and introduce a new mint task to call the contract's mintTo() method.

To start, edit the replace the existing deploy.js file with a task-based implementation:

const { task } = require("hardhat/config");
const { getAccount } = require("./helpers");


task("check-balance", "Prints out the balance of your account").setAction(async function (taskArguments, hre) {
    const account = getAccount();
    console.log(`Account balance for ${account.address}: ${await account.getBalance()}`);
});

task("deploy", "Deploys the NFT.sol contract").setAction(async function (taskArguments, hre) {
    const nftContractFactory = await hre.ethers.getContractFactory("NFT", getAccount());
    const nft = await nftContractFactory.deploy();
    console.log(`Contract deployed to address: ${nft.address}`);
});

We've made quite a few changes here, but the overall logic is the same. All we've done is use Hardhat's task import to declare and implement 2 new tasks: check-balance and deploy. Check balance uses a getAccount() helper method which we will define shortly to fetch the account and make an async call getBalance(). Deploy works almost the same as the prior implementation.

Next, define some common helpers that we can reuse in later pieces of code. Create a new helpers.js file and add in 3 new helper methods:

const { ethers } = require("ethers");


// Helper method for fetching environment variables from .env
function getEnvVariable(key, defaultValue) {
    if (process.env[key]) {
        return process.env[key];
    }
    if (!defaultValue) {
        throw `${key} is not defined and no default value was provided`;
    }
    return defaultValue;
}

// Helper method for fetching a connection provider to the Ethereum network
function getProvider() {
    return ethers.getDefaultProvider(getEnvVariable("NETWORK", "rinkeby"), {
        alchemy: getEnvVariable("ALCHEMY_KEY"),
    });
}

// Helper method for fetching a wallet account using an environment variable for the PK
function getAccount() {
    return new ethers.Wallet(getEnvVariable("ACCOUNT_PRIVATE_KEY"), getProvider());
}

module.exports = {
    getEnvVariable,
    getProvider,
    getAccount,
}

This new helpers file moves common logic we will need to use a lot (like getting an environment variable or the account based on the PK we added in the .env file) and exports them as easy-to-use helpers.
Note: connection provider objects allow us to connect to Alchemy or other network providers. Read more about that in the ethers.js documentation

The getProvider() helper also lets us use other EVM networks (like Ethereum mainnet or Polygon) by optinally setting a NETWORK environment variable in .env.

Last, lets slightly modify the hardhat.config.js configuration file to import our newly defined tasks

/**
* @type import('hardhat/config').HardhatUserConfig
*/

require('dotenv').config();
require("@nomiclabs/hardhat-ethers");
require("./scripts/deploy.js");

const { ALCHEMY_KEY, ACCOUNT_PRIVATE_KEY } = process.env;

module.exports = {
   solidity: "0.8.0",
   defaultNetwork: "rinkeby",
   networks: {
    hardhat: {},
    rinkeby: {
      url: `https://eth-rinkeby.alchemyapi.io/v2/${ALCHEMY_KEY}`,
      accounts: [`0x${ACCOUNT_PRIVATE_KEY}`]
    },
    ethereum: {
      chainId: 1,
      url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_KEY}`,
      accounts: [`0x${ACCOUNT_PRIVATE_KEY}`]
    },
  },
}

We can now run our new tasks using the Hardhat CLI to quickly repeat common actions:

> npx hardhat
Hardhat version 2.7.0

Usage: hardhat [GLOBAL OPTIONS] <TASK> [TASK OPTIONS]

GLOBAL OPTIONS:

  --config              A Hardhat config file.
  --emoji               Use emoji in messages.
  --help                Shows this message, or a tasks help if its name is provided
  --max-memory          The maximum amount of memory that Hardhat can use.
  --network             The network to connect to.
  --show-stack-traces   Show stack traces.
  --tsconfig            A TypeScript config file.
  --verbose             Enables Hardhat verbose logging
  --version             Shows hardhats version.


AVAILABLE TASKS:

  check         Check whatever you need
  check-balance Prints out the balance of your account <----- THATS OUR NEW TASK
  clean         Clears the cache and deletes all artifacts
  compile       Compiles the entire project, building all artifacts
  console       Opens a hardhat console
  deploy        Deploys the NFT.sol contract  <----- THATS OUR NEW TASK
  flatten       Flattens and prints contracts and their dependencies
  help          Prints this message
  node          Starts a JSON-RPC server on top of Hardhat Network
  run           Runs a user-defined script after compiling the project
  test          Runs mocha tests

To get help for a specific task run: npx hardhat help [task]

> npx hardhat check-balance
...
Account balance for 0x6Ed80feA9f7cc8DB42F91919bF3E5990Be0AA64F: 496162367925141460

> npx hardhat deploy
...
Contract deployed to address: 0x61E815D04578B3E251d5Dca28b7CA99378dF66FE

Note: you might get a message from ethers.js that you are being throttled by Alchemy. Don't worry, ethers.js intelligently waits for your rate-limiting to end before continuing with the task execution.

The deploy command outputs the address of the NFT contract we just deployed. Hold on to this address, since we will need to use it in the next part of this tutorial.

Adding a minting task

Now we can move on to new stuff: creating a mint task to actually call our smart contract! Add a new mint.js file to your scripts folder and write the task:

const { task } = require("hardhat/config");
const { getContract } = require("./helpers");

task("mint", "Mints from the NFT contract")
.addParam("address", "The address to receive a token")
.setAction(async function (taskArguments, hre) {
    const contract = await getContract("NFT", hre);
    const transactionResponse = await contract.mintTo(taskArguments.address, {
        gasLimit: 500_000,
    });
    console.log(`Transaction Hash: ${transactionResponse.hash}`);
});

This new task, which takes in an --address CLI flag, gets an instance of the contract we deployed in the previous step and calls it's mintTo() function with the address we will pass in the command line. In order for this to work, we will need to do 2 more things:

  1. Set the address for the contract we deployed as an environment variable
  2. Implement a new helper -- getContract() in order to get an instance of the NFT contract

Adding a new environment variable
Open up the .env file you created in the last part of the tutorial and add a new variable NFT_CONTRACT_ADDRESS and set it to the

ALCHEMY_KEY = "alchemy-api-key"
ACCOUNT_PRIVATE_KEY = "private-key"
NETWORK="rinkeby"
NFT_CONTRACT_ADDRESS="0x61E815D04578B3E251d5Dca28b7CA99378dF66FE"

Adding the getContract() helper
Now we will need to implement a new helper for fetching contract instances that we can then make calls to directly. Here's what helper.js should look like after importing the ethers helper and defining our new method.

const { ethers } = require("ethers");
const { getContractAt } = require("@nomiclabs/hardhat-ethers/internal/helpers");


// Helper method for fetching environment variables from .env
function getEnvVariable(key, defaultValue) {
    if (process.env[key]) {
        return process.env[key];
    }
    if (!defaultValue) {
        throw `${key} is not defined and no default value was provided`;
    }
    return defaultValue;
}

// Helper method for fetching a connection provider to the Ethereum network
function getProvider() {
    return ethers.getDefaultProvider(getEnvVariable("NETWORK", "rinkeby"), {
        alchemy: getEnvVariable("ALCHEMY_KEY"),
    });
}

// Helper method for fetching a wallet account using an environment variable for the PK
function getAccount() {
    return new ethers.Wallet(getEnvVariable("ACCOUNT_PRIVATE_KEY"), getProvider());
}

// Helper method for fetching a contract instance at a given address
function getContract(contractName, hre) {
    const account = getAccount();
    return getContractAt(hre, contractName, getEnvVariable("NFT_CONTRACT_ADDRESS"), account);
}

module.exports = {
    getEnvVariable,
    getProvider,
    getAccount,
    getContract,
}

Note that we also need to export this new helper as part of module.exports in Lines 34 - 39. The very last step is importing the new mint.js file to our hardhat.config.js configuration so that it is picked up by Hardhat:

/**
* @type import('hardhat/config').HardhatUserConfig
*/

require('dotenv').config();
require("@nomiclabs/hardhat-ethers");
require("./scripts/deploy.js");
require("./scripts/mint.js");

const { ALCHEMY_KEY, ACCOUNT_PRIVATE_KEY } = process.env;

module.exports = {
   solidity: "0.8.0",
   defaultNetwork: "rinkeby",
   networks: {
    hardhat: {},
    rinkeby: {
      url: `https://eth-rinkeby.alchemyapi.io/v2/${ALCHEMY_KEY}`,
      accounts: [`0x${ACCOUNT_PRIVATE_KEY}`]
    },
    ethereum: {
      chainId: 1,
      url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_KEY}`,
      accounts: [`0x${ACCOUNT_PRIVATE_KEY}`]
    },
  },
}

We are now ready to go! To mint tokens, call our new mint task:

> npx hardhat mint --address 0xb9720BE63Ea8896956A06d2dEd491De125fD705E
========= NOTICE =========
Request-Rate Exceeded  (this message will not be repeated)

The default API keys for each service are provided as a highly-throttled,
community resource for low-traffic projects and early prototyping.

While your application will continue to function, we highly recommended
signing up for your own API keys to improve performance, increase your
request rate/limit and enable other perks, such as metrics and advanced APIs.

For more details: https://docs.ethers.io/api-keys/
==========================
Transaction Hash: 0x16ea832e486f115fb828dbec20e578a008675998256c807682d8cbdca17264d3

If you take that transaction hash over to Etherscan, you should see something like this:

This transaction record shows us who called the contract function, what the outcome was (Tokens Transferred) and how much the transaction cost the caller in both gas fees and cost to transact.

Summary

If you followed this part of the tutorial right, your project should be structured like this:

> tree -L 3 -I 'node_modules*|cache*'
.
├── LICENSE
├── README.md
├── artifacts
│   ├── @openzeppelin
│   │   └── contracts
│   ├── build-info
│   │   └── f179c78b6322d2fddc1e72511467aa46.json
│   └── contracts
│       └── NFT.sol
├── contracts
│   └── NFT.sol
├── hardhat.config.js
├── package-lock.json
├── package.json
└── scripts
    ├── deploy.js
    ├── helpers.js
    └── mint.js

8 directories, 10 files

This is now a fully functioning NFT item minted to your wallet and ready to be used. You can continue to call npx hardhat mint --address {address} in order to mint more tokens to your wallet. You can also call deploy again to deploy the contract another time.

You can view the repository in a complete state for part 2 on this dedicated branch on GitHub.

Next Steps

If you stop here, you already have a deployed NFT contract that can be easily minted from yourself. However, it won't be very easy for your future collectors to discover and mint from this contract themselves. Additionally, minting tokens is currently free, meaning you can't paid for your work. Most importantly, the tokens you mint are not associated with any metadata, so they can't be visually represented on OpenSea or on your dedicated website.

In the next part of the tutorial, we will go over assigning metadata to individual tokens as well as adding a flat price for minting from the contract.