Creating a secure and functional ERC20 token is one of the foundational skills in Ethereum smart contract development. This guide walks you through building a fully compliant ERC20 token from scratch—complete with safety checks, proper inheritance, and best practices for deployment. Whether you're launching a new project or learning blockchain development, this tutorial provides everything you need to understand how real-world tokens are built and secured.
We’ll construct a sample token called CAT Token, implementing core ERC20 functionality while integrating critical security libraries like SafeMath. Along the way, you'll learn about contract architecture, overflow protection, and secure transfer mechanisms.
Setting Up Your Development Environment
Before writing any code, we need to initialize a clean Truffle project. Avoid using pre-built templates or Truffle Boxes so we can fully understand each component.
mkdir erc20-test
cd erc20-test
truffle initThis creates the standard Truffle structure:
erc20-test/
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.jsTo connect to a local test network (like Ganache), configure truffle.js to point to the default Ethereum RPC port:
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*" // Match any network ID
}
}
};With this setup, you're ready to begin coding your ERC20-compliant token.
👉 Discover how to deploy and manage digital assets securely on a leading blockchain platform.
Understanding the ERC20 Standard
The ERC20 standard defines a common set of rules for Ethereum tokens, enabling interoperability across wallets, exchanges, and decentralized applications. It evolved from a simpler interface known as ERC20Basic, which includes only essential functions.
ERC20Basic Interface
pragma solidity ^0.4.24;
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address _who) public view returns (uint256);
function transfer(address _to, uint256 _value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}Key components:
totalSupply()– Returns the total number of tokens in circulation.balanceOf()– Queries the token balance of any given address.transfer()– Allows users to send tokens directly to another address.Transferevent – Logs every token movement for transparency and indexing.
These functions form the foundation of all ERC20 tokens. However, they lack advanced features like delegated spending.
Extending to Full ERC20 Functionality
The full ERC20 interface builds upon ERC20Basic by adding approval and allowance mechanisms—enabling third-party spending under user authorization.
pragma solidity ^0.4.24;
import "./ERC20Basic.sol";
contract ERC20 is ERC20Basic {
function allowance(address _owner, address _spender) public view returns (uint256);
function transferFrom(address _from, address _to, uint256 _value) public returns (bool);
function approve(address _spender, uint256 _value) public returns (bool);
event Approval(address indexed owner, address indexed spender, uint256 value);
}New features include:
approve()– Lets a user authorize another address (e.g., a DApp) to spend a specified amount of their tokens.allowance()– Checks how many tokens a spender is still allowed to withdraw.transferFrom()– Enables authorized transfers on behalf of another user.Approvalevent – Logs every time an allowance is set.
This design supports use cases such as staking, automated payments, and multi-signature wallets.
Preventing Integer Overflow with SafeMath
Ethereum’s Solidity language does not automatically prevent integer overflows or underflows—a critical vulnerability that could allow attackers to manipulate balances.
For example:
- Adding
1to the maximumuint256value causes it to wrap around to0. - Subtracting from
0results in the maximum possible number.
To mitigate this risk, developers use the SafeMath library, originally developed by OpenZeppelin.
SafeMath Core Functions
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
assert(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}By using SafeMath, arithmetic operations will throw an error instead of wrapping—a crucial safeguard for financial contracts.
👉 Learn how top-tier platforms ensure secure handling of digital assets and smart contracts.
Building the CAT Token Contract
Now we implement a complete ERC20 token named CAT Token, combining inheritance and SafeMath protection.
Project Structure
contracts/
├── Cat.sol
├── ERC20.sol
├── ERC20Basic.sol
└── SafeMath.solCore Contract Implementation
pragma solidity ^0.4.24;
import "./ERC20.sol";
import "./SafeMath.sol";
contract CAT is ERC20 {
using SafeMath for uint256;
string public constant name = "CAT Token";
string public constant symbol = "CAT";
uint8 public constant decimals = 18;
uint256 internal totalSupply_;
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) internal allowed;
constructor() public {
totalSupply_ = 10 ** 10 * 10 ** 18; // 10 billion tokens
balances[msg.sender] = totalSupply_;
emit Transfer(0, msg.sender, totalSupply_);
}
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0), "Cannot transfer to zero address");
require(_value <= balances[msg.sender], "Insufficient balance");
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
function allowance(address _owner, address _spender) public view returns (uint256) {
return allowed[_owner][_spender];
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_value <= balances[_from], "Not enough balance");
require(_value <= allowed[_from][msg.sender], "Exceeds allowance");
require(_to != address(0), "Invalid recipient");
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
}This implementation ensures:
- Full ERC20 compliance
- Protection against overflow/underflow
- Clear event logging
- Proper access control via
requirestatements
Frequently Asked Questions
What is an ERC20 token?
An ERC20 token is a standardized smart contract on the Ethereum blockchain that represents a fungible digital asset. It follows a defined set of functions and events, allowing seamless integration with wallets, exchanges, and dApps.
Why use SafeMath in ERC20 contracts?
SafeMath prevents integer overflow and underflow vulnerabilities by validating arithmetic operations. Without it, malicious actors could exploit mathematical edge cases to steal funds or inflate balances.
Can I change the decimal places after deployment?
No. The decimals value is hardcoded at deployment and cannot be modified. Choose carefully—most tokens use 18 decimals (like Ether), but stablecoins like USDC use 6.
What’s the difference between transfer() and transferFrom()?
transfer() sends tokens directly from the caller’s account. transferFrom() allows a third party (with prior approval via approve()) to move tokens on behalf of another user—useful for automated services or DEX trades.
Is the CAT Token compatible with modern wallets?
Yes. As long as the contract follows the ERC20 standard and emits correct events (Transfer, Approval), it will work with MetaMask, Trust Wallet, Ledger, and most major platforms.
How do I test my ERC20 contract?
Use Truffle or Hardhat with unit tests written in JavaScript or Solidity. Test scenarios should include:
- Token transfers between accounts
- Approval and allowance logic
- Edge cases like zero-value transfers
- Revert conditions (e.g., insufficient balance)
👉 Access powerful tools for testing, deploying, and managing blockchain assets efficiently.
Final Thoughts
Building an ERC20 token involves more than just writing code—it requires deep attention to security, standards compliance, and user experience. By following best practices like using SafeMath, enforcing input validation with require, and emitting standard events, you create robust and trustworthy digital assets.
Whether you're launching your own project or mastering blockchain development, understanding how ERC20 contracts work is essential knowledge in today’s decentralized ecosystem.
Core keywords used naturally throughout: ERC20, smart contract, Ethereum, token development, SafeMath, Solidity, blockchain, digital assets.