In 2025, decentralized exchanges (DEXs) like Uniswap and PancakeSwap are revolutionizing crypto trading by letting users swap tokens without intermediaries. Want to build your own DEX? This beginner-friendly tutorial guides you through creating a simple DEX on Ethereum, using Solidity for smart contracts and React for the front-end. Whether you’re a developer or a crypto enthusiast, let’s build a token-swapping platform together!

What Is a Decentralized Exchange (DEX)?
A DEX is a blockchain-based platform that allows users to trade cryptocurrencies directly from their wallets, without a central authority like Coinbase. It uses smart contracts to manage trades, ensuring transparency and security. Our DEX will let users swap ETH for a custom ERC-20 token using an automated market maker (AMM) model.
Key features of a DEX include:
- Peer-to-Peer Trading: Users trade directly, no middleman needed.
- Liquidity Pools: Users provide tokens to pools, earning fees.
- Self-Custody: Users control their funds, reducing hack risks.
This tutorial focuses on creating a basic AMM-based DEX for token swaps.
Why Build a DEX in 2025?
DEXs are booming, with billions in trading volume. Building your own DEX lets you:
- Innovate: Create unique trading features or support new tokens.
- Learn Blockchain: Master smart contracts and DeFi mechanics.
- Join the DeFi Wave: Contribute to the decentralized finance ecosystem.
Our DEX will teach you core concepts like liquidity pools and price calculations.
Tools You’ll Need
To build the DEX, gather these tools:
- Node.js: For JavaScript execution. Download from nodejs.org.
- Hardhat: For smart contract development. Install via
npm install --save-dev hardhat
. - Ethers.js: For Ethereum interactions. Install with
npm install ethers
. - MetaMask: For wallet integration. Get it at metamask.io.
- React: For the front-end. We’ll set this up later.
These tools are developer-friendly and widely used in 2025 for DeFi projects.
Step-by-Step: Building a Decentralized Exchange
We’ll create a DEX with a liquidity pool for swapping ETH and a custom ERC-20 token. The smart contracts will handle trades, and a React app will provide a user interface. Let’s get started!
Step 1: Set Up the Hardhat Project
Create a project directory and initialize Hardhat:
mkdir dex-2025 cd dex-2025 npx hardhat npm install @openzeppelin/contracts
Choose the JavaScript project option and install OpenZeppelin for secure ERC-20 token contracts.
Step 2: Create the ERC-20 Token
Create a file named contracts/MyToken.sol
for the custom token:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20 { constructor(uint256 initialSupply) ERC20("MyToken", "MTK") { _mint(msg.sender, initialSupply); } }
This creates “MyToken” (MTK) with an initial supply set during deployment.
Step 3: Write the DEX Smart Contract
Create a file named contracts/DEX.sol
for the DEX logic:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract DEX is ReentrancyGuard { IERC20 public token; uint256 public totalLiquidity; mapping(address => uint256) public liquidity; event LiquidityProvided(address indexed provider, uint256 ethAmount, uint256 tokenAmount); event LiquidityRemoved(address indexed provider, uint256 ethAmount, uint256 tokenAmount); event Swap(address indexed user, bool ethToToken, uint256 amountIn, uint256 amountOut); constructor(address tokenAddress) { token = IERC20(tokenAddress); } function provideLiquidity(uint256 tokenAmount) external payable nonReentrant { require(msg.value > 0 && tokenAmount > 0, "Invalid amounts"); uint256 ethAmount = msg.value; token.transferFrom(msg.sender, address(this), tokenAmount); liquidity[msg.sender] += ethAmount; totalLiquidity += ethAmount; emit LiquidityProvided(msg.sender, ethAmount, tokenAmount); } function removeLiquidity(uint256 ethAmount) external nonReentrant { require(liquidity[msg.sender] >= ethAmount, "Insufficient liquidity"); uint256 tokenAmount = (ethAmount * token.balanceOf(address(this))) / totalLiquidity; liquidity[msg.sender] -= ethAmount; totalLiquidity -= ethAmount; (bool success, ) = msg.sender.call{value: ethAmount}(""); require(success, "ETH transfer failed"); token.transfer(msg.sender, tokenAmount); emit LiquidityRemoved(msg.sender, ethAmount, tokenAmount); } function swapEthToToken() external payable nonReentrant returns (uint256) { require(msg.value > 0, "Invalid ETH amount"); uint256 tokenReserve = token.balanceOf(address(this)); uint256 ethReserve = address(this).balance - msg.value; uint256 tokenAmount = getAmountOut(msg.value, ethReserve, tokenReserve); token.transfer(msg.sender, tokenAmount); emit Swap(msg.sender, true, msg.value, tokenAmount); return tokenAmount; } function swapTokenToEth(uint256 tokenAmount) external nonReentrant returns (uint256) { require(tokenAmount > 0, "Invalid token amount"); uint256 tokenReserve = token.balanceOf(address(this)); uint256 ethReserve = address(this).balance; uint256 ethAmount = getAmountOut(tokenAmount, tokenReserve, ethReserve); token.transferFrom(msg.sender, address(this), tokenAmount); (bool success, ) = msg.sender.call{value: ethAmount}(""); require(success, "ETH transfer failed"); emit Swap(msg.sender, false, tokenAmount, ethAmount); return ethAmount; } function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256) { require(amountIn > 0 && reserveIn > 0 && reserveOut > 0, "Invalid reserves"); uint256 amountInWithFee = amountIn * 997; // 0.3% fee uint256 numerator = amountInWithFee * reserveOut; uint256 denominator = (reserveIn * 1000) + amountInWithFee; return numerator / denominator; } }
This contract:
- Manages a liquidity pool for ETH and MTK.
- Allows users to provide or remove liquidity.
- Enables swapping ETH for MTK or vice versa using a constant product formula (x * y = k).
- Charges a 0.3% fee per trade, mimicking Uniswap.

Step 4: Deploy the Contracts
Create a deployment script in scripts/deploy.js
:
const hre = require("hardhat"); async function main() { const [deployer] = await ethers.getSigners(); const initialSupply = ethers.parseEther("1000000"); // 1M MTK const MyToken = await hre.ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(initialSupply); await token.deployed(); console.log("MyToken deployed to:", token.target); const DEX = await hre.ethers.getContractFactory("DEX"); const dex = await DEX.deploy(token.target); await dex.deployed(); console.log("DEX deployed to:", dex.target); await token.approve(dex.target, initialSupply); console.log("Token approval granted"); } main().catch((error) => { console.error(error); process.exitCode = 1); });
Configure hardhat.config.js
for Sepolia (use an API key from Infura or Alchemy):
require("@nomicfoundation/hardhat-toolbox"); module.exports = { solidity: "0.8.0", networks: { sepolia: { url: "https://sepolia.infura.io/v3/YOUR_API_KEY", accounts: ["YOUR_PRIVATE_KEY"] } } };
Deploy with npx hardhat run scripts/deploy.js --network sepolia
. Note the token and DEX contract addresses.
Step 5: Build a React Front-End
Create a React app for users to interact with the DEX:
npx create-react-app dex-dapp cd dex-dapp npm install ethers
Replace src/App.js
with:
import { useState } from 'react'; import { ethers } from 'ethers'; import './App.css'; const dexAddress = 'YOUR_DEX_ADDRESS'; const tokenAddress = 'YOUR_TOKEN_ADDRESS'; const dexABI = [/* YOUR_DEX_ABI */]; // From Hardhat artifacts const tokenABI = [/* YOUR_TOKEN_ABI */]; // From Hardhat artifacts function App() { const [account, setAccount] = useState(null); const [ethAmount, setEthAmount] = useState(''); const [tokenAmount, setTokenAmount] = useState(''); const connectWallet = async () => { if (window.ethereum) { const provider = new ethers.BrowserProvider(window.ethereum); const accounts = await provider.send('eth_requestAccounts', []); setAccount(accounts[0]); } else { alert('Install MetaMask!'); } }; const provideLiquidity = async () => { if (!account || !ethAmount || !tokenAmount) return; const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const tokenContract = new ethers.Contract(tokenAddress, tokenABI, signer); const dexContract = new ethers.Contract(dexAddress, dexABI, signer); await tokenContract.approve(dexAddress, ethers.parseEther(tokenAmount)); await dexContract.provideLiquidity(ethers.parseEther(tokenAmount), { value: ethers.parseEther(ethAmount) }); alert('Liquidity provided!'); }; const swapEthToToken = async () => { if (!account || !ethAmount) return; const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const dexContract = new ethers.Contract(dexAddress, dexABI, signer); await dexContract.swapEthToToken({ value: ethers.parseEther(ethAmount) }); alert('Swap successful!'); }; const swapTokenToEth = async () => { if (!account || !tokenAmount) return; const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const tokenContract = new ethers.Contract(tokenAddress, tokenABI, signer); const dexContract = new ethers.Contract(dexAddress, dexABI, signer); await tokenContract.approve(dexAddress, ethers.parseEther(tokenAmount)); await dexContract.swapTokenToEth(ethers.parseEther(tokenAmount)); alert('Swap successful!'); }; return ( My DEX 2025 {!account ? ( Connect Wallet ) : ( Connected: {account}Provide Liquidity setEthAmount(e.target.value)} /> setTokenAmount(e.target.value)} /> Provide LiquiditySwap Tokens setEthAmount(e.target.value)} /> Swap ETH to MTK setTokenAmount(e.target.value)} /> Swap MTK to ETH )} ); } export default App;
Add styling in src/App.css
:
.App { text-align: center; padding: 50px; font-family: Arial, sans-serif; } input { padding: 10px; margin: 10px; width: 200px; } button { padding: 10px 20px; margin: 10px; cursor: pointer; background-color: #0288d1; color: white; border: none; border-radius: 5px; } button:hover { background-color: #0277bd; }
Run with npm start
. Connect MetaMask to Sepolia, provide liquidity with ETH and MTK, and swap tokens. Get test ETH from sepoliafaucet.com. Send some MTK to test accounts via MetaMask.

Step 6: Test the DEX
Test the DEX with multiple MetaMask accounts:
- Provide liquidity with ETH and MTK.
- Swap ETH for MTK and vice versa.
- Remove liquidity and verify balances.
- Try invalid swaps (e.g., zero amounts) to ensure they fail.
Monitor events using Alchemy or Hardhat’s console.
Benefits of Building a DEX
Creating a DEX offers:
- Financial Freedom: Enable trustless trading for users worldwide.
- Innovation: Experiment with new AMM models or fee structures.
- Community Impact: Support the DeFi ecosystem in 2025.
DEXs empower users to trade securely without intermediaries.
Challenges of Building a DEX
DEX development has challenges:
- Security Risks: Smart contract bugs can lead to exploits, like Uniswap’s early flash loan attacks.
- Gas Costs: Ethereum fees remain high in 2025, impacting user adoption.
- Liquidity: Attracting enough liquidity providers is critical for tight spreads.
Mitigate these with audits, layer-2 solutions, and incentives for liquidity providers.
Tips for Developers Building a DEX
To create a successful DEX:
- Audit Contracts: Use MythX or Slither to detect vulnerabilities.
- Optimize Gas: Minimize storage operations and consider layer-2 deployment.
- Test Extensively: Simulate attacks with Hardhat on testnets.
- Study Leaders: Learn from Uniswap or SushiSwap.
These practices ensure your DEX is secure and user-friendly.
Resources for Learning More
Deepen your DEX knowledge with these resources:
- Ethereum Docs: DeFi and AMMs at ethereum.org.
- Uniswap Docs: AMM mechanics at docs.uniswap.org.
- DeFi Communities: Join discussions on Ethereum Stack Exchange or DeFi forums.
Stay curious to master DEX development in 2025.
Conclusion
Building a decentralized exchange is an exciting way to dive into DeFi and create a platform for trustless trading. By developing a DEX with Solidity and React, you’ve learned how to manage liquidity pools and token swaps on Ethereum. Keep exploring advanced features like multi-token pools or governance, and share your DEX ideas in the comments below!