What is an AMM?
An Automated Market Maker (AMM) is a smart contract that holds reserves of two (or more) tokens and allows users to swap between them algorithmically — no order book, no matching engine, no counterparty needed. Price is determined by the ratio of reserves according to a mathematical formula.
The constant product formula
Uniswap V2 uses the simplest and most influential AMM formula: x * y = k. Where x and y are the reserves of two tokens, and k is a constant that must hold after every trade (minus fees).
// k = reserve0 * reserve1 must remain constant after a swap
//
// Before swap: reserve0 = 1000, reserve1 = 1000, k = 1,000,000
// User swaps in 100 of token0:
// newReserve0 = 1100
// newReserve1 = k / newReserve0 = 1,000,000 / 1100 ≈ 909
// User receives: 1000 - 909 = 91 of token1
//
// The price moved because the reserves changed.
// Larger trades cause more 'slippage' — worse prices.A minimal AMM swap implementation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
contract SimpleAMM is ReentrancyGuard {
IERC20 public tokenA;
IERC20 public tokenB;
uint256 public reserveA;
uint256 public reserveB;
uint256 public constant FEE_NUMERATOR = 997;
uint256 public constant FEE_DENOMINATOR = 1000; // 0.3% fee
event Swap(address indexed sender, uint256 amountIn, uint256 amountOut, bool aToB);
constructor(address _tokenA, address _tokenB) {
tokenA = IERC20(_tokenA);
tokenB = IERC20(_tokenB);
}
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256 amountOut) {
require(amountIn > 0, 'Insufficient input');
require(reserveIn > 0 && reserveOut > 0, 'No liquidity');
uint256 amountInWithFee = amountIn * FEE_NUMERATOR;
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = (reserveIn * FEE_DENOMINATOR) + amountInWithFee;
amountOut = numerator / denominator;
}
function swapAForB(uint256 amountIn, uint256 minAmountOut) external nonReentrant {
uint256 amountOut = getAmountOut(amountIn, reserveA, reserveB);
require(amountOut >= minAmountOut, 'Slippage too high');
tokenA.transferFrom(msg.sender, address(this), amountIn);
tokenB.transfer(msg.sender, amountOut);
reserveA += amountIn;
reserveB -= amountOut;
emit Swap(msg.sender, amountIn, amountOut, true);
}
}Price impact and slippage
The larger a swap relative to the pool's reserves, the more the price moves against the swapper. This is called price impact. minAmountOut (a slippage tolerance parameter) protects users from getting a dramatically worse price than expected due to front-running or thin liquidity.
Why AMMs replaced order books
- Always available — no need for a counterparty willing to trade
- Fully on-chain — no off-chain matching engine
- Permissionless — anyone can add liquidity and earn fees
- Composable — other contracts can call swap functions directly