// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IBalancerVault { function flashLoan( address recipient, IERC20[] memory tokens, uint256[] memory amounts, bytes memory userData ) external; } interface IUniswapV2Router { function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external returns (uint256[] memory amounts); } 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); } /// @title Balancer Flash Loan Receiver for Arbitrage Execution /// @notice Receives flash loans from Balancer and executes arbitrage paths contract FlashLoanReceiver { address public owner; IBalancerVault public immutable vault; struct ArbitragePath { address[] tokens; // Token path address[] exchanges; // DEX addresses uint24[] fees; // Uniswap V3 fees (0 for V2) bool[] isV3; // true if Uniswap V3, false if V2 uint256 minProfit; // Minimum profit required } event ArbitrageExecuted( address indexed initiator, uint256 profit, uint8 pathLength ); modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } constructor(address _vault) { owner = msg.sender; vault = IBalancerVault(_vault); } /// @notice Execute arbitrage using Balancer flash loan /// @param tokens Token addresses to borrow /// @param amounts Amounts to borrow /// @param path Encoded arbitrage path function executeArbitrage( IERC20[] memory tokens, uint256[] memory amounts, bytes memory path ) external onlyOwner { // Request flash loan from Balancer Vault vault.flashLoan(address(this), tokens, amounts, path); } /// @notice Callback from Balancer Vault after flash loan /// @param tokens Tokens received /// @param amounts Amounts received /// @param feeAmounts Fee amounts (always 0 for Balancer) /// @param userData Encoded arbitrage path function receiveFlashLoan( IERC20[] memory tokens, uint256[] memory amounts, uint256[] memory feeAmounts, bytes memory userData ) external { require(msg.sender == address(vault), "Only vault can call"); // Decode arbitrage path ArbitragePath memory path = abi.decode(userData, (ArbitragePath)); // Execute arbitrage swaps uint256 currentAmount = amounts[0]; address currentToken = address(tokens[0]); for (uint256 i = 0; i < path.tokens.length - 1; i++) { address tokenIn = path.tokens[i]; address tokenOut = path.tokens[i + 1]; address exchange = path.exchanges[i]; // Approve token for exchange IERC20(tokenIn).approve(exchange, currentAmount); if (path.isV3[i]) { // Uniswap V3 swap IUniswapV3Router.ExactInputSingleParams memory params = IUniswapV3Router .ExactInputSingleParams({ tokenIn: tokenIn, tokenOut: tokenOut, fee: path.fees[i], recipient: address(this), deadline: block.timestamp, amountIn: currentAmount, amountOutMinimum: 0, // Accept any amount (risky in production!) sqrtPriceLimitX96: 0 }); currentAmount = IUniswapV3Router(exchange).exactInputSingle(params); } else { // Uniswap V2 swap address[] memory swapPath = new address[](2); swapPath[0] = tokenIn; swapPath[1] = tokenOut; uint256[] memory swapAmounts = IUniswapV2Router(exchange) .swapExactTokensForTokens( currentAmount, 0, // Accept any amount (risky in production!) swapPath, address(this), block.timestamp ); currentAmount = swapAmounts[swapAmounts.length - 1]; } currentToken = tokenOut; } // Calculate profit uint256 loanAmount = amounts[0]; uint256 totalRepayment = loanAmount + feeAmounts[0]; // feeAmounts is 0 for Balancer require(currentAmount >= totalRepayment, "Insufficient profit"); uint256 profit = currentAmount - totalRepayment; require(profit >= path.minProfit, "Profit below minimum"); // Repay flash loan (transfer tokens back to vault) for (uint256 i = 0; i < tokens.length; i++) { tokens[i].transfer(address(vault), amounts[i] + feeAmounts[i]); } // Emit event emit ArbitrageExecuted(owner, profit, uint8(path.tokens.length)); // Keep profit in contract } /// @notice Withdraw profits /// @param token Token to withdraw /// @param amount Amount to withdraw function withdrawProfit(address token, uint256 amount) external onlyOwner { IERC20(token).transfer(owner, amount); } /// @notice Emergency withdraw /// @param token Token address (or 0x0 for ETH) function emergencyWithdraw(address token) external onlyOwner { if (token == address(0)) { payable(owner).transfer(address(this).balance); } else { uint256 balance = IERC20(token).balanceOf(address(this)); IERC20(token).transfer(owner, balance); } } receive() external payable {} }