Understanding the ERC-20 standard is essential for any developer diving into Ethereum smart contracts and decentralized applications. As one of the most widely adopted token standards on the blockchain, ERC-20 defines a common set of rules that ensure interoperability between different tokens and platforms. This guide breaks down the core components of ERC-20 in Solidity, explores a complete implementation, and demonstrates practical use cases such as creating custom tokens and building decentralized token swaps.
What Is an ERC-20 Token?
An ERC-20 token is any smart contract deployed on the Ethereum blockchain that adheres to the EIP-20 specification. These tokens are fungible, meaning each unit is identical and interchangeable—just like traditional currency. They power a vast ecosystem of decentralized finance (DeFi) protocols, NFT marketplaces, governance systems, and more.
ERC-20 tokens enable key functionalities such as:
- Transferring tokens between addresses
- Querying account balances
- Approving third-party spending of tokens on behalf of the owner
These operations are standardized so that wallets, exchanges, and other contracts can interact with any ERC-20-compliant token without needing custom integration.
👉 Learn how to securely deploy and manage ERC-20 tokens using trusted tools.
The ERC-20 Interface
At the heart of every ERC-20 token is its interface. Below is the minimal required interface written in Solidity:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}Each function serves a specific purpose:
totalSupply()returns the total number of tokens in circulation.balanceOf()checks how many tokens an address holds.transfer()allows a user to send tokens directly to another address.allowance()queries how many tokens one address is allowed to spend on behalf of another.approve()sets the spending limit for a designated address.transferFrom()enables a third party (like a DApp) to transfer tokens from one user to another, provided approval was granted.
These functions form the foundation for secure and composable token interactions across the Ethereum ecosystem.
Building a Basic ERC-20 Contract
Here's a simple but fully functional implementation of an ERC-20 token:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "./IERC20.sol";
contract ERC20 is IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name;
string public symbol;
uint8 public decimals;
constructor(string memory _name, string memory _symbol, uint8 _decimals) {
name = _name;
symbol = _symbol;
decimals = _decimals;
}
function transfer(address recipient, uint256 amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
function _mint(address to, uint256 amount) internal {
balanceOf[to] += amount;
totalSupply += amount;
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal {
balanceOf[from] -= amount;
totalSupply -= amount;
emit Transfer(from, address(0), amount);
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function burn(address from, uint256 amount) external {
_burn(from, amount);
}
}This contract includes optional but useful features like mint and burn, which allow authorized parties to increase or reduce the total supply.
Creating Your Own Custom Token
Thanks to libraries like OpenZeppelin, launching your own ERC-20 token is straightforward. Here’s an example of a custom token built by extending a base ERC-20 contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "./ERC20.sol";
contract MyToken is ERC20 {
constructor(string memory name, string memory symbol, uint8 decimals)
ERC20(name, symbol, decimals)
{
// Mint 100 tokens to the deployer
// 1 token = 1 * (10 ** decimals)
_mint(msg.sender, 100 * 10 ** uint256(decimals));
}
}This contract mints 100 tokens upon deployment and assigns them to the deployer. You can customize the name, symbol, and decimal precision—critical for ensuring compatibility with wallets and exchanges.
👉 Discover how to launch and distribute your token securely today.
Swapping Tokens with Smart Contracts
One powerful application of ERC-20 tokens is decentralized peer-to-peer trading. The TokenSwap contract below enables two parties to exchange different ERC-20 tokens directly:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "./IERC20.sol";
contract TokenSwap {
IERC20 public token1;
address public owner1;
uint256 public amount1;
IERC20 public token2;
address public owner2;
uint256 public amount2;
constructor(
address _token1,
address _owner1,
uint256 _amount1,
address _token2,
address _owner2,
uint256 _amount2
) {
token1 = IERC20(_token1);
owner1 = _owner1;
amount1 = _amount1;
token2 = IERC20(_token2);
owner2 = _owner2;
amount2 = _amount2;
}
function swap() public {
require(msg.sender == owner1 || msg.sender == owner2, "Not authorized");
require(
token1.allowance(owner1, address(this)) >= amount1,
"Token 1 allowance too low"
);
require(
token2.allowance(owner2, address(this)) >= amount2,
"Token 2 allowance too low"
);
_safeTransferFrom(token1, owner1, owner2, amount1);
_safeTransferFrom(token2, owner2, owner1, amount2);
}
function _safeTransferFrom(
IERC20 token,
address sender,
address recipient,
uint256 amount
) private {
bool sent = token.transferFrom(sender, recipient, amount);
require(sent, "Token transfer failed");
}
}How It Works
Imagine Alice wants to trade 10 AliceCoin for 20 BobCoin with Bob:
- Alice and Bob agree on terms and deploy the
TokenSwapcontract. - Alice calls
approve()on her AliceCoin contract to allowTokenSwapto spend 10 tokens. - Bob does the same with his BobCoin contract for 20 tokens.
- Either party calls
swap()to execute the trade atomically.
If either approval is missing or insufficient, the transaction reverts—ensuring safety and fairness.
👉 Explore secure ways to test and deploy token swap contracts.
Frequently Asked Questions
What is the purpose of the allowance function in ERC-20?
The allowance function checks how many tokens a spender is allowed to transfer from an owner’s account. It enables secure delegation of spending rights without transferring ownership.
Can I modify the total supply after deploying an ERC-20 token?
Yes—but only if your contract includes functions like mint or burn. Some tokens have fixed supplies; others are inflationary or deflationary based on protocol rules.
Why use _safeTransferFrom instead of calling transferFrom directly?
Wrapping transferFrom in a private _safeTransferFrom ensures proper error handling. If the transfer fails (e.g., due to insufficient balance or allowance), it reverts with a clear message.
Is it safe to use raw transferFrom in production?
It's safer to wrap external calls in helper functions that validate success. Direct calls may silently fail on some tokens that don’t properly emit reverts.
How do decimals affect my token?
Decimals determine the divisibility of your token. For example, with 18 decimals (like ETH), 1 full token equals 1 followed by 18 zeros at the smallest unit level (wei).
What are common security risks when implementing ERC-20?
Key risks include integer overflow/underflow (mitigated in Solidity 0.8+), reentrancy attacks (rare in pure transfers), and improper access control on mint/burn functions.
Core Keywords: ERC-20 token, Solidity smart contract, Ethereum blockchain, create ERC-20 token, token swap contract, fungible tokens, blockchain development