Files
mev-beta/orig/contracts/balancer/FlashLoanReceiver.sol
Administrator c54c569f30 refactor: move all remaining files to orig/ directory
Completed clean root directory structure:
- Root now contains only: .git, .env, docs/, orig/
- Moved all remaining files and directories to orig/:
  - Config files (.claude, .dockerignore, .drone.yml, etc.)
  - All .env variants (except active .env)
  - Git config (.gitconfig, .github, .gitignore, etc.)
  - Tool configs (.golangci.yml, .revive.toml, etc.)
  - Documentation (*.md files, @prompts)
  - Build files (Dockerfiles, Makefile, go.mod, go.sum)
  - Docker compose files
  - All source directories (scripts, tests, tools, etc.)
  - Runtime directories (logs, monitoring, reports)
  - Dependency files (node_modules, lib, cache)
  - Special files (--delete)

- Removed empty runtime directories (bin/, data/)

V2 structure is now clean:
- docs/planning/ - V2 planning documents
- orig/ - Complete V1 codebase preserved
- .env - Active environment config (not in git)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 10:53:05 +01:00

189 lines
6.1 KiB
Solidity

// 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 {}
}