This is the third article in our series “Predators in the Pools: The Art and Math of JIT Liquidity.” Now we move on to the engineering side: how to turn mathematical formulas into production code that executes faster than the blink of an eye.
In 2026, JIT liquidity is no longer a battle of ideas — it’s a battle of infrastructure. If your bot is written in Python and talks to a public node over HTTP, you won’t just lose — you won’t even notice when your profit gets taken.
1. Technology Stack: Why Rust Dominates
In the MEV world, the fight is over microseconds of latency.
- Language: Rust. Using the Alloy library (which replaced ethers-rs) has become the industry standard. Alloy lets you work with Ethereum data types without unnecessary memory allocations and provides extremely fast serialization and deserialization of JSON-RPC requests.
- Connectivity: IPC/WebSockets. Bots do not use HTTP. They connect to a local node via IPC (Inter-Process Communication) to minimize the network stack overhead.
- Own Node (Geth/Reth): Top-tier players use Reth (Rust Ethereum) because it allows the engine to be customized for parallel transaction simulation.
2. Smart Contract: Atomicity or Death
For a JIT attack to be safe, the operations “Enter the Pool” and “Exit the Pool” must be tied together with ironclad logic. If you add liquidity but fail to withdraw it within the same block, you risk getting hit with massive Impermanent Loss.
The bot uses an Executor Contract. Its job is to receive a command from an off-chain script and execute a series of calls to the Uniswap v3 NonfungiblePositionManager (NFPM).
Example contract architecture (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);
}
// Main function: called by the bot to execute a JIT operation in a single pass
function performJit(
INonfungiblePositionManager.MintParams calldata mintParams
) external {
require(msg.sender == owner, "Unauthorized");
// 1. Supply the contract with tokens (usually the tokens are already there or come via a Flash Loan)
// 2. Add liquidity (Mint)
(uint256 tokenId, uint128 liquidity, , ) = nftManager.mint(mintParams);
// NOTE: In a classic Flashbots bundle this contract
// will finish execution here, and the withdrawal will happen in a second transaction.
// Alternatively, if a specialized router is used, everything can happen in one.
}
// Function for instantly collecting 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: Stitching Reality Together
JIT bot transactions must surround the victim’s transaction. This is done using Bundles.
The bot sends an array of transactions to Flashbots (or another MEV relay such as BeaverBuild or Titan):
- [Tx_Mint] — Your liquidity add transaction.
- [Tx_Target] — The user transaction from the mempool (the one you intercepted).
- [Tx_Burn] — Your withdrawal transaction.
A lesser-known detail: To save gas, advanced bots sometimes skip the Burn in the same block if they’re confident the price won’t move out of the tick in the next block. But that already becomes a “passive LP” risk. A real JIT predator always closes the position in block $N$.
4. Off-chain Bot Logic (Off-chain Engine)
A Rust bot runs in an infinite loop:
- Mempool streaming: listens to newPendingTransactions.
- Simulation: For each large transaction, the bot performs a “virtual execution” via eth_call or revm (Rust EVM) to estimate the fees it would earn.
- Bundle generation: If profit > 0, a bundle is assembled.
- Bidding: The bot adds coinbase.transfer() — a bribe for the validator.
Insider note: In 2026, the validator tip can reach up to 99% of the bot’s profit. Bots compete for the remaining 1% of net profit, because with volumes in the millions of dollars, even 1% is serious money.
5. Practical Tip: Using multicall
For maximum efficiency, use multicall logic inside your contract. This allows approve, mint, decreaseLiquidity, and collect to be executed in a single transaction if you are using your own funds rather than Flash Loans. This saves roughly 40–60k gas on repeated calls.
Article Summary
The architecture of a JIT bot represents the pinnacle of smart contract engineering. You create “disposable” liquidity that exists only for fractions of a second.
In the next article: we move to the “heavy artillery.” How do you execute JIT attacks without having millions of your own capital? We’ll break down Flash-JIT and the use of Flash Loans to capture 99.9% of liquidity in giant pools.
JIT Liquidity Mastery: The Complete Guide to MEV in Uniswap: Part 3 of 5