---
# System prepended metadata

title: Understanding Uniswap V2 Code (1/n) - Swap Mechanism

---

# Understanding Uniswap V2 Code (1/n) - Swap Mechanism
> A deep dive into Uniswap V2's swap mechanism - from user click to AMM math
## TLDR
- **Architecture**: Core (immutable protocol) + Periphery (upgradable user interface)
- **Key insight**: Optimistic transfer pattern
- **Math**: xy=k with 0.3% fee built into the constant product check
- **Example**: Full trace of 100 token0 → 90 token1 swap with all state changes in concrete numbers

## Architecture Overview
```
 ┌─────────────────────────────────────────────────────┐                                                      
 │  v2-periphery (User Interaction Layer)              │                                                      
 │  ├── Router (SwapExactTokensForTokens, etc)         │                                                       
 │  ├── Library (Price oracles, multicall helpers)     │                                                       
 │  └── Interfaces (What users interact with)          │                                                       
 └─────────────────────────────────────────────────────┘                                                      
                         ↓ calls                                   
 ┌─────────────────────────────────────────────────────┐                                                      
 │  v2-core (The Protocol/AMM Layer)                   │                                                      
 │  ├── Factory (Creates and tracks pairs)             │                                                       
 │  ├── Pairs (AMM logic, liquidity, swaps)            │                                                       
 │  └── ERC20 (LP token implementation)                │                                                       
 └─────────────────────────────────────────────────────┘
```
### Design Logic
V2-Core - The Immutable Protocol
- Once deployed, v2 contracts should be **immutable and unchangeable**
- Because **billions of dollars depend on it working exactly the same way forever**
- **One Pair Per Token Pair**: one pool = max liquidity concentration

V2-Periphery - The Upgradable User Inteface Layer
- Can be upgraded or replaced without touching the core protocol
- Provide the public-facing functions users and apps interact with
- Include the Router for swaps, and Libraries for helpers like price oracles and multicalls

## Uniswap V2 Swap Flow
### High-Level Flow
`User -> Router -> Pair Contract -> Token Transfer -> Reserves Updated`


### Step 1: User Initiates Swap (Router - periphery)
User calls `swapExactTokensForTokens` to swap an exact amount of token A for token B
```solidity
// UniswapV2Router02.sol
function swapExactTokensForTokens(
    uint amountIn,              // amount of token user is selling
    uint amountOutMin,          // min amount user is willing to accept (slippage protection)
    address[] calldata path,    // token route: eg. [USDC, ETH, DAI] means USDC -> ETH -> DAI
    address to,                 // address receive the output token
    uint deadline               // latest timestamp this tx can execute (MEV protection)
) external virtual override ensure(deadline) returns (uint[] memory amounts) 
{
    // calculate the amountOut for the multi-hop swap
    amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
    // check the amountOut against the min amount accepted
    require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
    // initiate the first swap
    TransferHelper.safeTransferFrom(
        path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
    );
    // handle the rest of the swap chain
    _swap(amounts, path, to);
}
```
Router is the trusted entry point that
- Validate inputs (deadline, min amounts)
- Calculate optimal swap path
- Handle ERC20 token transfer to the pair
- Calls the pair contract's `swap()` function

### Step 2: `swapExactTokensForTokens` internally calls `getAmountsOut` and `getAmountOut`
`getAmountsOut` calculate the output token amounts for a multi-hop swap path
- Example: 
    - Input: 1000 DAI, Path [DAI -> USDC -> ETH]
    - Output: [1000, 906, 8.28]
```solidity
// UniswapV2Router02.sol
function getAmountsOut(
    address factory,
    uint amountIn,
    address[] memory path)
internal view returns (uint[] memory amounts) 
{
    require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
    amounts = new uint[](path.length);
    amounts[0] = amountIn;
    // loop through each pair in the path, and calculate the amountOut
    for (uint i; i < path.length - 1; i++) {
        (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
        amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
    }
}
```

`getAmountOut` calculate the output token amount for a single swap
- Formule to calculate amountOut: (detailed derivation in below section)
    - `amountOut = (reserveOut x amountIn * 0.997) / (reserveIn + amountIn * 0.997)`
```solidity
// UniswapV2Router02.sol
function getAmountOut(
    uint amountIn, 
    uint reserveIn,
    uint reserveOut
) internal pure returns (uint amountOut) 
{
    require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
    require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
    uint amountInWithFee = amountIn.mul(997); // subtract 0.3% fee
    uint numerator = amountInWithFee.mul(reserveOut);
    uint denominator = reserveIn.mul(1000).add(amountInWithFee);
    amountOut = numerator / denominator;
}
```
### Step 3: `swapExactTokensForTokens` then internally calls `_swap`
`_swap` executes the actual token swaps through multiple pairs in sequence

```solidity
function _swap(
    uint[] memory amounts,  // array of amountOut at each step
    address[] memory path,  // array of token address
    address _to             // final destination address
) internal virtual {
    // loop through each consecutive pair in the path
    for (uint i; i < path.length - 1; i++) {
        // extract current pair
        (address input, address output) = (path[i], path[i + 1]);
        // sort 2 tokens in canonical order and return token0 address
        (address token0,) = UniswapV2Library.sortTokens(input, output);
        // get pre-calculated amountOut for this swap
        uint amountOut = amounts[i + 1];
        // check if amountOut is token0 or not, update the output with amountOut
        (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
        // determine output recipient
        address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
        // call pair contract's swap function
        IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
            amount0Out, amount1Out, to, new bytes(0)
        );
    }
}
```


### Step 4: Pair Contract Receives Swap Call (Pair - core)
`_swap()` calls `IUniswapV2Pair(...).swap()` in v2-core
```solidity
// UniswapV2Pair.sol
function swap(
    uint amount0Out,        // amount of token0 to send out
    uint amount1Out,        // amount of token1 to send out
    address to,             // address to send the output token
    bytes calldata data     // for flash swap
) external lock {
// Step 1: Validation
    // At least one of the token amount is positive
    require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
    // Get current reserves
    (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
    // Verify requested outputs don't exceed reserves
    require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');

// Step 2: Optimistically transfer output tokens    
    uint balance0;
    uint balance1;
    { // scope for _token{0,1}, avoids stack too deep errors
    // Validate recipient: can't send to token addresses themselves
    address _token0 = token0;
    address _token1 = token1;
    require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
    // Optimistically send output token first
    if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
    if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens

// Step 3: Execute flash loan callback (if needed)
    if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);

// Step 4: Check input tokens received
    balance0 = IERC20(_token0).balanceOf(address(this));
    balance1 = IERC20(_token1).balanceOf(address(this));
    }
    uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
    uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
    require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');

// Step 5: Validate the constant product formula (k)
    { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
    uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
    uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
    require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
    }

// Step 6: Update reserves & Emit event
    _update(balance0, balance1, _reserve0, _reserve1);
    emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
```
**Key insights about the swap mechanism**

🔄 **The Optimistic Transfer pattern**
- Pair sends output tokens first (without checking if the user has already deposited enough amount)
- Then checks if input tokens arrived
- Why? This enables Flash Swaps - borrow funds now, use them, and repay later in the same transaction

💰 **The Constant Product Formula Check**
- The 0.3% fee is built into the K check, not a separate step
- `balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2)`
    - `balance0Adjusted = balance0 * 1000 - amount0In * 3`  (0.3% fee deducted)
    - `balance1Adjusted = balance1 * 1000 - amount1In * 3`  (0.3% fee deducted)
- The math: `(reserve0 + amount0In - fee) * (reserve1 - amount0Out) >= reserve0 * reserve1`
- This ensures `k` never decreases, guaranteeing the AMM formula holds

### How does the `xy = k` is used to calculate `amountOut`
- Starting point: `reserveIn x reserveOut = k`
- After swap: `(reserveIn + amountIn) * (reserveOut - amountOut) = k`
- After apply the 0.3% fee: `(reserveIn + amountIn * 0.997) * (reserveOut - amountOut) >= reserveIn x reserveOut`
    - Enforce that `k` doesn't decrease to ensure the pool remains solvent and prevents mev exploitation
- Solve for `amountOut` step by step based on `(reserveIn + amountIn * 0.997) * (reserveOut - amountOut) = reserveIn x reserveOut`
    - `reserveOut - amountOut = reserveIn x reserveOut / (reserveIn + amountIn * 0.997)`
    - `amountOut = reserveOut - reserveIn x reserveOut / (reserveIn + amountIn * 0.997)`
    - `amountOut = (reserveOut x (reserveIn + amountIn * 0.997) - reserveIn x reserveOut) / (reserveIn + amountIn * 0.997)`
    - `amountOut = (reserveOut x amountIn * 0.997) / (reserveIn + amountIn * 0.997)`


### Example Flow With Concrete Numbers
```
==========
Initial State
- Pair contract reserves:
    - reserve0 (token0): 1,000 tokens
    - reserve1 (token1): 1,000 tokens
- User balances
    - Has 100 token0
    - Want to swap for token1

==========
Step 1: User transfers input to pair
- User sends 100 token0 to pair address
    - token0.transfer(pair_address, 100);
- State after transfer:
    - Pair's token0 balance: 1,000 + 100 = 1,100
    - Pair's token1 balance: 1,000 (unchanged)
    - Pair's recorded reserves: {1,000, 1,000} (not updated yet)

==========
Step 2: User calls swap() with calculated output
- User calls calculate how much token1 they should get
    - amountOut = (amountIn x (1 - fee%)) x reserveOut / (reserveIn + amountIn x (1 - fee%))
    - amountOut = (100 x 0.997) x 1000 / (1000 + 100 x 0.997) = 90 token1
- User calls
    - pair.swap(0, 90, user_address, "");

==========
Step 3: Pair checks - Output requirement
- Check: amount0Out > 0 || amount1Out > 0
            0 ❌             90 ✅

==========
Step 4: Pair checks - Sufficient liquidity
- Check: amount0Out < _reserve0 && amount1Out < _reserve1
            0       <    1000    &&    90     <    6000
            ✅                         ✅

==========
Step 5: Optimistic transfer
- if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);  // 0 > 0? No
- if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // 90 > 0? YES ✅
- Transfer happens
    - Pair sends 531 token1 to user address
- State after transfer
    - Pair's token0 balance: 1,100
    - Pair's token1 balance: 1,000 - 90 = 910
    - User's token1 balance: 90

==========
Step 6: Pair checks input
- Now pair checks if user actually paid by compare current balances vs old reserves
- uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
- uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
- Calculate amount0In
    - balance0 = 1,100; _reserve0 = 1,000; amount0Out = 0
    - balance0 > _reserve0 - amount0Out ✅
    - amount0In = 1,100 - (1,000 - 0) = 100
- Calculate amount1In
    - balance1 = 910; _reserve1 = 1,000; amount1Out = 90
    - balance1 > _reserve1 - amount1Out ❌ 
    - amount1In = 0
- Check: amount0In > 0 || amount1In > 0
            100    > 0 ||    0      > 0
            ✅                ❌

==========
Step 7: Validate k invariant
- Calculate balance0Adjusted with 0.3% fee
    = balance0 x 1000 - amount0In x 3
    = 1,100 x 1000 - 100 x 3 = 1,099,970
- Calculate balance1Adjusted with 0.3% fee
    = balance1 × 1000 - amount1In × 3
    = 910 x 1,000 - 0 = 910,000
- Check k
    - Left side: balance0Adjusted x balance1Adjusted = 1,000,727,000,000
    - Right side: reserve0 * reserve1 * 1,000,000 = 1,000,000,000,000
    - Left >= Right ✅ 

==========
Step 8: Update reserves
- New state
    - reserve0 = 1,100
    - reserve1 = 910
```

## Reference
- UniswapV2 doc: https://docs.uniswap.org/contracts/v2/overview
- UniswapV2 core repo: https://github.com/Uniswap/v2-core
- UniswapV2 periphery repo: https://github.com/Uniswap/v2-periphery

## Discussion
Found an error? Have questions? 
- Twitter: [@chloe_zhuX](https://x.com/Chloe_zhuX)
- Telegram: [@Chloe_zhu](https://t.me/chloe_zhu)
- GitHub: [@Chloezhu010](https://github.com/Chloezhu010)

---

*Last updated: Nov 1st, 2025*
*Part of my #LearnInPublic Solidity series*