DeFi Deep Dives
Automated Market Makers Explained: How AMMs Actually Work
TL;DR
Automated market makers replace traditional order books with mathematical formulas that price assets algorithmically. The core idea is simple — a smart contract holds two tokens and uses a formula like x * y = k to determine the exchange rate. In this article, I break down the math behind constant product AMMs, walk through impermanent loss with actual numbers, explain how Uniswap V3's concentrated liquidity changed the game, and build a minimal AMM in Solidity from scratch. If you're building in DeFi or providing liquidity, this is the foundation you need to understand.
What Is an AMM?
Automated market makers are the backbone of decentralized exchanges. Every time you swap tokens on Uniswap, SushiSwap, or Curve, you're interacting with an AMM — a smart contract that algorithmically determines the price of assets based on the ratio of tokens in a liquidity pool.
In traditional finance, exchanges use order books. Buyers place bids, sellers place asks, and a matching engine connects them. This works well when you have deep liquidity and professional market makers willing to continuously quote prices. On-chain, order books are impractical. Every order placement, cancellation, and modification would require a gas-consuming transaction. The latency of block times (12 seconds on Ethereum) makes front-running trivial and market making unprofitable.
AMMs solve this by removing the order book entirely. Instead of matching buyers and sellers, a liquidity pool holds reserves of two tokens. Anyone can trade against the pool, and the price is determined by a deterministic formula applied to the current reserve balances. No counterparty needed. No market maker quoting spreads. Just math.
I've built AMM-based protocols for clients ranging from simple token swaps to complex multi-asset pools with dynamic fees. The concept is deceptively simple, but the details — pricing, slippage, capital efficiency, and impermanent loss — require genuine understanding. Let's get into it.
The Math — Constant Product Formula
The most widely used AMM formula is the constant product invariant, popularized by Uniswap V1 and V2:
x * y = kWhere:
x= reserve of Token A in the pooly= reserve of Token B in the poolk= a constant that must be maintained after every trade
The key insight: the product of the two reserves must remain constant before and after any swap (excluding fees). This creates a hyperbolic curve where the price of one token in terms of the other is always the ratio of reserves.
Price Determination
The spot price of Token A in terms of Token B is:
Price(A) = y / xIf a pool holds 100 ETH and 300,000 USDC:
k = 100 * 300,000 = 30,000,000
Price(ETH) = 300,000 / 100 = 3,000 USDC per ETHExecuting a Swap
Suppose someone wants to buy 1 ETH from this pool. The pool must maintain k = 30,000,000 after the trade.
After removing 1 ETH, the new ETH reserve is 99. We solve for the new USDC reserve:
99 * y_new = 30,000,000
y_new = 303,030.30 USDCThe buyer must deposit 303,030.30 - 300,000 = 3,030.30 USDC to receive 1 ETH.
Notice the effective price is 3,030.30 USDC — higher than the spot price of 3,000. This difference is price impact (or slippage). Larger trades relative to pool size create more slippage. This is a feature, not a bug — it protects the pool from being drained at favorable prices.
The Slippage Problem
If someone tried to buy 50 ETH from that same pool:
50 * y_new = 30,000,000
y_new = 600,000 USDC
Cost = 600,000 - 300,000 = 300,000 USDC
Effective price = 6,000 USDC per ETHThat's a 100% premium over spot. The constant product formula makes it mathematically impossible to drain a pool — as one reserve approaches zero, the price approaches infinity. This is elegant but capital-inefficient, which is exactly what Uniswap V3 addresses.
How Liquidity Providers Work
Liquidity providers (LPs) are the people who deposit tokens into pools, enabling trading. Without LPs, there's no pool, and without a pool, there's no exchange.
To become an LP on a standard constant product AMM, you deposit equal value of both tokens. If ETH is $3,000 and you want to add liquidity to the ETH/USDC pool, you deposit (for example) 10 ETH and 30,000 USDC.
In return, you receive LP tokens — ERC-20 tokens representing your proportional share of the pool. If the total pool is 100 ETH and 300,000 USDC and you deposited 10 ETH + 30,000 USDC, you own 10% of the pool.
How LPs Earn
Every swap incurs a fee (typically 0.3% on Uniswap V2). This fee is added to the pool reserves, increasing the value of LP tokens over time.
If the pool processes $1,000,000 in daily volume, that's $3,000 in daily fees distributed proportionally to all LPs. Your 10% share earns you $300/day.
Annualized, that's $109,500 on a $60,000 deposit — a 182% APR. Sounds incredible, right? It can be. But there's a catch that most DeFi tutorials gloss over: impermanent loss.
Impermanent Loss Explained with Real Numbers
Impermanent loss (IL) is the difference between holding tokens in a liquidity pool versus simply holding them in your wallet. It occurs whenever the price ratio of the pooled tokens changes from when you deposited.
Let me walk through a concrete example.
Setup
You deposit into an ETH/USDC pool when ETH = $3,000:
- 10 ETH + 30,000 USDC = $60,000 total
- You own 10% of the pool (100 ETH / 300,000 USDC total)
Scenario: ETH price doubles to $6,000
Arbitrageurs will trade against the pool until the pool price matches the external market price. The constant product invariant determines the new reserve balances.
k = 100 * 300,000 = 30,000,000At the new price, the pool must satisfy:
y / x = 6,000 (new price ratio)
x * y = 30,000,000 (constant product)Substituting:
x * 6000x = 30,000,000
6000x^2 = 30,000,000
x^2 = 5,000
x = 70.71 ETH
y = 424,264.07 USDCYour 10% share is now:
7.071 ETH + 42,426.41 USDC
Value = (7.071 * 6,000) + 42,426.41 = $84,852.81If you had simply held:
10 ETH + 30,000 USDC
Value = (10 * 6,000) + 30,000 = $90,000Impermanent loss = $90,000 - $84,852.81 = $5,147.19 (5.72%)
You still made money ($84,852 vs your initial $60,000), but you made *less* than if you had done nothing. That $5,147 is the cost of providing liquidity.
IL Formula
For any price change ratio r (new price / old price), the impermanent loss percentage is:
IL = 2 * sqrt(r) / (1 + r) - 1Some reference points:
| Price Change | IL |
|---|---|
| 1.25x | 0.6% |
| 1.50x | 2.0% |
| 2x | 5.7% |
| 3x | 13.4% |
| 5x | 25.5% |
A 5x price move in either direction costs you a quarter of your position value versus holding. This is why high-volume pools with correlated assets (like stablecoin pairs) are often safer for LPs — less price divergence means less IL.
The name "impermanent" is somewhat misleading. The loss becomes permanent the moment you withdraw. If the price returns to the original ratio, the loss disappears. But betting on mean reversion is not a strategy — it's hope.
Uniswap V3 Concentrated Liquidity
Uniswap V3 introduced concentrated liquidity, which was the single biggest innovation in AMM design since the constant product formula itself.
The problem with V2: your liquidity is spread uniformly across the entire price curve from 0 to infinity. If ETH is trading at $3,000, your capital providing liquidity at $0.01 or $1,000,000 is completely unused. For most pools, over 99% of deposited capital does zero work.
How Concentrated Liquidity Works
V3 lets LPs choose a price range for their liquidity. Instead of providing liquidity from 0 to infinity, you might provide liquidity from $2,500 to $3,500. Your capital is concentrated in that range, making it far more capital-efficient.
If you concentrate your liquidity into a range that's 1/10th of the full curve, you provide the same depth as 10x more capital in a V2 pool. This is a massive efficiency gain.
The Tick System
V3 divides the price space into discrete ticks. Each tick represents a 0.01% (1 basis point) price change. Liquidity positions are defined by a lower tick and an upper tick.
tick = log(sqrt(price)) / log(sqrt(1.0001))Or equivalently:
price = 1.0001^tickWhen the current price moves across a tick boundary, the active liquidity changes. The contract tracks liquidityNet at each tick — the net liquidity that becomes active or inactive when that tick is crossed.
This is a fundamentally different data structure from V2. Instead of a single (x, y, k) state, V3 maintains a mapping of tick ranges to liquidity amounts, and the current tick determines which positions are active.
Capital Efficiency in Numbers
Consider an LP with $100,000 who believes ETH will trade between $2,800 and $3,200.
Uniswap V2: $100,000 spread across the entire curve. Capital utilization within the $2,800-$3,200 range is roughly $1,300.
Uniswap V3: $100,000 concentrated in the $2,800-$3,200 range. Full $100,000 is active. That's approximately 77x more capital efficiency.
The trade-off: if the price moves outside your range, you earn zero fees and your position is entirely in the less valuable token. Concentrated liquidity amplifies both returns and impermanent loss. It requires active management — this is not a set-and-forget strategy.
Building an AMM from Scratch — Solidity
Here's a minimal but functional constant product AMM. This is not production-ready (it lacks flash loan protection, fee-on-transfer token support, and oracle functionality), but it demonstrates the core mechanics.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract SimpleAMM is ERC20, ReentrancyGuard {
IERC20 public immutable tokenA;
IERC20 public immutable tokenB;
uint256 public reserveA;
uint256 public reserveB;
uint256 public constant FEE_NUMERATOR = 3;
uint256 public constant FEE_DENOMINATOR = 1000;
event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB, uint256 lpTokens);
event LiquidityRemoved(address indexed provider, uint256 amountA, uint256 amountB, uint256 lpTokens);
event Swap(address indexed trader, address tokenIn, uint256 amountIn, uint256 amountOut);
error InsufficientLiquidity();
error InsufficientOutput();
error InvalidToken();
error ZeroAmount();
constructor(
address _tokenA,
address _tokenB
) ERC20("AMM LP Token", "AMM-LP") {
tokenA = IERC20(_tokenA);
tokenB = IERC20(_tokenB);
}
function addLiquidity(
uint256 amountA,
uint256 amountB
) external nonReentrant returns (uint256 lpTokens) {
if (amountA == 0 || amountB == 0) revert ZeroAmount();
if (totalSupply() == 0) {
lpTokens = sqrt(amountA * amountB);
} else {
uint256 lpFromA = (amountA * totalSupply()) / reserveA;
uint256 lpFromB = (amountB * totalSupply()) / reserveB;
lpTokens = lpFromA < lpFromB ? lpFromA : lpFromB;
}
if (lpTokens == 0) revert InsufficientLiquidity();
tokenA.transferFrom(msg.sender, address(this), amountA);
tokenB.transferFrom(msg.sender, address(this), amountB);
reserveA += amountA;
reserveB += amountB;
_mint(msg.sender, lpTokens);
emit LiquidityAdded(msg.sender, amountA, amountB, lpTokens);
}
function removeLiquidity(
uint256 lpTokens
) external nonReentrant returns (uint256 amountA, uint256 amountB) {
if (lpTokens == 0) revert ZeroAmount();
amountA = (lpTokens * reserveA) / totalSupply();
amountB = (lpTokens * reserveB) / totalSupply();
if (amountA == 0 || amountB == 0) revert InsufficientLiquidity();
_burn(msg.sender, lpTokens);
reserveA -= amountA;
reserveB -= amountB;
tokenA.transfer(msg.sender, amountA);
tokenB.transfer(msg.sender, amountB);
emit LiquidityRemoved(msg.sender, amountA, amountB, lpTokens);
}
function swap(
address tokenIn,
uint256 amountIn,
uint256 minAmountOut
) external nonReentrant returns (uint256 amountOut) {
if (amountIn == 0) revert ZeroAmount();
bool isTokenA = tokenIn == address(tokenA);
if (!isTokenA && tokenIn != address(tokenB)) revert InvalidToken();
(IERC20 inputToken, IERC20 outputToken, uint256 reserveIn, uint256 reserveOut) = isTokenA
? (tokenA, tokenB, reserveA, reserveB)
: (tokenB, tokenA, reserveB, reserveA);
inputToken.transferFrom(msg.sender, address(this), amountIn);
uint256 amountInAfterFee = amountIn * (FEE_DENOMINATOR - FEE_NUMERATOR) / FEE_DENOMINATOR;
amountOut = (reserveOut * amountInAfterFee) / (reserveIn + amountInAfterFee);
if (amountOut < minAmountOut) revert InsufficientOutput();
if (isTokenA) {
reserveA += amountIn;
reserveB -= amountOut;
} else {
reserveB += amountIn;
reserveA -= amountOut;
}
outputToken.transfer(msg.sender, amountOut);
emit Swap(msg.sender, tokenIn, amountIn, amountOut);
}
function getSpotPrice(bool priceOfA) external view returns (uint256) {
if (reserveA == 0 || reserveB == 0) revert InsufficientLiquidity();
return priceOfA
? (reserveB * 1e18) / reserveA
: (reserveA * 1e18) / reserveB;
}
function getAmountOut(
address tokenIn,
uint256 amountIn
) external view returns (uint256) {
bool isTokenA = tokenIn == address(tokenA);
if (!isTokenA && tokenIn != address(tokenB)) revert InvalidToken();
(uint256 reserveIn, uint256 reserveOut) = isTokenA
? (reserveA, reserveB)
: (reserveB, reserveA);
uint256 amountInAfterFee = amountIn * (FEE_DENOMINATOR - FEE_NUMERATOR) / FEE_DENOMINATOR;
return (reserveOut * amountInAfterFee) / (reserveIn + amountInAfterFee);
}
function sqrt(uint256 x) internal pure returns (uint256 y) {
if (x == 0) return 0;
uint256 z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}What This Contract Does
- `addLiquidity` — First depositor sets the initial ratio. Subsequent depositors must match the current ratio. LP tokens are minted proportionally using the geometric mean (
sqrt(amountA * amountB)) for the initial deposit.
- `removeLiquidity` — Burns LP tokens and returns the proportional share of both reserves. Because fees accumulate in the reserves, LPs receive more tokens than they deposited (assuming positive trading volume).
- `swap` — Implements the constant product formula with a 0.3% fee. The fee is deducted from the input amount before calculating the output, which means the fee remains in the pool and accrues to LPs.
- `getAmountOut` — A view function for frontends to quote prices before executing swaps.
What's Missing for Production
- TWAP oracle for manipulation-resistant price feeds
- Flash loan support (Uniswap V2's flash swap pattern)
- Fee-on-transfer token handling
- Minimum liquidity lock to prevent the first depositor attack
- Emergency pause mechanism
- Multi-sig admin for parameter updates
If you're building a production AMM, reach out through my services page — I've shipped DeFi protocols that handle real capital.
The Economics of Being an LP
Being a liquidity provider is often marketed as "passive income in DeFi." The reality is more nuanced.
When LPing Is Profitable
- High volume, low volatility pairs — Stablecoin pairs (USDC/USDT) generate consistent fees with minimal IL
- Mean-reverting assets — Pairs that oscillate within a range give you fee income while IL stays manageable
- Incentivized pools — Many protocols distribute governance tokens to LPs, which can offset IL
When LPing Is Not Profitable
- Trending markets — If ETH goes on a 3x run, you'd have been better off just holding ETH
- Low-volume pools — Fees don't compensate for IL and smart contract risk
- Concentrated positions in volatile markets — V3 positions that go out of range earn nothing and suffer maximum IL for that range
The LP Return Formula
Your actual return as an LP is:
Net LP Return = Fee Income - Impermanent Loss - Gas Costs - Opportunity CostMost LPs focus on fee income and ignore the other three terms. Academic research (notably from Lund University and Bancor) has shown that over 50% of Uniswap V3 LPs are net negative after accounting for IL. The profitable minority tend to be sophisticated operators running active management strategies — rebalancing ranges, hedging with options, and using MEV-aware execution.
This doesn't mean LPing is a bad idea. It means you need to do the math for your specific situation before committing capital.
AMMs vs Order Books
The AMM vs order book debate isn't about which is "better" — it's about which is appropriate for the context.
| Dimension | AMMs | Order Books |
|---|---|---|
| Liquidity | Algorithmic, always available | Depends on market makers |
| Capital Efficiency | Low (V2) to High (V3) | High — capital deployed at specific prices |
| Execution | Guaranteed at a price (with slippage) | May not fill if no counterparty |
| Price Discovery | Follows external markets via arbitrage | Native price discovery |
| Front-running | Vulnerable to MEV/sandwich attacks | Less vulnerable on centralized platforms |
| Composability | Fully composable with other DeFi protocols | Limited on-chain composability |
| User Experience | Simple — just swap | Complex — orders, cancellations, fills |
| Gas Cost | Single transaction | Multiple transactions per order lifecycle |
AMMs dominate DeFi because they're simple, permissionless, and composable. Any protocol can plug into Uniswap's liquidity without permission. Order books are winning on L2s and app-specific chains (dYdX on Cosmos, Hyperliquid) where gas costs are negligible and throughput is high enough for order management.
The future is likely hybrid. We're already seeing designs like Uniswap V4 hooks that allow order-book-like behavior within an AMM framework, and protocols like Maverick that automate liquidity repositioning to mimic market making strategies.
Key Takeaways
- AMMs replace order books with math — the constant product formula
x * y = kdetermines prices based on reserve ratios, making trustless exchange possible without counterparties.
- Price impact is proportional to trade size relative to pool depth — large trades on small pools get bad prices. This is the core mechanism that protects liquidity.
- Impermanent loss is real and quantifiable — a 2x price move costs 5.7% versus holding. A 5x move costs 25.5%. Run the numbers before depositing.
- Concentrated liquidity (V3) is a massive capital efficiency upgrade — but it requires active management and amplifies IL within the chosen range.
- LP profitability depends on fee income exceeding IL — high volume and low volatility is the sweet spot. Most passive LPs on V3 underperform simple holding.
- The constant product formula makes pools un-drainable — price approaches infinity as reserves approach zero. This is mathematically elegant and practically important.
- The AMM design space is still evolving — V4 hooks, dynamic fees, and hybrid order-book-AMM models are the frontier. If you're building in DeFi, understanding these primitives is non-negotiable.
About the Author
I'm Uvin Vindula — a Web3 and AI engineer working across Sri Lanka and the UK. I build DeFi protocols, smart contract systems, and full-stack dApps for clients who need things to actually work in production. If you're building something in the AMM or broader DeFi space, I'd be interested to hear about it. Find me at uvin.lk↗ or reach out through my services page.
Working on a Web3 or AI project?

Uvin Vindula
Web3 and AI engineer based in Sri Lanka and the UK. Author of The Rise of Bitcoin. Director of Blockchain and Software Solutions at Terra Labz. Founder of uvin.lk — Sri Lanka's Bitcoin education platform with 10,000+ learners.