If you're building anything that submits transactions on Solana — especially swaps — you need to think about MEV. Maximal Extractable Value is the profit that searchers and validators can extract by reordering, inserting, or censoring transactions in a block.
On Solana, the most common MEV attack is the sandwich: a bot sees your pending swap, buys the token before you (pushing the price up), lets your trade execute at the worse price, then sells immediately after. You lose, they profit.
I run a Telegram trading bot for Solana (@solscanitbot) that processes hundreds of swaps. Without MEV protection, my users were getting consistently worse fills than they should have. Here's how I fixed it.
The Problem
When you submit a transaction to a regular Solana RPC endpoint, it enters the public mempool. Searcher bots monitor this mempool and can front-run your trades in milliseconds.
The typical flow without protection:
- User wants to buy token X for 1 SOL
- Bot creates Jupiter swap transaction
- Bot sends to RPC via
sendRawTransaction - Searcher bot sees the pending transaction
- Searcher buys token X, your trade executes at a worse price
- Searcher sells for profit
The user still gets their tokens, but they pay more than they should. On a 1 SOL trade the difference might be small. On 10+ SOL trades it adds up fast.
Jito Bundles: The Fix
Jito operates a modified validator client that processes transaction bundles atomically. A bundle is a set of transactions that either all execute in sequence or none execute. More importantly, bundles are sent directly to the Jito block engine rather than the public mempool.
This means searcher bots never see your transaction until it's already confirmed.
The trade-off: you pay a small tip to the Jito validator (I use 10,000 lamports, which is 0.00001 SOL — basically nothing).
Implementation
I use the jito-js-rpc package. Setup is minimal:
const { JitoJsonRpcClient } = require('jito-js-rpc');
const JITO_BLOCK_ENGINE = 'https://mainnet.block-engine.jito.wtf/api/v1';
const jito = new JitoJsonRpcClient(JITO_BLOCK_ENGINE);
const JITO_TIP_LAMPORTS = 10000;
After creating and signing a swap transaction, instead of sending it through the regular RPC, I send it through Jito:
async function sendViaJito(serializedTx) {
try {
const encoded = Buffer.from(serializedTx).toString('base64');
const result = await jito.sendTxn(
{ serializedTxn: encoded },
{ bundleOnly: true }
);
if (result && result.result) return result.result;
return null;
} catch {
return null;
}
}
The key parameter is bundleOnly: true — this ensures the transaction is only processed as part of a Jito bundle, never leaked to the public mempool.
Fallback Strategy
Jito doesn't always succeed. The block engine might be congested, or the tip might not be competitive enough during high-activity periods. So I always implement a fallback:
let txId;
const jitoResult = await sendViaJito(rawTx);
if (jitoResult) {
txId = jitoResult;
} else {
// Fallback to regular RPC with skipPreflight
txId = await connection.sendRawTransaction(rawTx, {
skipPreflight: true,
maxRetries: 3,
});
}
This way users always get their trade through. Jito-protected when possible, standard RPC as fallback.
Combining with Jupiter Priority Fees
Jupiter's swap API supports dynamic priority fees and compute unit optimization:
const swapBody = {
quoteResponse: quote,
userPublicKey: wallet.publicKey,
wrapAndUnwrapSol: true,
prioritizationFeeLamports: 'auto',
dynamicComputeUnitLimit: true,
};
Setting prioritizationFeeLamports to 'auto' lets Jupiter estimate the right priority fee based on current network conditions. Combined with Jito bundles, this gives you both MEV protection and fast inclusion.
Results
After adding Jito bundle support:
- Sandwich attacks: effectively zero on Jito-routed trades
- Trade success rate: improved ~15% (fewer failed transactions from front-running)
- Additional cost: 0.00001 SOL per trade (negligible)
- Latency: comparable to regular RPC submission
The Jito route succeeds about 70-80% of the time in my experience. The other 20-30% falls back to standard RPC, which is fine for smaller trades where MEV impact is minimal.
Should You Bother?
If you're building a swap tool, trading bot, or any dApp that submits transactions programmatically — yes. The implementation is maybe 30 lines of code and the jito-js-rpc package handles the complexity.
Your users won't notice when it works (their trades just fill at expected prices), but they'll definitely notice when it doesn't (getting consistently worse fills than the quoted price).
The bot runs at t.me/solscanitbot if you want to see it in action. Every trade is Jito-protected by default.
Previously: Building a Solana Trading Bot on Telegram
Top comments (0)