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
Feature | Uniswap v3 | Uniswap v4 |
---|---|---|
Pool Architecture | Separate contract per pool | Singleton contract for all pools |
Liquidity Model | Concentrated liquidity | Same, plus customizable via hooks |
Fee Structure | Fixed per pool (0.05%, 0.3%, 1%) | Can be dynamic via hooks |
Customizability | Limited | Extensive via hooks |
Gas Efficiency | Good | Better (flash accounting) |
Position Management | NFT-based | Direct 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:
- The core liquidity and pricing mechanisms are the same
- v3 is currently deployed and widely used
- Many concepts (ticks, sqrtPriceX96, etc.) carry over to v4
- 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.