ERC20 Token Development
Circle Layer Blockchain is fully compatible with the ERC20 standard, allowing developers to create and deploy standard-compliant tokens seamlessly.
ERC20 Standard Interfaceβ
Circle Layer supports the complete ERC20 interface with all standard functions and events:
// ----------------------------------------------------------------------------
// ERC Token Standard #20 Interface
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md
// ----------------------------------------------------------------------------
contract ERC20Interface {
function totalSupply() public constant returns (uint);
function balanceOf(address tokenOwner) public constant returns (uint balance);
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
function transfer(address to, uint tokens) public returns (bool success);
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
Implementation Exampleβ
Basic ERC20 Token Contractβ
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyToken {
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
uint256 _totalSupply
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
totalSupply = _totalSupply * 10**_decimals;
balanceOf[msg.sender] = totalSupply;
emit Transfer(address(0), msg.sender, totalSupply);
}
function transfer(address to, uint256 value) public returns (bool) {
require(balanceOf[msg.sender] >= value, "Insufficient balance");
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
return true;
}
function approve(address spender, uint256 value) public returns (bool) {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value) public returns (bool) {
require(balanceOf[from] >= value, "Insufficient balance");
require(allowance[from][msg.sender] >= value, "Allowance exceeded");
balanceOf[from] -= value;
balanceOf[to] += value;
allowance[from][msg.sender] -= value;
emit Transfer(from, to, value);
return true;
}
}
Deployment Guideβ
Using Remix IDEβ
- Open Remix: Navigate to remix.ethereum.org
- Create Contract: Create a new file with your ERC20 contract
- Compile: Select Solidity compiler version 0.8.0 or higher
- Deploy:
- Select "Injected Web3" environment
- Ensure MetaMask is connected to Circle Layer testnet
- Set constructor parameters
- Click "Deploy"
Using Hardhatβ
Project Setupβ
npm install --save-dev hardhat @nomiclabs/hardhat-ethers ethers
npx hardhat init
Hardhat Configurationβ
// hardhat.config.js
require("@nomiclabs/hardhat-ethers");
module.exports = {
solidity: "0.8.19",
networks: {
circlelayer: {
url: "https://testnet-rpc.circlelayer.com",
chainId: 28525,
accounts: [process.env.PRIVATE_KEY]
}
}
};
Deployment Scriptβ
// scripts/deploy.js
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying with account:", deployer.address);
console.log("Account balance:", (await deployer.getBalance()).toString());
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy(
"My Token", // name
"MTK", // symbol
18, // decimals
1000000 // total supply (1M tokens)
);
await token.deployed();
console.log("Token deployed to:", token.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Deploy Commandβ
npx hardhat run scripts/deploy.js --network circlelayer
Advanced Featuresβ
Mintable Tokensβ
contract MintableToken is ERC20Interface {
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function mint(address to, uint256 amount) public onlyOwner {
totalSupply += amount;
balanceOf[to] += amount;
emit Transfer(address(0), to, amount);
}
}
Burnable Tokensβ
function burn(uint256 amount) public {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
Pausable Tokensβ
contract PausableToken is ERC20Interface {
bool public paused = false;
address public owner;
modifier whenNotPaused() {
require(!paused, "Token is paused");
_;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function pause() public onlyOwner {
paused = true;
}
function unpause() public onlyOwner {
paused = false;
}
function transfer(address to, uint256 value) public whenNotPaused returns (bool) {
// transfer logic
}
}
Token Verificationβ
Contract Verification on Explorerβ
- Navigate to explorer-testnet.circlelayer.com
- Search for your contract address
- Click "Verify Contract"
- Submit source code and constructor parameters
Metadata Standardsβ
contract MyToken {
// Standard metadata
string public name = "My Token";
string public symbol = "MTK";
uint8 public decimals = 18;
// Optional metadata
string public website = "https://mytoken.com";
string public github = "https://github.com/mytoken/token";
// Token logo (IPFS hash)
string public logoURI = "ipfs://QmTokenLogo...";
}
Testing Your Tokenβ
Unit Tests with Hardhatβ
// test/MyToken.test.js
const { expect } = require("chai");
describe("MyToken", function () {
let token;
let owner;
let addr1;
let addr2;
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
token = await Token.deploy("Test Token", "TEST", 18, 1000000);
await token.deployed();
});
it("Should have correct initial supply", async function () {
expect(await token.totalSupply()).to.equal(ethers.utils.parseEther("1000000"));
expect(await token.balanceOf(owner.address)).to.equal(ethers.utils.parseEther("1000000"));
});
it("Should transfer tokens between accounts", async function () {
await token.transfer(addr1.address, ethers.utils.parseEther("100"));
expect(await token.balanceOf(addr1.address)).to.equal(ethers.utils.parseEther("100"));
});
it("Should handle approvals correctly", async function () {
await token.approve(addr1.address, ethers.utils.parseEther("100"));
expect(await token.allowance(owner.address, addr1.address)).to.equal(ethers.utils.parseEther("100"));
});
});
Run Testsβ
npx hardhat test
Integration with DeFiβ
Adding Liquidity to DEXβ
// Example: Adding token to Circle Layer DEX
const tokenContract = new ethers.Contract(tokenAddress, tokenABI, signer);
const dexRouter = new ethers.Contract(routerAddress, routerABI, signer);
// Approve tokens for DEX
await tokenContract.approve(routerAddress, ethers.utils.parseEther("1000"));
// Add liquidity
await dexRouter.addLiquidityETH(
tokenAddress,
ethers.utils.parseEther("1000"), // token amount
0, // min token amount
0, // min ETH amount
owner.address, // to
Math.floor(Date.now() / 1000) + 3600 // deadline
);
Best Practicesβ
Security Considerationsβ
- Use OpenZeppelin: Consider using OpenZeppelin contracts
- Audit Smart Contracts: Have contracts audited before mainnet deployment
- Test Thoroughly: Comprehensive testing on testnet
- Handle Edge Cases: Check for integer overflow/underflow
- Access Control: Implement proper owner/admin controls
Gas Optimizationβ
- Efficient Storage: Use appropriate data types
- Batch Operations: Combine multiple operations when possible
- Event Optimization: Use indexed parameters judiciously
- Code Optimization: Remove unnecessary computations
Token Economicsβ
- Supply Management: Plan token distribution carefully
- Inflation/Deflation: Consider burn mechanisms
- Utility: Ensure tokens have clear use cases
- Governance: Plan for future governance needs
Common Issues & Solutionsβ
Deployment Failuresβ
Issue: "Gas estimation failed" Solution: Increase gas limit or check for contract errors
Issue: "Nonce too low" Solution: Reset MetaMask account or check pending transactions
Transfer Issuesβ
Issue: "Insufficient balance" Solution: Verify account balance and token amount
Issue: "Allowance exceeded" Solution: Check and increase allowance if needed
Referencesβ
- EIP-20 Standard: https://eips.ethereum.org/EIPS/eip-20
- OpenZeppelin Contracts: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20
- Circle Layer Testnet Explorer: https://explorer-testnet.circlelayer.com
- Circle Layer Faucet: https://faucet.circlelayer.com
ERC20 token development on Circle Layer provides developers with a fast, low-cost environment for creating and deploying standard-compliant tokens with full Ethereum ecosystem compatibility.