To jest trzeci artykuł z naszej serii „Drapieżniki w pulach: Sztuka i matematyka płynności JIT”. Przechodzimy do części inżynieryjnej: jak przekształcić wzory matematyczne w kod, który wykonuje się szybciej niż mrugnięcie okiem.
W 2026 roku płynność JIT to nie rywalizacja pomysłów, lecz infrastruktury. Jeśli Twój bot jest napisany w Pythonie i łączy się z publicznym węzłem przez HTTP, nie tylko przegrasz — nawet nie zauważysz, kiedy Twoje zyski zostaną zabrane.
1. Stos technologiczny: Dlaczego Rust dominuje
W świecie MEV walka toczy się o mikrosekundy opóźnień (latency).
- Język: Rust. Korzystanie z biblioteki Alloy (zastępującej ethers-rs) stało się standardem branżowym. Alloy pozwala pracować z typami danych Ethereum bez zbędnych alokacji pamięci i zapewnia superszybką serializację i deserializację zapytań JSON-RPC.
- Łączność: IPC/WebSockets. Boty nie używają HTTP. Łączą się z lokalnym węzłem przez IPC (Inter-Process Communication), aby zminimalizować obciążenie sieci.
- Własny węzeł (Geth/Reth): Najlepsi gracze korzystają z Reth (Rust Ethereum), ponieważ pozwala to dostosować silnik do równoległej symulacji transakcji.
2. Smart kontrakt: Atomowość albo śmierć
Aby atak JIT był bezpieczny, operacje „Wejście do puli” i „Wyjście z puli” muszą być powiązane żelazną logiką. Jeśli dodasz płynność, ale nie uda Ci się jej wycofać w tym samym bloku, ryzykujesz ogromne straty Impermanent Loss.
Bot używa kontraktu Executor. Jego zadaniem jest przyjąć polecenie od skryptu off-chain i wykonać serię wywołań do NonfungiblePositionManager (NFPM) Uniswap v3.
Przykład architektury kontraktu (Solidity 0.8.x):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface INonfungiblePositionManager {
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
function mint(MintParams calldata params) external returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
function decreaseLiquidity(uint256 tokenId, uint128 liquidity, uint256 amount0Min, uint256 amount1Min, uint256 deadline) external;
function collect(uint256 tokenId, address recipient, uint128 amount0Max, uint128 amount1Max) external;
}
contract JITExecutor {
address private immutable owner;
INonfungiblePositionManager public immutable nftManager;
constructor(address _nftManager) {
owner = msg.sender;
nftManager = INonfungiblePositionManager(_nftManager);
}
// Główna funkcja: wywoływana przez bota do wykonania JIT w jednym przebiegu
function performJit(
INonfungiblePositionManager.MintParams calldata mintParams
) external {
require(msg.sender == owner, "Unauthorized");
// 1. Dostarcz kontraktowi tokeny (zwykle tokeny już tam są lub przychodzą przez Flash Loan)
// 2. Dodaj płynność (Mint)
(uint256 tokenId, uint128 liquidity, , ) = nftManager.mint(mintParams);
// UWAGA: W klasycznym bundle Flashbots ten kontrakt
// zakończy wykonanie tutaj, a wypłata nastąpi w drugiej transakcji.
// Alternatywnie, jeśli używamy specjalnego routera, wszystko może odbyć się w jednym przebiegu.
}
// Funkcja do natychmiastowego pobrania zysków
function withdrawJit(uint256 tokenId, uint128 liquidity) external {
nftManager.decreaseLiquidity(tokenId, liquidity, 0, 0, block.timestamp);
nftManager.collect(tokenId, msg.sender, type(uint128).max, type(uint128).max);
}
}
3. Flashbots Bundles: Sklejanie rzeczywistości
Transakcje bota JIT muszą otaczać transakcję ofiary. Do tego używa się mechanizmu Bundles.
Bot wysyła do Flashbots (lub innego MEV relay, np. BeaverBuild czy Titan) tablicę transakcji:
- [Tx_Mint] — Twoja transakcja dodania płynności.
- [Tx_Target] — Transakcja użytkownika z mempoolu (którą przechwyciłeś).
- [Tx_Burn] — Twoja transakcja wycofania.
Mało znany szczegół: Aby oszczędzić gaz, zaawansowane boty czasami pomijają Burn w tym samym bloku, jeśli są pewne, że cena nie wyjdzie z ticku w następnym bloku. Ale to już ryzyko „pasywnego LP”. Prawdziwy drapieżnik JIT zawsze zamyka pozycję w bloku $N$.
4. Logika bota off-chain (Off-chain Engine)
Bot w Rust działa w nieskończonej pętli:
- Streaming mempoolu: nasłuchuje newPendingTransactions.
- Symulacja: Dla każdej dużej transakcji bot wykonuje „wirtualne wykonanie” przez eth_call lub revm (Rust EVM), aby oszacować opłaty, które zarobi.
- Generowanie bundle: jeśli zysk > 0, tworzony jest pakiet.
- Licytacja: bot dodaje coinbase.transfer() — łapówkę dla walidatora.
Insider info: W 2026 roku napiwek dla walidatora może sięgać 99% zysku bota. Boty walczą o pozostały 1% czystego zysku, bo przy wolumenach w milionach dolarów nawet 1% to ogromne pieniądze.
5. Praktyczna wskazówka: użycie multicall
Dla maksymalnej wydajności używaj logiki multicall w swoim kontrakcie. Pozwala to wykonać approve, mint, decreaseLiquidity i collect w jednej transakcji, jeśli korzystasz z własnych środków, a nie Flash Loans. Oszczędza to około 40–60k gazu przy powtarzanych wywołaniach.
Podsumowanie artykułu
Architektura bota JIT to szczyt inżynierii smart kontraktów. Tworzysz „jednorazową” płynność, która istnieje tylko ułamek sekundy.
W następnym artykule: przejdziemy do „ciężkiej artylerii”. Jak przeprowadzać ataki JIT bez posiadania własnych milionów? Omówimy Flash-JIT i użycie Flash Loans do przejęcia 99,9% płynności w gigantycznych pulach.
Mistrzostwo Płynności JIT: Kompletny przewodnik po MEV w Uniswap: Część 3 z 5