How to create and deploy your ERC20 Token

Test and deploy with hardhat on Polygon mumbai Testnet and add it to uniswap

·

15 min read

Table of contents

No heading

No headings in the article.

Crytocurrencies has been around for sometime now and many people are getting into it. Most of them are only using this technology to trade online but very few are using it for development. If you are one of those who want to Learn how to create and deploy ERC20 token you are in the right place.

In this tuturial, we will be learning how to:

  1. Create ERC20 Token
  2. Test the smart contract and deploy to polygon mumbai Testnet
  3. Add the token into circulation. i.e Uniswap

All what we will be doing can be seen on my github. Click to clone the repository Let's get our hands dirty

  1. Create ERC20 Token We are going to be using REMIX to create our Token. REMIX is an IDE for writing smart contracts, easy to use and free.

Open remix.ethereum.org and let's get started. Select the contracts folder and create a new file called TWToken.sol

.sol is the extension for solidity files.

Remix.PNG

In the *Token.sol file, the first thing to do is to add the liscense-idenfier and the pragma to specify the solidity version.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

What ^0.8.0 means is that, this code will be compiled for versions that starts with 0.8. 0 upward.

We are going to be using ERC20Capped to create our ERC20 token. ERC20Capped is an extension of ERC20 that adds a cap to the supply of tokens. A Cap is the number of a token total supply.

ERC20Capped will have all the functions of ERC20 and with additional functions for us to use. We will have to import it Openzeppelin contracts which is a library of modular, reusable, secure smart contracts for the Ethereum network, written in Solidity.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

We also imported Ownable.sol from Openzeppelin. This will give us the access to onlyOwner. See explanation below.

Like i earlier mentioned, ERC20Capped is an extension of ERC20, so what is an ERC20 Token. It is a fungible token which means it can be exchanged with another token. All ERC20 tokens are interchangeable. To know more about ERC20 tokens check out What Is ERC-20 and What Does It Mean for Ethereum?.

Next. We declare a new contract TWToken and inherit ERC20Capped and Ownable from openzeppelin.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract TWToken is ERC20Capped, Ownable {

}

The contract before the name is solidity's way of declaring a new contract. The is is solidity's way of doing inheritance. This means that our contract TWToken is inheriting functions from ERC20Capped and Ownable.

ERC20Capped has a constructor method that receives a uint256 variable which is the cap. We also have access to the ERC20 constructors which allow us to specify the name and the symbol of the token.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract TWToken is ERC20Capped, Ownable {
 constructor(uint256 cap) ERC20("TWToken", "TWT") ERC20Capped(cap){}
}

constructor(uint256 cap) will force you to provide a value when deploying your contract.

Now, we need to mint the token, but ERC20Capped does not allow us to call a function in the constructor since ERC20Capped is immutable. This is because ERC20Capped has a cap value and calling the mint function in the constructor will not check to make sure the the mint function does not send a value larger than the cap value.

What we want is to be able to give ourselves some of our token on deployment, luckily for use, we can call ERC20._mint() in the constructor, but this will not check if the number of tokens we are sending to ourselves is less than the cap.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract TWToken is ERC20Capped, Ownable {
 constructor(uint256 cap) ERC20("TWToken", "TWT") ERC20Capped(cap){
     ERC20._mint(msg.sender, 10000*10**18);
 }
}

ERC20._mint(msg.sender, 10000*1018);** means, on deployment 10000TWT tokens should be send to our account.

10000*10**18 is the value in Wei

Now we are ready to compile our smart contract. Click on the compile icon on the side bar and click on Compile TWToken.sol

compile.PNG

After compiling, lets deploy our smart contract. Click on the deploy icon on the side bar and select the VM as javascriptVM, click on the the down arrow on the deploy field and enter 100000000000000000000000 wei which is equal to 100 000 eth. Click on the transact button

deploy.PNG

A new dropdown will be added below Deployed Contracts. Click on it and you will see all the inherited functions from ERC20Capped and Onwable. For now we will not use Ownable in this example but we will use it later when we want to send our tokens to other addresses. Ownable has onlyOwner modifier that will ensure that only the owner of the contract can call the method.

deployed.PNG

If you click on cap you will see the total number of our TWT token, we will never have more that the amount. If you click on totalSupply you will see the total number of TWT tokens that is in circulation.

Let me show you an example to cover the use of cap value and ownable First, create a function that takes and array of address. This function can be use as an airdrop to send out TWT tokens to users. The function should have the onlyOwner modifier which will ensure that the account calling the function is the owner of the contract.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract TWToken is ERC20Capped, Ownable {
 constructor(uint256 cap) ERC20("TWToken", "TWT") ERC20Capped(cap){
     ERC20._mint(msg.sender, 10000*10**18); //10,000 eth
 }

 function issueAirDrop(address payable[] memory _addresses) public onlyOwner {
     for(uint i = 0; i < _addresses.length; i++){
         _mint(_addresses[i], 90000*10**18); //90,000 eth
     }
 }
}

Compile and deploy the contract and provide 100000000000000000000000 as the cap when deploying.

airdrop.PNG

We have deployed our contract with 100,000 eth and on deployment we sent 10,000 eth to the owner's account so we are left with 90,000 eth. Copy an account from Account above different from the owner of the contract (usually the first account if you didn't change it). After copying the second account, select the first account so that we will be able to call the issueAirDrop() if not it will fail since onlyOwner is making sure that only the creator of the smart contract can call the issueAirDrop()

airdrop.PNG

Beside the issueAirDrop enter the address you want to send TWT token to. Since we are expecting an array, add ["Your_Address_goes_here"]. Replace Your_Address_goes_here with the address you copied and click on the button. The remaining 90,000 eth* will be sent to that address, but if you click on it again you will have an error. This is because we have reached out cap value*

cap.PNG

There are many other functions that you can call as you can see when you deployed the smart contract. You can read more on ERC20Capped.

  1. Test the smart contract and deploy to polygon mumbai Testnet

We are going to be using VSCodefor our project.

You should have Node.js installed if not install it.

Go to where you want to create the project on your computer and create a folder called TalkWebToken

We are going to be using Hardhat to do out test and deployment.

cd into the newly created folder and type code . and press enter to open the folder in VSCode IDE, Open the Terminal from VSCode and run npm init --yes after run npm install --save-dev hardhat. Wait for it to install. In the same directory, run npx hardhat

hardhat.PNG

Select:

Create a basic sample project and press enter > press enter > yes for .gitignore

Our project directory will be populated with default hardhat folders and files.

Now, lets install some packages that we will need. npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers

For more information check out the documentation of Hardhat.

Copy our smart contract from REMIX and create a File inside the Contracts folder called TWToken.sol and paste our smart contract in it.

contract.PNG

Install Openzeppelin so we can have access to the ERC20 contracts npm install @openzeppelin/contracts

a. Lets write the code to deploy our TWToken Inside the scripts folder, create a file called deploy.js and write the following lines of code

//we import the hardhat runtime environment from the hardhat package that we installed
const hre = require("hardhat");

//In order to use await in our main function, we added async to the function
async function main() {
    //We use ethers form hre to call the getContractFactory and pass the exact name of our token
    const TWToken = await hre.ethers.getContractFactory("TWToken");
    //We now have the contract factory of out smart contract, we can now use it to deploy our contract
    //Our contract is expecting a cap value on deployment. 100000 eth was is send when deploying
    //ethers.utils.parseEther("100000.0") will be explained below
    const twtoken = await TWToken.deploy(ethers.utils.parseEther("100000.0"))

    //we can now wait for our contract to the deployed
    await twtoken.deployed();

//Add this line so that we can see the contract address when we succesfully deploy our contract
    //We will use this address to import our token, so keep it somewhere
    console.log("TWToken deploy to ", twtoken.address);
}


//We call the main function which returns and observable
main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.log(error);
        process.exit(1)
    })

JavaScript has a mas normal it can compute and if we what to deploy our contract by passing a wei figure as an argument, it will fail when it is too large. So we use ethers.utils.parseEther("100000.0"). This takes eth and convert it into wei. For more info read ethers utility functions.

b. Lets compile our project In the terminal, run npx hardhat compile, this will run and create an artifact folder that has the abi of our contract. In simple terms ,an abi (Application Binary Interface) is a way to provide our smart contract functions to the user interface. We will not be building the UI in this example.

Once you contract has been successfully compiled, lets test it to make sure it is functioning as expected.

c. Testing our contract We will be using Chai to test our contract. In the test folder, create a file called twtoken.js.

We import expect from chai and ethers from hardhat

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

expect is use to verify the outcome we get from our contract to make sure that it is what we are expecting.

This is the content of twtoken.js, explanations in the comment

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

//To start writing the test, we wrap it in a describe
//We will be using ES6 syntax to declare a function
describe("TWToken contract", () => {
    //we decalre a global variable that will hold the contractFactory of our token
    let twtoken = null;
    //a beforeEach is a function that runs all the time before each test
    beforeEach(async () => {
        // deploy our contract like we did above
        const TWToken = await ethers.getContractFactory("TWToken");
        //we use the global varaible declares above to hold our deployed contract
        //so we can use it out of beforeEach
        twtoken = await TWToken.deploy(ethers.utils.parseEther("100000.0"));
        await twtoken.deployed();
    });

    //A test can have a describe and several 'it' in it
    describe("TWToken", () => {
        //Lets check if when our contract was deployed, 10,000 eth was send to the owner of the contract
        //Hardhat provide us with free accounts that we can use, and the first account is the owner of the contract
        it("Owner of contract should have 10,000 eth", async () => {
            //let get all the free accounts by using getSigners() an ethers.js function. You can look it up on ethers doc
            const accounts =  await ethers.getSigners();
            //we sent 10,000 eth to the onwer so let's check if the balanceof the account = 10,000
            //If you look at REMIX, when deployed our contract, there is a balaceOf function that takes and address
            //this will return the balance of the given address

            //use the deployed contract to call the balanceOf function and pass the first account address to it
            const ownersBalance = await twtoken.balanceOf(accounts[0].address);
            //expect to see if it eqauls 10,000
            //balanceOf returns a wei, so we use formatEther to convert it to eth
            expect(ethers.utils.formatEther(ownersBalance)).to.equal("10000.0");
        });

        //Lets check if our deploy contract has a market cap of 100,000 eth
        it("Should have market cap of 100,000 eth", async () => {
            //call the cap function from our smart contract which returns a wei
            const capValue = await twtoken.cap();
            expect(ethers.utils.formatEther(capValue)).to.equal("100000.0");
        });

        //Lets test our airdrop function
        it("Should send an airdrop of 90,000eth", async () => {
            const accounts = await ethers.getSigners();
            await twtoken.issueAirDrop([accounts[1].address]);
            //get the balance of the account and check if it equals 90,000 eth
            var balance = await twtoken.balanceOf(accounts[1].address);
            expect(ethers.utils.formatEther(balance)).to.equal("90000.0");
        });

        //Lets try to call the airdrop function the second time, it shoudl fail
        it("Should fail when more than cap value", async () => {
            const accounts = await ethers.getSigners();
            //calling it the first time will pass. Each test in a new instance and not related to the old one
            await twtoken.issueAirDrop([accounts[1].address]);
            //call it the second time
            //On Remix when we tried to do this we had an error, go and copy the exact error
            await expect(twtoken.issueAirDrop([accounts[1].address])).to.be.revertedWith("ERC20Capped: cap exceeded");
            //to.be.revertedWith() is one of the ways hardhat handles errors
        });

        //Lets test the onlyOwner role. It should fail when a different account tries to call issueAirDrop
        it("Should not allow others to call the function", async () => {
            //lets get accounts and use the a different account from the first which is the owner
            const accounts = await ethers.getSigners();
            //We use the first account as owner to issue an air drop to account 2
            await expect(twtoken.connect(accounts[1]).issueAirDrop([accounts[2].address])).to.be.revertedWith("Ownable: caller is not the owner")
        })
    })
})

Open your terminal and write npx hardhat test make sure all your tests passes.

test.PNG

d. Deploy the contract to polygon mumbai testnet We will need to create and account in Alchemywhich will provide us with a url that we will deploy our contract to.

alchemy.PNG

Create and account and log in, then click on +Create App

create app.PNG

File the provided fields.

polygn.PNG

Name: TWToken Description: Talk web3 token Environment: Development Chain: Polygon Network: Polygon Mumbai

After creating you app, Click on the View Key and copy your alchemy url

keyview.PNG

Next, you will need to add Polygon Mumbai testnet to your metamask wallet. If you already have it then good, if not Set It UP before you continue.

After setting up your polycon mumbai testnet, copy the address of the account and request for some matic From polygon faucet Provide your address and submit

faucet.PNG

Let's go back to the IDE. Open the terminal and run npm install dotenv. We use this to create a .env so that we can store our private variables and make sure the .env is in the .gitignore file which should be there by default.

On the root folder create a .env file.

env.PNG

In the .env file, do the follow

//Go to alchemy and copy your url and replace 'Replace_Alchemy_Url' with it
ALCHEMY_URL="Replace_Alchemy_Url"
//Go to metamask and select the polygon mumbai testnet witt some matic in it
//Click on the 3 dots and select account details
//Click on export private key
//Enter your password and copy your private key. be very secret with your private key
//Repalce the 'your_Private_key' with it
PRIVATE_KEY="your_private_key"

How to get your private key

dots.PNG

exportkey.PNG

Now, we will need to update hardhat.config.js file to deploy our contract to polygon mumbai testnet.

First, we import hardhat-waffle which contain an instance of ethers and will also import dotenv.

require("@nomiclabs/hardhat-waffle");
require("dotenv").config({path: ".env"});

Update the config file as bellow

require("@nomiclabs/hardhat-waffle");
require("dotenv").config({path: ".env"});

//Lets get access to our secret keys in the .env file
//process.env is the way to get access to the .env file in hardhat
const ALCHEMY_URL = process.env.ALCHEMY_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;

//Lets add our networ and point it to mumbai
module.exports = {
  solidity: "0.8.4",
  networks: {
    mumbai: {
      url: ALCHEMY_URL,
      accounts: [PRIVATE_KEY]   //The accounst is an array
    }
  }
};

Now we are ready to deploy our token to polygon mumbai testnet. Open up the terminal and run npx hardhat run scripts/deploy.js --network mumbai. It should log the address

address.PNG

Hurray!!! We have successfully deployed our contract to polygon mumbai testnet. Copy the contract address and go to Polygonscan and search for it to see the transactions

search.PNG

scan.PNG

Copy your address and open your .env and save it there for future use.

envfuture.PNG

Now, lets import our token into metamask. Open metamask and select the polygon mumbai account and click on the Assest menu and click on Import token

impott.PNG

Paste the address

adImport.PNG

You should see the Token symbol and Token decimal click Add custom token. You will then see the amount 10000 which was given to the owner when deployed the contract. Select Import token.

ttoskne.PNG

Now we have our TWToken on the blockchain but no one can see it or use it.

  1. Add the token into circulation

To add our token into circulation, we need to go to Uniswap and create a Liquidity Pool for our token. In simple terms, Uniswap is a decentralized exchange platform.

Open Uniswap and on the ethereum dropdown select Polygon. Where you see swap pool chart select pool.

Make sure your metamask is pointed to your polygon mumbai testnet account and there is some matic in it.

Click on Connect wallet to connect your wallet. Your UI should look like this

uni.PNG

  1. Click on New position
  2. Copy the contract address and click on Select a token
  3. Paste it and import TWT token, Click on Import.
  4. Interchange the two positions. Click on matic and select TWT and click on the select a token and select MATIC
  5. Select 0.3%

matic.PNG

  1. Under Set starting price, Choose 0.00001 which means that 0.00001TWT = 1 MATIC. This value is specified by you not a standard value.
  2. Under Set Price range min price = 0.0000001 and *max price = 1. What this means is, as long as your TWT/MATIC exchange rate is within that range, your LP increases.
  3. We need to do a deposit, under the Deposit Accounts put a value you want to swap our TWT for MATIC.
  4. Click on Approve TWT

aprov.PNG

  1. Confirm the transaction in metamask, it will consume some gas.

sub.PNG

Finally, you can see the transaction on Polygonscan and in your metamask wallet.

Conclusion Deploying our Token on ethereum testnets is the same proceedure. All you will need to do is to edit the hardhat-config.js to point to the ethereum testnet (rinkeby or ropsten,...) and when deploying the contract instead of writing npx hardhat run scripts/deploy.js --network mumbai you will write npx hardhat run scripts/deploy.js --network rinkeby.

hardhat-config.js for rinkeby testnets

require("@nomiclabs/hardhat-waffle");
require("dotenv").config({path: ".env"});

//Lets get access to our secret keys in the .env file
//process.env is the way to get access to the .env file in hardhat
const ALCHEMY_URL = process.env.ALCHEMY_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;

//Lets add our networ and point it to rinkeby
module.exports = {
  solidity: "0.8.4",
  networks: {
    rinkeby: {
      url: ALCHEMY_URL,
      accounts: [PRIVATE_KEY]   //The accounst is an array
    }
  }
};

hardhat-config.js for ropsten testnet

require("@nomiclabs/hardhat-waffle");
require("dotenv").config({path: ".env"});

//Lets get access to our secret keys in the .env file
//process.env is the way to get access to the .env file in hardhat
const ALCHEMY_URL = process.env.ALCHEMY_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;

//Lets add our networ and point it to rinkeby
module.exports = {
  solidity: "0.8.4",
  networks: {
    ropsten: {
      url: ALCHEMY_URL,
      accounts: [PRIVATE_KEY]   //The accounst is an array
    }
  }
};

Thank you for reading till the end, i hope you learned as much as i did when i was writing this article. Next, we will be creating a whitelist app to get address for our airdrop. Leave a comment let me know what you think. If this post helped you, go by right hand side of the page leave a reaction. You can also reach me on Twitter.