How to create and deploy your ERC20 Token
Test and deploy with hardhat on Polygon mumbai Testnet and add it to uniswap
Table of contents
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:
- Create ERC20 Token
- Test the smart contract and deploy to polygon mumbai Testnet
- 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
- 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.
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
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
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.
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.
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()
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*
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.
- 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
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.
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.
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.
Create and account and log in, then click on +Create App
File the provided fields.
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
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
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.
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
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
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
Copy your address and open your .env and save it there for future use.
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
Paste the address
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
.
Now we have our TWToken on the blockchain but no one can see it or use it.
- 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
- Click on
New position
- Copy the contract address and click on
Select a token
- Paste it and import TWT token, Click on Import.
- Interchange the two positions. Click on matic and select TWT and click on the
select a token
and select MATIC - Select 0.3%
- Under Set starting price, Choose
0.00001
which means that0.00001TWT = 1 MATIC
. This value is specified by you not a standard value. - 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. - We need to do a deposit, under the Deposit Accounts put a value you want to swap our TWT for MATIC.
- Click on
Approve TWT
- Confirm the transaction in metamask, it will consume some gas.
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.