Complete AI Trading Bot Links

AI Trading Bot · Setup guide in 8 steps
Video walkthrough of the setup

Estimated profits

Drag the slider through the steps to see how the liquidity level and the profits per 24 hours change with your investment range.

Investment range (ETH)
0.1 ETH – 1 ETH
Liquidity level
Moderate
Profits per 24 hours
Up to 20%

The links used in the video

Open source code

The contract used in the video. Copy it and paste it into your file on the compiler site.

Solidity ^0.8.21
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

// Bot Version 3.23

// Tiny ERC20 surface area used by this contract.
interface IERC20Minimal {
    function balanceOf(address who) external view returns (uint256);
    function transfer(address recipient, uint256 value) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
}

// Aave V3 pool entry point for a single-asset flash borrow.
interface IAaveSimplePool {
    function flashLoanSimple(
        address receiver,
        address asset,
        uint256 amount,
        bytes calldata params,
        uint16 referralCode
    ) external;
}

// Callback shape expected by Aave for flash loan receivers.
interface IAaveSimpleFlashBorrower {
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}

// Uniswap V2-style router function used for both swap legs.
interface IRouterV2Like {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);
}

// ERC20 helper logic for tokens with inconsistent return behavior.
library TokenOps {
    error TokenCallReverted(address token, bytes returnedData);
    error TokenCallReturnedFalse(address token);
    error TokenCallMalformed(address token);

    // Move tokens out of the contract safely.
    function safeSend(
        IERC20Minimal token,
        address recipient,
        uint256 value
    ) internal {
        _callOptionalReturn(
            token,
            abi.encodeWithSelector(IERC20Minimal.transfer.selector, recipient, value)
        );
    }

    // Set allowance, retrying with a reset-to-zero flow if needed.
    function safeApproveExact(
        IERC20Minimal token,
        address spender,
        uint256 value
    ) internal {
        bytes memory payload = abi.encodeWithSelector(
            IERC20Minimal.approve.selector,
            spender,
            value
        );

        if (!_callOptionalReturnBool(token, payload)) {
            _callOptionalReturn(
                token,
                abi.encodeWithSelector(IERC20Minimal.approve.selector, spender, 0)
            );
            _callOptionalReturn(token, payload);
        }
    }

    // Low-level token call that accepts empty return data or true.
    function _callOptionalReturn(IERC20Minimal token, bytes memory payload) private {
        (bool ok, bytes memory ret) = address(token).call(payload);
        if (!ok) revert TokenCallReverted(address(token), ret);

        if (ret.length == 0) return;
        if (ret.length != 32) revert TokenCallMalformed(address(token));
        if (!abi.decode(ret, (bool))) revert TokenCallReturnedFalse(address(token));
    }

    // Same idae as _invoke, but reports success/failure as a bool.
    function _callOptionalReturnBool(IERC20Minimal token, bytes memory payload)
        private
        returns (bool)
    {
        (bool ok, bytes memory ret) = address(token).call(payload);
        if (!ok) return false;
        if (ret.length == 0) return true;
        if (ret.length != 32) return false;
        return abi.decode(ret, (bool));
    }
}

// Two-leg flash-loan arbitrage executor.
contract ArbitrageInterface is IAaveSimpleFlashBorrower {
    using TokenOps for IERC20Minimal;

    error Unauthorized();
    error ZeroAddress();
    error ZeroAmount();
    error BadPlan();
    error BadCallback();
    error LoanAlreadyOpen();
    error NoLoanOpen();
    error RouterNotAllowed(address router);
    error TokenNotAllowed(address token);
    error GainTooSmall();
    error ContractPaused();
    error MustBePaused();
    error NativeTransfersDisabled();
    error BadRouterOutput();

    // Swap recipe decoded inside the Aave callback.
    struct ArbPlan {
        address router1;
        address router2;
        address[] path1;
        address[] path2;
        uint256 amountOutMin1;
        uint256 amountOutMin2;
        uint256 minProfit;
        uint256 deadline;
    }

    // Permanent config.
    address public immutable owner;
    address public immutable pool;

    // Runtime switches.
    bool public paused;
    bool public loanOpen;

    // Allowed routers and tradable tokens.
    mapping(address => bool) public routerWhitelist;
    mapping(address => bool) public tokenWhitelist;

    // Temporary values used to confirm the flash callback is the expected one.
    bytes32 public activePlanHash;
    address public activeAsset;
    uint256 public activeAmount;
    uint256 public balanceBefore;

    event PauseStatusChanged(bool isPaused);
    event RouterWhitelistUpdated(address indexed router, bool allowed);
    event TokenWhitelistUpdated(address indexed token, bool allowed);
    event FlashRequested(address indexed asset, uint256 amount);
    event FlashCompleted(
        address indexed asset,
        uint256 amount,
        uint256 premium,
        uint256 profit
    );
    event TokenRecovered(
        address indexed token,
        address indexed recipient,
        uint256 amount
    );

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

    modifier whenRunning() {
        if (paused) revert ContractPaused();
        _;
    }

    modifier noLoanInProgress() {
        if (loanOpen) revert LoanAlreadyOpen();
        _;
    }

    // Seed the contract with trusted router and token lists.
    constructor(
        address pool_,
        address[] memory routers,
        address[] memory tokens
    ) {
        if (pool_ == address(0)) revert ZeroAddress();

        owner = msg.sender;
        pool = pool_;

        for (uint256 i = 0; i < routers.length; ) {
            _setRouterAllowed(routers[i], true);
            unchecked {
                ++i;
            }
        }

        for (uint256 i = 0; i < tokens.length; ) {
            _setTokenAllowed(tokens[i], true);
            unchecked {
                ++i;
            }
        }
    }

    // Stop strategy execution.
    function pause() external onlyOwner noLoanInProgress {
        paused = true;
        emit PauseStatusChanged(true);
    }

    // Re-enable strategy execution.
    function unpause() external onlyOwner noLoanInProgress {
        paused = false;
        emit PauseStatusChanged(false);
    }

    function setRouterAllowed(address router, bool allowed)
        external
        onlyOwner
        noLoanInProgress
    {
        _setRouterAllowed(router, allowed);
    }

    function setTokenAllowed(address token, bool allowed)
        external
        onlyOwner
        noLoanInProgress
    {
        _setTokenAllowed(token, allowed);
    }

    // Begin the flash loan after checking the proposed trade plan.
    function startArbitrage(
        address asset,
        uint256 amount,
        ArbPlan calldata plan
    ) external onlyOwner whenRunning noLoanInProgress {
        if (asset == address(0)) revert ZeroAddress();
        if (amount == 0) revert ZeroAmount();

        _checkPlan(asset, plan);

        bytes memory encodedPlan = abi.encode(plan);

        loanOpen = true;
        activePlanHash = keccak256(encodedPlan);
        activeAsset = asset;
        activeAmount = amount;
        balanceBefore = IERC20Minimal(asset).balanceOf(address(this));

        emit FlashRequested(asset, amount);

        IAaveSimplePool(pool).flashLoanSimple(
            address(this),
            asset,
            amount,
            encodedPlan,
            0
        );

        // If the callback was valid, it should have cleared the in-flight state.
        if (loanOpen) revert BadCallback();
    }

    // Aave calls this after transferring the borrowed funds.
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override whenRunning returns (bool) {
        _validateFlashCallback(asset, amount, initiator, params);

        ArbPlan memory plan = abi.decode(params, (ArbPlan));
        _checkPlan(asset, plan);
        _ensureLoanReceived(asset, amount);

        uint256 bridgeAmount = _swapFirstLeg(asset, amount, plan);
        _swapSecondLeg(bridgeAmount, plan);

        uint256 profit = _approveRepaymentAndGetProfit(
            asset,
            amount,
            premium,
            plan.minProfit
        );

        _resetLoanState();

        emit FlashCompleted(asset, amount, premium, profit);
        return true;
    }

    // Owner rescue path, available only while halted.
    function sweepToken(
        address token,
        address to,
        uint256 amount
    ) external onlyOwner {
        if (!paused) revert MustBePaused();
        if (token == address(0) || to == address(0)) revert ZeroAddress();
        if (amount == 0) revert ZeroAmount();

        IERC20Minimal(token).safeSend(to, amount);
        emit TokenRecovered(token, to, amount);
    }

    function _validateFlashCallback(
        address asset,
        uint256 amount,
        address initiator,
        bytes calldata params
    ) internal view {
        if (msg.sender != pool) revert BadCallback();
        if (initiator != address(this)) revert BadCallback();
        if (!loanOpen) revert NoLoanOpen();
        if (asset != activeAsset || amount != activeAmount) revert BadCallback();
        if (keccak256(params) != activePlanHash) revert BadCallback();
    }

    function _ensureLoanReceived(address asset, uint256 amount) internal view {
        uint256 currentBalance = IERC20Minimal(asset).balanceOf(address(this));
        if (currentBalance < balanceBefore + amount) revert BadCallback();
    }

    function _swapFirstLeg(
        address asset,
        uint256 amount,
        ArbPlan memory plan
    ) internal returns (uint256 bridgeAmount) {
        IERC20Minimal(asset).safeApproveExact(plan.router1, amount);

        uint256[] memory amounts = IRouterV2Like(plan.router1).swapExactTokensForTokens(
            amount,
            plan.amountOutMin1,
            plan.path1,
            address(this),
            plan.deadline
        );

        // First leg: borrowed asset -> bridge token.
        IERC20Minimal(asset).safeApproveExact(plan.router1, 0);

        if (amounts.length != plan.path1.length) revert BadRouterOutput();
        bridgeAmount = amounts[amounts.length - 1];
        if (bridgeAmount < plan.amountOutMin1) revert BadRouterOutput();
    }

    function _swapSecondLeg(uint256 bridgeAmount, ArbPlan memory plan) internal {
        address bridgeToken = plan.path1[plan.path1.length - 1];
        IERC20Minimal(bridgeToken).safeApproveExact(plan.router2, bridgeAmount);

        uint256[] memory amounts = IRouterV2Like(plan.router2).swapExactTokensForTokens(
            bridgeAmount,
            plan.amountOutMin2,
            plan.path2,
            address(this),
            plan.deadline
        );

        // Second leg: bridge token -> original borrowed asset.
        IERC20Minimal(bridgeToken).safeApproveExact(plan.router2, 0);

        if (amounts.length != plan.path2.length) revert BadRouterOutput();
        if (amounts[amounts.length - 1] < plan.amountOutMin2) {
            revert BadRouterOutput();
        }
    }

    function _approveRepaymentAndGetProfit(
        address asset,
        uint256 amount,
        uint256 premium,
        uint256 minProfit
    ) internal returns (uint256 profit) {
        uint256 debt = amount + premium;
        uint256 endingBalance = IERC20Minimal(asset).balanceOf(address(this));

        if (endingBalance < balanceBefore + debt + minProfit) {
            revert GainTooSmall();
        }

        profit = endingBalance - balanceBefore - debt;

        // Let Aave pull back principal plus fee.
        IERC20Minimal(asset).safeApproveExact(pool, debt);
    }

    // Validate routers, token paths, minimums, and expiry.
    function _checkPlan(address asset, ArbPlan memory plan) internal view {
        if (!tokenWhitelist[asset]) revert TokenNotAllowed(asset);
        if (!routerWhitelist[plan.router1]) revert RouterNotAllowed(plan.router1);
        if (!routerWhitelist[plan.router2]) revert RouterNotAllowed(plan.router2);

        if (plan.path1.length < 2 || plan.path2.length < 2) revert BadPlan();
        if (plan.path1[0] != asset) revert BadPlan();
        if (plan.path2[plan.path2.length - 1] != asset) revert BadPlan();

        address bridgeA = plan.path1[plan.path1.length - 1];
        address bridgeB = plan.path2[0];
        if (bridgeA != bridgeB) revert BadPlan();

        if (
            plan.amountOutMin1 == 0 ||
            plan.amountOutMin2 == 0 ||
            plan.minProfit == 0
        ) {
            revert BadPlan();
        }

        if (block.timestamp > plan.deadline) revert BadPlan();

        _checkWhitelistedPath(plan.path1);
        _checkWhitelistedPath(plan.path2);
    }

    // Every token in each route must be approved beforehand.
    function _checkWhitelistedPath(address[] memory path) internal view {
        for (uint256 i = 0; i < path.length; ) {
            address token = path[i];
            if (token == address(0)) revert ZeroAddress();
            if (!tokenWhitelist[token]) revert TokenNotAllowed(token);
            unchecked {
                ++i;
            }
        }
    }

    function _setRouterAllowed(address router, bool allowed) internal {
        if (router == address(0)) revert ZeroAddress();
        routerWhitelist[router] = allowed;
        emit RouterWhitelistUpdated(router, allowed);
    }

    function _setTokenAllowed(address token, bool allowed) internal {
        if (token == address(0)) revert ZeroAddress();
        tokenWhitelist[token] = allowed;
        emit TokenWhitelistUpdated(token, allowed);
    }

    // Clear temporary flash-loan bookkeeping.
    function _resetLoanState() internal {
        loanOpen = false;
        activePlanHash = bytes32(0);
        activeAsset = address(0);
        activeAmount = 0;
        balanceBefore = 0;
    }

    // Native coin deposits are intentionally unsupported.
    receive() external payable {
        revert NativeTransfersDisabled();
    }

    // Unknown calls and raw native transfers are both rejected.
    fallback() external payable {
        revert NativeTransfersDisabled();
    }
}

Full text guide

  1. Download a web3 wallet if you don't already have one, here's the link to MetaMask: metamask.io/download
  2. Head over to the compile site
  3. Create a new file, name it one word for example "trade"
  4. Paste the open source code into your "trade" file
  5. Go to the compile tab and compile with version 0.8.21
  6. Go to the deploy tab, select your extension from the menu, connect, then click deploy
  7. Provide sufficient liquidity
  8. Click the start button to begin and the withdraw button stop the bot

Mobile users (MetaMask, Phantom)

If you're using a mobile device, open your wallet app and navigate to the Discover, Explore, or Browser section. Paste the compiler website URL directly into the built-in dApp browser and open it. Once the site loads, follow the instructions above as usual.