Back to terminal

FlashArb executor contract

Borrows from Aave V3 on BNB Chain, buys on the cheap venue, sells on the rich venue and repays in a single atomic transaction. It reverts unless your minimum profit is met, so a failed attempt costs gas only.

Deploy steps

  1. 1. Copy the source below into Remix (Solidity 0.8.20+, optimizer on).
  2. 2. Deploy to BNB Chain mainnet with constructor argument 0x6807dc923806fE8Fd134338EABCA509979a7e0cB (Aave V3 Pool).
  3. 3. Paste the deployed address into Bot configuration on the terminal.
  4. 4. Keep a little BNB in your wallet for gas. No capital sits in the contract — profits accrue there and you sweep them with withdraw(token).
  5. 5. Audit the code before sending real value through it.

Wired addresses

Aave V3 Pool0x6807dc923806fE8Fd134338EABCA509979a7e0cB
PancakeSwap V2 router0x10ED43C718714eb63d5aA57B78B54704E256024E
SushiSwap V2 router0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506
Uniswap V3 quoter0x78D78E420Da98ad378D7799bE8f4AF69033EB077
WBNB0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
USDT0x55d398326f99059fF775485246999027B3197955

FlashArb.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/*
 * FlashArb — Aave V3 flashloan DEX arbitrage executor for BNB Chain.
 *
 * Flow (single transaction):
 *   1. flashLoanSimple(asset, amount) from the Aave V3 Pool
 *   2. swap asset -> intermediate on buyRouter   (the cheap venue)
 *   3. swap intermediate -> asset on sellRouter  (the rich venue)
 *   4. repay amount + premium, keep the remainder as profit
 *   5. revert unless profit >= minProfit  (atomic: no loss beyond gas)
 *
 * Both routers must be UniswapV2-style (PancakeSwap V2, SushiSwap V2, Biswap...).
 * DEPLOY AND AUDIT AT YOUR OWN RISK. Arbitrage is competitive and can lose gas.
 */

interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface IPool {
    function flashLoanSimple(
        address receiverAddress,
        address asset,
        uint256 amount,
        bytes calldata params,
        uint16 referralCode
    ) external;
}

interface IV2Router {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);
}

contract FlashArb {
    address public immutable owner;
    IPool public immutable POOL;

    error NotOwner();
    error NotPool();
    error Unprofitable(uint256 got, uint256 required);

    event ArbExecuted(
        address indexed asset,
        uint256 amount,
        uint256 premium,
        uint256 profit
    );

    constructor(address pool) {
        owner = msg.sender;
        POOL = IPool(pool);
    }

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    /// @notice Kick off a flashloan arbitrage loop.
    function executeArb(
        address asset,
        uint256 amount,
        address intermediate,
        address buyRouter,
        address sellRouter,
        uint256 minProfit
    ) external onlyOwner {
        bytes memory params = abi.encode(intermediate, buyRouter, sellRouter, minProfit);
        POOL.flashLoanSimple(address(this), asset, amount, params, 0);
    }

    /// @notice Aave V3 callback. Only the Pool may call this.
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        if (msg.sender != address(POOL)) revert NotPool();
        if (initiator != address(this)) revert NotOwner();

        (
            address intermediate,
            address buyRouter,
            address sellRouter,
            uint256 minProfit
        ) = abi.decode(params, (address, address, address, uint256));

        // Leg 1: asset -> intermediate on the cheap venue
        _swap(buyRouter, asset, intermediate, amount);

        // Leg 2: intermediate -> asset on the rich venue
        uint256 mid = IERC20(intermediate).balanceOf(address(this));
        _swap(sellRouter, intermediate, asset, mid);

        uint256 owed = amount + premium;
        uint256 balance = IERC20(asset).balanceOf(address(this));
        if (balance < owed + minProfit) revert Unprofitable(balance, owed + minProfit);

        IERC20(asset).approve(address(POOL), owed);
        emit ArbExecuted(asset, amount, premium, balance - owed);
        return true;
    }

    function _swap(address router, address tokenIn, address tokenOut, uint256 amountIn) internal {
        IERC20(tokenIn).approve(router, amountIn);
        address[] memory path = new address[](2);
        path[0] = tokenIn;
        path[1] = tokenOut;
        IV2Router(router).swapExactTokensForTokens(
            amountIn,
            0, // profit is enforced atomically at the end of executeOperation
            path,
            address(this),
            block.timestamp
        );
    }

    /// @notice Sweep any token (profits, dust) to the owner.
    function withdraw(address token) external onlyOwner {
        IERC20(token).transfer(owner, IERC20(token).balanceOf(address(this)));
    }

    receive() external payable {}
}