Token Standards in Ethereum Part-I (ERC20)
Creating and deploying your own tokens on the internet is too easy. Blockchain and Smart Contracts have made it possible. Yes, you heard that right. You can deploy your own tokens on the internet. And that too without creating a whole new blockchain like Bitcoin or Ethereum.
These tokens are just a piece of code running on top of a blockchain.
If you are unaware of blockchain, please refer to this article for a better understanding of blockchain.
Why am I writing this?
As a part of my learning I started learning about token standards and the problem I faced was the lack of resources where you can find everything about a token standard. Not a single resource was containing everything about a token standard.
ERC20 was easy so it didn’t take much time. But ERC721 and ERC1155 took too much time to gather information and put them all together.
Yes, it was easy to import Openzeppelin contracts and create the tokens but you will only learn the concepts if you practice all by yourself. So, just read these posts and start developing and deploying your tokens.
Let’s get started with tokens.
Prerequisites
knowledge of solidity language and how it works (deployment and interaction).
Knowledge about interfaces. Resource
Basically, tokens are a type of cryptocurrency that runs on top of a blockchain with some special use cases, for example, you can create tokens to fund your projects which are called ICO(Initial Coin Offering) similar to the share market, or you can also use tokens to assign a particular digital asset which has a specific value, apart from this, tokens can be used to represent ownership for some kind of data.
They are also being used in governance, how? The person holding a specific amount of token of a company gets the privilege to vote for the company’s decisions. And there are many more use cases. The only thing you need is a Smart Contract deployed on a blockchain.
Now there is a problem with interoperability, as there are billions of people with different ideas so it will become very difficult to utilize those tokens if every person is creating a new kind of token. This is because every token is nothing but a smart contract running on the blockchain, and smart contracts are codes written by developers. And different developers might implement the code in different ways. There would be chaos all over the network. Because the wallet providers and exchanges will have to write custom code to talk to different contracts with different kinds of functionality.
Simply put, it’s impossible to write custom code for each contract individually.
To overcome this we have ERC token standards.
Ethereum Request for Comments
ERC means Ethereum Request for Comments. Which helps the Ethereum environment to set rules or standards for the application layer of Ethereum Blockchain and make it more secure and scalable. Don’t get confused between ERC and EIP, EIPs are the proposals for core Ethereum functions and ERC is for the application layer.
ERCs are a part of EIPs. Learn more.
ERC token standards have a set of predefined rules that the developer should strictly follow while developing a token. There are different token standards as per demands. And they all have different sets of rules.
Token standards give you an interface that you must implement when developing the respective token contract. You can add other functionalities also as per your demand but the standard interface should be implemented.
Different ERC Standards
There are lots of different ERC token standards out there but we will talk about some most popular and more commonly used ones. Which are ERC-20, ERC-721, and ERC-1155. There are many more standards that are being proposed to improve the old ones with backward compatibility, we will talk about them on the go.
ERC-20 — Fungible Tokens
ERC-721 — Non-Fungible Tokens
ERC-1155 — Both Fungible and Non-Fungible Tokens(Also semi-Fungible)
Don’t know the difference between Fungible and Non-Fungible? Let’s learn.
Fungible tokens can be defined as interchangeable, which means if you and I have 100 tokens, then we can exchange them and nothing will change. No profit no loss. Everything will be the same as before. Similar to any fiat currency like Dollars, Euros, Rupees, etc.
Non-fungible tokens(NFTs) are just the opposite of this. Which means every token is different from the other one. Every token is unique. Because Non-Fungible tokens are not only the representation of digits but they have external assets assigned to them, for example, any image, painting, ticket for a concert, music file, game collectibles, and many more things. This is why they are unique.
The first standard to be proposed on the Ethereum network which is the most famous and basic one that comes to everyone’s mind when we talk about the token standard is the ERC-20 token standard.
ERC-20
ERC-20 token standard was proposed in 2015 for fungible tokens. Fungible means interchangeable i.e. every token is the same as another token. This standard has nine functions and two events that should be implemented while creating an ERC-20 token.
ERC-20 has the following functionalities:
- transfer tokens from one account to another.
- get the current token balance of an account.
- get the name, decimal, total supply, and symbol of the token available on the network.
- approve whether a number of tokens from an account can be spent by a third-party account.
Let’s have a look at the ERC-20 interface.
ERC20 Interface
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
First, we will study the functions.
The first three functions, which are the name()
, symbol()
, and decimal()
are considered to be optional and not included in the interface of an ERC20 contract. But they are really important and we can’t ignore them.
1. The name() function should return the name of the token which is stored in a state variable.
function name()public view returns (string);
2. The symbol() function should return the abbreviation of the token and this is also stored in a state variable.
function symbol()public view returns (string);
3. decimals() is the important one to notice because solidity doesn’t support floating numbers and in order to perform transactions with values like 1.5, 2.4, etc., we have to break the number into lower denominations. Remember how the ether token can be broken into lower denominations like Gwei, Finney, and Wei.
Normally we assign 18 decimals points and it is also recommended to do so.
function decimals()public view returns (uint8);
4. totalSupply() This function should return the total amount of tokens in the market. The total supply of a token should only be affected by two functions, minting or burning. Minting means supplying newly created tokens and burning means destroying the tokens. We will talk about both of these later.
function totalSupply()public view returns (uint256);
5. balanceOf () takes an address as an argument and returns its token balance. How does it know about my balance?
We define a mapping inside our contract which keeps track of our balance, every time there is a transaction of tokens this mapping should be updated as this would be the only source of balance.
This is what the mapping looks like:mapping(address => uint ) internal balance;
This balanceOf the
function just returns the value from mapping.
function balanceOf(address _owner)public viewreturns (uint256 balance);
6. transfer() is the function used to send tokens from one address to another. Takes an address
to which tokens are supposed to be sent and a uint
which is the number of tokens to be sent, as arguments. And it should deduct the tokens from msg.sender
and add the amount to the recipient. How do the deduction and addition work?
As I said earlier about the balance
mapping. We simply decrease and increase the number of tokens in the mapping.
function transfer(address _to, uint256 _value)public returns (bool success);
7. transferFrom() does the same thing as the transfer
function but there is a difference. This function is used to send tokens from one address to another address by a third address. How can someone spend my tokens? Is it safe? Yes, because any address can only spend your token if you have approved it to do so. How do we do it? That’s what the next function is about.
function transferFrom(address _from, address _to, uint256 _value)public returns (bool success);
8. approve() is used to approve any account to spend tokens from the account that is approving it. It takes two arguments first is the address of the spender and the second is the value of tokens that the spender is allowed to spend. We then update the nested mapping with this input. Which mapping?
The mapping for approval is written like this:mapping (address=> mapping (address => amount)) internal allowed;
function approve(address _spender, uint256 _value)public returns (bool success);
9. allowance() is used to check the remaining tokens for the approved address to spend. It simply returns the data reading from the allowed
mapping.
function allowance(address _owner, address _spender)public view returns (uint256 remaining);
These were the functions to be defined while creating an ERC20 token. Apart from these, we have two events that must be triggered at the desired times. They are:
- Transfer: Every time there is a transfer of tokens from one account to another, this event should be triggered. It’s pretty obvious that this should emit inside the functions transfer and transferFrom.
But there are two more functions at the time of implementation where this event is emitted.Mint
andburn
functions. We will talk about these in a while. For now, just remember when emitting this event in amint
function, the sender address should be zero address, and in aburn
function, the receiver should be zero address.
Zero address looks like this:0x0000000000000000000000000000000000000000
In solidityaddress(0)
means this zero address.
event Transfer(address indexed _from, address indexed _to, uint256 _value);
2. Approval: This event should be triggered inside the approve
function, every time someone approves any address.
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
Every ERC20 token should implement these functions in the contract.
Have a look at this simple implemented smart contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ERC20Interface {
function allowance(address tokenOwner, address spender) public virtual view returns (uint remaining);
function balanceOf(address tokenOwner) public virtual view returns (uint balance);
function totalSupply() public view virtual returns (uint);
function transferFrom(address from, address to, uint tokens) public virtual returns (bool success);
function transfer(address to, uint tokens) public virtual returns (bool success);
function approve(address spender, uint tokens) public virtual returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
event Supply(address indexed to, uint indexed amount, uint remainingSupply);
}
contract Token is ERC20Interface {
string public name;
string public symbol;
uint8 public decimals;
uint256 public _totalSupply;
uint public maximumSupply;
address public owner;
mapping(address => uint) private balances;
mapping(address => mapping(address => uint)) private allowed;
constructor() {
name = "MyFirstToken";
symbol = "MFT";
decimals = 18;
_totalSupply = 5000000 * 10**18;
maximumSupply = 10000000 * 10**18;
owner= msg.sender;
balances[msg.sender] = _totalSupply;
emit Transfer(address(0), msg.sender, _totalSupply);
}
function remainingToken() public view returns (uint remainingSupply) {
return ( maximumSupply - _totalSupply);
}
modifier ownable () {
require (msg.sender== owner," only owner can perform this");
_;
}
function totalSupply() public override view returns (uint) {
return _totalSupply ;
}
function balanceOf(address tokenOwner) public override view returns (uint balance) {
return balances[tokenOwner];
}
function allowance(address tokenOwner, address spender) public override view returns (uint remaining) {
return allowed[tokenOwner][spender];
}
function approve(address spender, uint tokens) public override returns (bool success) {
allowed[msg.sender][spender] = tokens;
emit Approval(msg.sender, spender, tokens);
return true;
}
function transfer(address to, uint tokens) public override returns (bool success) {
balances[msg.sender] = (balances[msg.sender] -= tokens);
balances[to] = (balances[to] += tokens);
emit Transfer(msg.sender, to, tokens);
return true;
}
function transferFrom(address from, address to, uint tokens) public override returns (bool success) {
require(balances[from] >=tokens, "You don't have enough tokens");
require(allowed[from][msg.sender] >=tokens, "You are not allowed to send tokens");
balances[from] = (balances[from] -= tokens);
allowed[from][msg.sender] = (allowed[from][msg.sender] -= tokens);
balances[to] = (balances[to]+= tokens);
emit Transfer(from, to, tokens);
return true;
}
function mint (address to ,uint amount) public ownable {
require(_totalSupply + amount <= maximumSupply,"You are exceeding the maximum supply");
balances[to] += amount;
_totalSupply += amount;
emit Supply(to,amount,remainingToken());
}
function burn (uint amount) public {
require(balances[msg.sender] >= amount, "not enough balance to burn");
balances[msg.sender] -= amount;
emit Supply(msg.sender, amount, remainingToken());
}
}
This is a really simple implementation where you can see the contract token inherits from the interface and implements every function defined inside the interface.
Notice there are two extra functions mint
and burn
.
MINT
The mint
the function is for minting new tokens. When you see the code inside it, you will notice that it increases the balance of any address. Which means there are new tokens being minted to that wallet. But why do we emit the transfer event here? This is because minting is considered a transfer of tokens from the zero address to the given address.
BURN
Now, what about the burn
function? The burn the
function really is a transfer of tokens but the recipient is always a zero address. Why so? Zero address is being used as a black hole in Ethereum, which means it’s unlikely to guess the private key of the zero address. Let’s not be too much technical here, just get the point that whenever you need to destroy some token meaning remove the token from the market you send it to this address, and boom, it’s gone forever. nobody can access those tokens now. This is the reason zero address holds tokens worth millions of dollars.
That’s pretty much it about the ERC20 token standard. This is really simple and easy.
But this is only for fungible tokens, what about the non-fungible tokens NFTs? ERC-721 is the standard for NFTs. Let’s talk about this in the next post.
Make sure to share your reviews with me by commenting here, and also connect with me on Twitter.