// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "./LiquidityPoolETH.sol"; import "./interfaces/ISwapRouter.sol"; import "./interfaces/IWETH.sol"; import "./interfaces/ICurvePool.sol"; import "./interfaces/IAggregationRouter.sol"; /** * @title SwapRouter * @notice Swaps ETH/WETH to stablecoins via Uniswap V3, Curve, or 1inch * @dev Primary: Uniswap V3, Secondary: Curve, Optional: 1inch aggregation */ contract SwapRouter is ReentrancyGuard { using SafeERC20 for IERC20; enum SwapProvider { UniswapV3, Curve, OneInch } // Contract addresses (Ethereum Mainnet) address public immutable uniswapV3Router; // legacy SwapRouter on Ethereum mainnet address public immutable curve3Pool; // 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7 address public immutable oneInchRouter; // 0x1111111254EEB25477B68fb85Ed929f73A960582 (optional) // Token addresses (Ethereum Mainnet) address public immutable weth; // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 address public immutable usdt; // 0xdAC17F958D2ee523a2206206994597C13D831ec7 address public immutable usdc; // 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 address public immutable dai; // 0x6B175474E89094C44Da98b954EedeAC495271d0F // Uniswap V3 fee tiers (0.05% = 500, 0.3% = 3000, 1% = 10000) uint24 public constant FEE_TIER_LOW = 500; // 0.05% uint24 public constant FEE_TIER_MEDIUM = 3000; // 0.3% uint24 public constant FEE_TIER_HIGH = 10000; // 1% event SwapExecuted( SwapProvider provider, LiquidityPoolETH.AssetType inputAsset, address inputToken, address outputToken, uint256 amountIn, uint256 amountOut ); error ZeroAmount(); error ZeroAddress(); error InsufficientOutput(); error InvalidAssetType(); error SwapFailed(); /** * @notice Constructor * @param _uniswapV3Router Uniswap V3 SwapRouter address * @param _curve3Pool Curve 3pool address * @param _oneInchRouter 1inch Router address (can be address(0) if not used) * @param _weth WETH address * @param _usdt USDT address * @param _usdc USDC address * @param _dai DAI address */ constructor( address _uniswapV3Router, address _curve3Pool, address _oneInchRouter, address _weth, address _usdt, address _usdc, address _dai ) { require(_uniswapV3Router != address(0), "SwapRouter: zero Uniswap router"); require(_curve3Pool != address(0), "SwapRouter: zero Curve pool"); require(_weth != address(0), "SwapRouter: zero WETH"); require(_usdt != address(0), "SwapRouter: zero USDT"); require(_usdc != address(0), "SwapRouter: zero USDC"); require(_dai != address(0), "SwapRouter: zero DAI"); uniswapV3Router = _uniswapV3Router; curve3Pool = _curve3Pool; oneInchRouter = _oneInchRouter; weth = _weth; usdt = _usdt; usdc = _usdc; dai = _dai; } /** * @notice Swap to stablecoin using best available route * @param inputAsset Input asset type (ETH or WETH) * @param stablecoinToken Target stablecoin address (USDT, USDC, or DAI) * @param amountIn Input amount * @param amountOutMin Minimum output amount (slippage protection) * @param routeData Optional route data for specific provider * @return amountOut Output amount */ function swapToStablecoin( LiquidityPoolETH.AssetType inputAsset, address stablecoinToken, uint256 amountIn, uint256 amountOutMin, bytes calldata routeData ) external payable nonReentrant returns (uint256 amountOut) { if (amountIn == 0) revert ZeroAmount(); if (stablecoinToken == address(0)) revert ZeroAddress(); if (!_isValidStablecoin(stablecoinToken)) revert("SwapRouter: invalid stablecoin"); // Convert ETH to WETH if needed if (inputAsset == LiquidityPoolETH.AssetType.ETH) { IWETH(weth).deposit{value: amountIn}(); inputAsset = LiquidityPoolETH.AssetType.WETH; } // Approve WETH for swap IERC20 wethToken = IERC20(weth); // Use forceApprove for OpenZeppelin 5.x (or approve directly) wethToken.approve(uniswapV3Router, amountIn); if (oneInchRouter != address(0)) { wethToken.approve(oneInchRouter, amountIn); } // Try Uniswap V3 first (primary) uint256 outputAmount = _executeUniswapV3Swap(stablecoinToken, amountIn, amountOutMin); if (outputAmount >= amountOutMin) { // Transfer output to caller IERC20(stablecoinToken).safeTransfer(msg.sender, outputAmount); emit SwapExecuted(SwapProvider.UniswapV3, inputAsset, weth, stablecoinToken, amountIn, outputAmount); return outputAmount; } // Try Curve for stable/stable swaps (if USDT/USDC/DAI and routeData provided) // Note: Curve 3pool doesn't support WETH directly, would need intermediate swap // For now, revert if Uniswap fails revert SwapFailed(); } /** * @notice Execute Uniswap V3 swap (internal) * @param stablecoinToken Target stablecoin * @param amountIn Input amount * @param amountOutMin Minimum output * @return amountOut Output amount */ function _executeUniswapV3Swap( address stablecoinToken, uint256 amountIn, uint256 amountOutMin ) internal returns (uint256 amountOut) { ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ tokenIn: weth, tokenOut: stablecoinToken, fee: FEE_TIER_MEDIUM, // 0.3% fee tier recipient: address(this), deadline: block.timestamp + 300, // 5 minutes amountIn: amountIn, amountOutMinimum: amountOutMin, sqrtPriceLimitX96: 0 // No price limit }); amountOut = ISwapRouter(uniswapV3Router).exactInputSingle(params); return amountOut; } /** * @notice Check if token is a valid stablecoin * @param token Token address to check * @return True if valid stablecoin */ function _isValidStablecoin(address token) internal view returns (bool) { return token == usdt || token == usdc || token == dai; } // Allow contract to receive ETH receive() external payable {} }