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!

