A Solidity smart contract that executes atomic arbitrage trades using Aave V3 flash loans across Uniswap V2 and SushiSwap — requiring zero upfront capital.
1. Owner calls requestFlashLoan(DAI, 100k, WETH, uniswapRouter, sushiRouter)
2. Aave V3 sends 100k DAI to the contract, triggers executeOperation() callback
3. Swap DAI -> WETH on Uniswap V2 (where WETH is cheaper)
4. Swap WETH -> DAI on SushiSwap (where WETH is more expensive)
5. Repay Aave: 100k DAI + 0.05% fee
6. Remaining DAI = profit, held in contract
7. Owner calls withdraw() to collect
The entire flow executes atomically in a single transaction. If the trade is not profitable after fees, the transaction reverts and no funds are lost.
src/
├── FlashLoanArbitrage.sol # Main contract
└── interfaces/
├── IERC20.sol # Standard ERC20
├── IFlashLoanSimpleReceiver.sol # Aave V3 callback interface
├── IPool.sol # Aave V3 pool (flashLoanSimple)
├── IPoolAddressesProvider.sol # Aave V3 address resolver
└── IUniswapV2Router02.sol # Shared by Uniswap V2 & SushiSwap
test/
└── FlashLoanArbitrage.t.sol # Fork tests (6 test cases)
script/
└── DeployFlashLoanArbitrage.s.sol # Deployment script
Owner-only entry point. Initiates a flash loan from Aave V3 and executes a two-leg swap.
| Parameter | Description |
|---|---|
tokenBorrow |
Token to borrow and repay (e.g., DAI) |
amount |
Amount to borrow |
tokenIntermediate |
Token to swap through (e.g., WETH) |
buyDex |
Router address where tokenBorrow -> tokenIntermediate is cheaper |
sellDex |
Router address where tokenIntermediate -> tokenBorrow is more expensive |
Owner-only. Transfers the contract's full balance of the specified token (or ETH) to the owner.
Aave V3 callback. Cannot be called directly — only the Aave Pool can invoke this. Validates the caller and initiator, performs both swaps, checks profitability, and approves repayment.
| Contract | Address |
|---|---|
| Aave V3 PoolAddressesProvider | 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e |
| Uniswap V2 Router | 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D |
| SushiSwap Router | 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F |
| WETH | 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
| DAI | 0x6B175474E89094C44Da98b954EedeAC495271d0F |
- Foundry (forge, cast)
- An Ethereum mainnet archive RPC URL (required for fork testing)
git clone <repo-url> && cd flash-loan-executor
forge install
cp .env.example .env
# Edit .env with your RPC URL and keysforge buildTests run against a mainnet fork pinned to block 18,000,000 (~Sep 2023).
source .env
forge test -vvv| Test | What it verifies |
|---|---|
test_successfulArbitrage_DAI_WETH |
Full flash loan + two-swap flow produces profit |
test_revert_unprofitableArbitrage |
Reverts when no price discrepancy exists |
test_revert_onlyOwnerCanRequestFlashLoan |
Non-owner cannot trigger flash loan |
test_revert_onlyOwnerCanWithdraw |
Non-owner cannot withdraw |
test_withdrawAfterProfit |
Owner can withdraw tokens from contract |
test_revert_executeOperationOnlyPool |
Direct calls to executeOperation revert |
source .env
forge script script/DeployFlashLoanArbitrage.s.sol:DeployFlashLoanArbitrage \
--rpc-url $MAINNET_RPC_URL \
--broadcast \
--verifyThis contract is unaudited and intended for educational purposes. Known risks:
- Slippage:
amountOutMinis set to0on both swaps. Profitability is enforced atomically after both swaps complete, but individual legs are exposed to sandwich attacks. - Single owner: No multisig or timelock. The owner address has full control over flash loan execution and fund withdrawal.
- Max approvals:
_approveIfNeededgrantstype(uint256).maxallowance to DEX routers and the Aave Pool on first use. - MEV exposure: Arbitrage transactions on mainnet are visible in the mempool and can be frontrun by MEV searchers.
Foundry | Solidity 0.8.20 | Aave V3 | Uniswap V2 | SushiSwap | Ethereum Mainnet
MIT