🪙 Day 13 of #30DaysOfSolidity — Building a Token Sale (Sell Your ERC-20 for ETH with Foundry)

🧩 Overview

Welcome to Day 13 of my #30DaysOfSolidity journey!

Today, we’ll build something that powers almost every token project — a Token Sale Contract (or Pre-Sale Contract) where users can buy ERC-20 tokens with Ether.

We’ll use Foundr…


This content originally appeared on DEV Community and was authored by Saurav Kumar

🧩 Overview

Welcome to Day 13 of my #30DaysOfSolidity journey!

Today, we’ll build something that powers almost every token project — a Token Sale Contract (or Pre-Sale Contract) where users can buy ERC-20 tokens with Ether.

We’ll use Foundry — a blazing-fast framework for smart contract development.
By the end, you’ll understand how to:

  • Sell your ERC-20 tokens for ETH 💰
  • Manage pricing, sales, and withdrawals
  • Deploy using Foundry

🚀 What We’re Building

We’re creating two contracts:

  1. MyToken.sol — ERC-20 token contract
  2. TokenSale.sol — lets users buy tokens with ETH

The owner will:

  • Set a price (tokens per ETH)
  • Fund the sale contract with tokens
  • Withdraw ETH and unsold tokens

🧱 Project Structure

day-13-token-sale/
├── src/
│   ├── MyToken.sol
│   └── TokenSale.sol
├── script/
│   └── Deploy.s.sol
├── test/
│   └── TokenSale.t.sol
├── foundry.toml
└── README.md

⚙️ Foundry Setup (Step-by-Step)

If you don’t have Foundry yet, here’s how to set it up 👇

1️⃣ Install Foundry

curl -L https://foundry.paradigm.xyz | bash
foundryup

2️⃣ Create a new project

forge init day-13-token-sale
cd day-13-token-sale

3️⃣ Install OpenZeppelin (ERC-20 contracts)

forge install OpenZeppelin/openzeppelin-contracts

🪙 Step 1 — Create the Token

File: src/MyToken.sol

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

import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import "openzeppelin-contracts/contracts/access/Ownable.sol";

/// @title MyToken - Simple ERC20 Token
contract MyToken is ERC20, Ownable {
    constructor(string memory name_, string memory symbol_, uint256 initialSupply)
        ERC20(name_, symbol_)
    {
        _mint(msg.sender, initialSupply * 10 ** decimals());
    }

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

This is a simple ERC-20 token with an initial supply minted to the deployer.

💸 Step 2 — Create the Token Sale Contract

File: src/TokenSale.sol

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

import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-contracts/contracts/access/Ownable.sol";

/// @title TokenSale - Sell tokens for ETH
contract TokenSale is Ownable {
    IERC20 public token;
    uint256 public tokensPerEth;
    bool public saleActive;

    event TokensPurchased(address indexed buyer, uint256 ethSpent, uint256 tokensBought);
    event PriceUpdated(uint256 oldPrice, uint256 newPrice);
    event SaleToggled(bool active);
    event EtherWithdrawn(address indexed to, uint256 amount);
    event TokensWithdrawn(address indexed to, uint256 amount);

    constructor(address tokenAddress, uint256 _tokensPerEth) {
        require(tokenAddress != address(0), "Invalid token address");
        token = IERC20(tokenAddress);
        tokensPerEth = _tokensPerEth;
        saleActive = true;
    }

    function buyTokens() public payable {
        require(saleActive, "Sale not active");
        require(msg.value > 0, "Send ETH to buy tokens");

        uint256 tokensToBuy = (msg.value * tokensPerEth) / 1 ether;
        require(tokensToBuy > 0, "Not enough ETH for 1 token");

        require(token.balanceOf(address(this)) >= tokensToBuy, "Not enough tokens");

        token.transfer(msg.sender, tokensToBuy);
        emit TokensPurchased(msg.sender, msg.value, tokensToBuy);
    }

    function setPrice(uint256 _tokensPerEth) external onlyOwner {
        require(_tokensPerEth > 0, "Invalid price");
        emit PriceUpdated(tokensPerEth, _tokensPerEth);
        tokensPerEth = _tokensPerEth;
    }

    function toggleSale(bool _active) external onlyOwner {
        saleActive = _active;
        emit SaleToggled(_active);
    }

    function withdrawEther(address payable to) external onlyOwner {
        uint256 amount = address(this).balance;
        require(amount > 0, "No Ether");
        (bool sent, ) = to.call{value: amount}("");
        require(sent, "Transfer failed");
        emit EtherWithdrawn(to, amount);
    }

    function withdrawTokens(address to) external onlyOwner {
        uint256 amount = token.balanceOf(address(this));
        require(amount > 0, "No tokens");
        token.transfer(to, amount);
        emit TokensWithdrawn(to, amount);
    }

    receive() external payable {
        buyTokens();
    }
}

⚙️ Step 3 — Deploy with Foundry

File: script/Deploy.s.sol

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

import "forge-std/Script.sol";
import "../src/MyToken.sol";
import "../src/TokenSale.sol";

contract Deploy is Script {
    function run() external {
        vm.startBroadcast();

        // Deploy ERC20 Token
        MyToken token = new MyToken("MyToken", "MTK", 1_000_000);

        // Deploy Token Sale
        uint256 price = 1000 * 10 ** 18; // 1000 tokens per 1 ETH
        TokenSale sale = new TokenSale(address(token), price);

        // Transfer tokens to sale contract
        token.transfer(address(sale), 100_000 * 10 ** 18);

        vm.stopBroadcast();
    }
}

🧪 Step 4 — Build and Deploy

Compile

forge build

Run a Local Node

anvil

Deploy the Contracts

forge script script/Deploy.s.sol:Deploy --rpc-url http://127.0.0.1:8545 --private-key <YOUR_PRIVATE_KEY> --broadcast

💰 Step 5 — Interact with Your Contract

  • Buyers can call buyTokens() and send ETH.
  • Tokens are automatically transferred to their wallets.
  • Owner can:

    • Change price (setPrice)
    • Pause sale (toggleSale)
    • Withdraw ETH (withdrawEther)
    • Withdraw unsold tokens (withdrawTokens)

🧮 Example Calculation

If you set tokensPerEth = 1000 * 10^18:

ETH Sent Tokens Received
1 ETH 1000 Tokens
0.5 ETH 500 Tokens
0.1 ETH 100 Tokens

🧠 What You’ll Learn

✅ ERC-20 token creation
✅ Handling Ether in contracts
✅ Token pricing & conversion
✅ Secure withdrawal patterns
✅ Deployment using Foundry

🔐 Security Tips

  • Fund the sale contract before making it public.
  • Use onlyOwner modifiers to secure functions.
  • Validate ETH amounts to avoid reentrancy or precision issues.
  • Consider whitelisting buyers for real-world sales.

📊 Future Improvements

  • Add cap limits per user
  • Integrate vesting & timelocks
  • Add USDT or stablecoin support
  • Build a React frontend for users to interact with your sale

🧾 Conclusion

You just created a Token Sale DApp using Foundry and Solidity — the foundation of many Web3 projects like ICOs, presales, and launchpads.

Every token economy begins here: a simple smart contract that turns Ether into tokens.

Keep building! 🚀

🧡 Follow the Journey

I’m documenting #30DaysOfSolidity — from basics to advanced DeFi & Web3 projects.

👉 Follow me on Dev.to
👉 Connect on LinkedIn
👉 Read all previous days


This content originally appeared on DEV Community and was authored by Saurav Kumar


Print Share Comment Cite Upload Translate Updates
APA

Saurav Kumar | Sciencx (2025-10-13T16:06:55+00:00) 🪙 Day 13 of #30DaysOfSolidity — Building a Token Sale (Sell Your ERC-20 for ETH with Foundry). Retrieved from https://www.scien.cx/2025/10/13/%f0%9f%aa%99-day-13-of-30daysofsolidity-building-a-token-sale-sell-your-erc-20-for-eth-with-foundry-2/

MLA
" » 🪙 Day 13 of #30DaysOfSolidity — Building a Token Sale (Sell Your ERC-20 for ETH with Foundry)." Saurav Kumar | Sciencx - Monday October 13, 2025, https://www.scien.cx/2025/10/13/%f0%9f%aa%99-day-13-of-30daysofsolidity-building-a-token-sale-sell-your-erc-20-for-eth-with-foundry-2/
HARVARD
Saurav Kumar | Sciencx Monday October 13, 2025 » 🪙 Day 13 of #30DaysOfSolidity — Building a Token Sale (Sell Your ERC-20 for ETH with Foundry)., viewed ,<https://www.scien.cx/2025/10/13/%f0%9f%aa%99-day-13-of-30daysofsolidity-building-a-token-sale-sell-your-erc-20-for-eth-with-foundry-2/>
VANCOUVER
Saurav Kumar | Sciencx - » 🪙 Day 13 of #30DaysOfSolidity — Building a Token Sale (Sell Your ERC-20 for ETH with Foundry). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/10/13/%f0%9f%aa%99-day-13-of-30daysofsolidity-building-a-token-sale-sell-your-erc-20-for-eth-with-foundry-2/
CHICAGO
" » 🪙 Day 13 of #30DaysOfSolidity — Building a Token Sale (Sell Your ERC-20 for ETH with Foundry)." Saurav Kumar | Sciencx - Accessed . https://www.scien.cx/2025/10/13/%f0%9f%aa%99-day-13-of-30daysofsolidity-building-a-token-sale-sell-your-erc-20-for-eth-with-foundry-2/
IEEE
" » 🪙 Day 13 of #30DaysOfSolidity — Building a Token Sale (Sell Your ERC-20 for ETH with Foundry)." Saurav Kumar | Sciencx [Online]. Available: https://www.scien.cx/2025/10/13/%f0%9f%aa%99-day-13-of-30daysofsolidity-building-a-token-sale-sell-your-erc-20-for-eth-with-foundry-2/. [Accessed: ]
rf:citation
» 🪙 Day 13 of #30DaysOfSolidity — Building a Token Sale (Sell Your ERC-20 for ETH with Foundry) | Saurav Kumar | Sciencx | https://www.scien.cx/2025/10/13/%f0%9f%aa%99-day-13-of-30daysofsolidity-building-a-token-sale-sell-your-erc-20-for-eth-with-foundry-2/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.