Skip to main content

Creating a ERC20 token

Introduction

In this tutorial, we will be creating a ERC20 token using the OpenZeppelin framework and HardHat. You should follow Chapter 2 to setup your environment.

What is ERC20?

ERC20 is a standard for tokens on the Ethereum blockchain. It defines a set of rules that all tokens must follow so that they are compatible with each other. For example, if you create a token that follows the ERC20 standard, you can interact with any other ERC20 token using the same functions and methods.

Creating a new file

Open the contracts folder and create a new file called MyToken.sol. This is where we will write our smart contract.

test/
├── contracts
│ ├── MyToken.sol #<--- create this file
├── scripts
├── test
├── hardhat.config.ts
├── package.json
├── tsconfig.json

Install VSCode extension for Solidity (optional)

If you are using VSCode, you can install the Solidity extension to get syntax highlighting and other features.

Solidity extension

Writing the smart contract

Defining the dependencies

Open the MyToken.sol file and paste the following code which will define the version of Solidity that we are using.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

Then we will import the openzeppelin-solidity/contracts/token/ERC20/ERC20.sol file using the import keyword which similar to import in TypeScript and Python.

import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

Keep in mind that since we installed the OpenZeppelin framework using NPM our solidity compiler will find the @openzeppelin/contracts/token/ERC20/ERC20.sol package in the node_modules folder. If you did not install the openzeppelin package, your solidity compiler will raise an error.

danger

Keep in mind that since we installed the OpenZeppelin framework using NPM our solidity compiler will find the @openzeppelin/contracts/token/ERC20/ERC20.sol package in the node_modules folder. If you did not install the openzeppelin package, your solidity compiler will raise an error.

import error

Defining the contract

Now we will define our contract. In this example, we will define a token called MSBD5017 token with symbol name M57 and a total supply of 1000 tokens.

contract MSBD5017Token is ERC20 {
constructor() ERC20("MSBD5017", "M57") {
_mint(msg.sender, 1000);
}
}

The first line of code tells the compiler that our contract is inheriting from the ERC20 contract from openzeppelin package.

contract MSBD5017Token is ERC20 {

The next line of code is the constructor of our contract and called super constructor of the ERC20 contract. The super constructor takes two parameters, the name of the token and the symbol of the token in this case MSBD5017 and M57.

constructor() ERC20("MSBD5017", "M57") {

The last line of code is the line that actually creates the token and assigns the total supply to the address that deployed the contract. In this case, we are assigning the total supply to the address that deployed the contract. Keep in mind that msg.sender is the address of the account that deployed the contract. And the reason why this is fixed supply token is that we do not provide any other way to mint a new token, so only the msg.sender have the token.

_mint(msg.sender, 1000);

That's it, we have created our first ERC20 token.

Complete code

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MSBD5017Token is ERC20 {
constructor() ERC20("MSBD5017", "M57") {
_mint(msg.sender, 1000);
}
}

Writing the test

Now we will write a test to test our smart contract. Open the test folder and create a new file called MyToken.test.ts.

test/
├── contracts
├── scripts
├── test
│ ├── MyToken.test.ts #<--- create this file
├── hardhat.config.ts
├── package.json
├── tsconfig.json

Defining the dependencies

We will first define the dependencies that we will need for our test.

import { expect } from "chai";
import { ethers } from "hardhat";

Writing the test

Now we will write the test. We will first deploy the contract and then we will test the contract.

describe("Given MyToken", function () {
it("Owner should get the initial supply", async function () {
const [owner] = await ethers.getSigners();
const MyToken = await ethers.getContractFactory("MSBD5017Token");
const myToken = await MyToken.deploy();
await myToken.deployed();
const ownerBalance = await myToken.balanceOf(owner.address);
expect(ownerBalance).to.equal(1000);
});
});

Explanation

The first line of code is the describe block which is used to group the tests. In this case, we are grouping the tests for the MyToken contract. We can put some useful message in the describe block to describe what we are testing.

describe("Given MyToken", function () {

The next line of code is the it block which is used to define a test.

it("Owner should get the initial supply", async function () {

The next line of code is the const [owner] = await ethers.getSigners(); which is used to get the list of accounts that are available in the network. Since we are running in a testing environment, we will use the default account provided by HardHat.

const [owner] = await ethers.getSigners();

The next line of code is the const MyToken = await ethers.getContractFactory("MSBD5017Token"); which is used to get the contract factory for the MSBD5017Token contract.

danger

The contract name must match the name of the contract in the MyToken.sol file.

const MyToken = await ethers.getContractFactory("MSBD5017Token");

The next line of code is the const myToken = await MyToken.deploy(); which is used to deploy the contract.

const myToken = await MyToken.deploy();

The next line of code is the await myToken.deployed(); which is used to wait for the contract to be deployed.

await myToken.deployed();

The next line of code is the const ownerBalance = await myToken.balanceOf(owner.address); which is used to get the balance of the owner.

const ownerBalance = await myToken.balanceOf(owner.address);

The next line of code is the expect(ownerBalance).to.equal(1000); which is used to test the balance of the owner.

expect(ownerBalance).to.equal(1000);

Running the test

Now we will run the test. Open the terminal and run the following command.

npx hardhat test
$

Deploying the contract

Get your private key

In this section, we will use MetaMask as your primary wallet. If you do not have MetaMask installed, please install it first.

First open the MetaMask metamask

Then click the Export Private Key button metamask

Then copy the private key

Create a .env file

We will first create a .env file in the root folder of the project so that we can store the private key of the account that we will use to deploy the contract.

Copy the following line and paste it in the .env file. Remember to replace the YOUR_PRIVATE_KEY with your own private key.

PK=YOUR_PRIVATE_KEY

Install dotenv package

Run the following command to install the dotenv package.

npm install dotenv

Modify the hardhat.config.ts file

tip

We are using Testnet for the rest of the chapter.

Replace the content of the hardhat.config.ts file with the following code.

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from "dotenv";

dotenv.config();

const config: HardhatUserConfig = {
solidity: "0.8.17",
networks: {
etherdata: {
url: "http://rpc.debugchain.net",
accounts: process.env.PK !== undefined ? [process.env.PK] : [],
},
},
};

export default config;
note

accounts: process.env.PK !== undefined ? [process.env.PK] : [], this line of code is used to get the private key from the .env file.

Modify the scripts/deploy.ts file

Replace the content of the scripts/deploy.ts file with the following code.

import { ethers } from "hardhat";

async function main() {
const Contract = await ethers.getContractFactory("MSBD5017Token");
const contract = await Contract.deploy();

await contract.deployed();
console.log("Contract deployed to:", contract.address);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
note

As you can see, we are using almost the same code as the test to deploy the contract.

Claim your Ether from the faucet

Go to our faucet website to claim your testing Ether.

faucet faucet

Deploy the contract

Run the following command to deploy the contract.

npx hardhat run scripts/deploy.ts --network etherdata
$

tip

Congratulations! You have successfully deployed your first smart contract. You can download the source code of this tutorial from here.