Let's set aside the MetaCoin project for now and start building our own contract from scratch. A perfect example is the ERC20 token contract—one of the most popular DApp standards on Ethereum during 2017-2018. This elegant interface design became the de facto standard adopted by exchanges and wallet applications, facilitating early-stage fundraising and liquidity for thousands of projects.
Project Initialization
We'll create a new blank project without using any Truffle Box templates:
$ mkdir erc20-test
$ cd erc20-test/
$ truffle initThe basic directory structure will look like this:
erc20-test/
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.jsConfigure truffle.js for local Ganache/Test-RPC testing:
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*" // Match any network id
}
}
};ERC20 Basic Contract Interface
The original ERC20 Basic contract supports three functions and one event:
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 total token supplybalanceOf(): Checks token balance of any addresstransfer(): Moves tokens between accounts- Security note: Always verify sender balance before transfer
Enhanced ERC20 Interface
The expanded ERC20 contract adds critical functionality:
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:
allowance(): Checks approved spending limitstransferFrom(): Allows approved transfersapprove(): Grants spending permissions
Secure Mathematics with SafeMath
pragma solidity ^0.4.24;
library SafeMath {
function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
if (_a == 0) return 0;
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 c) {
c = _a + _b;
assert(c >= _a);
return c;
}
}Security features:
- Overflow protection for all arithmetic operations
- Internal pure functions for gas efficiency
- Explicit checks for mathematical validity
Implementing CAT Token
Our complete CAT token 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_ = 1 * (10 ** 10) * (10 ** 18); // 10 billion tokens with 18 decimals
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), "Invalid recipient 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 approve(address _spender, uint256 _value) public returns (bool) {
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], "Insufficient source balance");
require(_value <= allowed[_from][msg.sender], "Allowance exceeded");
require(_to != address(0), "Invalid recipient address");
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;
}
}Testing and Deployment
👉 Learn about deploying smart contracts with Truffle's comprehensive testing framework. The complete test suite should verify:
- Initial token distribution
- Basic transfer functionality
- Allowance mechanisms
- Edge cases and security checks
Frequently Asked Questions
What makes ERC20 tokens special?
ERC20 provides a standardized interface that ensures compatibility across wallets, exchanges, and other Ethereum applications. This standardization reduces development effort and increases interoperability.
How many decimal places should my token use?
Most tokens use 18 decimals to match Ethereum's ether denomination, but you can choose any value between 0-18 based on your tokenomics.
What's the difference between transfer() and transferFrom()?
transfer() moves tokens directly from the sender's balance, while transferFrom() allows an approved spender to move tokens on behalf of the owner.
👉 Discover more advanced token features like minting, burning, and pausable contracts to enhance your token's functionality.
How do I ensure my token contract is secure?
- Always use SafeMath for arithmetic operations
- Implement comprehensive unit tests
- Consider formal verification for critical contracts
- Have your code audited by security professionals
Conclusion
This guide has walked you through creating a production-ready ERC20 token contract from scratch. By implementing proper security measures, clear interfaces, and thorough testing, you can deploy tokens that will safely handle millions of dollars in value.