DeFi

Flash Loan Arbitrage: Zero-Capital DeFi Profits Explained

Flash loans let you borrow millions of dollars in crypto with no collateral โ€” and repay it in the same transaction. This guide explains how flash loan arbitrage works and how to build one.

A
AI Agents Hubยท2025-04-07ยท5 min readยท912 words

Builder of AI agents, crypto trading bots, and open-source automation tools. Sharing practical guides on how to build, deploy, and profit from AI and DeFi technology.

What Is a Flash Loan?

A flash loan is a collateral-free loan that must be borrowed and repaid within a single blockchain transaction.

This is only possible because of how blockchains work: if you can't repay the loan within the same transaction, the entire transaction is reverted as if it never happened. There's zero risk to the lender.

The logic:

  1. Borrow $1,000,000 USDC from Aave
  2. Use it to execute an arbitrage
  3. Repay $1,000,000 + fee ($900 = 0.09%)
  4. Keep the profit

All of this happens in one transaction โ€” one block confirmation.

Why Flash Loans Matter

Flash loans democratize arbitrage. Before flash loans, to profit from a $500K arbitrage opportunity you needed $500K. Now you need:

  • Knowledge to build the smart contract
  • A few dollars in gas fees
  • An opportunity to exploit

This is one of the most level playing fields in finance.

How Flash Loan Arbitrage Works

Transaction begins:
โ”‚
โ”œโ”€ 1. Borrow 1,000,000 USDC from Aave (fee: 900 USDC)
โ”‚
โ”œโ”€ 2. Buy ETH on Uniswap at $3,000 โ†’ receive 333.33 ETH
โ”‚
โ”œโ”€ 3. Sell ETH on Curve at $3,015 โ†’ receive 1,004,995 USDC
โ”‚
โ”œโ”€ 4. Repay Aave: 1,000,900 USDC
โ”‚
โ””โ”€ Profit: 4,095 USDC kept in your wallet

Transaction ends โ€” all settled in ~12 seconds.

Building a Flash Loan Contract (Aave V3)

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

import { FlashLoanSimpleReceiverBase } from "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import { IPoolAddressesProvider } from "@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

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

contract FlashLoanArbitrage is FlashLoanSimpleReceiverBase {
    address public owner;
    
    IUniswapV2Router constant uniswap = IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    IUniswapV2Router constant sushiswap = IUniswapV2Router(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F);
    
    constructor(address _addressProvider) 
        FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider)) 
    {
        owner = msg.sender;
    }
    
    /**
     * Called by Aave after sending us the borrowed funds.
     * We must repay amountOwed before this function returns.
     */
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        // Decode our arbitrage parameters
        (address tokenA, address tokenB, uint256 minProfit) = abi.decode(
            params, (address, address, uint256)
        );
        
        // Step 1: Buy tokenB with tokenA on Uniswap
        IERC20(asset).approve(address(uniswap), amount);
        address[] memory path1 = new address[](2);
        path1[0] = asset;
        path1[1] = tokenB;
        
        uint256[] memory amounts1 = uniswap.swapExactTokensForTokens(
            amount, 0, path1, address(this), block.timestamp + 300
        );
        
        uint256 tokenBReceived = amounts1[amounts1.length - 1];
        
        // Step 2: Sell tokenB back to tokenA on Sushiswap (higher price)
        IERC20(tokenB).approve(address(sushiswap), tokenBReceived);
        address[] memory path2 = new address[](2);
        path2[0] = tokenB;
        path2[1] = asset;
        
        uint256[] memory amounts2 = sushiswap.swapExactTokensForTokens(
            tokenBReceived, 0, path2, address(this), block.timestamp + 300
        );
        
        uint256 finalAmount = amounts2[amounts2.length - 1];
        
        // Step 3: Verify profit and repay
        uint256 amountOwed = amount + premium;
        require(finalAmount >= amountOwed + minProfit, "Insufficient profit");
        
        // Approve repayment
        IERC20(asset).approve(address(POOL), amountOwed);
        
        // Transfer profit to owner
        IERC20(asset).transfer(owner, finalAmount - amountOwed);
        
        return true;
    }
    
    /**
     * Initiate the flash loan arbitrage
     */
    function executeArbitrage(
        address asset,
        uint256 amount,
        address tokenA,
        address tokenB,
        uint256 minProfit
    ) external {
        require(msg.sender == owner, "Not owner");
        
        bytes memory params = abi.encode(tokenA, tokenB, minProfit);
        
        POOL.flashLoanSimple(
            address(this),  // receiver
            asset,          // asset to borrow
            amount,         // amount
            params,         // our encoded params
            0               // referral code
        );
    }
}

Finding Flash Loan Opportunities

The hard part isn't the code โ€” it's finding profitable opportunities before others do.

import { ethers } from 'ethers'

async function findFlashLoanOpportunity(tokenA: string, tokenB: string, amount: bigint) {
  // Get price on Uniswap
  const uniswapPrice = await getUniswapPrice(tokenA, tokenB, amount)
  
  // Get price on Sushiswap
  const sushiPrice = await getSushiswapPrice(tokenA, tokenB, amount)
  
  // Get price on Curve
  const curvePrice = await getCurvePrice(tokenA, tokenB, amount)
  
  const prices = [uniswapPrice, sushiPrice, curvePrice]
  const maxPrice = prices.reduce((a, b) => a > b ? a : b)
  const minPrice = prices.reduce((a, b) => a < b ? a : b)
  
  const spreadPct = Number(maxPrice - minPrice) / Number(minPrice) * 100
  const flashLoanFee = 0.09  // Aave fee
  
  if (spreadPct > flashLoanFee + 0.1) {  // Need > fee + 0.1% margin
    return {
      profitable: true,
      buyExchange: prices.indexOf(minPrice),
      sellExchange: prices.indexOf(maxPrice),
      estimatedProfit: amount * BigInt(Math.floor((spreadPct - flashLoanFee) * 100)) / 10000n
    }
  }
  
  return { profitable: false }
}

The MEV Reality

In practice, flash loan arbitrage is extremely competitive. MEV (Maximal Extractable Value) bots:

  • Monitor the mempool for your transaction
  • Copy it and pay higher gas to front-run you
  • Or sandwich you to extract your profit

To compete, you need:

  • Private RPC endpoints โ€” Submit through Flashbots to avoid mempool exposure
  • Gas optimization โ€” Minimize gas to maximize profit window
  • Speed โ€” Identify opportunities faster than competitors

Who Can Still Profit?

Flash loan arbitrage at scale is dominated by MEV specialists. But opportunities still exist for:

  1. Less-watched DEXs โ€” Newer or smaller chains with less bot competition
  2. Complex multi-hop routes โ€” More complexity = fewer bots that find it
  3. Cross-chain โ€” Bridges create arbitrage opportunities that single-chain bots miss
  4. Novel protocols โ€” New DeFi protocols often have mispricings for weeks

Practical Resources

  • Aave Flash Loan Docs: docs.aave.com
  • Flashbots (MEV protection): flashbots.net
  • Tenderly (simulate transactions before sending): tenderly.co
  • Our DeFi bot template includes flash loan scaffold code

Flash loan arbitrage is genuinely one of the most intellectually interesting areas of DeFi development. Even if not immediately profitable, the skills you build are immensely valuable.

Related Articles