ERC-20 Token Standard
This guide explains the ERC-20 token standard, how to create your own ERC-20 token on Ettios, and best practices for token implementation.
Prerequisites
Before you begin, make sure you have:
- Basic knowledge of Solidity programming
- A development environment set up (see our Development Environment guide)
- Some ETTIA for deploying contracts
What is ERC-20?
ERC-20 (Ethereum Request for Comment 20) is a token standard for fungible tokens on Ethereum-compatible blockchains. It defines a common set of rules that all fungible tokens should implement, ensuring interoperability between different tokens and applications.
Fungible tokens are interchangeable with each other, meaning each token has the same value and properties as any other token of the same type. This is similar to how one dollar bill is equal to any other dollar bill.
Common ERC-20 Token Use Cases
- Cryptocurrencies and stablecoins
- Governance tokens for DAOs
- Utility tokens for platform access
- Reward and loyalty programs
- Security tokens representing assets
- Liquidity provider tokens in DeFi
ERC-20 Interface
The ERC-20 standard defines six required functions and two required events that any compliant token must implement:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
// Required functions
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);
// Required events
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}Function Descriptions
totalSupply()
Returns the total amount of tokens in existence.
balanceOf(address account)
Returns the amount of tokens owned by the specified account.
transfer(address recipient, uint256 amount)
Transfers a specified amount of tokens from the caller's account to the recipient's account.
allowance(address owner, address spender)
Returns the remaining number of tokens that the spender is allowed to spend on behalf of the owner.
approve(address spender, uint256 amount)
Sets the amount of tokens that the spender is allowed to transfer from the caller's account.
transferFrom(address sender, address recipient, uint256 amount)
Transfers tokens from one address to another, assuming the caller has been given allowance by the sender.
Creating an ERC-20 Token
Let's create a simple ERC-20 token using OpenZeppelin's battle-tested contracts. First, install the OpenZeppelin Contracts library:
npm install @openzeppelin/contractsNow, create a file called MyToken.sol with the following code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor(string memory name, string memory symbol, uint256 initialSupply)
ERC20(name, symbol)
Ownable(msg.sender)
{
// Mint initial supply (18 decimals is the default)
_mint(msg.sender, initialSupply * 10 ** decimals());
}
// Optional: Allow the owner to mint more tokens later
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}Customizing Your Token
You can customize your token by modifying the constructor parameters and adding additional functionality:
Token Name & Symbol
The name and symbol parameters define your token's display name and ticker symbol, e.g., "My Awesome Token" and "MAT".
Initial Supply
The initialSupply parameter defines how many tokens to create initially. Note that this value is multiplied by 10^18 to account for decimals.
Additional Features
You can add features like token burning, pausing, capped supply, or other mechanisms by importing additional OpenZeppelin contracts:
ERC20Burnable- Add burn functionalityERC20Pausable- Allow pausing of token transfersERC20Capped- Set a maximum supply capERC20Snapshot- Create snapshots of balances at specific points in time
Deploying Your Token
You can deploy your token using Hardhat, Truffle, or Remix. Here's an example of a Hardhat deployment script:
const hre = require("hardhat");
async function main() {
const MyToken = await hre.ethers.getContractFactory("MyToken");
// Deploy with name "My Token", symbol "MTK", and initial supply of 1,000,000 tokens
const myToken = await MyToken.deploy("My Token", "MTK", 1000000);
await myToken.deployed();
console.log("MyToken deployed to:", myToken.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});To deploy to the Ettios mainnet:
npx hardhat run scripts/deploy.js --network ettiosMainnetBest Practices
- Always have your contracts audited by security professionals before deploying to mainnet
- Use established libraries like OpenZeppelin rather than writing token contracts from scratch
- Consider adding a timelocked admin role for sensitive operations
- Thoroughly test your token on testnet before deploying to mainnet
- Document your token's tokenomics and distribution plan
Interacting with Your Token
Once deployed, you can interact with your token using a variety of tools:
Using Hardhat
// Get token instance
const MyToken = await ethers.getContractFactory("MyToken");
const token = MyToken.attach("YOUR_TOKEN_ADDRESS");
// Check balance
const balance = await token.balanceOf("ADDRESS");
console.log("Balance:", ethers.utils.formatEther(balance));
// Transfer tokens
await token.transfer("RECIPIENT_ADDRESS", ethers.utils.parseEther("100"));Using Web Frontend
// Connect to wallet (ethers.js example)
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
// Initialize contract
const tokenContract = new ethers.Contract(
tokenAddress,
tokenAbi,
signer
);
// Get balance
const balance = await tokenContract.balanceOf(await signer.getAddress());
console.log("Balance:", ethers.utils.formatEther(balance));
// Transfer tokens
await tokenContract.transfer(recipientAddress, ethers.utils.parseEther(amount));Next Steps
Now that you've created your ERC-20 token, you might want to explore: