Ethereum Request for Comment NO. 20. (ERC-20)

Kushimo BashirKushimo Bashir
7 min read

Grab your goodies and read along… As you are about to learn everything about what ERC-20 is all about, why it exists, what it does, and what exactly ERC-20 is. 😇

ERC-20 stands for Ethereum Request for Comment NO. 20. It was proposed by a smart contract developer to address the need for a standard within smart contracts on the Ethereum blockchain. As is known, Ethereum itself is a Turing-complete system.

So, basically, ERC-20 is the technical standard for fungible tokens created using the Ethereum blockchain. Fungible tokens are exchangeable tokens that can be in the form of an asset, cryptocurrency, or anything unique on the Ethereum Blockchain. Don’t forget they are exchangeable.

ERC-20 allows developers to create their own token for their smart contract, which can be used with other products and services on the Ethereum blockchain network. As we all know, as the owner of a contract, we might want a form of interaction on the contract that might require the exchange of currency or even buying and selling. That’s why ERC-20 is important if a developer wants to create their own token. Note: A token is different from the native currency of Ethereum (Ether). The owner of a contract might decide to burn or destroy his token, but he can’t burn or destroy Ether, hence why ERC-20 is important.

I believe with the above you should understand why ERC-20 is important and the reason why smart contract developers implement it in their contracts.

Now we are diving into the technical aspect where we talk about the functions and methods to be used inside the smart contract that wants to implement the function of ERC-20.

  • TotalSupply: This is to return the number of tokens to be issued on that smart contract. Don’t fret if you don’t understand it now; as you go along, you will definitely understand it.

  • BalanceOf: This is the balance of the token owner.

  • Transfer: This is used in transferring a specific amount of tokens to a specified address. Just see this as you doing your normal bank transfer.

  • TransferFrom: This transfers tokens from one address to another on behalf of the token owner. This is mainly used for a recurring transfer.

  • Approve: This allows a third party (spender) to withdraw a specific amount of token from a specified account up to a value.

  • Allowance: This is to return a set of tokens from a spender to the owner. This works in line with Approve.

Now we are going to the coding aspect, don’t forget I said all functions above must be included to your smart contract if you want to implement ERC-20 protocol.

firstly, we are going to write out the all interface functions used by the ERC-20

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface ERC20Interface {
    function totalSupply() external view returns (uint);
    function balanceOf(address tokenOwner) external view returns (uint balance);
    function allowance(address tokenOwner, address spender) external view returns (uint remaining);
    function approve(address spender, uint tokens) external returns (bool success);
    function transfer(address to, uint tokens) external returns (bool success);
    function transferFrom(address from, address to, uint tokens) external returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

The functions above are what I explained initially. The smart contract would have to implement all functions inside the interface.
Now we are about to create our token that can be used in our smart contract application. I will call it SwtToken. Don't play!! 🚀🚀
Follow along.
I will be explaining every function inside our contract line by line

firstly we create our smart contract name SwtToken implementing the funtions inside our ECC20 interface

contract SwtToken is ERC20Interface {
}

Then, we created some state variables to store our token name, symbol, total supply, decimals(how divisible the token is) on the blockchain.

   string public swtSymbol;
    string public name;
    uint public decimals; 
    uint public _totalSupply;

we then declare a mapping that associates an address (like a user's Ethereum address) with a uint, which represents the number of tokens owned by that address and we also declare a nested mapping, where the first address is the owner, the second address is the spender, and the uint represents the number of tokens that the spender is allowed to spend on behalf of the owner.

    mapping(address => uint) balances;
    mapping(address => mapping(address => uint)) allowed;

below is the constructor function, which runs only once when the contract is deployed. It initializes the state variables of the contract.

constructor(){
        swtSymbol = "SWT";
        name = "SwtToken";
        decimals = 2;
        _totalSupply = 1000000;
        balances[msg.sender] = _totalSupply;
        emit Transfer(address(0), msg.sender, _totalSupply);
}

Now, let's look closely into the funtions.

  1. balanceOf(address tokenOwner):This function returns the balance of a specific address (tokenOwner).

         function balanceOf(address tokenOwner) public view returns (uint balance) {
             return balances[tokenOwner];
         }
    
  2. transfer(address to, uint tokens): This function allows the msg.sender to transfer tokens to another address (to).

    function transfer(address to, uint tokens) public returns (bool success) {
        require(balances[msg.sender] >= tokens, "Insufficient balance");
        balances[msg.sender] -= tokens;
        balances[to] += tokens;
        emit Transfer(msg.sender, to, tokens);
        return true;
    }
  1. transferFrom(address from, address to, uint tokens): This function allows a third-party (the msg.sender) to transfer tokens on behalf of the from address to the to address.

         function transferFrom(address from, address to, uint tokens) public returns (bool success) {
             require(balances[from] >= tokens, "Insufficient balance");
             require(allowed[from][msg.sender] >= tokens, "Allowance exceeded");
             balances[from] -= tokens;
             allowed[from][msg.sender] -= tokens;
             balances[to] += tokens;
             emit Transfer(from, to, tokens);
             return true;
         }
    
    1. approve(address spender, uint tokens): This function allows the msg.sender to approve a spender to spend a specified number of tokens on their behalf.

           function approve(address spender, uint tokens) public returns (bool success) {
               allowed[msg.sender][spender] = tokens;
               emit Approval(msg.sender, spender, tokens);
               return true;
           }
      
      1. allowance(address tokenOwner, address spender): This function returns the remaining number of tokens that a spender is allowed to spend on behalf of a tokenOwner.]

             function allowance(address tokenOwner, address spender) public view returns (uint remaining) {
                 return allowed[tokenOwner][spender];
             }
        
      2. approveAndCall(address spender, uint tokens, bytes memory data): This function not only approves a spender but also immediately calls a function on the spender contract to notify it of the approval.

             function approveAndCall(address spender, uint tokens, bytes memory data) public returns (bool success) {
                 allowed[msg.sender][spender] = tokens;
                 emit Approval(msg.sender, spender, tokens);
                 ApproveAndCallFallback(spender).receiveApproval(msg.sender, tokens, address(this), data);
                 return true;
             }
        
      3. fallback() external payable: This is the fallback function, which is called if someone sends Ether to the contract or calls a function that doesn't exist.

             fallback() external payable {
                 revert();
             }
        

Below is the entire contract code;

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;


interface ERC20Interface {
    function totalSupply() external view returns (uint);
    function balanceOf(address tokenOwner) external view returns (uint balance);
    function allowance(address tokenOwner, address spender) external view returns (uint remaining);
    function approve(address spender, uint tokens) external returns (bool success);
    function transfer(address to, uint tokens) external returns (bool success);
    function transferFrom(address from, address to, uint tokens) external returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

abstract contract ApproveAndCallFallback {
    function receiveApproval(address from, uint256 tokens, address token, bytes memory data) public virtual;
}

 contract SwtToken is ERC20Interface {
    string public swtSymbol;
    string public name;
    uint public decimals; 
    uint public _totalSupply;

    mapping(address => uint) balances;
    mapping(address => mapping(address => uint)) allowed;

  constructor()  {
        swtSymbol = "SWT";
        name = "SwtToken";
        decimals = 2;
        _totalSupply = 1000000;
        balances[msg.sender] = _totalSupply;  // Assuming msg.sender is the contract creator
        emit Transfer(address(0), msg.sender, _totalSupply);
    }

    function totalSupply() public view returns (uint) {
        return _totalSupply - balances[address(0)];
    }

    function balanceOf(address tokenOwner) public view returns (uint balance) {
        return balances[tokenOwner];
    }

    function transfer(address to, uint tokens) public returns (bool success) {
        require(balances[msg.sender] >= tokens, "Insufficient balance");
        balances[msg.sender] -= tokens;
        balances[to] += tokens;
        emit Transfer(msg.sender, to, tokens);
        return true;
    }

    function transferFrom(address from, address to, uint tokens) public returns (bool success) {
        require(balances[from] >= tokens, "Insufficient balance");
        require(allowed[from][msg.sender] >= tokens, "Allowance exceeded");
        balances[from] -= tokens;
        allowed[from][msg.sender] -= tokens;
        balances[to] += tokens;
        emit Transfer(from, to, tokens);
        return true;
    }

    function approve(address spender, uint tokens) public returns (bool success) {
        allowed[msg.sender][spender] = tokens;
        emit Approval(msg.sender, spender, tokens);
        return true;
    }

    function allowance(address tokenOwner, address spender) public view returns (uint remaining) {
        return allowed[tokenOwner][spender];
    }

    function approveAndCall(address spender, uint tokens, bytes memory data) public returns (bool success) {
        allowed[msg.sender][spender] = tokens;
        emit Approval(msg.sender, spender, tokens);
        ApproveAndCallFallback(spender).receiveApproval(msg.sender, tokens, address(this), data);
        return true;
    }

    fallback() external payable {
        revert();
    }

    receive() external payable { }
}

Thanks for reading
I hope this explains how ERC-20 works under the hood

1
Subscribe to my newsletter

Read articles from Kushimo Bashir directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Kushimo Bashir
Kushimo Bashir

I am a Mobile Application Developer. I have vast knowledge in coding and programming. The technologies I use includes Flutter & Dart for mobile applications, Python for Data Structures. I am a personality with an excellent and distinctive analytical thinking, competent working individual and exceptionally working with a team towards achieving specific goals. I am cheerful, optimistic and possess a good mind for learning new skills.