diff --git a/README.md b/README.md index f9938f3..5da2817 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ FlashArbAI is a decentralized finance (DeFi) arbitrage bot that leverages Balancer V2 flash loans to identify and execute arbitrage opportunities across decentralized exchanges (DEXs). The project integrates with the Goat Framework and utilizes the Eliza AI agent plugin to provide a conversational interface for users. Users can interact with the AI agent to discover supported token pairs, monitor arbitrage opportunities, and execute trades when profitable opportunities arise. -The core of the project is the `Arbitrage` smart contract, which handles flash loans, token swaps, and profit calculations. The contract is deployed on the Mode Sepolia Testnet and supports DEXs that share the same interface as Uniswap V3. +The core of the project is the `Arbitrage` smart contract, which handles flash loans, token swaps, and profit calculations. The contract is deployed and supports DEXs that share the same interface as Uniswap V3. ## Features @@ -46,16 +46,6 @@ The `Arbitrage` contract is the core of the FlashArbAI project. It implements th - `_amountOut`: Minimum amount of output tokens expected. - `_fee`: Pool fee for the swap. -#### Events - -- **`TokensSwapped`**: - - Emitted when a token swap is executed. - - Parameters: - - `tokenIn`: Address of the input token. - - `tokenOut`: Address of the output token. - - `amountIn`: Amount of input tokens swapped. - - `minAmountOut`: Minimum amount of output tokens received. - ## Setup Instructions ### Prerequisites @@ -84,50 +74,28 @@ The `Arbitrage` contract is the core of the FlashArbAI project. It implements th forge build ``` -4. Deploy the contract to the Mode Sepolia Testnet: +4. Deploy the contract, here is an example for Sepolia Testnet: ```bash - forge script script/DeployArbitrage.s.sol:DeployArbitrage --private-key --broadcast --verify --verifier blockscout --verifier-url https://sepolia.explorer.mode.network/api/ + forge script script/DeployArbitrage.s.sol:DeployArbitrage --private-key --broadcast --verify --verifier blockscout --verifier-url https://sepolia.explorer.network/api/ ``` ### Configuration -1. Update the foundry.toml file with your Mode Sepolia Testnet RPC URL and private key. - -2. Configure the Eliza AI agent plugin in the Goat Framework to interact with the Arbitrage contract. +1. Update the foundry.toml file with your network RPC URL. ## Usage -### Interacting with the AI Agent -1. Start the Goat Framework with the Eliza AI agent plugin. -2. Use the conversational interface to query supported token pairs: -``` -User: What token pairs are supported for arbitrage? -Eliza: Supported token pairs are: ETH/USDC, USDC/DAI, WBTC/ETH. -``` - -3. Monitor arbitrage opportunities: -``` -User: Are there any arbitrage opportunities for ETH/USDC? -Eliza: Yes, there is an arbitrage opportunity for ETH/USDC. Would you like to execute the trade? -``` - -4. Execute the trade: -``` -User: Execute the trade. -Eliza: Executing trade... Trade completed successfully. Profit: 0.5 ETH. -``` - ### Executing Trades Programmatically 1. Call the `executeTrade` function on the Arbitrage contract using Foundry: ```bash cast send "executeTrade(address[],address[],address[],uint24,uint256)" \ "[, ]" "[, ]" "[, ]" \ - --rpc-url --private-key + --rpc-url --private-key ``` 2. Monitor the TokensSwapped event to track trade execution and profits: ```bash cast logs --from-block --to-block --address \ - --topic "TokensSwapped(address,address,uint256,uint256)" --rpc-url + --topic "TokensSwapped(address,address,uint256,uint256)" --rpc-url ``` ## Testing @@ -155,5 +123,4 @@ This project is licensed under the MIT License. See the `LICENSE` file for detai ## Acknowledgments - Balancer Labs for the Balancer V2 flash loan functionality. -- Uniswap Labs for the Uniswap V3 integration. -- Goat Framework and Eliza AI agent for the conversational interface. \ No newline at end of file +- Uniswap Labs for the Uniswap V3 integration. \ No newline at end of file diff --git a/script/DeployArbitrage.s.sol b/script/DeployArbitrage.s.sol index ca85cea..dd10132 100644 --- a/script/DeployArbitrage.s.sol +++ b/script/DeployArbitrage.s.sol @@ -13,7 +13,7 @@ contract DeployArbitrage is Script { function setUp() public { helperConfig = new HelperConfig(); - modeConfig = helperConfig.getModeSepoliaConfig(); + modeConfig = helperConfig.getSepoliaETHConfig(); } function run() public returns (FlashArbitrage) { diff --git a/src/FlashArbitrage.sol b/src/FlashArbitrage.sol index 7918696..f7832f1 100644 --- a/src/FlashArbitrage.sol +++ b/src/FlashArbitrage.sol @@ -1,10 +1,9 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.18; -import "@balancer/balancer-v2-monorepo/pkg/interfaces/contracts/vault/IVault.sol"; -import "@balancer/balancer-v2-monorepo/pkg/interfaces/contracts/vault/IFlashLoanRecipient.sol"; +import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; +import "@balancer-labs/v2-interfaces/contracts/vault/IFlashLoanRecipient.sol"; import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; -import {IQuoterV2} from "@uniswap/v3-periphery/contracts/interfaces/IQuoterV2.sol"; /** * @title FlashArbitrage @@ -12,133 +11,100 @@ import {IQuoterV2} from "@uniswap/v3-periphery/contracts/interfaces/IQuoterV2.so * @notice Advanced DEX arbitrage executor leveraging Balancer flash loans * @dev Implements cross-DEX arbitrage strategies using flash loans and optimized swap paths */ -contract FlashArbitrage is IFlashLoanRecipient { - /// @notice Balancer V2 Vault for flash loan operations - IVault private constant BALANCER_VAULT = IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8); - - address public strategist; - - /// @notice Defines parameters for a complete arbitrage operation - struct ArbStrategy { - address[] dexRouters; // Addresses of DEX routers to use - address[] priceQuoters; // Price quoter contracts for each DEX - address[] tradingPath; // Token addresses in trading sequence - uint24 poolFee; // Trading fee tier (3000 = 0.3%) - } +contract Arbitrage is IFlashLoanRecipient { + IVault private constant VAULT = IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8); + + address public owner; - event ArbitrageExecuted(address sourceToken, address targetToken, uint256 inputAmount, uint256 minimumReturn); + struct Trade { + address[] routerPath; + address[] tokenPath; + uint24 fee; + } constructor() { - strategist = msg.sender; + owner = msg.sender; } - /** - * @notice Initiates an arbitrage operation with flash loan - * @param _dexRouters Array of DEX router addresses for the arbitrage path - * @param _priceQuoters Array of price quoter addresses for each DEX - * @param _tradingPath Array of token addresses in the arbitrage sequence - * @param _poolFee Fee tier for the pools to use - * @param _loanSize Size of the flash loan to initiate - */ - function executeArbitrage( - address[] memory _dexRouters, - address[] memory _priceQuoters, - address[] memory _tradingPath, - uint24 _poolFee, - uint256 _loanSize - ) external { - bytes memory strategyData = abi.encode( - ArbStrategy({ - dexRouters: _dexRouters, - priceQuoters: _priceQuoters, - tradingPath: _tradingPath, - poolFee: _poolFee - }) - ); + function executeTrade(address[] memory _routerPath, address[] memory _tokenPath, uint24 _fee, uint256 _flashAmount) + external + { + bytes memory data = abi.encode(Trade({routerPath: _routerPath, tokenPath: _tokenPath, fee: _fee})); - // Configure flash loan parameters - IERC20[] memory loanTokens = new IERC20[](1); - loanTokens[0] = IERC20(_tradingPath[0]); + // Token to flash loan, by default we are flash loaning 1 token. + IERC20[] memory tokens = new IERC20[](1); + tokens[0] = IERC20(_tokenPath[0]); - uint256[] memory loanAmounts = new uint256[](1); - loanAmounts[0] = _loanSize; + // Flash loan amount. + uint256[] memory amounts = new uint256[](1); + amounts[0] = _flashAmount; - BALANCER_VAULT.flashLoan(this, loanTokens, loanAmounts, strategyData); + VAULT.flashLoan(this, tokens, amounts, data); } - /** - * @notice Handles the flash loan callback and executes the arbitrage strategy - * @dev Implements the core arbitrage logic: borrow → swap A → swap B → repay - */ function receiveFlashLoan( IERC20[] memory tokens, uint256[] memory amounts, uint256[] memory feeAmounts, bytes memory userData ) external override { - require(msg.sender == address(BALANCER_VAULT), "Unauthorized callback"); - - ArbStrategy memory strategy = abi.decode(userData, (ArbStrategy)); - uint256 loanAmount = amounts[0]; - - // Execute first leg of arbitrage - executeSwap( - strategy.dexRouters[0], - strategy.tradingPath[0], - loanAmount, - strategy.tradingPath[1], - 0, // Accept any output for first swap - strategy.poolFee + require(msg.sender == address(VAULT)); + + // Decode our swap data so we can use it + Trade memory trade = abi.decode(userData, (Trade)); + uint256 flashAmount = amounts[0]; + + // Since balancer called this function, we should have funds to begin swapping... + + // We perform the 1st swap. + // We swap the flashAmount of token0 and expect to get X amount of token1 + _swapOnV3(trade.routerPath[0], trade.tokenPath[0], flashAmount, trade.tokenPath[1], 0, trade.fee); + + // We perform the 2nd swap. + // We swap the contract balance of token1 and + // expect to at least get the flashAmount of token0 + _swapOnV3( + trade.routerPath[1], + trade.tokenPath[1], + IERC20(trade.tokenPath[1]).balanceOf(address(this)), + trade.tokenPath[0], + flashAmount, + trade.fee ); - // Execute second leg of arbitrage - uint256 intermediateBalance = IERC20(strategy.tradingPath[1]).balanceOf(address(this)); - executeSwap( - strategy.dexRouters[1], - strategy.tradingPath[1], - intermediateBalance, - strategy.tradingPath[0], - loanAmount, // Minimum output must cover flash loan - strategy.poolFee - ); - - // Repay flash loan - IERC20(strategy.tradingPath[0]).transfer(address(BALANCER_VAULT), loanAmount); + // Transfer back what we flash loaned + IERC20(trade.tokenPath[0]).transfer(address(VAULT), flashAmount); - // Transfer profits to strategist - uint256 profitAmount = IERC20(strategy.tradingPath[0]).balanceOf(address(this)); - if (profitAmount > 0) { - IERC20(strategy.tradingPath[0]).transfer(strategist, profitAmount); - } + // Transfer any excess tokens [i.e. profits] to owner + IERC20(trade.tokenPath[0]).transfer(owner, IERC20(trade.tokenPath[0]).balanceOf(address(this))); } - /** - * @notice Executes a single swap on a DEX - * @dev Optimized for exact input swaps with minimum output requirements - */ - function executeSwap( - address _dexRouter, + // -- INTERNAL FUNCTIONS -- // + + function _swapOnV3( + address _router, address _tokenIn, - uint256 _swapAmount, + uint256 _amountIn, address _tokenOut, - uint256 _minReturn, - uint24 _poolFee + uint256 _amountOut, + uint24 _fee ) internal { - IERC20(_tokenIn).approve(_dexRouter, _swapAmount); + // Approve token to swap + IERC20(_tokenIn).approve(_router, _amountIn); - ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({ + // Setup swap parameters + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ tokenIn: _tokenIn, tokenOut: _tokenOut, - fee: _poolFee, + fee: _fee, recipient: address(this), deadline: block.timestamp, - amountIn: _swapAmount, - amountOutMinimum: _minReturn, + amountIn: _amountIn, + amountOutMinimum: _amountOut, sqrtPriceLimitX96: 0 }); - ISwapRouter(_dexRouter).exactInputSingle(swapParams); - - emit ArbitrageExecuted(_tokenIn, _tokenOut, _swapAmount, _minReturn); + // Perform swap + ISwapRouter(_router).exactInputSingle(params); } } diff --git a/src/interfaces/ISwapRouter02.sol b/src/interfaces/ISwapRouter02.sol deleted file mode 100644 index 33f3b2a..0000000 --- a/src/interfaces/ISwapRouter02.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -interface ISwapRouter02 { - struct ExactInputSingleParams { - address tokenIn; - address tokenOut; - uint24 fee; - address recipient; - uint256 amountIn; - uint256 amountOutMinimum; - uint160 sqrtPriceLimitX96; - } - - function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); -}