AMM basics

How automated market makers replaced order books — and the math that makes them work.

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).

solidity
// 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

solidity
// 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
←   Gas optimizationLiquidity pools   →