Fungible Tokens: Minting

Fungible Tokens: Minting

Wednesday Feb 21, 2024

Fungible token with minting mechanism

In this course we will extend the ERC20 token we created in the previous course to include a minting mechanism. This course will also form the basis for our course on creating a faucet dApp (decentralized app).

Why would we want to mint tokens?

Honestly, there are not many really good reasons for minting tokens as in most cases minting tokens will lead to inflation. The best way to combat inflation is to have some kind of burning mechanism in place to combat the inflation. You can learn about burning tokens in our ERC20 burning course.

However, if you want to create a faucet dApp where users can get some of your tokens for free, then you will need to have a minting function in your token contract.

What is a minting mechanism?

Minting is the process of creating new tokens. This is done by calling a function in our smart contract that will add tokens to the total supply. If your token is being used for a use-case other than the faucet, I highly recommend you do not implement a minting mechanism or if you have to, make sure that:

  1. You lock down the minting mechanism using the onlyOwner modifier in conjunction with the Ownable contract from OpenZeppelin.
  2. You have a burning mechanism in place to combat inflation.

How do we implement a minting mechanism?

This course assumes you have set up the development environment as outlined in the Smart Contracts Introduction Course. If you haven't done so, it's recommended that you do so before continuing.

Setting up our project

Before we start, we need to create a few files.

In the contracts folder:

  1. Create a new file called ERC20Mint.sol.

In the test folder:

  1. Create a new file called ERC20Mint.ts.

Writing our smart contract

In the ERC20Mint.sol file, add the following code and read the comments:

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.18;
3
4import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5import "@openzeppelin/contracts/access/Ownable.sol";
6
7contract ERC20TokenMint is ERC20, Ownable {
8    /**
9     * This is our constructor.
10     * @param owner The address of the owner
11     * @param name The name of the token
12     * @param symbol The symbol of the token
13     * @param totalSupply The total supply of the token
14     */
15    constructor(
16        address owner,
17        string memory name,
18        string memory symbol,
19        uint256 totalSupply
20    ) ERC20(name, symbol) {
21        _mint(owner, totalSupply);
22    }
23
24    /**
25     * This is our minting function that can be called by anyone.
26     * It will send new tokens to the caller's address.
27     * @param amount The amount of tokens to mint.
28     */
29    function mint(uint256 amount) public {
30        _mint(msg.sender, amount);
31    }
32}

We are inheriting from ERC20 and Ownable. We are also calling the _mint function to mint tokens to the owner's address.

In this case, we allow our mint to be called by anyone since the purpose of this contract will be to allow users to get our tokens from a faucet.

Writing our tests

In the ERC20Mint.ts file, add the following code and read the comments:

1import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
2import { expect } from "chai";
3import { ethers } from "hardhat";
4
5describe("ERC20-mint", function () {
6  // We deploy the contract and mint 100 tokens to the owner
7  async function deployERC20MintFixture() {
8    const [owner, account_1, account_2] = await ethers.getSigners();
9    const ERC20Mint = await ethers.getContractFactory("ERC20TokenMint");
10    const erc20Mint = await ERC20Mint.deploy(
11      owner.address,
12      "Test Token",
13      "TST",
14      "100000000000000000000"
15    );
16    return { erc20Mint, owner, account_1, account_2 };
17  }
18
19  describe("Deployment Tests", function () {
20    it("Should set the right owner", async function () {
21      const { erc20Mint, owner } = await loadFixture(deployERC20MintFixture);
22      expect(await erc20Mint.owner()).to.equal(owner.address);
23    });
24
25    it("Should mint 100 tokens to the owner", async function () {
26      const { erc20Mint, owner } = await loadFixture(deployERC20MintFixture);
27      expect((await erc20Mint.balanceOf(owner.address)).toString()).to.equal(
28        ethers.parseEther("100")
29      );
30    });
31  });
32
33  describe("Test Minting Function", function () {
34    it("Should mint 10 tokens to the owner", async function () {
35      const { erc20Mint, owner } = await loadFixture(deployERC20MintFixture);
36      await erc20Mint.mint(ethers.parseEther("10"));
37      expect((await erc20Mint.balanceOf(owner.address)).toString()).to.equal(
38        ethers.parseEther("110")
39      );
40    });
41
42    it("Should mint 10 tokens to account_1 (non-owner)", async function () {
43      const { erc20Mint, account_1 } = await loadFixture(
44        deployERC20MintFixture
45      );
46      await erc20Mint.connect(account_1).mint(ethers.parseEther("10"));
47      expect(
48        (await erc20Mint.balanceOf(account_1.address)).toString()
49      ).to.equal(ethers.parseEther("10"));
50      expect((await erc20Mint.totalSupply()).toString()).to.equal(
51        ethers.parseEther("110")
52      );
53    });
54  });
55});

Let's have a look at the tests we have written:

  1. We created a deployment fixture that will deploy our contract and mint 100 tokens to the owner.

  2. We test that the deployment has been done correctly.

  3. We created a describe block for our minting function called Test Minting Function. This block contains two tests:

    1. We test that the owner can mint tokens to their own address.
    2. We test that a non-owner can mint tokens to their own address (This is important for our faucet dApp).

In both cases we check that the balance of the address has increased by 10 tokens and that the total supply has increased by 10 tokens.

Compiling and running our smart contracts

To run our tests and make sure everything works, run the following command in your terminal:

1npx hardhat test

Now that we have our tests, we can deploy our ERC20 mintable token to the test network.

Deploying our smart contract

To deploy our smart contract, we need to create a deployment script. In the scripts folder, create a new file called deploy-erc20mint.ts and add the following code:

1// We require the Hardhat Runtime Environment explicitly here. This is optional
2// but useful for running the script in a standalone fashion through `node <script>`.
3//
4// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
5// will compile your contracts, add the Hardhat Runtime Environment's members to the
6// global scope, and execute the script.
7import hre from "hardhat";
8
9const config = {
10  owner_address: "your address here",
11  token_name: "Test Token",
12  token_symbol: "TT",
13  total_supply: "100000000000000000000", // 100 tokens (18 decimal places)
14}
15
16async function main() {
17  const { owner_address, token_name, token_symbol, total_supply } = config;
18
19  console.log(`Deploying token ${token_name}. Owner: ${owner_address}...`);
20
21  const Token = await hre.ethers.getContractFactory("ERC20TokenMint");
22  const token = await Token.deploy(
23    owner_address,
24    token_name,
25    token_symbol,
26    total_supply
27  );
28
29  console.log(`Deployed token. Owner: ${owner_address}`);
30
31  console.log("Waiting 1 minute for Etherscan to index the contract...");
32  await new Promise((r) => setTimeout(r, 60000));
33
34  console.log("Verifying contract...");
35  await hre.run("verify:verify", {
36    address: token.getAddress(),
37    constructorArguments: [
38      owner_address,
39      token_name,
40      token_symbol,
41      total_supply,
42    ],
43  });
44
45  console.log("Contract verified! 🎉");
46  console.log(
47    `Please find the verified contract on Etherscan: https://sepolia.etherscan.io/address/${token.getAddress()}`
48  );
49}
50
51// We recommend this pattern to be able to use async/await everywhere
52// and properly handle errors.
53main().catch((error) => {
54  console.error(error);
55  process.exitCode = 1;
56});

YES

You have successfully created your ERC20 mintable token!

To deploy it to the test net, you can run the following command:

1npx hardhat run scripts/deploy-erc20mint.ts --network sepolia

Conclusion

In this course we learned you learned how to create a mintable ERC20 token which will be the basis for our faucet dApp.