Decentralized Exchange (DEX) and Automated Market Maker (AMM)
Why DEXs Matter
A Decentralized Exchange (DEX) is the core infrastructure of DeFi. Unlike Binance or Coinbase — where you deposit funds and trust the exchange to hold them — a DEX lets you trade directly from your wallet. No registration, no KYC, no withdrawal limits. Your assets never leave your custody.
Uniswap, the first major AMM-based DEX, launched in 2018 and revolutionized crypto trading. Today, DEXs process over $100 billion in monthly volume across Ethereum, BNB Chain, Arbitrum, and other networks.
Why this matters for your career:
- DEXs are the backbone of DeFi — every token needs liquidity to be tradeable
- Understanding AMM mechanics is essential for any Web3 developer
- Building a DEX is a classic smart contract interview challenge
- DEX concepts (liquidity pools, impermanent loss, slippage) appear in every DeFi protocol
What Is an Automated Market Maker (AMM)?
Before AMMs, crypto exchanges used order books — buyers and sellers post orders, and a matching engine finds counterparts. This works well for popular pairs (BTC/USDT) but fails for new or low-volume tokens.
Uniswap's breakthrough was the Constant Product Formula:
$$x \times y = k$$
- $x$ = the amount of Token A in the pool
- $y$ = the amount of Token B in the pool
- $k$ = a constant (the product never changes)
This single formula guarantees that the pool always has liquidity, no matter how large the trade. When someone buys Token A, $x$ decreases and $y$ increases. The price adjusts automatically based on the ratio.
How the Formula Works in Practice
| Scenario | Token A in Pool | Token B in Pool | Price (A/B) | |:---------|:---------------:|:---------------:|:-----------:| | Initial | 100 ETH | 200,000 USDC | 1 ETH = 2,000 USDC | | Buy 10 ETH | 90 ETH | ~222,222 USDC | 1 ETH = ~2,469 USDC | | Buy another 10 ETH | 80 ETH | ~250,000 USDC | 1 ETH = ~3,125 USDC |
Notice that each purchase makes ETH more expensive — this is slippage, and it protects the pool from being drained. The larger the trade relative to the pool size, the more slippage occurs.
Implementing the AMM
// contracts/SimpleDEX.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SimpleDEX is Ownable {
IERC20 public tokenA;
IERC20 public tokenB;
uint256 public reserveA;
uint256 public reserveB;
uint256 public totalLiquidity;
mapping(address => uint256) public liquidity;
// Events
event LiquidityAdded(
address indexed provider,
uint256 amountA,
uint256 amountB,
uint256 shares
);
event LiquidityRemoved(
address indexed provider,
uint256 amountA,
uint256 amountB,
uint256 shares
);
event Swapped(
address indexed trader,
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 amountOut
);
constructor(address _tokenA, address _tokenB) Ownable(msg.sender) {
tokenA = IERC20(_tokenA);
tokenB = IERC20(_tokenB);
}
// === Add Liquidity ===
function addLiquidity(
uint256 _amountA,
uint256 _amountB
) external returns (uint256 shares) {
require(_amountA > 0 && _amountB > 0, "Amounts must be greater than 0");
// Transfer tokens to contract
tokenA.transferFrom(msg.sender, address(this), _amountA);
tokenB.transferFrom(msg.sender, address(this), _amountB);
if (totalLiquidity == 0) {
// First time adding liquidity
shares = _amountA; // Simplified: shares = amountA
} else {
// Calculate proportional shares
shares = (_amountA * totalLiquidity) / reserveA;
}
reserveA += _amountA;
reserveB += _amountB;
liquidity[msg.sender] += shares;
totalLiquidity += shares;
emit LiquidityAdded(msg.sender, _amountA, _amountB, shares);
}
// === Remove Liquidity ===
function removeLiquidity(
uint256 _shares
) external returns (uint256 amountA, uint256 amountB) {
require(_shares > 0, "Shares must be greater than 0");
require(liquidity[msg.sender] >= _shares, "Insufficient shares");
// Calculate withdrawable amounts
amountA = (reserveA * _shares) / totalLiquidity;
amountB = (reserveB * _shares) / totalLiquidity;
liquidity[msg.sender] -= _shares;
totalLiquidity -= _shares;
reserveA -= amountA;
reserveB -= amountB;
// Transfer tokens back
tokenA.transfer(msg.sender, amountA);
tokenB.transfer(msg.sender, amountB);
emit LiquidityRemoved(msg.sender, amountA, amountB, _shares);
}
// === Calculate Swap Output ===
function getAmountOut(
uint256 _amountIn,
uint256 _reserveIn,
uint256 _reserveOut
) public pure returns (uint256) {
require(_amountIn > 0, "Input amount must be greater than 0");
require(_reserveIn > 0 && _reserveOut > 0, "Insufficient liquidity");
// 0.3% fee
uint256 amountInWithFee = _amountIn * 997;
uint256 numerator = amountInWithFee * _reserveOut;
uint256 denominator = (_reserveIn * 1000) + amountInWithFee;
return numerator / denominator;
}
// === Swap Tokens ===
function swapAForB(uint256 _amountIn) external returns (uint256 amountOut) {
require(_amountIn > 0, "Input amount must be greater than 0");
amountOut = getAmountOut(_amountIn, reserveA, reserveB);
require(amountOut > 0, "Output is zero");
tokenA.transferFrom(msg.sender, address(this), _amountIn);
tokenB.transfer(msg.sender, amountOut);
reserveA += _amountIn;
reserveB -= amountOut;
emit Swapped(msg.sender, address(tokenA), address(tokenB), _amountIn, amountOut);
}
function swapBForA(uint256 _amountIn) external returns (uint256 amountOut) {
require(_amountIn > 0, "Input amount must be greater than 0");
amountOut = getAmountOut(_amountIn, reserveB, reserveA);
require(amountOut > 0, "Output is zero");
tokenB.transferFrom(msg.sender, address(this), _amountIn);
tokenA.transfer(msg.sender, amountOut);
reserveB += _amountIn;
reserveA -= amountOut;
emit Swapped(msg.sender, address(tokenB), address(tokenA), _amountIn, amountOut);
}
// === View Current Prices ===
function getPriceA() external view returns (uint256) {
require(reserveA > 0 && reserveB > 0, "Insufficient liquidity");
return (reserveB * 1e18) / reserveA;
}
function getPriceB() external view returns (uint256) {
require(reserveA > 0 && reserveB > 0, "Insufficient liquidity");
return (reserveA * 1e18) / reserveB;
}
}
Testing the AMM
// scripts/test_dex.js
async function main() {
const [deployer, user1, user2] = await hre.ethers.getSigners();
// Deploy tokens
const VibeToken = await hre.ethers.getContractFactory("VibeToken");
const tokenA = await VibeToken.deploy(deployer.address);
await tokenA.waitForDeployment();
const tokenB = await VibeToken.deploy(deployer.address);
await tokenB.waitForDeployment();
// Deploy DEX
const SimpleDEX = await hre.ethers.getContractFactory("SimpleDEX");
const dex = await SimpleDEX.deploy(
await tokenA.getAddress(),
await tokenB.getAddress()
);
await dex.waitForDeployment();
console.log("DEX deployed at:", await dex.getAddress());
// Prepare liquidity
const amountA = hre.ethers.parseEther("1000");
const amountB = hre.ethers.parseEther("1000");
await tokenA.approve(await dex.getAddress(), amountA);
await tokenB.approve(await dex.getAddress(), amountB);
// Add liquidity
await dex.addLiquidity(amountA, amountB);
console.log("Liquidity added: 1000 A + 1000 B");
// Check price
const priceA = await dex.getPriceAInB();
console.log("1 A = ", hre.ethers.formatEther(priceA), "B");
// Swap A for B
const swapAmount = hre.ethers.parseEther("100");
await tokenA.connect(user1).approve(await dex.getAddress(), swapAmount);
// Give user1 some A tokens
await tokenA.transfer(user1.address, swapAmount);
const result = await dex.connect(user1).swapAForB(swapAmount);
console.log(`Swapped 100 A for B tokens`);
// Check new price (slippage effect)
const newPriceA = await dex.getPriceAInB();
console.log("After swap, 1 A = ", hre.ethers.formatEther(newPriceA), "B");
console.log("Slippage occurred! Deeper pools reduce slippage.");
}
main().catch(console.error);
How to Use Vibe Coding to Build a DEX
Instead of writing the AMM math yourself, describe your DEX requirements to AI. Here is a battle-tested prompt:
🔥 Vibe Coding Prompt for DEX Creation "Build a decentralized exchange with the following features:
- Support ETH/Token trading pairs (not just Token/Token)
- Use the x * y = k AMM formula from Uniswap V2
- Charge 0.3% fee on every swap, automatically added to the pool
- Liquidity providers can add or remove liquidity at any time
- Write a test script: deploy → add liquidity → swap → remove liquidity
- Calculate and display slippage for each trade"
AMM Security Considerations
| Issue | Why It Matters | Mitigation | |:------|:---------------|:-----------| | Front-running | Attackers see pending swaps and trade before them | Use a commit-reveal scheme or a private mempool | | Oracle manipulation | Attackers can drain pools by manipulating the spot price | Use TWAP (Time-Weighted Average Price) oracles | | Flash loan attacks | Attackers borrow millions, manipulate price, and repay in one transaction | Integrate a TWAP oracle for critical operations | | Impermanent loss | LPs lose value when prices diverge from the initial ratio | Educate LPs about IL; consider concentrated liquidity | | Slippage sandwich attacks | Bots sandwich user trades to extract profit | Let users set a minimum output amount (slippage tolerance) | | Infinite approval | DEX can drain all tokens from users who approved it | Encourage users to approve only the amount they are swapping |
Summary
You have now built a fully functional Automated Market Maker — the same technology that powers Uniswap, PancakeSwap, and SushiSwap:
- What you built: A constant-product AMM with liquidity pools, swap functions, price queries, and LP token tracking
- Why it matters: DEXs are the backbone of DeFi — they enable permissionless trading without intermediaries
- How it works: The $x \times y = k$ formula guarantees liquidity at any price; the 0.3% fee compensates liquidity providers; slippage protects the pool from being drained
Key takeaways:
- The constant product formula $x \times y = k$ is the foundation of all AMM-based DEXs
- Slippage is not a bug — it is a feature that protects pools from large, price-moving trades
- The 0.3% fee (997/1000) is the Uniswap standard; it gets distributed to liquidity providers
- Liquidity providers earn fees proportional to their share of the pool
- Impermanent loss is the main risk for LPs — it occurs when the external price diverges from the pool price
- Front-running and sandwich attacks are the main security concerns — use slippage tolerance to protect users
What Is Next: Lending Protocol
Now that you understand how decentralized trading works, the next chapter teaches you how to build a lending protocol — the other pillar of DeFi alongside DEXs. You will learn how users can deposit assets to earn interest, borrow assets against collateral, and how liquidation works when positions become undercollateralized. This is the technology behind Aave, Compound, and Morpho.