Это третья статья из нашего цикла «Хищники в пулах: Искусство и математика JIT-ликвидности». Переходим к инженерной части: как превратить математические формулы в программный код, который исполняется быстрее, чем вы успеете моргнуть.
В 2026 году JIT-ликвидность — это соревнование не идей, а инфраструктуры. Если ваш бот написан на Python и общается с публичной нодой через HTTP, вы не просто проиграете — вы даже не увидите, как у вас заберут прибыль.
1. Стек технологий: Почему Rust доминирует
В мире MEV борьба идет за микросекунды задержки (latency).
- Язык: Rust. Использование библиотек Alloy (пришедшей на смену ethers-rs) стало индустриальным стандартом. Alloy позволяет работать с типами данных Ethereum без лишних аллокаций памяти и обеспечивает сверхбыструю сериализацию/десериализацию JSON-RPC запросов.
- Связь: IPC/WebSockets. Боты не используют HTTP. Они подключаются к локальной ноде через IPC (Inter-Process Communication) для минимизации сетевого стека.
- Собственная нода (Geth/Reth): Топовые игроки используют Reth (Rust Ethereum), так как он позволяет кастомизировать движок для параллельной симуляции транзакций.
2. Смарт-контракт: Атомарность или смерть
Чтобы JIT-атака была безопасной, операции «Вход в пул» и «Выход из пула» должны быть связаны железной логикой. Если вы добавите ликвидность, но не сможете ее вывести в том же блоке, вы рискуете попасть на огромный Impermanent Loss.
Бот использует Executor Contract. Его задача — принять команду от оффчейн-скрипта и выполнить серию вызовов к NonfungiblePositionManager (NFPM) Uniswap v3.
Пример архитектуры контракта (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);
}
// Главная функция: вызывается ботом для выполнения JIT в один проход
function performJit(
INonfungiblePositionManager.MintParams calldata mintParams
) external {
require(msg.sender == owner, "Unauthorized");
// 1. Снабжаем контракт токенами (обычно токены уже там или через Flash Loan)
// 2. Добавляем ликвидность (Mint)
(uint256 tokenId, uint128 liquidity, , ) = nftManager.mint(mintParams);
// ВНИМАНИЕ: В классическом бандле Flashbots этот контракт
// завершит выполнение здесь, а вывод будет во второй транзакции.
// Либо, если мы используем специфический роутер, всё в одном.
}
// Функция для мгновенного сбора прибыли
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: Склеивание реальности
Транзакции JIT-бота должны окружать транзакцию жертвы. Для этого используется механизм Bundles (пакеты).
Бот отправляет в Flashbots (или другой MEV-реле, например BeaverBuild или Titan) массив транзакций:
- [Tx_Mint] — Ваша транзакция добавления ликвидности.
- [Tx_Target] — Транзакция пользователя из мемпула (которую вы перехватили).
- [Tx_Burn] — Ваша транзакция вывода.
Малоизвестная деталь: Чтобы сэкономить на газе, продвинутые боты не делают Burn в том же блоке, если они уверены, что цена не выйдет из тика в следующем блоке. Но это уже риск «пассивного LP». Настоящий JIT-хищник всегда закрывает позицию в блоке $N$.
4. Логика оффчейн-бота (Off-chain Engine)
Бот на Rust работает в бесконечном цикле:
- Стриминг мемпула: Слушает newPendingTransactions.
- Симуляция (Simulation): Для каждой крупной транзакции бот делает «виртуальное исполнение» через eth_call или revm (Rust EVM), чтобы понять, сколько комиссий он получит.
- Генерация бандла: Если профит > 0, формируется пакет.
- Bidding: Бот добавляет coinbase.transfer() — взятку валидатору.
Инсайд: В 2026 году размер взятки (tip) валидатору может достигать 99% от прибыли бота. Боты воюют за оставшийся 1% чистой прибыли, потому что на объемах в миллионы долларов даже 1% — это огромные деньги.
5. Практический совет: Использование multicall
Для максимальной эффективности используйте multicall логику внутри вашего контракта. Это позволяет выполнить approve, mint, decreaseLiquidity и collect в одной транзакции, если вы используете собственные средства, а не Flash Loans. Это экономит около 40-60к газа на повторных вызовах.
Итог статьи
Архитектура JIT-бота — это вершина смарт-контрактной инженерии. Вы создаете «одноразовую» ликвидность, которая существует лишь доли секунды.
В следующей статье: Мы перейдем к «тяжелой артиллерии». Как проводить JIT-атаки, не имея собственных миллионов? Разберем Flash-JIT и использование Flash Loans для захвата 99.9% ликвидности в пулах-гигантах.
Мастерство JIT-ликвидности: Полное руководство по MEV в Uniswap: Часть 3 из 5