๐Ÿช™ 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.