| name | jito-bundles-and-priority-fees |
| description | Master Jito bundles for MEV protection and priority fee optimization on Solana - bundle submission, tip strategies, and transaction landing. Use for trading bots, high-priority transactions, and MEV-aware applications. |
Jito Bundles and Priority Fees
Role framing: You are a Solana transaction optimization specialist who understands Jito infrastructure, priority fees, and MEV dynamics. Your goal is to help applications land transactions reliably and protect against MEV extraction.
Initial Assessment
- What are you building: trading bot, DEX, NFT mint, or general dApp?
- Transaction volume: occasional high-priority or sustained high-frequency?
- MEV exposure: are your transactions susceptible to front-running or sandwich attacks?
- Budget: what's acceptable tip/fee spend per transaction?
- Latency requirements: how critical is landing in the next slot?
- Do you need atomic bundles (multiple txs that must execute together)?
Core Principles
- Priority fees are for congestion, tips are for Jito: Different mechanisms, different purposes.
- Bundles are atomic: All transactions in a bundle execute or none do.
- Tips go to validators, not network: Jito tips incentivize block builders to include your bundle.
- Higher tip ≠ guaranteed inclusion: You're competing with other bundles; tip relative to value.
- Bundles can fail silently: No on-chain record if bundle wasn't included.
- Not all validators run Jito: ~80-90% of stake, but some slots won't process bundles.
Workflow
1. Understanding the Landscape
Standard Solana Transaction Path:
User → RPC → Leader Validator → Block
Jito Bundle Path:
User → Jito Block Engine → Jito Relayer → Leader Validator → Block
Key difference: Bundles go through Jito's infrastructure,
which sequences them to prevent MEV extraction.
Priority fees vs Jito tips:
| Aspect | Priority Fee | Jito Tip |
|---|
| Where paid | On-chain, part of tx | Separate tip tx in bundle |
| Who receives | Validator + burn | Validator (via Jito) |
| Guarantees | Higher priority in mempool | Bundle inclusion if won auction |
| Use case | General tx prioritization | MEV protection, atomic execution |
2. When to Use What
| Scenario | Recommendation |
|---|
| Normal dApp transaction | Priority fee only |
| Trading bot (no MEV concern) | Priority fee, maybe Jito for speed |
| DEX swap (MEV risk) | Jito bundle for protection |
| Arbitrage | Jito bundle (atomic multi-tx) |
| NFT mint (competitive) | Priority fee + Jito bundle |
| Liquidation | Jito bundle (need speed + protection) |
3. Priority Fee Implementation
import {
ComputeBudgetProgram,
Transaction,
TransactionInstruction,
} from '@solana/web3.js';
function addPriorityFee(
transaction: Transaction,
microLamports: number,
computeUnits: number = 200_000
): Transaction {
const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: computeUnits,
});
const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: microLamports,
});
transaction.instructions = [
computeLimitIx,
computePriceIx,
...transaction.instructions,
];
return transaction;
}
Priority fee tiers (as of late 2024):
| Network State | Micro-lamports | ~Cost per 200k CU |
|---|
| Quiet | 1,000 | 0.0000002 SOL |
| Normal | 10,000 | 0.000002 SOL |
| Busy | 50,000 | 0.00001 SOL |
| Congested | 200,000 | 0.00004 SOL |
| Extreme | 1,000,000+ | 0.0002+ SOL |
4. Jito Bundle Implementation
import { Connection, Keypair, Transaction, VersionedTransaction } from '@solana/web3.js';
const JITO_ENDPOINTS = {
mainnet: {
blockEngine: 'https://mainnet.block-engine.jito.wtf',
},
};
const JITO_TIP_ACCOUNTS = [
'96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5',
'HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe',
'Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY',
'ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49',
'DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh',
'ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt',
'DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL',
'3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT',
];
function createTipInstruction(
fromPubkey: PublicKey,
tipLamports: number
): TransactionInstruction {
const tipAccount = new PublicKey(
JITO_TIP_ACCOUNTS[Math.floor(Math.random() * JITO_TIP_ACCOUNTS.length)]
);
return SystemProgram.transfer({
fromPubkey,
toPubkey: tipAccount,
lamports: tipLamports,
});
}
async function sendJitoBundle(
transactions: (Transaction | VersionedTransaction)[],
tipLamports: number,
payer: Keypair
): Promise<string> {
const serializedTxs = transactions.map((tx) => {
if (tx instanceof VersionedTransaction) {
return Buffer.from(tx.serialize()).toString('base64');
}
return Buffer.from(tx.serialize()).toString('base64');
});
const response = await fetch(
`${JITO_ENDPOINTS.mainnet.blockEngine}/api/v1/bundles`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'sendBundle',
params: [serializedTxs],
}),
}
);
const result = await response.json();
if (result.error) {
throw new Error(`Bundle failed: ${result.error.message}`);
}
return result.result;
}
async function checkBundleStatus(bundleId: string): Promise<any> {
const response = await fetch(
`${JITO_ENDPOINTS.mainnet.blockEngine}/api/v1/bundles`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'getBundleStatuses',
params: [[bundleId]],
}),
}
);
return response.json();
}
5. Tip Strategy
function calculateTip(
expectedProfitLamports: number,
urgency: 'low' | 'medium' | 'high' | 'critical'
): number {
const urgencyMultiplier = {
low: 0.01,
medium: 0.05,
high: 0.10,
critical: 0.20,
};
const baseTip = expectedProfitLamports * urgencyMultiplier[urgency];
const minTip = 10_000;
const maxTip = 100_000_000;
return Math.max(minTip, Math.min(maxTip, Math.floor(baseTip)));
}
6. Bundle Construction Best Practices
async function protectedSwap(
swapIx: TransactionInstruction[],
payer: Keypair,
connection: Connection
): Promise<string> {
const swapTx = new Transaction();
swapTx.add(...swapIx);
addPriorityFee(swapTx, 10_000, 400_000);
const tipTx = new Transaction();
tipTx.add(createTipInstruction(payer.publicKey, 100_000));
const { blockhash } = await connection.getLatestBlockhash();
swapTx.recentBlockhash = blockhash;
tipTx.recentBlockhash = blockhash;
swapTx.feePayer = payer.publicKey;
tipTx.feePayer = payer.publicKey;
swapTx.sign(payer);
tipTx.sign(payer);
const bundleId = await sendJitoBundle([swapTx, tipTx], 100_000, payer);
return bundleId;
}
7. Monitoring and Retry Logic
async function sendBundleWithRetry(
transactions: Transaction[],
tipLamports: number,
payer: Keypair,
maxRetries: number = 5
): Promise<{ success: boolean; signature?: string; attempts: number }> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const bundleId = await sendJitoBundle(transactions, tipLamports, payer);
await new Promise((r) => setTimeout(r, 500));
const status = await checkBundleStatus(bundleId);
if (status.result?.value?.[0]?.confirmation_status === 'confirmed') {
return {
success: true,
signature: status.result.value[0].transactions[0],
attempts: attempt,
};
}
tipLamports = Math.floor(tipLamports * 1.5);
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error);
}
await new Promise((r) => setTimeout(r, 100));
}
return { success: false, attempts: maxRetries };
}
Templates / Playbooks
Fee/Tip Decision Matrix
interface FeeStrategy {
scenario: string;
priorityFee: number;
jitoTip: number;
useBundle: boolean;
}
const strategies: FeeStrategy[] = [
{
scenario: 'Normal dApp interaction',
priorityFee: 10_000,
jitoTip: 0,
useBundle: false,
},
{
scenario: 'Time-sensitive but not MEV-exposed',
priorityFee: 50_000,
jitoTip: 0,
useBundle: false,
},
{
scenario: 'DEX swap (MEV protection)',
priorityFee: 10_000,
jitoTip: 50_000,
useBundle: true,
},
{
scenario: 'Arbitrage',
priorityFee: 10_000,
jitoTip: 'dynamic (% of profit)',
useBundle: true,
},
{
scenario: 'NFT mint (competitive)',
priorityFee: 200_000,
jitoTip: 1_000_000,
useBundle: true,
},
{
scenario: 'Liquidation',
priorityFee: 100_000,
jitoTip: 500_000,
useBundle: true,
},
];
RPC + Jito Configuration
const infraConfig = {
rpcRead: process.env.RPC_READ_URL,
rpcSend: process.env.RPC_SEND_URL,
jito: {
blockEngine: process.env.JITO_BLOCK_ENGINE,
region: 'ny',
},
fallback: {
onJitoFailure: 'retry_with_rpc',
maxJitoRetries: 3,
priorityFeeEscalation: 1.5,
},
};
Common Failure Modes + Debugging
"Bundle not landing"
- Cause: Tip too low, competing with higher-tipping bundles
- Detection: Bundle status shows "dropped" or no confirmation
- Fix: Increase tip; check current tip floor via Jito API; retry with escalating tips
"Bundle landed but transaction failed"
- Cause: On-chain error (slippage, insufficient funds, etc.)
- Detection: Transaction shows error on explorer
- Fix: This is application logic issue, not Jito issue. Debug the transaction itself
"Transaction landed via RPC before bundle"
- Cause: RPC was faster than Jito path
- Detection: Signature exists but bundle shows "dropped"
- Fix: Not a problem if tx succeeded. For MEV protection, ensure you're not sending to RPC simultaneously
"Bundle simulation failed"
- Cause: Transaction would fail on-chain
- Detection: Jito returns simulation error
- Fix: Fix the transaction before sending; ensure fresh blockhash
"Rate limited by Jito"
- Cause: Exceeded request limits
- Detection: 429 response
- Fix: Implement backoff; contact Jito for higher limits if needed
Quality Bar / Validation
Implementation is complete when:
Output Format
Provide:
- Strategy recommendation: Use Jito bundles, priority fees, or both
- Configuration code: Priority fee and tip calculations
- Bundle construction: How to structure transactions
- Monitoring setup: How to track success rates
- Cost estimate: Expected fees per transaction type
Examples
Simple Example: Add Priority Fee to Swap
Input: "Make this swap transaction land faster"
Output:
import { ComputeBudgetProgram, Transaction } from '@solana/web3.js';
const swapIx = ;
const tx = new Transaction();
tx.add(
ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 })
);
tx.add(
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50_000 })
);
tx.add(swapIx);
Complex Example: MEV-Protected Arbitrage Bundle
Input: "Build an arb bot that uses Jito for MEV protection"
Output:
class JitoArbBot {
private jitoClient: JitoClient;
private minProfitLamports = 50_000;
async executeArb(
buyIx: TransactionInstruction[],
sellIx: TransactionInstruction[],
expectedProfitLamports: number
): Promise<ArbResult> {
const tip = this.calculateTip(expectedProfitLamports);
const netProfit = expectedProfitLamports - tip;
if (netProfit < this.minProfitLamports) {
return { executed: false, reason: 'Profit below threshold after tip' };
}
const bundle = await this.buildArbBundle(buyIx, sellIx, tip);
const result = await this.sendBundleWithRetry(bundle, tip);
return {
executed: result.success,
signature: result.signature,
expectedProfit: expectedProfitLamports,
actualTip: tip,
netProfit: netProfit,
};
}
private calculateTip(profitLamports: number): number {
return Math.max(
10_000,
Math.min(1_000_000_000, Math.floor(profitLamports * 0.05))
);
}
}