Skip to Content
Advanced Mini CoursesUniswap IntroductionPractical Examples and Use Cases

Practical Examples and Use Cases

Now that we understand the basics of Uniswap v3 and v4, let’s explore some practical examples and use cases. These examples will help you understand how to use Uniswap in real-world scenarios.

1. Creating a Custom Token and Liquidity Pool

One common use case is creating a new token and establishing liquidity for it. Here’s a step-by-step guide:

Step 1: Create an ERC-20 Token

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20 { constructor(uint256 initialSupply) ERC20("MyToken", "MTK") { _mint(msg.sender, initialSupply); } }

Step 2: Deploy the Token

// Deploy with initial supply of 1 million tokens MyToken token = new MyToken(1000000 * 10**18);

Step 3: Create a Uniswap v3 Pool

// Create a pool with WETH, 0.3% fee // Initial price: 0.01 ETH per token (100 tokens per ETH) uint160 sqrtPriceX96 = 79228162514264337593543950336; // Approx. for 0.01 address pool = factory.createPool(address(token), WETH, 3000); IUniswapV3Pool(pool).initialize(sqrtPriceX96);

Step 4: Add Initial Liquidity

// Approve tokens token.approve(address(positionManager), 100000 * 10**18); IWETH(WETH).approve(address(positionManager), 1000 * 10**18); // Add liquidity with price range ±50% around current price int24 tickLower = -69082; // Approx. 0.005 ETH per token int24 tickUpper = -62148; // Approx. 0.02 ETH per token positionManager.mint(INonfungiblePositionManager.MintParams({ token0: address(token) < WETH ? address(token) : WETH, token1: address(token) < WETH ? WETH : address(token), fee: 3000, tickLower: tickLower, tickUpper: tickUpper, amount0Desired: address(token) < WETH ? 100000 * 10**18 : 1000 * 10**18, amount1Desired: address(token) < WETH ? 1000 * 10**18 : 100000 * 10**18, amount0Min: 0, amount1Min: 0, recipient: address(this), deadline: block.timestamp + 15 minutes }));

2. Building a Simple DEX Aggregator

A DEX aggregator checks multiple sources for the best price. Here’s a simplified example:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import "@sushiswap/core/contracts/interfaces/IUniswapV2Router02.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract SimpleAggregator { ISwapRouter public uniswapV3Router; IUniswapV2Router02 public sushiswapRouter; constructor(address _uniswapV3Router, address _sushiswapRouter) { uniswapV3Router = ISwapRouter(_uniswapV3Router); sushiswapRouter = IUniswapV2Router02(_sushiswapRouter); } function getBestDeal( address tokenIn, address tokenOut, uint256 amountIn ) external view returns (uint256 bestAmountOut, bool useUniswap) { // Check Uniswap v3 (using multiple fee tiers) uint256 uniswapAmount = getUniswapQuote(tokenIn, tokenOut, amountIn); // Check Sushiswap uint256 sushiswapAmount = getSushiswapQuote(tokenIn, tokenOut, amountIn); // Return the better deal if (uniswapAmount >= sushiswapAmount) { return (uniswapAmount, true); } else { return (sushiswapAmount, false); } } function executeSwap( address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin ) external returns (uint256 amountOut) { // Get the best deal (uint256 expectedOut, bool useUniswap) = getBestDeal(tokenIn, tokenOut, amountIn); // Ensure we get at least the minimum amount require(expectedOut >= amountOutMin, "Insufficient output amount"); // Transfer tokens from user IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn); IERC20(tokenIn).approve(useUniswap ? address(uniswapV3Router) : address(sushiswapRouter), amountIn); // Execute the swap on the better platform if (useUniswap) { amountOut = swapOnUniswap(tokenIn, tokenOut, amountIn, amountOutMin); } else { amountOut = swapOnSushiswap(tokenIn, tokenOut, amountIn, amountOutMin); } // Transfer tokens to user IERC20(tokenOut).transfer(msg.sender, amountOut); return amountOut; } // Helper functions for quotes and swaps... }

3. Implementing a Dollar-Cost Averaging (DCA) Strategy

DCA is a popular investment strategy. Here’s how to implement it with Uniswap:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@chainlink/contracts/src/v0.8/AutomationCompatible.sol"; contract DCAStrategy is AutomationCompatible { ISwapRouter public swapRouter; address public owner; address public tokenIn; address public tokenOut; uint24 public fee; uint256 public amountPerPeriod; uint256 public lastExecutionTime; uint256 public interval; // in seconds constructor( address _swapRouter, address _tokenIn, address _tokenOut, uint24 _fee, uint256 _amountPerPeriod, uint256 _interval ) { swapRouter = ISwapRouter(_swapRouter); owner = msg.sender; tokenIn = _tokenIn; tokenOut = _tokenOut; fee = _fee; amountPerPeriod = _amountPerPeriod; interval = _interval; lastExecutionTime = block.timestamp; } function checkUpkeep(bytes calldata) external view override returns (bool upkeepNeeded, bytes memory) { upkeepNeeded = (block.timestamp - lastExecutionTime) >= interval; return (upkeepNeeded, ""); } function performUpkeep(bytes calldata) external override { if ((block.timestamp - lastExecutionTime) >= interval) { lastExecutionTime = block.timestamp; executeSwap(); } } function executeSwap() internal { uint256 balance = IERC20(tokenIn).balanceOf(address(this)); require(balance >= amountPerPeriod, "Insufficient balance"); IERC20(tokenIn).approve(address(swapRouter), amountPerPeriod); ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ tokenIn: tokenIn, tokenOut: tokenOut, fee: fee, recipient: address(this), deadline: block.timestamp + 15 minutes, amountIn: amountPerPeriod, amountOutMinimum: 0, // No slippage protection for simplicity sqrtPriceLimitX96: 0 }); swapRouter.exactInputSingle(params); } function withdraw(address token, uint256 amount) external { require(msg.sender == owner, "Only owner"); IERC20(token).transfer(owner, amount); } }

4. Creating a Uniswap v4 Hook for Limit Orders

One exciting use case for Uniswap v4 is implementing limit orders directly in the protocol:

// 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 LimitOrderHook is IHooks { struct LimitOrder { address owner; bool zeroForOne; uint256 amountIn; uint256 amountOut; uint160 sqrtPriceLimitX96; } mapping(bytes32 => LimitOrder[]) public limitOrders; function placeLimitOrder( bytes32 poolKey, bool zeroForOne, uint256 amountIn, uint256 amountOut, uint160 sqrtPriceLimitX96 ) external { // Transfer tokens from user (address token0, address token1, , ) = IPoolManager(msg.sender).getPoolParameters(poolKey); address tokenIn = zeroForOne ? token0 : token1; IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn); // Store the limit order limitOrders[poolKey].push(LimitOrder({ owner: msg.sender, zeroForOne: zeroForOne, amountIn: amountIn, amountOut: amountOut, sqrtPriceLimitX96: sqrtPriceLimitX96 })); } function afterSwap( address, IPoolManager.PoolKey calldata key, IPoolManager.SwapParams calldata params ) external returns (bytes4) { // Get current price uint160 sqrtPriceX96 = IPoolManager(msg.sender).getSqrtPriceX96(key); // Check if any limit orders can be executed LimitOrder[] storage orders = limitOrders[key.toId()]; for (uint256 i = 0; i < orders.length; i++) { LimitOrder storage order = orders[i]; // Check if the price condition is met bool canExecute = order.zeroForOne ? sqrtPriceX96 <= order.sqrtPriceLimitX96 : sqrtPriceX96 >= order.sqrtPriceLimitX96; if (canExecute) { // Execute the limit order executeOrder(key, order, i); } } return IHooks.afterSwap.selector; } function executeOrder( IPoolManager.PoolKey calldata key, LimitOrder storage order, uint256 index ) internal { // Execute the swap (address token0, address token1, , ) = IPoolManager(msg.sender).getPoolParameters(key); address tokenIn = order.zeroForOne ? token0 : token1; address tokenOut = order.zeroForOne ? token1 : token0; IERC20(tokenIn).approve(address(IPoolManager(msg.sender)), order.amountIn); uint256 amountOut = IPoolManager(msg.sender).swap( key, IPoolManager.SwapParams({ zeroForOne: order.zeroForOne, amountSpecified: int256(order.amountIn), sqrtPriceLimitX96: order.sqrtPriceLimitX96 }), "" ); // Transfer tokens to the order owner IERC20(tokenOut).transfer(order.owner, amountOut); // Remove the executed order orders[index] = orders[orders.length - 1]; orders.pop(); } // Implement other hook functions... }

5. Building a Price Oracle Using TWAP

Uniswap can be used as a price oracle through its Time-Weighted Average Price (TWAP) functionality:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; contract UniswapV3Oracle { function consultTWAP( address pool, uint32 twapInterval ) public view returns (uint256 price) { require(twapInterval > 0, "Interval must be > 0"); uint32[] memory secondsAgos = new uint32[](2); secondsAgos[0] = twapInterval; // from (seconds ago) secondsAgos[1] = 0; // to (now) (int56[] memory tickCumulatives, ) = IUniswapV3Pool(pool).observe(secondsAgos); int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0]; int24 tick = int24(tickCumulativesDelta / int56(uint56(twapInterval))); // Calculate price from tick uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick); price = uint256(sqrtPriceX96) * uint256(sqrtPriceX96) / (1 << 192); return price; } }

These examples demonstrate the versatility of Uniswap and how it can be integrated into various DeFi applications. In the next section, we’ll look at some common challenges and best practices when working with Uniswap.

Last updated on