DEV Community

Cover image for Hands-On Hardhat Part-3 (Write Your Custom Own Token)
azattech
azattech

Posted on

Hands-On Hardhat Part-3 (Write Your Custom Own Token)

Hands-On Hardhat Part-3

In the previous parts, we set up the project, wrote and deployed our first contract. In this part, we‘re gonna learn the ERC-20 standard and write our ERC-20 standard token.

EIP

To understand ERC, we should first understand EIP (Ethereum Improvement Proposals). As you know, Ethereum and other blockchain networks are community-based. At this point, anyone who wants to improve the network can present a proposal. For example, if Vitalik Buterin wants to do something for the Ethereum network, he presents a proposal or if you want to do something for the Ethereum network, you have to present a proposal for it. So it is like Bitcoin Improvement Proposals (BIP). Simply put, EIPs are drafts of ideas for new features or design functions put forward by any community member to be reviewed by stakeholders in the Ethereum ecosystem.

However, these proposals have fundamental standards and you must clearly state them; Abstract, Motivation, Specification, Rationale, Backwards Compatibility, Test Cases, Reference Implementation, Security Considerations.
For now, I’m not gonna detail all of them I’m just giving an example of a proposal.

And also EIPs have 3 types:

  1. Standards Track EIP
    • Core
    • Networking
    • Interface
    • ERC
  2. Meta EIP
  3. Informational EIP

for furthermore details please check out this

In other words, after you have prepared tight documentation, you can present your proposal. But, not everything happens at once because the EIP has its processes.

The life cycle of an EIP involves distinct stages. An 'idea' must first be 'drafted,' allowing the proposal to be put into the EIP repository by an EIP Editor. The proposal then enters the 'review' stage, during which an author requests a date for peer review. An Editor assigns the proposal a 'last-call' status at the end of which it must be granted a 'final' state, typically after 14 days.
Any EIP can be pushed into the 'stagnant' state if left without progression for six months. A high level of technical competence is required for an EIP to be submitted, and a successful ERC is one that has not only entered a state of 'final' but is also in use by projects.

EIP Process

Image description

ERC20

ERC stands for “Ethereum Request for Comments”, it is a specialized subtype of EIP, relating to application-level changes that do not affect the Ethereum protocol itself.

It was introduced by Fabian Vogelsteller in 2015-11-19. It was an assigned GitHub issue number 20 and named “ERC20. The ERC20 is a proposal that intends to standardize how a token contract should be defined, how we interact with such a token contract, and how these contracts interact with each other.

→ ERC20 standard is for fungible tokens created using the Ethereum blockchain.
→ A fungible token is one that is interchangeable with another token.
→This standard interface allows any tokens on Ethereum to be re-used by other applications: from wallets to decentralized exchanges.

There are many ERCs such as ERC721 vs ERC1155 I’m not gonna explain all of them to see all ERCs please check out here.

As I mentioned before every proposal have fundamental standards and you must clearly state them, so according to EIP-20’s documentation ERC20 token’s Specification has to provide at least the following functions and events:

Image description

Before the coding let me explain a few things, for now I’m not gonna implement ERC20Interface. Because I don’t wanna reinvent the wheel. Probably my implementation will have some vulnerabilities and don't gas saving. So there are some implementations by various teams I mean I’m just gonna use the wheel, for today I will continue with Openzeppelin.

OpenZeppelin

OpenZeppelin is an open-source framework to build secure smart contracts it provides a complete suite of security products and audit services to build, manage, and inspect all aspects of software development and operations for decentralized applications.

Now let’s start coding;

First, we need to add openzeppelin dependency, to add this dependency open your terminal and run this command line;

npm install @openzeppelin/contracts
Enter fullscreen mode Exit fullscreen mode

Second, in your project structure, open contracts package, create named MyOwnToken.sol file and then import openzeppelin ERC20 contract.

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
Enter fullscreen mode Exit fullscreen mode

Lastly, write your MyOwnToken and inherit it from ERC20.

your code should follows as;

Image description

Your constructor takes a named initialSupply parameter and inherited ERC20 contract which takes 2 parameters token name and token symbol. And also in the constructor’s scope there is a _mint() function which sets how many tokens will be mint. Now maybe some of you get confused why we did like that? When you open ERC20.sol file you see it is inherited from IERC20 interface which is I mentioned above that ERC20 standard all codes that we must write come by openzeppelin. If we didn’t inherit MyOwnToken from ERC20 we would have to write functions that ERC20 has. And probably the codes that we wrote would not be efficient and safety as openzeppelin’s codes.

Image description

That’a all folks we wrote our custom token.

Next, before writing tests and deploying our smart contract there is one more thing that I want to mention it. For example you want to your token should burnable or has some access controls etc. and you’re struggling to write them, there is an openzeppelin playground, you choose options and the code of your token is written automatically just not for ERC20 also ERC721 and ERC1155 exist. To checkout playground visit here.

Image description

Now, let’s deploy and write our contract test.
First, open your terminal write compile command line;

npx hardhat compile
Enter fullscreen mode Exit fullscreen mode

After compiling (I assume that you didn’t get any errors), create a named deployMyOwnToken.js file inside scripts package write your script codes for deploying;

Image description

After your script writing is done open 2 terminal one is to launch node and another is for deploying.

in first terminal write

npx hardhat node
Enter fullscreen mode Exit fullscreen mode

in the another one write;

npx hardhat run ./scripts/deployMyOwnToken.js --network localhost
Enter fullscreen mode Exit fullscreen mode

Image description

So far so so good until now.

Let’s write unit tests. Create a named my-own-token-test.js file inside test package and files code follows as;

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("MyOwnToken", function () {
  let totalSupply = "10000000000000000000000"; // 10000 * 1e18
  let MyOwnToken, myOwnToken, owner, address1, address2, address3;

  beforeEach(async function () {
    /**
     * A ContractFactory in ethers.js is an abstraction used to deploy new smart contracts,
     * so MyOwnToken here is a factory for instances of our token contract.
     */
    MyOwnToken = await ethers.getContractFactory("MyOwnToken");

    /** A Signer in ethers.js is an object that represents an Ethereum account.
     * It's used to send transactions to contracts and other accounts.
     * */
    [owner, address1, address2, ...address3] = await ethers.getSigners();

    myOwnToken = await MyOwnToken.deploy(totalSupply);
  });

  describe("Deployment", function () {
    it("Should assign the total supply of tokens to the owner", async function () {
      const ownerBalance = await myOwnToken.balanceOf(owner.address);
      expect(await myOwnToken.totalSupply()).to.equal(ownerBalance);
    });
  });

  describe("Transactions", function () {
    it("Should transfer tokens between accounts", async function () {
      const ownerBalance = await myOwnToken.balanceOf(owner.address);

      // Transfer 2000 tokens from owner to addr1
      await myOwnToken.transfer(address1.address, 2000);
      const address1Balance = await myOwnToken.balanceOf(address1.address);
      expect(address1Balance).to.equal(2000);

      // Transfer 2000 tokens from address1 to address2
      // We use .connect(signer) to send a transaction from another account
      await myOwnToken.connect(address1).transfer(address2.address, 2000);
      const address2Balance = await myOwnToken.balanceOf(address2.address);
      expect(address2Balance).to.equal(2000);
    });

    it("Should fail if sender doesn’t have enough tokens", async function () {
      const initialOwnerBalance = await myOwnToken.balanceOf(owner.address);

      // Try to send 1 token from address1 (0 tokens) to owner (1000000 tokens).
      // `require` will evaluate false and revert the transaction.
      await expect(
        myOwnToken.connect(address1).transfer(owner.address, 1)
      ).to.be.revertedWith("ERC20: transfer amount exceeds balance");

      // Owner balance shouldn't have changed.
      expect(await myOwnToken.balanceOf(owner.address)).to.equal(
        initialOwnerBalance
      );
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

specify your testfolder and run test command line;

npx hardhat test ./test/my-own-token-test.js
Enter fullscreen mode Exit fullscreen mode

Image description

Well, it's done see you in the next parts.

References:
https://eips.ethereum.org/EIPS/eip-1
https://eips.ethereum.org/EIPS/eip-20
https://dev.to/yakult/a-concise-hardhat-tutorial-part-2-writing-erc20-2jpm

Connect with me on social media: Twitter && LinkedIn && Github

Top comments (1)

Collapse
 
isslerman profile image
Marcos Issler

Thank you for sharing. Very good content.