How to Create a Decentralized Exchange (DEX):2025 A Technical Tutorial

How to Create a Decentralized Exchange (DEX):2025 A Technical Tutorial

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!

Decentralized exchange concept

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.

DEX trading platform

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.
DEX smart contract

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.

DEX DApp interface

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:

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!

发表回复