DEV Community

Cover image for πŸͺ™ Day 12 of #30DaysOfSolidity β€” Build Your Own ERC-20 Token using Foundry
Saurav Kumar
Saurav Kumar

Posted on

πŸͺ™ Day 12 of #30DaysOfSolidity β€” Build Your Own ERC-20 Token using Foundry

Series: 30 Days of Solidity
Topic: Implementing ERC-20 Token Standard with Foundry
Difficulty: Beginner β†’ Intermediate
Estimated Time: 45 mins


🧩 Introduction

Today’s challenge: Let’s create our own digital currency!

We’re going to implement an ERC-20 Token β€” the most widely used token standard in the Ethereum ecosystem. Whether it’s your favorite DeFi protocol, a DAO’s governance token, or an in-game currency, most fungible assets follow the ERC-20 interface.

This project will teach you how to design, build, and test your own ERC-20 token from scratch using Foundry, the blazing-fast Solidity development toolkit.


πŸš€ What We’ll Learn

By the end of this article, you’ll understand:

  • βœ… What makes a token ERC-20 compliant
  • βœ… The purpose of each standard function (transfer, approve, transferFrom, etc.)
  • βœ… How to manage balances and allowances
  • βœ… How to mint and burn tokens safely
  • βœ… How to test your token using Foundry’s Forge

πŸ› οΈ Tech Stack

Tool Purpose
Foundry (Forge) For building, testing, and deploying smart contracts
Solidity (v0.8.19) Smart contract programming language
Anvil Local Ethereum test node
Cast CLI for contract interaction

⚑ No Hardhat. No JavaScript. Just pure Solidity and Rust-speed Foundry magic.


🧱 File Structure

day-12-erc20-token/
β”œβ”€ src/
β”‚  └─ DayToken.sol
β”œβ”€ script/
β”‚  └─ DeployDayToken.s.sol
β”œβ”€ test/
β”‚  └─ DayToken.t.sol
β”œβ”€ foundry.toml
└─ README.md
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Step 1: Initialize a Foundry Project

Start fresh with a new Foundry workspace:

forge init day-12-erc20-token
cd day-12-erc20-token
Enter fullscreen mode Exit fullscreen mode

Remove the sample files (optional):

rm -rf src/Counter.sol test/Counter.t.sol
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Step 2: Create the ERC-20 Token Contract

We’ll create our token β€” DayToken (DAY) β€” with minting and burning capabilities, plus basic ownership control.

πŸ“ File: src/DayToken.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @title DayToken - A minimal ERC20 implementation built with Foundry
/// @author 
/// @notice This contract demonstrates how to create and manage ERC20 tokens manually
contract DayToken {
    string public name = "DayToken";
    string public symbol = "DAY";
    uint8 public constant decimals = 18;
    uint256 public totalSupply;

    address public owner;

    mapping(address => uint256) private balances;
    mapping(address => mapping(address => uint256)) private allowances;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    constructor(uint256 initialSupply) {
        owner = msg.sender;
        _mint(msg.sender, initialSupply * 10 ** uint256(decimals));
    }

    function balanceOf(address account) external view returns (uint256) {
        return balances[account];
    }

    function transfer(address to, uint256 amount) external returns (bool) {
        require(to != address(0), "Invalid address");
        require(balances[msg.sender] >= amount, "Insufficient balance");

        balances[msg.sender] -= amount;
        balances[to] += amount;

        emit Transfer(msg.sender, to, amount);
        return true;
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowances[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function allowance(address _owner, address spender) external view returns (uint256) {
        return allowances[_owner][spender];
    }

    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        uint256 allowed = allowances[from][msg.sender];
        require(allowed >= amount, "Allowance exceeded");
        require(balances[from] >= amount, "Insufficient balance");

        balances[from] -= amount;
        balances[to] += amount;
        allowances[from][msg.sender] = allowed - amount;

        emit Transfer(from, to, amount);
        return true;
    }

    function mint(address to, uint256 amount) external onlyOwner {
        _mint(to, amount);
    }

    function burn(address from, uint256 amount) external onlyOwner {
        require(balances[from] >= amount, "Insufficient balance");
        balances[from] -= amount;
        totalSupply -= amount;
        emit Transfer(from, address(0), amount);
    }

    function _mint(address to, uint256 amount) internal {
        require(to != address(0), "Invalid address");
        balances[to] += amount;
        totalSupply += amount;
        emit Transfer(address(0), to, amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Step 3: Write Unit Tests with Foundry

πŸ“ File: test/DayToken.t.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import "../src/DayToken.sol";

contract DayTokenTest is Test {
    DayToken token;
    address alice = address(0x1);
    address bob = address(0x2);

    function setUp() public {
        token = new DayToken(1000);
    }

    function testInitialSupply() public {
        uint256 expected = 1000 * 10 ** token.decimals();
        assertEq(token.totalSupply(), expected);
    }

    function testTransfer() public {
        token.transfer(alice, 100 ether);
        assertEq(token.balanceOf(alice), 100 ether);
    }

    function testApproveAndTransferFrom() public {
        token.approve(alice, 200 ether);
        vm.prank(alice);
        token.transferFrom(address(this), bob, 200 ether);
        assertEq(token.balanceOf(bob), 200 ether);
    }

    function testMintOnlyOwner() public {
        token.mint(alice, 50 ether);
        assertEq(token.balanceOf(alice), 50 ether);
    }

    function testFailMintNotOwner() public {
        vm.prank(alice);
        token.mint(bob, 10 ether); // should revert
    }

    function testBurn() public {
        uint256 supplyBefore = token.totalSupply();
        token.burn(address(this), 100 ether);
        assertEq(token.totalSupply(), supplyBefore - 100 ether);
    }
}
Enter fullscreen mode Exit fullscreen mode

Run tests:

forge test -vv
Enter fullscreen mode Exit fullscreen mode

πŸ“œ Step 4: Deploy Script

πŸ“ File: script/DeployDayToken.s.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Script.sol";
import "../src/DayToken.sol";

contract DeployDayToken is Script {
    function run() external {
        uint256 deployerKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerKey);

        DayToken token = new DayToken(1_000_000);
        console.log("Token deployed at:", address(token));

        vm.stopBroadcast();
    }
}
Enter fullscreen mode Exit fullscreen mode

Deploy on a local node (Anvil):

anvil
Enter fullscreen mode Exit fullscreen mode

Then in another terminal:

forge script script/DeployDayToken.s.sol --rpc-url http://localhost:8545 --private-key <YOUR_PRIVATE_KEY> --broadcast
Enter fullscreen mode Exit fullscreen mode

πŸ” Security Considerations

  • Owner-only mint/burn: Prevents unauthorized token inflation.
  • Avoid public minting: Never expose mint() to arbitrary callers.
  • Zero address checks: Ensures tokens aren’t sent to dead accounts.
  • Use modifiers carefully: Restrict ownership and sensitive actions.

For production-grade deployments, use OpenZeppelin’s ERC-20 implementation to reduce security risks and ensure full compliance.


🧭 Key Takeaways

Concept Description
ERC-20 Standard for fungible tokens on Ethereum
Foundry (Forge) Tool for testing, scripting, and deploying Solidity contracts
Allowances Mechanism allowing third parties to spend tokens
Minting/Burning Controls total supply
Events Emitted for every Transfer and Approval for on-chain visibility

🌐 Next Steps

Now that you’ve created your ERC-20 token:

  • Try deploying it to a testnet (Sepolia or Base)
  • Integrate it into a simple frontend wallet
  • Add extensions like burnable, pausable, or governance features

🏁 Wrap Up

You’ve just built your own digital currency from scratch using Foundry! πŸš€
This exercise not only reinforces your understanding of Solidity standards but also gives you a real foundation for DeFi, DAOs, and beyond.

πŸ“– Read more posts in my #30DaysOfSolidity journey here πŸ‘‰
https://dev.to/sauravkumar8178/

Top comments (0)