Skip to Content

Introduction to Uniswap v4

Uniswap v4 builds upon the foundation of v3 while introducing several major innovations. In this section, we’ll explore what’s new in v4 and how it differs from v3.

Key Innovations in Uniswap v4

1. Singleton Contract Architecture

In Uniswap v3, each pool is a separate smart contract. In v4, all pools exist within a single contract, which:

  • Reduces gas costs for deployment and interaction
  • Simplifies the protocol architecture
  • Makes it easier to add new features

2. Hooks

Hooks are the most significant innovation in v4. They allow custom code to be executed at specific points in a swap or liquidity operation.

With hooks, you can:

  • Implement dynamic fees based on market conditions
  • Create on-chain limit orders
  • Build custom liquidity incentives
  • Protect against MEV (Miner Extractable Value)
  • And much more!

3. Flash Accounting

Uniswap v4 uses ā€œflash accountingā€ to track balances in memory during a transaction and only commit them to storage at the end. This significantly reduces gas costs.

4. Native Liquidity Routing

v4 makes it easier to route trades through multiple pools in a single transaction, improving capital efficiency.

Comparison: Uniswap v3 vs v4

FeatureUniswap v3Uniswap v4
Pool ArchitectureSeparate contract per poolSingleton contract for all pools
Liquidity ModelConcentrated liquiditySame, plus customizable via hooks
Fee StructureFixed per pool (0.05%, 0.3%, 1%)Can be dynamic via hooks
CustomizabilityLimitedExtensive via hooks
Gas EfficiencyGoodBetter (flash accounting)
Position ManagementNFT-basedDirect in singleton (no NFTs required)

Code Example: Creating a Pool in Uniswap v4

Here’s how pool creation looks in Uniswap v4:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@uniswap/v4-core/contracts/interfaces/IUniswapV4Singleton.sol"; contract PoolCreator { IUniswapV4Singleton public singleton; constructor(address _singleton) { singleton = IUniswapV4Singleton(_singleton); } function createPool( address tokenA, address tokenB, uint24 fee, address hook, uint160 sqrtPriceX96 ) external returns (bytes32 poolKey) { // Create pool key (encoding pool parameters) poolKey = keccak256(abi.encode(tokenA, tokenB, fee, hook)); // Initialize the pool singleton.initializePool(poolKey, sqrtPriceX96); return poolKey; } }

Adding Liquidity in Uniswap v4

In v4, liquidity provision is simpler and doesn’t require NFTs:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@uniswap/v4-core/contracts/interfaces/IUniswapV4Singleton.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract LiquidityProvider { IUniswapV4Singleton public singleton; constructor(address _singleton) { singleton = IUniswapV4Singleton(_singleton); } function addLiquidity( bytes32 poolKey, int24 tickLower, int24 tickUpper, uint256 amount0, uint256 amount1 ) external { // Get pool tokens (address token0, address token1, , ) = singleton.getPoolParameters(poolKey); // Approve tokens IERC20(token0).approve(address(singleton), amount0); IERC20(token1).approve(address(singleton), amount1); // Add liquidity singleton.addLiquidity( poolKey, tickLower, tickUpper, amount0, amount1, address(this) ); } }

Swapping in Uniswap v4

Swapping in v4 is also more streamlined:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@uniswap/v4-core/contracts/interfaces/IUniswapV4Singleton.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract TokenSwapper { IUniswapV4Singleton public singleton; constructor(address _singleton) { singleton = IUniswapV4Singleton(_singleton); } function swap( bytes32 poolKey, bool zeroForOne, uint256 amountIn, uint256 amountOutMinimum ) external returns (uint256 amountOut) { // Get pool tokens (address token0, address token1, , ) = singleton.getPoolParameters(poolKey); // Determine which token we're swapping address tokenIn = zeroForOne ? token0 : token1; // Approve tokens IERC20(tokenIn).approve(address(singleton), amountIn); // Execute swap amountOut = singleton.swap( poolKey, zeroForOne, amountIn, amountOutMinimum, "" // No hook data ); return amountOut; } }

Creating a Custom Hook

One of the most powerful features of Uniswap v4 is the ability to create custom hooks. Here’s a simple example of a hook that implements dynamic fees:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; import "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; contract DynamicFeeHook is IHooks { // Base fee is 0.3% uint24 public baseFee = 3000; // Fee increases with volatility function beforeSwap( address, IPoolManager.PoolKey calldata key, IPoolManager.SwapParams calldata params ) external returns (bytes4) { // Get current price uint160 sqrtPriceX96 = IPoolManager(msg.sender).getSqrtPriceX96(key); // Calculate fee based on price volatility (simplified example) uint24 dynamicFee = calculateDynamicFee(sqrtPriceX96); // Update pool fee IPoolManager(msg.sender).setFee(key, dynamicFee); return IHooks.beforeSwap.selector; } function calculateDynamicFee(uint160 sqrtPriceX96) internal view returns (uint24) { // This is a simplified example // In a real implementation, you would track price changes over time // For demonstration, we'll just use the last digits of the price uint24 volatilityFactor = uint24(sqrtPriceX96 % 1000); // Increase fee with volatility, but cap at 1% uint24 fee = baseFee + volatilityFactor; if (fee > 10000) fee = 10000; // Max 1% return fee; } // Implement other hook functions... }

Should You Learn v3 First?

Yes! Even though v4 introduces new concepts, understanding v3 is essential because:

  1. The core liquidity and pricing mechanisms are the same
  2. v3 is currently deployed and widely used
  3. Many concepts (ticks, sqrtPriceX96, etc.) carry over to v4
  4. Understanding v3’s limitations helps appreciate v4’s improvements

In the next section, we’ll dive deeper into practical examples and use cases for Uniswap v3 and v4.

Last updated on