Building a DeFi Trading Bot with AI Agents: Arbitrage and Yield Automation

How to use AI agents to write, test, and deploy a DeFi trading bot — covering cross-DEX arbitrage detection, gas optimization, and flash loan integration.

Uniswap V3 arbitrage opportunities close in 1–3 blocks (~15 seconds). Your bot needs to find, validate, and execute faster than 200 competing bots — here's how AI agents give you an edge.

Forget the fantasy of a single AI model that magically prints money. The edge isn't in a monolithic brain; it's in a swarm of specialized, AI-assisted agents that each solve a piece of the problem faster and more reliably than you can manually code. One agent scouts for price discrepancies, another simulates the trade path, a third battles for gas optimization, and a fourth reviews your Solidity for the reentrancy bug you're about to deploy. Your role shifts from coder to conductor. Let's build that orchestra.

DeFi Bot Architecture: What AI Can and Cannot Automate

Your bot's architecture is a pipeline of failure points. AI can supercharge specific stages, but it cannot—and should not—replace the deterministic core of on-chain execution.

What AI Agents Can Do:

  • Pattern Recognition in Mempool & Logs: Parse thousands of pending transactions and event logs to predict impending price movements or identify nascent arbitrage paths. A fine-tuned model can spot complex, multi-hop opportunities across DEXs (Uniswap, Curve, Balancer) faster than static rule-based scanners.
  • Dynamic Parameter Tuning: Adjust gas premiums, slippage tolerances, and route complexity in real-time based on network congestion and competitor bot activity, moving beyond simple "fast" or "slow" settings.
  • Natural Language to Alerting: Translate high-level strategies ("alert me on stablecoin depegs above 2% on Arbitrum") into a working monitoring agent that scrapes Chainlink oracles and Twitter sentiment.
  • Automated Code Review: An agent integrated into your IDE can pre-flag common vulnerabilities as you write, acting as a first-pass audit.

What Stays Firmly in Your Court:

  • Smart Contract Logic: The on-chain executor (your Solidity contract) must be deterministic, gas-optimized, and secure. An AI can suggest optimizations, but you own the final, audited bytecode.
  • Private Key Management: AI should never have direct, unencrypted access to private keys or mnemonics. Signing must happen in isolated, secure environments.
  • Final Execution Authority: The "send transaction" command is a human or a rigorously tested, automated guardrail system's decision. An AI agent can recommend; you must architect the commit.

The architecture, therefore, is a hybrid: AI-powered off-chain agents for intelligence and optimization, feeding into a bulletproof, manually-vetted on-chain execution core.

Setting Up Your Dev Environment: Foundry, Anvil, and AI Pair Programming

You need a feedback loop measured in seconds, not minutes. Hardhat is fine, but for speed, we're using Foundry. Its native Rust core means your compile-test cycle is near-instantaneous, which is critical when your AI pair programmer is generating and iterating on code dozens of times an hour.


curl -L https://foundry.paradigm.xyz | bash
foundryup
forge init defi-bot-core --no-git
cd defi-bot-core
forge install openzeppelin/openzeppelin-contracts --no-commit

# Terminal 2: Your mainnet playground
anvil --fork-url $ALCHEMY_MAINNET_URL --fork-block-number 21000000 --chain-id 31337

This anvil command spins up a local fork of Ethereum mainnet at a specific block. You interact with real contract states (like Uniswap pools) for free. Now, integrate the AI. In VS Code (Ctrl+Shift+P to open the command palette), install the Continue.dev extension. Create a continue.json config to give it context:

{
  "models": [
    {
      "title": "Claude 3.5 Sonnet",
      "provider": "openai",
      "model": "claude-3-5-sonnet"
    }
  ],
  "contextProviders": [
    "github",
    "terminal",
    "file"
  ],
  "tabAutocompleteModel": {
    "title": "Starcoder",
    "provider": "openai",
    "model": "starcoder-7b"
  }
}

Now, with Cmd/Ctrl + I, you can ask: "Write a Foundry test that simulates a flash loan from Aave v3 on the forked mainnet." The AI uses your open files and terminal output as context, generating code that actually works with your setup.

AI-Assisted Arbitrage Detection Logic

A naive scanner checks reserve0/reserve1 ratios. A competitive one uses The Graph to index historical swaps and Dune Analytics to track pool fees, then predicts the next 3-block window. We'll build a simple, effective scanner that an AI can help you extend.

First, the core math. Create src/ArbScanner.sol:

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

library ArbMath {
    /**
     * @dev Calculates output amount for a given path of tokens and reserves.
     * @param amounts Array of input amounts (first element is input, subsequent are calculated outputs).
     * @param path Array of token addresses representing the trade path.
     * @param reserves Array of paired reserves for each pool in the path.
     */
    function getAmountsOut(
        uint[] memory amounts,
        address[] memory path,
        uint[][] memory reserves
    ) internal pure returns (uint[] memory) {
        require(path.length >= 2, "PATH");
        require(reserves.length == path.length - 1, "RESERVES");

        for (uint i; i < path.length - 1; i++) {
            (uint reserveIn, uint reserveOut) = (reserves[i][0], reserves[i][1]);
            amounts[i + 1] = (amounts[i] * 997 * reserveOut) / (reserveIn * 1000 + amounts[i] * 997);
        }
        return amounts;
    }

    /**
     * @dev Identifies a triangular arbitrage opportunity across three pools.
     * Returns profit in the starting token and the optimal input amount.
     */
    function findTriangularArb(
        uint inputAmount,
        address[3] memory tokens,
        uint[2] memory reservesAB,
        uint[2] memory reservesBC,
        uint[2] memory reservesCA
    ) internal pure returns (uint profit, uint optimalInput) {
        // A -> B
        uint amountB = (inputAmount * 997 * reservesAB[1]) / (reservesAB[0] * 1000 + inputAmount * 997);
        // B -> C
        uint amountC = (amountB * 997 * reservesBC[1]) / (reservesBC[0] * 1000 + amountB * 997);
        // C -> A
        uint finalAmount = (amountC * 997 * reservesCA[1]) / (reservesCA[0] * 1000 + amountC * 997);

        profit = finalAmount > inputAmount ? finalAmount - inputAmount : 0;
        optimalInput = inputAmount;
    }
}

This is your deterministic, on-chain calculable core. The AI agent's job is to populate the reserves arrays. Using web3.py with an optimized provider is key. Here's where latency directly kills profits:

ProviderAvg. RPC LatencySuitability for Bot
Self-hosted Erigon/Geth node~40msOptimal. Full mempool visibility, lowest latency.
Alchemy~95msGood. Reliable, with specialized APIs.
Infura~120msAcceptable for monitoring, risky for execution.

Create scripts/scanner_agent.py. Use Continue (Cmd/Ctrl + I) to prompt: "Write a Python function using web3.py that fetches real-time reserves for a Uniswap V3 pool, given its contract address, and accounts for fee tiers."

from web3 import Web3
import asyncio
from typing import Tuple

# AI-Suggested optimization: Use async for concurrent pool calls
class ReserveFetcher:
    def __init__(self, rpc_url: str):
        self.w3 = Web3(Web3.HTTPProvider(rpc_url))
        self.erc20_abi = [...]  # Abbreviated ABI

    async def get_reserves_uniswap_v3(self, pool_addr: str, token0: str, token1: str) -> Tuple[int, int]:
        """Fetches effective reserves for a Uniswap V3 pool."""
        pool_abi = [{"inputs":[],"name":"slot0","outputs":[{"internalType":"uint160","name":"sqrtPriceX96"},{"internalType":"int24","name":"tick"],"stateMutability":"view","type":"function"}]
        pool = self.w3.eth.contract(address=pool_addr, abi=pool_abi)

        # Get sqrtPrice and liquidity
        sqrt_price_x96, tick, _, _, _, _ = pool.functions.slot0().call()
        liquidity = pool.functions.liquidity().call()

        # AI-generated helper: Convert sqrtPriceX96 to a reserve ratio.
        # This is a simplified model; a full agent would incorporate tick math.
        price = (sqrt_price_x96 ** 2) / (2 ** 192)
        # Assume for simplicity reserve1 / reserve0 = price
        # Real implementation uses liquidity * sqrtPrice calculations
        reserve0 = liquidity / (price ** 0.5) if price > 0 else 0
        reserve1 = liquidity * (price ** 0.5)

        return (int(reserve0), int(reserve1))

# The AI agent would manage a list of 100+ pools, calling this concurrently,
# feeding results into the ArbMath library to find profitable paths.

Writing Flash Loan Integration with AI Code Review

Flash loans are the capital engine of your arbitrage bot. A single mistake here means insolvency. We'll use Aave V3 and integrate an AI code review step.

Create src/FlashArbExecutor.sol. As you type, AI extensions like Codeium or GitHub Copilot will autocomplete standard patterns. More importantly, use Slither (via the VS Code terminal) for static analysis as you write.

slither . --filter-paths "src/FlashArbExecutor.sol"

Now, the contract. Notice the // AI-REVIEW comments. These are points where you should use your IDE's AI (e.g., Ctrl+I with Continue) to ask for an analysis.

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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IFlashLoanSimpleReceiver} from "@aave/v3-core/contracts/flashloan/interfaces/IFlashLoanSimpleReceiver.sol";

contract FlashArbExecutor is IFlashLoanSimpleReceiver, ReentrancyGuard {
    using SafeERC20 for IERC20;

    address public immutable POOLS;
    address public owner;

    constructor(address aavePoolAddress) {
        POOLS = aavePoolAddress;
        owner = msg.sender;
    }

    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override nonReentrant returns (bool) {
        require(msg.sender == POOLS, "CALLER_NOT_POOL");
        require(initiator == address(this), "INITIATOR_NOT_SELF");

        (address arbContract, bytes memory tradeData) = abi.decode(params, (address, bytes));

        // AI-REVIEW: Are there any reentrancy risks in this approval pattern?
        IERC20(asset).approve(arbContract, amount);

        // Execute the pre-validated arbitrage trade
        (bool success, ) = arbContract.call(tradeData);
        require(success, "ARB_TRADE_FAILED");

        // Repay flash loan
        uint256 totalOwed = amount + premium;
        require(IERC20(asset).balanceOf(address(this)) >= totalOwed, "INSUFFICIENT_BALANCE");

        // AI-REVIEW: Is this the most gas-efficient approval/transfer pattern for Aave V3?
        IERC20(asset).approve(POOLS, totalOwed);
        return true;
    }

    function requestFlashLoan(address asset, uint256 amount, address arbContract, bytes calldata tradeData) external {
        require(msg.sender == owner, "ONLY_OWNER");
        // AI-REVIEW: Should we add a gas limit check here to prevent front-running?
        POOLS.call(abi.encodeWithSignature("flashLoanSimple(address,address,uint256,bytes,uint16)", address(this), asset, amount, abi.encode(arbContract, tradeData), 0));
    }
}

Run Slither. It might flag: ReentrancyGuard: reentrant call — fix: use OpenZeppelin ReentrancyGuard modifier on all state-changing functions. You've already done this, but the AI review might suggest adding a nonReentrant modifier to requestFlashLoan as well, as an extra precaution against any unexpected callback patterns.

Real Error & Fix: During testing, your script might fail with: Error: insufficient funds for gas * price + value — fix: check msg.value and gas estimation before sending. Your AI agent can help write the pre-flight check:

def validate_tx(w3, tx):
    try:
        estimated_gas = w3.eth.estimate_gas(tx)
        gas_cost = estimated_gas * tx['gasPrice']
        if w3.eth.get_balance(tx['from']) < gas_cost + tx.get('value', 0):
            return False, "Insufficient balance for gas"
        return True, ""
    except Exception as e:
        return False, str(e)

Gas Optimization: AI Suggestions vs Manual Tuning

Gas is your bid in the block space auction. AI can propose optimizations, but you must validate them. Foundry's forge snapshot is your benchmark tool.

  1. AI Suggestion: "Use immutable variables for constant addresses like POOLS." (Already done—good suggestion).
  2. AI Suggestion: "Pack multiple require statements." Bad Idea. This hurts readability and debug logs. Reject.
  3. AI Suggestion: "Use assembly for repetitive math." Proceed with Extreme Caution. An AI might generate a gas-efficient Yul snippet for the getAmountsOut function. Test it thoroughly on a fork.
// AI-generated Yul optimization for the swap calculation
function getAmountOutAssembly(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
    assembly {
        let numerator := mul(amountIn, 997)
        numerator := mul(numerator, reserveOut)
        let denominator := mul(reserveIn, 1000)
        denominator := add(denominator, mul(amountIn, 997))
        amountOut := div(numerator, denominator)
    }
}

Run forge test --gas-report on both the Solidity and Assembly versions. Does the assembly save 50 gas per call? Is it worth the audit risk and readability cost? For a function called hundreds of times per block, maybe. For a one-time check, no.

Manual Tuning is King: The most significant gains come from architectural choices:

  • Execute on L2s: Gas fees on Ethereum L2s average $0.01–$0.05 vs $3–$15 on mainnet (L2Beat, 2026). Deploy your bot on Arbitrum or Base.
  • Bundle Operations: Use a Diamond Proxy pattern or multicall to combine multiple trades into one transaction, amortizing the 21,000 base gas cost.
  • State Access Minimization: Design your contract so the hot execution path reads/writes the fewest storage slots possible.

Testing on Mainnet Fork Before Deployment

Deploying untested code is financial suicide. Your Anvil fork is the staging environment. Write exhaustive fuzz tests in Foundry.

Create test/FlashArbExecutor.t.sol. Use AI to generate the boilerplate, then harden it.

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

import "forge-std/Test.sol";
import "../src/FlashArbExecutor.sol";

contract FlashArbExecutorTest is Test {
    FlashArbExecutor executor;
    address constant AAVE_POOL = 0x...;
    IERC20 constant USDC = IERC20(0xA0b869...);

    function setUp() public {
        vm.createSelectFork(vm.envString("MAINNET_RPC_URL"), 21000000);
        executor = new FlashArbExecutor(AAVE_POOL);
        vm.deal(address(this), 100 ether);
    }

    function testFlashLoanRepayment() public {
        // Simulate a profitable arbitrage by mocking a trade contract
        MockArbContract mockArb = new MockArbContract(USDC);
        bytes memory tradeData = abi.encodeWithSignature("doTrade(uint256)", 1000e6);

        // Pre-fund the mock to ensure "profit"
        deal(address(USDC), address(mockArb), 1001e6);

        // Expect the approval to Aave to happen
        vm.expectCall(address(USDC), abi.encodeWithSelector(IERC20.approve.selector, AAVE_POOL, 1000e6 + 5e6)); // amount + premium
        executor.requestFlashLoan(address(USDC), 1000e6, address(mockArb), tradeData);
    }

    function testRevertsIfTradeFails() public {
        MockArbContract mockArb = new MockArbContract(USDC);
        bytes memory tradeData = abi.encodeWithSignature("doTrade(uint256)", 1000e6);
        // Do NOT fund the mock, so trade fails

        vm.expectRevert("ARB_TRADE_FAILED");
        executor.requestFlashLoan(address(USDC), 1000e6, address(mockArb), tradeData);
    }
}

Run forge test --fork-url $ALCHEMY_URL -vvv. This runs your tests against the forked mainnet state. Catching a bug here saves you from the next error on the list: execution reverted: ERC20: transfer amount exceeds balance — fix: add allowance check before transferFrom.

Real P&L Example: 30-Day Yield Farming Bot Performance

Let's move from arbitrage to another AI-amenable strategy: automated yield farming. An agent monitors APY across Aave, Compound, and Morpho, accounting for gas, token risk, and compounding frequency.

Assumptions:

  • Capital: $100,000 in USDC.
  • Strategy: Lend on the highest-yielding protocol, auto-compounding weekly.
  • Deployment: Polygon PoS (L2 gas ~$0.02 per tx).
  • AI Agent Role: Daily APY data fetching, simulating gas costs, and scheduling the optimal compound transaction.

30-Day Simulated Results (using historical APY data):

  • Average Base APY: 3.8% (Aave v3 Polygon).
  • AI-Identified Boosts: 4 episodes of "reward emission boosts" on Morpho, avg +2.1% temporary APY.
  • Compounding Cost: 4 transactions * $0.02 = $0.08.
  • Gas Optimization: AI suggested batching reward claims with compounding, saving ~40k gas per cycle.
  • Net APY Achieved: ~4.05%.
  • Estimated Net Profit: ~$331 (after gas).
  • Manual Comparison: A static "set and forget" deposit would have yielded ~$312.

The AI's edge ($19) came from timing the reward boosts and optimizing transaction structure. On $1M capital, that's $190/month—enough to cover infrastructure. The real value is scalability: the same AI agent can manage 50 strategies across 10 wallets.

Next Steps: From Prototype to Production

Your local fork bot works. Now, harden it for the mainnet war.

  1. Formal Audit: Smart contract audits cost $15K–$100K on average (Code4rena 2025 report). For a flash loan contract, this is non-negotiable. Use AI-assisted tools like MythX for preliminary analysis, but budget for a human audit from a reputable firm.
  2. Monitoring & Alerting: Deploy your AI agents as microservices. Use Tenderly to simulate every transaction before sending, creating a real-time "preview" of your profit/loss. Set up alerts for: failed transactions, sudden gas spikes, or deviation from expected profit margins.
  3. MEV Protection: You are now an MEV bot. Be aware of sandwich attacks. Use private transaction relays (like Flashbots Protect) to submit your arbitrage bundles, shielding them from front-running.
  4. Key Management: Move your bot's signing key off your development machine. Use a hardware signer (HSM) or a dedicated, air-gapped signing service. The AI agent should output raw transaction data; a separate, secure service should sign and broadcast.

The goal isn't to replace yourself with an AI. It's to use AI agents as force multipliers for your expertise—handling the data deluge, suggesting optimizations, and catching trivial bugs—so you can focus on the irreducible complexity of DeFi economics and security. Your edge is the synergy between your judgment and their tireless, rapid execution. Now go fork mainnet and start testing.