Нажмите ESC, чтобы закрыть

Заработок на MEV-ботах и расходы на газ: Стратегии арбитража

Чистая экономика MEV сводится к жесткой конкуренции за блоки транзакций, где миллисекундная задержка сети уничтожает весь потенциальный профит. Запуск собственного бота на Ethereum или EVM-сетях второго уровня давно не приносит легких денег. Рынок поделен между специализированными поисковиками, арендующими серверы в дата-центрах Франкфурта и Токио прямо у узлов валидаторов.

Реальные цифры и доходность

Одиночный бот, запущенный с домашнего сервера или стандартного облачного VPS, в 95% случаев работает в минус из-за газовых аукционов. Доходность зависит от стратегии:

  • Атомарный арбитраж (Atomic Arbitrage): Маржинальность сделок упала до доли процента. Чистый дневной профит успешного бота составляет от $100 до $3000 в периоды волатильности. Риск потери капитала нулевой, так как транзакция заворачивается в один атомарный смарт-контракт: если условия не сошлись, вся цепочка откатывается.
  • Сэндвич-атаки (Sandwich Attacks): Теоретическая доходность выше, от $500 до $10000 в сутки на парах с низкой ликвидностью в Uniswap v2 или аналогичных AMM. Практика жестче. Конкурентные боты выстраивают очереди через частные пулы транзакций, перебивая чужие взятки валидаторам.
  • Ликвидации (Liquidations): Спот-рынок кредитных протоколов вроде Aave приносит крупный куш, но требует миллионов долларов ликвидности для покрытия долгов и мгновенного погашения позиций. Одиночкам здесь делать нечего.

Структура расходов на газ и работу через билдеры

Главная статья расходов при запуске MEV-бота не успешные транзакции, а неудачные попытки и аукционы за приоритет включения (Gas Priority Auction).

  • Публичный мемпул: Отправка транзакции в обход частных релеев гарантирует убыток. Обычный бот видит транзакцию жертвы, шлет свою с более высоким газом, но боты-конкуренты перехватывают ее первыми через приватные RPC. В итоге ваша транзакция исполняется по худшей цене или вовсе ревертится, сжигая реальный газ в блокчейне.
  • Билдеры блоков и Flashbots: Современный арбитраж строится через eth_sendBundle. Вы формируете бандл из нескольких транзакций и отправляете его напрямую валидаторам (Beaverbuild, Titan, Flashbots).
  • Плата за неудачу: Плюс частных бандлов в том, что при несостоявшемся арбитраже транзакция просто не попадает в блок. Вы не платите комиссию сети за промах. Минус - валидатор забирает львиную долю прибыли в виде прямой взятки (coinbase.transfer), оставляя разработчику от 5 до 15% грязного профита.

Таблица метрик MEV-стратегий

СтратегияКапитал на стартСредний дневной чистый профитВероятность успехаОсновная причина убытка
Атомарный арбитражот $5,000$50 - $50015-25%Опережение конкурентов по задержке сети (latency)
Сэндвич-атакиот $20,000$200 - $3,0005-10%Переплата за газ в аукционе валидаторов
Ликвидацииот $100,000+$1,000 - $10,000+< 2%Нехватка капитала для погашения крупных займов

Пример отправки приватного бандла на Python

Ниже представлен рабочий каркас скрипта на Python с использованием библиотеки web3.py для отправки бандла через Flashbots-совместимый провайдер. Код очищен от лишних абстракций.

import os
from eth_account import Account
from eth_account.signers.local import LocalAccount
from web3 import Web3
from flashbots import flashbot
# Ключ для подписи запросов к relay (репутация в Flashbots).
# НЕ используйте ключ, на котором лежат средства.
signature_account: LocalAccount = Account.from_key(os.environ["FLASHBOTS_SIGNER_KEY"])
# Ключ отправителя транзакции
sender: LocalAccount = Account.from_key(os.environ["SENDER_PRIVATE_KEY"])
w3 = Web3(Web3.HTTPProvider(os.environ["ETH_NODE_URL"]))
flashbot(w3, signature_account, "https://relay-sepolia.flashbots.net")
def build_signed_tx(nonce: int) -> bytes:
    base_fee = w3.eth.get_block("latest")["baseFeePerGas"]
    tx = {
        "to": sender.address,          # пример: перевод самому себе
        "value": 0,
        "gas": 21_000,
        "maxFeePerGas": base_fee * 2 + Web3.to_wei(2, "gwei"),
        "maxPriorityFeePerGas": Web3.to_wei(2, "gwei"),
        "nonce": nonce,
        "chainId": w3.eth.chain_id,
        "type": 2,
    }
    return sender.sign_transaction(tx).rawTransaction  # в новых версиях: .raw_transaction
def send_private_bundle(target_block: int):
    nonce = w3.eth.get_transaction_count(sender.address)
    bundle = [{"signed_transaction": build_signed_tx(nonce)}]
    # Симуляция перед отправкой
    w3.flashbots.simulate(bundle, target_block)
    result = w3.flashbots.send_bundle(bundle, target_block_number=target_block)
    result.wait()  # ждёт, пока блок target_block будет обработан
    try:
        return result.receipts()
    except Exception:
        return None  # бандл не включён в блок: можно повторить на следующем блоке
if __name__ == "__main__":
    target = w3.eth.block_number + 1
    receipts = send_private_bundle(target)
    print("Включён в блок" if receipts else "Бандл не включён, повторите на следующем блоке")

Бандл действует только на один целевой блок, поэтому на практике отправку необходимо делать в цикле на несколько блоков подряд. Отсутствие включения не означает ошибку: конкуренция идёт по размеру чаевых билдеру.

Рентабельность собственного бота упирается в стоимость инфраструктуры. Аренда выделенного сервера в стойке с валидатором в дата-центре обходится в сумму от $500 в месяц. Без такого аппаратного ускорения бот физически не успевает просчитать состояние пула раньше соперников. Выигрывает тот, у кого короче кабель до валидатора и тоньше алгоритм поиска спреда.

Флеш-займы дают возможность проводить масштабные арбитражные операции без собственного крупного капитала, используя беззалоговое заимствование внутри единой транзакции с гарантированным откатом при убытках.

Флеш-займы и архитектура смарт-контракта

Иметь собственный капитал в несколько сотен тысяч долларов для арбитража не обязательно. Контракты интеграции с пулами Aave v3 или Balancer позволяют брать беззалоговые флеш-займы в рамках одной транзакции. Бот заимствует актив, прогоняет его через DEX-пары, возвращает долг с учетом комиссии 0.09% и забирает остаток на баланс создателя. Если баланс после всех расчетов уходит в минус, вся транзакция откатывается через revert, а эмитент теряет только газ за вызов функции.

Написание арбитражного контракта на Solidity требует жесткой оптимизации под байт-код EVM. Использование стандартных интерфейсов ERC-20 через SafeERC20 добавляет лишние проверки, которые съедают ценные единицы газа при проверке лимитов транзакции. Опытные разработчики пишут прямые низкоуровневые вызовы через call, минимизируя операции чтения и записи в постоянную память (SLOAD/SSTORE).

Пример Solidity-шаблона

Ниже представлен шаблон на флеш-займе Aave V3 для пулов Uniswap V2, без сторонних библиотек:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
}
/// Aave V3: flash loan одного актива
interface IPool {
    function flashLoanSimple(
        address receiverAddress,
        address asset,
        uint256 amount,
        bytes calldata params,
        uint16 referralCode
    ) external;
}
interface IUniswapV2Pair {
    function token0() external view returns (address);
    function getReserves() external view returns (uint112, uint112, uint32);
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
}
/// Арбитраж на флеш-займе Aave V3: asset -> tokenB (pair1) -> asset (pair2).
/// Пример для пулов типа Uniswap V2. Не аудировался.
contract FlashArbitrage {
    error Unauthorized();
    error InvalidCaller();
    error InvalidInitiator();
    error TransferFailed();
    error ApproveFailed();
    error InsufficientProfit(uint256 balanceAfter, uint256 required);
    address private immutable OWNER;
    IPool private immutable POOL;
    constructor(address aavePool) {
        OWNER = msg.sender;
        POOL = IPool(aavePool); // адрес Pool берите из PoolAddressesProvider нужной сети
    }
    modifier onlyOwner() {
        if (msg.sender != OWNER) revert Unauthorized();
        _;
    }
    /// Точка входа. Занимает `amount` токена `asset` и запускает цикл в executeOperation.
    function executeArbitrage(
        address asset,
        uint256 amount,
        address tokenB,
        address pair1,
        address pair2,
        uint256 minProfit
    ) external onlyOwner {
        bytes memory params = abi.encode(tokenB, pair1, pair2, minProfit);
        POOL.flashLoanSimple(address(this), asset, amount, params, 0);
    }
    /// Колбэк Aave V3. Вызывается пулом после выдачи займа.
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        // 1. Колбэк может вызвать только пул Aave...
        if (msg.sender != address(POOL)) revert InvalidCaller();
        // 2. ...и только по запросу самого контракта
        if (initiator != address(this)) revert InvalidInitiator();
        (address tokenB, address pair1, address pair2, uint256 minProfit) =
            abi.decode(params, (address, address, address, uint256));
        // Баланс уже включает занятые средства
        uint256 balanceBefore = IERC20(asset).balanceOf(address(this));
        uint256 amountB = _swap(pair1, asset, amount);
        _swap(pair2, tokenB, amountB);
        // Прибыль = balanceAfter - balanceBefore - premium (комиссия займа)
        uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
        uint256 required = balanceBefore + premium + minProfit;
        if (balanceAfter < required) revert InsufficientProfit(balanceAfter, required);
        // Aave забирает долг сам через transferFrom, поэтому нужен approve
        _safeApprove(asset, address(POOL), amount + premium);
        return true;
    }
    function withdraw(address token, uint256 amount) external onlyOwner {
        _safeTransfer(token, OWNER, amount);
    }
    function _swap(address pair, address tokenIn, uint256 amountIn) private returns (uint256 amountOut) {
        (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pair).getReserves();
        bool zeroForOne = tokenIn == IUniswapV2Pair(pair).token0();
        (uint256 reserveIn, uint256 reserveOut) = zeroForOne ? (r0, r1) : (r1, r0);
        // Формула Uniswap V2 (комиссия 0,3%)
        uint256 amountInWithFee = amountIn * 997;
        amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1000 + amountInWithFee);
        _safeTransfer(tokenIn, pair, amountIn);
        IUniswapV2Pair(pair).swap(
            zeroForOne ? 0 : amountOut,
            zeroForOne ? amountOut : 0,
            address(this),
            "" // пустой data: это обычный swap, не flash swap
        );
    }
    function _safeTransfer(address token, address to, uint256 amount) private {
        if (token.code.length == 0) revert TransferFailed();
        (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));
        if (!ok || (data.length != 0 && !abi.decode(data, (bool)))) revert TransferFailed();
    }
    function _safeApprove(address token, address spender, uint256 amount) private {
        if (token.code.length == 0) revert ApproveFailed();
        (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.approve, (spender, amount)));
        if (!ok || (data.length != 0 && !abi.decode(data, (bool)))) revert ApproveFailed();
    }
}

Инфраструктурный тупик AWS

Размещение поискового бота на виртуальных серверах Amazon Web Services или Google Cloud гарантирует слив бюджета. Виртуализация вносит задержки в сетевой стек, а общие каналы связи подвержены потере пакетов при пиковых нагрузках мемпула.

Конкурентоспособный сетап требует физического выделенного сервера (bare-metal) с процессором высокой тактовой частоты на одно ядро, быстрым NVMe-накопителем под локальную ноду Geth или Reth и прямым гигабитным каналом до дата-центров во Франкфурте. Запуск собственной легковесной ноды обязателен, так как публичные RPC-провайдеры вроде Infura или Alchemy искусственно режут скорость отдачи новых блоков и задерживают нотификации о pending-транзакциях на критические 100-300 миллисекунд.

Прибыль в MEV забирает не тот, у кого сложнее математическая модель, а тот, кто физически ближе к валидатору и умеет считать газовые издержки до каждой операции ассемблера в байткоде.

Сделать краткую выжимку этой статьи с помощью:

FAQ

Чистый ежедневный доход одиночного MEV-бота составляет от $50 до $500 при атомарном арбитраже и от $200 до $3,000 при сэндвич-атаках в периоды высокой рыночной волатильности. Отсутствие аппаратной колокации сервера рядом с узлами валидаторов приводит к сетевым задержкам, из-за чего стандартные конфигурации теряют прибыль на газовых аукционах и часто работают в минус.

Структура издержек включает базовую комиссию за выполнение транзакции в сети и приоритетную взятку валидаторам, отправляемую через приватные релеи Flashbots с помощью eth_sendBundle. Приватные транзакции исключают списание газа при неудачном арбитраже, однако в случае успеха бот вынужден отдавать от 85% до 95% от всей полученной прибыли непосредственно валидатору через функцию coinbase.transfer.

Самостоятельный запуск сэндвич-ботов на основных EVM-сетях не приносит прибыли из-за высокой сетевой задержки, жестких требований к капиталу и доминирования приватных мемпулов. Без доступа к выделенным bare-metal серверам с задержкой отклика менее одной миллисекунды одиночные разработчики переплачивают за приоритет в блоках, что полностью уничтожает маржинальность сделок.
Astra EXMON

Astra is the official voice of EXMON and the editorial collective dedicated to bringing you the most timely and accurate information from the crypto market. Astra represents the combined expertise of our internal analysts, product managers, and blockchain engineers.

...

Поделитесь своим мнением

Ваш e-mail не будет опубликован. Обязательные поля отмечены *