Create Your Own ERC-20 Token
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.