Create Your Own ERC-20 Token

Published by Mario Oettler on

Open the Remix IDE and paste the following code.

pragma solidity 0.8.21;

contract ERC20{

    mapping (address => uint256)                      private _balances;
    mapping (address => mapping (address => uint256)) private _allowed;
    uint256                                           private _totalSupply;
    string                                            private _name;
    string                                            private _symbol;
    uint8                                             private _decimals;


    // Events 
    
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);


    // Optional Functions

    function name() public view returns (string memory){

    }

    function symbol() public view returns (string memory){

    }

    function decimals() public view returns (uint8){

    }


    // Mandatory Functions

    function totalSupply() public view returns (uint256) {

    }

    function balanceOf(address owner) public view returns (uint256) {

    }

    function allowance(address owner, address spender) public view returns (uint256) {

    }

    function transfer(address to, uint256 value) public returns (bool) {

    }

    function approve(address spender, uint256 value) public returns (bool) {

    }

    function transferFrom(address from, address to, uint256 value) public returns (bool) {

    }
}

Now, we look at some lines that are interesting for us.

Line 5: This line declares a mapping that stores the token balance for each user.

Line 6: This line declares another mapping. This is where the allowances are stored. Users can allow other users to spend certain amounts of their token on their behalf.

Lines 7 – 10: This stores some values such as name, symbol, and decimals.

Lines 15 and 16 declare two events that MUST be emitted. The Transfer event must be emitted in the function transfer() and the Approval event must be emitted in the function approve().

Lines 19 – 31: You can see three functions that are optional – name(), symbol(), and decimals(). They simply return values such as the name or symbol of the token.

Lines 34 – 58 contain the mandatory functions.

Line 44: This function returns the allowance of a user.

Line 48 implements a simple token transfer.

Line 52: The approve() function sets the allowance. A token holder sets an amount that another user can spend on his behalf.

Line 56: In the transferFrom()function, a user uses their allowance and spends tokens from another account.

Now, we want to fill the functions. The ERC20 standard doesn’t specify what’s into these functions. That’s why each implementation can be different.

The token creator is responsible for secure and working function content. This poses a risk to the user. Unless the source code is available and verified, it is not clear to outsiders what the source code actually does.

Add the following code:

pragma solidity 0.8.21;

contract ERC20{

    mapping (address => uint256)                      private _balances;
    mapping (address => mapping (address => uint256)) private _allowed;
    uint256                                           private _totalSupply;
    string                                            private _name;
    string                                            private _symbol;
    uint8                                             private _decimals;


    // Events 

    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);


    // Optional Functions

    function name() public view returns (string memory){
        return _name;
    }

    function symbol() public view returns (string memory){
        return _symbol;
    }

    function decimals() public view returns (uint8){
        return _decimals;
    }


    // Mandatory Functions

    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address owner) public view returns (uint256) {
        return _balances[owner];
    }

    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowed[owner][spender];
    }

    function transfer(address to, uint256 value) public returns (bool) {
        require(value <= _balances[msg.sender]);         // avoid underflow
        require(_balances[to] + value >= _balances[to]); // avoid overflow
        require(to != address(0));                       // Make sure users don't send funds to the zero address

        _balances[msg.sender] = _balances[msg.sender] - value;
        _balances[to]         = _balances[to] + value;
  
        emit Transfer(msg.sender, to, value);
        return true;
  }

    function approve(address spender, uint256 value) public returns (bool) {
        require(spender != address(0));

        _allowed[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    function transferFrom(address from, address to, uint256 value) public returns (bool) {
        require(value <= _balances[from]);               // avoid underflow
        require(value <= _allowed[from][msg.sender]);    // avoid underflow
        require(_balances[to] + value >= _balances[to]); // avoid overflow
        require(to != address(0));                       // Make sure users don't send funds to the zero address

        _balances[from]            = _balances[from] - value;
        _balances[to]              = _balances[to] + value;
        _allowed[from][msg.sender] = _allowed[from][msg.sender] - value;
        emit Transfer(from, to, value);
        return true;
    }
}

Lines 49 – 51 and 69-72 perform some security checks to avoid integer overflows and underflows and sends to the zero address. Since Solidity 0.8.0 the compiler reverts in case of an underflow or overflow.

The rest is emitting events and adjusting the balances of the token holders.

Categories: