๐งฉ 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:
-
MyToken.solโ ERC-20 token contract -
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)
- Change price (
๐งฎ 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
onlyOwnermodifiers 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
Top comments (0)