Ceci est le troisième article de notre série « Prédateurs dans les pools : l’art et les mathématiques de la liquidité JIT ». Passons à la partie ingénierie : comment transformer des formules mathématiques en code qui s’exécute plus vite qu’un clin d’œil.
En 2026, la liquidité JIT n’est plus une bataille d’idées, mais d’infrastructure. Si votre bot est écrit en Python et communique avec un nœud public via HTTP, vous ne perdrez pas seulement — vous ne verrez même pas vos profits disparaître.
1. Stack technologique : pourquoi Rust domine
Dans le monde du MEV, la bataille se joue sur des microsecondes de latence.
- Langage : Rust. L’utilisation de la bibliothèque Alloy (remplaçant ethers-rs) est devenue la norme industrielle. Alloy permet de manipuler les types de données Ethereum sans allocations mémoire superflues et offre une sérialisation/désérialisation JSON-RPC ultra-rapide.
- Connectivité : IPC/WebSockets. Les bots n’utilisent pas HTTP. Ils se connectent à un nœud local via IPC (Inter-Process Communication) pour minimiser la pile réseau.
- Nœud propriétaire (Geth/Reth) : Les acteurs majeurs utilisent Reth (Rust Ethereum) car il permet de personnaliser le moteur pour la simulation parallèle de transactions.
2. Smart Contract : Atomicité ou mort
Pour qu’une attaque JIT soit sûre, les opérations « Entrer dans le pool » et « Sortir du pool » doivent être liées par une logique infaillible. Si vous ajoutez de la liquidité mais ne parvenez pas à la retirer dans le même bloc, vous risquez de subir de lourdes pertes Impermanent Loss.
Le bot utilise un Executor Contract. Sa tâche est de recevoir une commande d’un script off-chain et d’exécuter une série d’appels au NonfungiblePositionManager (NFPM) d’Uniswap v3.
Exemple d’architecture de contrat (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);
}
// Fonction principale : appelée par le bot pour exécuter un JIT en une seule passe
function performJit(
INonfungiblePositionManager.MintParams calldata mintParams
) external {
require(msg.sender == owner, "Unauthorized");
// 1. Fournir au contrat les tokens (généralement déjà présents ou via un Flash Loan)
// 2. Ajouter la liquidité (Mint)
(uint256 tokenId, uint128 liquidity, , ) = nftManager.mint(mintParams);
// ATTENTION : Dans un bundle Flashbots classique, ce contrat
// termine l’exécution ici, et le retrait se fait dans une deuxième transaction.
// Sinon, avec un routeur spécifique, tout peut se faire en une seule fois.
}
// Fonction pour collecter instantanément le profit
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 : assembler la réalité
Les transactions du bot JIT doivent entourer la transaction de la victime. Cela se fait grâce aux Bundles.
Le bot envoie un tableau de transactions à Flashbots (ou un autre relais MEV comme BeaverBuild ou Titan) :
- [Tx_Mint] — votre transaction d’ajout de liquidité.
- [Tx_Target] — la transaction de l’utilisateur depuis le mempool (celle que vous avez interceptée).
- [Tx_Burn] — votre transaction de retrait.
Détail peu connu : Pour économiser du gaz, les bots avancés sautent parfois le Burn dans le même bloc si le prix reste dans le tick au bloc suivant. Mais cela constitue un risque de « LP passif ». Un vrai prédateur JIT ferme toujours sa position dans le bloc $N$.
4. Logique du bot off-chain (Off-chain Engine)
Un bot Rust fonctionne en boucle infinie :
- Streaming du mempool : écoute de newPendingTransactions.
- Simulation : Pour chaque grosse transaction, le bot effectue une « exécution virtuelle » via eth_call ou revm (Rust EVM) pour estimer les frais qu’il percevra.
- Génération de bundle : si profit > 0, un paquet est constitué.
- Bidding : le bot ajoute coinbase.transfer() — un pot-de-vin pour le validateur.
Info insider : en 2026, le tip pour le validateur peut atteindre 99% du profit du bot. Les bots se battent pour le 1% restant, car sur des volumes de millions de dollars, même 1% représente une somme énorme.
5. Astuce pratique : utiliser multicall
Pour une efficacité maximale, utilisez la logique multicall dans votre contrat. Cela permet d’exécuter approve, mint, decreaseLiquidity et collect en une seule transaction si vous utilisez vos propres fonds et non des Flash Loans. Cela économise environ 40–60k de gaz sur des appels répétés.
Résumé de l’article
L’architecture d’un bot JIT est le summum de l’ingénierie des smart contracts. Vous créez une liquidité « jetable » qui n’existe que pendant une fraction de seconde.
Dans le prochain article : nous passerons à « l’artillerie lourde ». Comment réaliser des attaques JIT sans posséder des millions ? Nous analyserons Flash-JIT et l’utilisation des Flash Loans pour capturer 99,9% de la liquidité dans les pools géants.
Maîtrise de la liquidité JIT : Le guide complet du MEV sur Uniswap : Partie 3 sur 5