Naciśnij ESC, aby zamknąć

Boty MEV: Zarobki, Koszty Gazu i Strategie Arbitrażu

Czysta ekonomia MEV sprowadza się do brutalnej rywalizacji o bloki transakcyjne, gdzie millisekundowe opóźnienie sieci niszczy cały potencjalny zysk. Uruchomienie własnego bota na Ethereum lub sieciach warstwy drugiej EVM dawno przestało być źródłem łatwych pieniędzy. Rynek jest podzielony między wyspecjalizowane podmioty, które dzierżawią serwery w centrach danych we Frankfurcie i Tokio tuż obok węzłów walidatorów.

Realne liczby i rentowność

Samodzielny bot uruchomiony z domowego serwera lub standardowego VPS w chmurze w 95% przypadków przynosi straty ze względu na aukcje gasu. Rentowność zależy od obranej strategii:

  • Arbitraż atomowy (Atomic Arbitrage): Marżowość transakcji spadła do ułamków procenta. Czysty dzienny zysk skutecznego bota wynosi od $100 do $3000 w okresach podwyższonej zmienności. Ryzyko utraty kapitału jest zerowe, ponieważ transakcja jest zamykana w ramach jednego atomowego smart kontraktu: jeśli warunki się nie zgromadzą, cały ciąg operacji zostaje wycofany.
  • Ataki typu sandwich (Sandwich Attacks): Teoretyczna stopa zwrotu jest wyższa, od $500 do $10000 dziennie na parach o niskiej płynności na Uniswap v2 lub podobnych AMM. Praktyka jest jednak o wiele trudniejsza. Konkurencyjne boty ustawiają się w kolejki przez prywatne pule transakcji, przebijając oferty łapówek dla walidatorów.
  • Likwidacje (Liquidations): Rynek spot protokołów pożyczkowych takich jak Aave generuje potężne zyski, ale wymaga milionów dolarów płynności na pokrycie długów i natychmiastową spłatę pozycji. Samotni gracze nie mają tu czego szukać.

Struktura kosztów gasu i praca z builderami

Główną pozycją wydatków przy uruchamianiu bota MEV nie są udane transakcje, lecz nieudane próby oraz aukcje pierwszeństwa włączenia do bloku (Gas Priority Auction).

  • Publiczny mempool: Wysłanie transakcji z pominięciem prywatnych relayów gwarantuje stratę. Zwykły bot widzi transakcję ofiary, wysyła własną z wyższym gasem, ale konkurencyjne boty przejmują ją jako pierwsze przez prywatne RPC. W rezultacie Twoja transakcja wykonuje się po najgorszej cenie lub zostaje cofnięta, paląc realny gas w blockchainie.
  • Builderzy bloków i Flashbots: Nowoczesny arbitraż opiera się na eth_sendBundle. Formujesz paczkę (bundle) składającą się z kilku transakcji i wysyłasz ją bezpośrednio do walidatorów (Beaverbuild, Titan, Flashbots).
  • Opłata za niepowodzenie: Zaletą prywatnych paczek jest to, że w przypadku niepowodzenia arbitrażu transakcja po prostu nie trafi do bloku. Nie płacisz prowizji sieciowej za chybioną próbę. Wadą jest to, że walidator zgarnia lwią część zysku w formie bezpośredniej łapówki (coinbase.transfer), pozostawiając deweloperowi od 5 do 15% brutto zysku.

Tabela metryk strategii MEV

StrategiaKapitał na startŚredni dzienny czysty zyskPrawdopodobieństwo sukcesuGłówna przyczyna strat
Arbitraż atomowyod $5,000$50 - $50015-25%Przewaga konkurencji pod względem opóźnień sieci (latency)
Ataki typu sandwichod $20,000$200 - $3,0005-10%Przepłacanie za gas w aukcjach walidatorów
Likwidacjeod $100,000+$1,000 - $10,000+< 2%Brak kapitału na spłatę dużych pożyczek

Przykład wysyłania prywatnego bundle w Pythonie

Poniżej znajduje się roboczy szkielet skryptu w Pythonie z użyciem biblioteki web3.py do wysyłania paczki przez dostawcę kompatybilnego z Flashbots. Kod został oczyszczony z zbędnych abstrakcji.

import os
from eth_account import Account
from eth_account.signers.local import LocalAccount
from web3 import Web3
from flashbots import flashbot
# Klıucz do podpisywania zapytań do relay (reputacja w Flashbots).
# NIE używaj klucza, na którym trzymasz środki.
signature_account: LocalAccount = Account.from_key(os.environ["FLASHBOTS_SIGNER_KEY"])
# Klucz nadawcy transakcji
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,          # przykład: przelew do samego siebie
        "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  # w nowszych wersjach: .raw_transaction
def send_private_bundle(target_block: int):
    nonce = w3.eth.get_transaction_count(sender.address)
    bundle = [{"signed_transaction": build_signed_tx(nonce)}]
    # Symulacja przed wysłaniem
    w3.flashbots.simulate(bundle, target_block)
    result = w3.flashbots.send_bundle(bundle, target_block_number=target_block)
    result.wait()  # czeka, aż blok target_block zostanie przetworzony
    try:
        return result.receipts()
    except Exception:
        return None  # paczka nie została włączona do bloku: można ponowić w kolejnym bloku
if __name__ == "__main__":
    target = w3.eth.block_number + 1
    receipts = send_private_bundle(target)
    print("Włączono do bloku" if receipts else "Paczka nie została włączona, spróbuj ponownie w następnym bloku")

Paczka działa tylko dla jednego docelowego bloku, dlatego w praktyce wysyłkę trzeba powtarzać w pętli dla kilku kolejnych bloków. Brak uwzględnienia w bloku nie oznacza błędu: konkurencja toczy się o wysokość napiwku dla buildera.

Rentowność własnego bota sprowadza się do kosztów infrastruktury. Wynajem dedykowanego serwera w szafie rack z walidatorem w centrum danych kosztuje od $500 miesięcznie. Bez takiego przyspieszenia sprzętowego bot fizycznie nie zdąży przeliczyć stanu puli przed rywalami. Wygrywa ten, kto ma krótszy kabel do walidatora i sprawniejszy algorytm wyszukiwania spreadu.

Flash loany dają możliwość przeprowadzania na wielką skalę operacji arbitrażowych bez własnego wielkiego kapitału, wykorzystując pożyczki bez zabezpieczenia w ramach pojedynczej transakcji z gwarantowanym wycofaniem w razie strat.

Flash loany a architektura smart kontraktu

Posiadanie własnego kapitału rzędu kilkuset tysięcy dolarów do arbitrażu wcale nie jest konieczne. Kontrakty integracyjne z pulami Aave v3 czy Balancer pozwalają brać pożyczki flash loan bez zabezpieczenia w ramach jednej transakcji. Bot pożycza aktyw, przepuszcza go przez pary DEX, oddaje dług wraz z prowizją 0.09% i zgarnia resztę na saldo twórcy. Jeśli saldo po wszystkich rozliczeniach schodzi na minus, cała transakcja zostaje cofnięta przez revert, a emitent traci jedynie gas za wywołanie funkcji.

Pisanie kontraktu arbitrażowego w Solidity wymaga ostrej optymalizacji pod bytecode EVM. Używanie standardowych interfejsów ERC-20 przez SafeERC20 dokłada zbędne sprawdzenia, które pożerają cenne jednostki gasu przy weryfikacji limitów transakcji. Doświadczeni deweloperi piszą bezpośrednie niskopoziomowe wywołania przez call, minimalizując operacje odczytu i zapisu w pamięci stałej (SLOAD/SSTORE).

Przykład szablonu Solidity

Poniżej zaprezentowano szablon oparty na flash loanie Aave V3 dla pul Uniswap V2, bez zewnętrznych bibliotek:

// 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 jednego aktywa
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;
}
/// Arbitraż na flash loanie Aave V3: asset -> tokenB (pair1) -> asset (pair2).
/// Przykład dla pul typu Uniswap V2. Nie przeszło audytu.
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); // adres Pool bierz z PoolAddressesProvider odpowiedniej sieci
    }
    modifier onlyOwner() {
        if (msg.sender != OWNER) revert Unauthorized();
        _;
    }
    /// Punkt wejścia. Pożycza `amount` tokena `asset` i odpala pętlę w 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);
    }
    /// Callback Aave V3. Wywoływany przez pulę po udzieleniu pożyczki.
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        // 1. Callback może wywołać tylko pula Aave...
        if (msg.sender != address(POOL)) revert InvalidCaller();
        // 2. ...i tylko na żądanie samego kontraktu
        if (initiator != address(this)) revert InvalidInitiator();
        (address tokenB, address pair1, address pair2, uint256 minProfit) =
            abi.decode(params, (address, address, address, uint256));
        // Saldo uwzględnia już pożyczone środki
        uint256 balanceBefore = IERC20(asset).balanceOf(address(this));
        uint256 amountB = _swap(pair1, asset, amount);
        _swap(pair2, tokenB, amountB);
        // Zysk = balanceAfter - balanceBefore - premium (prowizja pożyczki)
        uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
        uint256 required = balanceBefore + premium + minProfit;
        if (balanceAfter < required) revert InsufficientProfit(balanceAfter, required);
        // Aave pobiera dług sam przez transferFrom, więc potrzebne jest 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);
        // Formuła Uniswap V2 (prowizja 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),
            "" // pusty data: to zwykły swap, nie 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();
    }
}

Ślepy zaułek infrastrukturalny w AWS

Stawianie bota searcherowego na wirtualnych serwerach Amazon Web Services czy Google Cloud gwarantuje przepalenie budżetu. Wirtualizacja wnosi opóźnienia do stosu sieciowego, a współdzielone kanały łączności łapią dropy pakietów przy pikowym obciążeniu mempoola.

Konkurencyjny setup wymaga fizycznego serwera dedykowanego (bare-metal) z wysokim taktowaniem jednordzeniowym CPU, szybkim dyskiem NVMe pod lokalny node Geth lub Reth oraz bezpośrednim łączem gigabitowym do datacenter we Frankfurcie. Odpalenie własnego lightweight node'a to konieczność, ponieważ publiczne RPC pokroju Infura czy Alchemy sztucznie tną prędkość dostarczania nowych bloków i opóźniają notyfikacje o pending transakcjach o krytyczne 100-300 milisekund.

Zysk w MEV zgarnia nie ten, kto ma bardziej skomplikowany model matematyczny, lecz ten, kto stoi fizycznie najbliżej walidatora i potrafi wyliczyć koszty gasu co do pojedynczej instrukcji asemblera w bajtkodzie.

Podsumuj ten wpis na blogu za pomocą:

FAQ

Czysty dzienny zysk indywidualnego bota MEV wynosi od $50 do $500 w przypadku tradycyjnego arbitrażu atomowego oraz od $200 do $3,000 przy atakach typu sandwich w okresach wysokiej zmienności rynkowej. Znaczna część teoretycznego przychodu jest natychmiast pochłaniana przez łapówki dla walidatorów w aukcjach bloków, przez co pojedyncze instalacje bez bezpośredniej kolokacji z węzłami walidującymi często generują straty.

Struktura wydatków bota MEV składa się z opłaty bazowej wykonania w sieci oraz bezpośredniej opłaty priorytetowej przekazywanej do builderów bloków przez prywatne przekaźniki typu Flashbots z wywołaniem eth_sendBundle. Wykorzystanie prywatnego przekaźnika eliminuje koszty gazu za odrzucone transakcje, jednak w przypadku udanego wykonania wyszukiwacz musi przekazać od 85% do 95% wygenerowanego zysku bezpośrednio do walidatora poprzez funkcję coinbase.transfer.

Niezależne boty realizujące ataki typu sandwich na głównych sieciach EVM są w większości nieopłacalne ze względu na opóźnienia sieciowe, wysokie wymagania kapitałowe oraz dominację instytucjonalnych wyszukiwaczy. Brak dostępu do prywatnych węzłów bare-metal o opóźnieniu poniżej milisekundy zmusza niezależnych deweloperów do licytowania zbyt wysokich opłat priorytetowych w aukcjach builderów, co całkowicie redukuje marżę zysku na trójkątach płynności.
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.

...

Dodaj opinię

Twój adres e-mail nie zostanie opublikowany. Obowiązkowe pola są oznaczone*