"The ABCs Of ERC20 Tokens: A Practical Implementation Guide".

"The ABCs Of ERC20 Tokens: A Practical Implementation Guide".

INTRODUCTION

Hello everyone in this article I will explain to you "How you can create your ERC20 token from scratch?". I am not only telling you the way but also providing you with the implementation of the ERC20 token so without wasting time let's get started.

DEFINITION

ERC stands for "Ethereum request for comment," and the ERC20 standard was implemented in 2015. An ERC20 token is a standard used for creating and issuing smart contracts on the Ethereum blockchain. Smart contracts can then be used to create smart property or tokenized assets that people can invest in.

INTERFACE

Before building our own ERC20 token. The Ethereum community gave us a code template, They told us that the function inside this template must be included in our code also, After this whatever function you want to add just add it, no problem but this common function must be included in everyone's code. Below is the code. If you don't know what is the interface? then interface simply means that we can use functions of interface contract inside our contract easily and we can implement those functions also(if the function is unimplemented). All functions inside the interface contract must have external visibility because it's mandatory. After all, we are using these functions inside another contract that's why visibility is external.

interface ERC20{
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address _owner) external view returns (uint256 balance);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
function approve(address _spender, uint256 _value) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

Let's implement the functions. First, we need to define some variables inside our contract like the name of the token, the symbol of the token, the decimal and the total supply.

//SPDX-License-Identifier: MIT
pragma solidity > 0.5.0 < 0.9.0;
contract OURERC20 is ERC20{
  string Name = "Sample";
  string Symbol = "SML";
// Actually decimal is equal to 10**18 but i am define 0 because interface
// contract decimal() function returns uint8 so we can not able to define big value to decimal
// that why we are writing 0. 10**18 means 10 to the power 18.
// Like if we want to mint 1 token then we need to write 1 with 18 zeros.
  uint8 decimal = 0;
  uint total_Supply = 10000;
}

Inside the above code, you can see the "is" word. Now you are thinking "What does this mean?" So when we are implementing or using functions from another contract then we simply inherit that contract inside our contract by using the "is" keyword and the name of the contract as well. What does this decimal and total supply mean? As we all know EVM does not support decimal values. Why? To prevent rounding errors related to the network nodes running on different architectures. There is no decimal number system present means we can't define uint or int values like "3.4, 0.0001 or 0.01" and you never heard about float datatype in solidity because float stores decimal values but EVM does not support decimals. That's why float datatype is not present in solidity. By the decimal function, your contract implements, effectively shifting all numbers by the declared number of zeros to simulate decimals. That is why we defined decimal variables to use decimal values. Here I directly assign the value but there is another way to assign this value like we can use constructor, How?

//SPDX-License-Identifier: MIT
pragma solidity > 0.5.0 < 0.9.0;
contract OURERC20 is ERC20{
  string Name;
  string Symbol;
  uint decimal;
  uint total_Supply;

constructor(string memory _name,string memory _symbol,uint _decimal,uint _totalSupply){
   Name = _name;
   Symbol = _symbol;
   decimal = _decimal;
   total_Supply = _totalSupply;
}
}

We can only define one constructor inside the contract and that is optional means if you want to define then do it otherwise no problem if you do not define it. You need to assign all values when you deploy the contract. Now let's build our first functions which are the name, the symbol, the decimal and the totalSupply return functions.

//SPDX-License-Identifier: MIT
pragma solidity > 0.5.0 < 0.9.0;
contract OURERC20 is ERC20{
  string Name = "Sample";
  string Symbol = "SML";
  uint8 decimal = 0;
  uint total_Supply = 10000;

  function name() view public override returns(string memory){
      return Name;
  }
  function symbol() view public override returns(string memory){
      return Symbol;
  }
  function decimals() view public override returns(uint8){
      return  decimal;
  }
  function totalSupply() view public override returns(uint256){
      return  total_Supply;
  }
}

These functions (name, symbol, decimal and totalSupply) just return the value of our state variables. Now you have one question in your mind: Why do we call these variables state variables? Whatever variable you defined on the contract level we call them state variable after all it stores our contract's current & actual state and stores on a contract level. Now another question comes to your mind: What do we call the variables that are defined inside the function? We call them local variables because they are not stored on the contract level and they are present only when the function is running. Now you are thinking about what these "view, override and public" words mean. Then don't worry I will explain each word. When we read the state variable inside the function and do not make any change in the state variable inside the function, simply means when we only read not change the state variables then we use the view keyword. The public keyword shows the visibility of our function, If you want to make your function private then you need to use the private keyword to make it private, Same here I just want my function visibility public and anyone can see this function that's why I use the public keyword in my function. Override, When we implement any unimplemented function of an inherited contract then we use the override keyword. I hope now your all doubts regarding the above functions are cleared. We can achieve the same thing without building the name, symbol, decimal and totalSupply function, How? simply we can make our state variables public then automatically the getter function for this variable is built.

//SPDX-License-Identifier: MIT
pragma solidity > 0.5.0 < 0.9.0;
contract OURERC20 is ERC20{
  string public Name = "Sample";
  string public Symbol = "SML";
  uint8 public decimal = 0;
  uint public total_Supply = 10000;
}

Let's move to another function that returns the balance of the given address. We need to create a mapping that helps us to fetch the balance of the address.

mapping(address => uint) AddressBalance; 
// This mapping helps us to fetch the balance of every address. 
function balanceOf(address _owner) view public override returns (uint256 balance){
      return AddressBalance[_owner];
// this line simply means AddressBalance[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] = 0;
// How much balance of tokens on a given address.
  }

One more state variable we need to define that the founder that holds all of our tokens in the starting stage and after that the founder transfers tokens to whom he/she wants to transfer using our transfer function that we will create. We need to also create a constructor function to assign the value of the founder. The "msg. sender" is a global variable and holds the address of the person who calls the function, in our case the deployer of the contract is "msg. sender". Lastly, let's create a getter function for the founder or you can make the state variable public to create its getter function automatically as I told you earlier.

//SPDX-License-Identifier: MIT
pragma solidity > 0.5.0 < 0.9.0;
contract OURERC20 is ERC20{
  string Name = "Sample";
  string Symbol = "SML";
  uint8 decimal = 0;
  uint total_Supply = 10000;
  address founder;
  mapping(address => uint) AddressBalance; 

  constructor(){
    founder = msg.sender;
    AddressBalance[founder] = total_Supply;
// Here we are sending all the tokens to the founder address.
// After all we need someone who own the tokens because if we not assign 
// all token to anyone then the tokens will lock inside the contract and no 
// one able to use that token.
  }
  function Founder() view public returns(address){
    return founder;
  }
}

Let's move to the next function which is our transfer function using this function we can transfer our tokens to any address.

  function transfer(address _to, uint256 _value) public  returns (bool success){
// Throw this line of code we are checking the caller of this function have the value
// in her account that he is going to transfer to _to address.
     require(AddressBalance[msg.sender] >= _value, "You have insufficient balance of tokens");
// This line deduct the amount from caller address that he/she transfer to another
// person account
     AddressBalance[msg.sender] -= _value;
// After deduct from caller address we need to add this value to the receiver address
// The below line do this thing
     AddressBalance[_to] += _value;
// Throw this line we are emiting the transaction, you can understand it like we are
// storing this transaction.
     emit Transfer(msg.sender, _to, _value);
// Here we are returning true if the all above line work currectly or transaction done successfully.
     return true;
  }

This next function is created to give someone access to use the tokens on behalf of the owner of the tokens.

// this mapping is create for giving the amount access.
// amountAccess[0xabc][0xdef] = 100 token. 
// address 0xabc gives access to address 0xdef of amount 100.
mapping(address => mapping(address => uint)) amountAccess; 
 function approve(address _spender, uint256 _value) public returns (bool success){
// This below line giving access to the spender to use _value on the behalf of the msg.sender
// And the msg.sender is the caller of this function.
    amountAccess[msg.sender][_spender] = _value;
// here we emit the approval to save data.
    emit Approval(msg.sender,_spender,_value);
    return true;
  }

The next function is connected with the above function. In the above function, we are giving amount access to the spender but in the below function, we are sending tokens from the owner account to another address. But you thinking why do we need this function if we already have a transfer function? The reason behind this is that in a big social media account the owner give access to other to write message or tweet on behalf of the owner same here for someone who wants to give access to other so he/she can do so through these function.

  function transferFrom(address _from, address _to, uint256 _value) public override returns (bool success){
// Here we do some security check like checking the owner gives access of this amount
// to the caller of function or not. 100 >= 10 or 10 >= 100.
     require(amountAccess[_from][msg.sender] >= _value, "you don't have access of this amount");
// This line checking that the owner account have the value of token or not.
     require(AddressBalance[_from] >= _value, "owner don't have enough token to transfer");
// Then we deduct the _value from the owner account.
     AddressBalance[_from] -= _value;
// And then we are deduction the value from the access amount. if we not deduct the
// amount from access amount then the spender get infinite amount to spend.
// It is same like loop if we not increase the value of i then i run infinitely.
     amountAccess[_from][msg.sender] -= _value;
// after all this we add the value to the receiver address.
     AddressBalance[_to] += _value;
     emit Transfer(_from,_to,_value);
     return true;
  }

In this function, we are returning the amount that the owner gives access to the spender. The reason for creating this function is to check how much amount is remaining to spend from the owner account. We can do the same thing by creating the amountAccess mapping public as I told you earlier but I want to show you how we can create this using function.

  function allowance(address _owner, address _spender) public view override returns (uint256 remaining){
// This line returning amountAccess[0xabc][0xdef] = 100 token remaining.
    return amountAccess[_owner][_spender];
  }

Below is the final code after coming above code. And one more thing this code is not perfect you can make changes inside this contract or functions. This is only for teaching purposes.

// //SPDX-License-Identifier: MIT
pragma solidity > 0.5.0 < 0.9.0;

interface ERC20{
function name() external  view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address _owner) external view returns (uint256 balance);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
function approve(address _spender, uint256 _value) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

contract OURERC20 is ERC20{
  string Name = "Sample";
  string Symbol = "SML";
  uint8 decimal = 0;
  uint total_Supply = 10000;
  address founder;
  mapping(address => uint) AddressBalance; 
  mapping(address => mapping(address => uint)) amountAccess;

  constructor(){
    founder = msg.sender;
    AddressBalance[founder] = total_Supply;
  }
  function Foundre() view public returns(address){
    return founder;
  }
  function name() view public override returns(string memory){
      return Name;
  }
  function symbol() view public override returns(string memory){
      return Symbol;
  }
    function decimals() view public override returns(uint8){
      return  decimal;
  }
    function totalSupply() view public override returns(uint256){
      return  total_Supply;
  } 
function balanceOf(address _owner) view public override returns (uint256 balance){
      return AddressBalance[_owner];
  }
  function transfer(address _to, uint256 _value) public override returns (bool success){
     require(AddressBalance[msg.sender] >= _value, "You have insufficient balance of tokens");
     AddressBalance[msg.sender] -= _value;
     AddressBalance[_to] += _value;
     emit Transfer(msg.sender, _to, _value);
     return true;
  }
  function approve(address _spender, uint256 _value) public override returns (bool success){
    amountAccess[msg.sender][_spender] = _value;
    emit Approval(msg.sender,_spender,_value);
    return true;
  }
  function transferFrom(address _from, address _to, uint256 _value) public override returns (bool success){
     require(amountAccess[_from][msg.sender] >= _value, "you don't have access of this amount");
     require(AddressBalance[_from] >= _value, "owner don't have enough token to transfer");
     AddressBalance[_from] -= _value;
     amountAccess[_from][msg.sender] -= _value;
     AddressBalance[_to] += _value;
     emit Transfer(msg.sender, _to, _value);
     return true;
  }
  function allowance(address _owner, address _spender) public view override returns (uint256 remaining){
    return amountAccess[_owner][_spender];
  }
}

There is another way to create your ERC20 tokens and that way is very simple and trusted because Openzeppelin provides us with a library through which we can create our ERC20 tokens. Let me show the code for this

// SPDX-License-Identifier: MIT
pragma solidity > 0.5.0 < 0.9.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";

contract OURERC20 is ERC20 {
    constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {
        // Mint 100 tokens to msg.sender
        // Similar to how
        // 1 dollar = 100 cents
        // 1 token = 1 * (10 ** decimals)
        // _mint is the function name of openzeppelin ERC20 token.
        // You can check on github code of ERC20 token.
        _mint(msg.sender, 100 * 10 ** uint(decimals()));
    }
// mint is our function that calls the ERC20 contract _mint function 
// this function is for if you wants to create more tokens in future.
// because through contructor you can only able to create 100 tokens
// You can increase the value of tokens in constructor before deploying it.
// but constructor is run only one time when we deploy the contract and after
// that you are not able to call the contructor function ones again.
    function mint(uint _tokens) public {
      _mint(msg.sender,_tokens*10**uint(decimals()));
    }
}

Now you are thinking if we can create our ERC20 tokens through this code then why do we need to write the above big code? Through that code, I just want to explain How the ERC20 contract actually looks like, and how we can create and through the above code we can write our own code and we can add new functions as well. It totally depends on what you choose.

Thank you for reading the whole blog. Keep reading, Keep learning, Keep growing. If I did any spelling mistake or something, I am extremely sorry for the mistake.