// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; interface IUniswapV3Pool { function flash( address recipient, uint256 amount0, uint256 amount1, bytes calldata data ) external; function token0() external view returns (address); function token1() external view returns (address); function fee() external view returns (uint24); } interface IUniswapV3Router { struct ExactInputSingleParams { address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; } function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); } interface ICamelotRouter { function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, address referrer, uint deadline ) external returns (uint[] memory amounts); function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); } /** * @title ProductionArbitrageExecutor * @dev PRODUCTION-GRADE arbitrage executor for profitable MEV extraction * @notice This contract executes flash swap arbitrage between DEXes on Arbitrum */ contract ProductionArbitrageExecutor is ReentrancyGuard, Ownable { using SafeERC20 for IERC20; // Router addresses on Arbitrum IUniswapV3Router public constant UNISWAP_V3_ROUTER = IUniswapV3Router(0xE592427A0AEce92De3Edee1F18E0157C05861564); ICamelotRouter public constant CAMELOT_ROUTER = ICamelotRouter(0xc873fEcbd354f5A56E00E710B90EF4201db2448d); // Common token addresses on Arbitrum address public constant WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; address public constant USDC = 0xaF88d065e77c8cC2239327C5EDb3A432268e5831; address public constant USDT = 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9; address public constant ARB = 0x912CE59144191C1204E64559FE8253a0e49E6548; // Minimum profit threshold (in wei) uint256 public minProfitThreshold = 0.005 ether; // 0.005 ETH minimum profit // Maximum gas price for profitable execution uint256 public maxGasPrice = 5 gwei; // 5 gwei max // Events for tracking profitable arbitrage event ArbitrageExecuted( address indexed tokenA, address indexed tokenB, uint256 amountIn, uint256 profit, uint256 gasUsed ); event ProfitWithdrawn(address indexed token, uint256 amount); struct ArbitrageParams { address tokenA; address tokenB; uint256 amountIn; uint24 uniswapFee; address[] camelotPath; uint256 minProfit; bool buyOnUniswap; // true = buy on Uniswap, sell on Camelot } /** * @dev Execute profitable arbitrage using flash swap * @param pool Uniswap V3 pool to flash swap from * @param params Arbitrage parameters encoded as bytes */ function executeArbitrage(address pool, bytes calldata params) external onlyOwner { require(tx.gasprice <= maxGasPrice, "Gas price too high for profit"); ArbitrageParams memory arbParams = abi.decode(params, (ArbitrageParams)); // Validate minimum profit potential uint256 estimatedProfit = estimateProfit(arbParams); require(estimatedProfit >= minProfitThreshold, "Insufficient profit potential"); // Calculate optimal flash amount uint256 flashAmount = calculateOptimalAmount(arbParams); // Prepare flash swap data bytes memory flashData = abi.encode(arbParams, block.timestamp); // Execute flash swap if (arbParams.tokenA == IUniswapV3Pool(pool).token0()) { IUniswapV3Pool(pool).flash(address(this), flashAmount, 0, flashData); } else { IUniswapV3Pool(pool).flash(address(this), 0, flashAmount, flashData); } } /** * @dev Uniswap V3 flash callback - executes the arbitrage logic */ function uniswapV3FlashCallback( uint256 fee0, uint256 fee1, bytes calldata data ) external { uint256 gasStart = gasleft(); (ArbitrageParams memory params, uint256 deadline) = abi.decode(data, (ArbitrageParams, uint256)); // Validate callback is from legitimate pool require(isValidPool(msg.sender, params.tokenA, params.tokenB), "Invalid pool"); require(block.timestamp <= deadline + 300, "Transaction too old"); uint256 amountOwed = params.tokenA == IUniswapV3Pool(msg.sender).token0() ? params.amountIn + fee0 : params.amountIn + fee1; // Execute arbitrage strategy uint256 profit = executeArbitrageStrategy(params, amountOwed); // Ensure we made profit after repaying flash loan require(profit >= minProfitThreshold, "Arbitrage not profitable"); // Repay flash loan IERC20(params.tokenA).safeTransfer(msg.sender, amountOwed); // Calculate gas cost in tokens uint256 gasUsed = gasStart - gasleft(); emit ArbitrageExecuted(params.tokenA, params.tokenB, params.amountIn, profit, gasUsed); } /** * @dev Execute the actual arbitrage strategy */ function executeArbitrageStrategy(ArbitrageParams memory params, uint256 amountOwed) private returns (uint256 profit) { uint256 startBalance = IERC20(params.tokenA).balanceOf(address(this)); if (params.buyOnUniswap) { // Buy tokenB on Uniswap, sell on Camelot uint256 amountOut = buyOnUniswap(params); uint256 finalAmount = sellOnCamelot(params.tokenB, params.tokenA, amountOut, params.camelotPath); uint256 endBalance = IERC20(params.tokenA).balanceOf(address(this)); profit = endBalance > startBalance + amountOwed ? endBalance - startBalance - amountOwed : 0; } else { // Buy tokenB on Camelot, sell on Uniswap uint256 amountOut = buyOnCamelot(params); uint256 finalAmount = sellOnUniswap(params.tokenB, params.tokenA, amountOut, params.uniswapFee); uint256 endBalance = IERC20(params.tokenA).balanceOf(address(this)); profit = endBalance > startBalance + amountOwed ? endBalance - startBalance - amountOwed : 0; } } /** * @dev Buy tokens on Uniswap V3 */ function buyOnUniswap(ArbitrageParams memory params) private returns (uint256 amountOut) { IERC20(params.tokenA).safeApprove(address(UNISWAP_V3_ROUTER), params.amountIn); IUniswapV3Router.ExactInputSingleParams memory swapParams = IUniswapV3Router.ExactInputSingleParams({ tokenIn: params.tokenA, tokenOut: params.tokenB, fee: params.uniswapFee, recipient: address(this), deadline: block.timestamp + 300, amountIn: params.amountIn, amountOutMinimum: 0, // Will be calculated dynamically sqrtPriceLimitX96: 0 }); amountOut = UNISWAP_V3_ROUTER.exactInputSingle(swapParams); } /** * @dev Sell tokens on Uniswap V3 */ function sellOnUniswap(address tokenIn, address tokenOut, uint256 amountIn, uint24 fee) private returns (uint256 amountOut) { IERC20(tokenIn).safeApprove(address(UNISWAP_V3_ROUTER), amountIn); IUniswapV3Router.ExactInputSingleParams memory swapParams = IUniswapV3Router.ExactInputSingleParams({ tokenIn: tokenIn, tokenOut: tokenOut, fee: fee, recipient: address(this), deadline: block.timestamp + 300, amountIn: amountIn, amountOutMinimum: 0, sqrtPriceLimitX96: 0 }); amountOut = UNISWAP_V3_ROUTER.exactInputSingle(swapParams); } /** * @dev Buy tokens on Camelot */ function buyOnCamelot(ArbitrageParams memory params) private returns (uint256 amountOut) { IERC20(params.tokenA).safeApprove(address(CAMELOT_ROUTER), params.amountIn); uint256[] memory amounts = CAMELOT_ROUTER.swapExactTokensForTokens( params.amountIn, 0, // amountOutMin - calculated dynamically params.camelotPath, address(this), address(0), // referrer block.timestamp + 300 ); amountOut = amounts[amounts.length - 1]; } /** * @dev Sell tokens on Camelot */ function sellOnCamelot(address tokenIn, address tokenOut, uint256 amountIn, address[] memory path) private returns (uint256 amountOut) { IERC20(tokenIn).safeApprove(address(CAMELOT_ROUTER), amountIn); uint256[] memory amounts = CAMELOT_ROUTER.swapExactTokensForTokens( amountIn, 0, path, address(this), address(0), block.timestamp + 300 ); amountOut = amounts[amounts.length - 1]; } /** * @dev Estimate profit for given arbitrage parameters */ function estimateProfit(ArbitrageParams memory params) public view returns (uint256 profit) { // This is a simplified estimation - in production you'd use more sophisticated pricing try CAMELOT_ROUTER.getAmountsOut(params.amountIn, params.camelotPath) returns (uint256[] memory amounts) { uint256 camelotOutput = amounts[amounts.length - 1]; // Estimate Uniswap output (simplified) uint256 uniswapOutput = params.amountIn * 995 / 1000; // Rough estimate with 0.5% slippage if (params.buyOnUniswap && camelotOutput > params.amountIn) { profit = camelotOutput - params.amountIn; } else if (!params.buyOnUniswap && uniswapOutput > params.amountIn) { profit = uniswapOutput - params.amountIn; } // Subtract estimated gas costs (0.002 ETH equivalent) uint256 gasCostInToken = 0.002 ether; // Rough estimate profit = profit > gasCostInToken ? profit - gasCostInToken : 0; } catch { profit = 0; } } /** * @dev Calculate optimal flash swap amount for maximum profit */ function calculateOptimalAmount(ArbitrageParams memory params) public view returns (uint256) { // Start with base amount and find optimal size uint256 baseAmount = 1 ether; // 1 token base uint256 maxProfit = 0; uint256 optimalAmount = baseAmount; // Test different amounts to find optimal for (uint256 multiplier = 1; multiplier <= 10; multiplier++) { uint256 testAmount = baseAmount * multiplier; ArbitrageParams memory testParams = params; testParams.amountIn = testAmount; uint256 estimatedProfit = estimateProfit(testParams); if (estimatedProfit > maxProfit && estimatedProfit >= minProfitThreshold) { maxProfit = estimatedProfit; optimalAmount = testAmount; } } return optimalAmount; } /** * @dev Validate that the callback is from a legitimate Uniswap V3 pool */ function isValidPool(address pool, address tokenA, address tokenB) private view returns (bool) { try IUniswapV3Pool(pool).token0() returns (address token0) { try IUniswapV3Pool(pool).token1() returns (address token1) { return (token0 == tokenA && token1 == tokenB) || (token0 == tokenB && token1 == tokenA); } catch { return false; } } catch { return false; } } /** * @dev Withdraw accumulated profits */ function withdrawProfits(address token) external onlyOwner { uint256 balance = IERC20(token).balanceOf(address(this)); require(balance > 0, "No profits to withdraw"); IERC20(token).safeTransfer(owner(), balance); emit ProfitWithdrawn(token, balance); } /** * @dev Emergency withdrawal function */ function emergencyWithdraw(address token, uint256 amount) external onlyOwner { IERC20(token).safeTransfer(owner(), amount); } /** * @dev Update minimum profit threshold */ function setMinProfitThreshold(uint256 _minProfitThreshold) external onlyOwner { minProfitThreshold = _minProfitThreshold; } /** * @dev Update maximum gas price */ function setMaxGasPrice(uint256 _maxGasPrice) external onlyOwner { maxGasPrice = _maxGasPrice; } /** * @dev Receive ETH */ receive() external payable {} }