TL;DR
Solana's high-throughput architecture doesn't make your protocol MEV-proof. Validators running custom block builders can reorder, insert, and delay transactions to extract value from your users. This guide covers practical defense patterns — from slippage-aware instruction design to Jito bundle integration — that every Solana DeFi developer should implement in 2026.
The Myth: "Solana Doesn't Have MEV"
Early Solana marketing positioned the chain as MEV-resistant. No mempool, deterministic execution order, 400ms slots — what's to front-run?
Reality told a different story. By 2024, Jito Labs had built an entire MEV infrastructure with tip-based transaction ordering. Validators running Jito's modified client earned millions in priority fees and tips. Sandwich bots became the most profitable participants on Solana, extracting an estimated $300M+ from DEX traders in 2025 alone.
The fundamental economics haven't changed: if a transaction reveals profitable information before execution, someone will exploit it. Solana just made the game faster.
How Sandwich Attacks Work on Solana
A sandwich attack on Solana follows the same three-step pattern as on Ethereum, adapted for Solana's architecture:
Step 1: Detection
The attacker monitors pending transactions through:
- Jito Block Engine: Validators share transaction bundles with searchers
-
RPC endpoints: Public RPCs expose
sendTransactionbefore inclusion - Staked connections: Validators sell direct access to upcoming transactions
User submits: Swap 1000 USDC → SOL on Raydium (slippage: 1%)
Attacker sees: Profitable sandwich opportunity
Step 2: Front-run
The attacker submits a transaction with higher priority that executes before the user's:
Attacker buys: 50,000 USDC → SOL (pushes price up)
User's swap: 1000 USDC → SOL (gets fewer SOL due to moved price)
Attacker sells: SOL → USDC (captures the price difference)
Step 3: Bundle Submission
On Solana, attackers use Jito bundles to atomically submit all three transactions:
// Pseudo-code for a sandwich bundle
let bundle = vec![
front_run_tx, // Buy before victim
victim_tx, // Victim's original swap
back_run_tx, // Sell after victim
];
jito_client.send_bundle(bundle).await?;
The bundle guarantees atomic execution — all three transactions land in the same slot in the specified order, or none do.
Defense Pattern 1: Slippage-Aware Instruction Design
The most fundamental protection is ensuring your protocol enforces minimum output amounts at the instruction level — not just at the UI level.
Bad: Trust the Frontend
// ❌ No minimum output check
pub fn swap(ctx: Context<Swap>, amount_in: u64) -> Result<()> {
let amount_out = calculate_output(amount_in, &ctx.accounts.pool)?;
transfer_tokens(ctx.accounts.user_token_out, amount_out)?;
Ok(())
}
Good: Enforce On-Chain
// ✅ On-chain minimum output enforcement
pub fn swap(
ctx: Context<Swap>,
amount_in: u64,
minimum_amount_out: u64,
) -> Result<()> {
let amount_out = calculate_output(amount_in, &ctx.accounts.pool)?;
require!(
amount_out >= minimum_amount_out,
SwapError::SlippageExceeded
);
transfer_tokens(ctx.accounts.user_token_out, amount_out)?;
Ok(())
}
Better: Time-Weighted Price Bounds
// ✅✅ TWAP-based slippage with staleness check
pub fn swap_with_oracle(
ctx: Context<SwapWithOracle>,
amount_in: u64,
max_price_deviation_bps: u16,
) -> Result<()> {
let oracle = &ctx.accounts.price_oracle;
let clock = Clock::get()?;
require!(
clock.unix_timestamp - oracle.last_update < MAX_ORACLE_STALENESS,
SwapError::StaleOracle
);
let spot_price = calculate_spot_price(&ctx.accounts.pool)?;
let oracle_price = oracle.twap_price;
let deviation = abs_diff(spot_price, oracle_price) * 10000 / oracle_price;
require!(
deviation <= max_price_deviation_bps as u64,
SwapError::PriceDeviationTooHigh
);
let amount_out = calculate_output(amount_in, &ctx.accounts.pool)?;
transfer_tokens(ctx.accounts.user_token_out, amount_out)?;
Ok(())
}
This pattern catches sandwich attacks mid-execution: if the front-run transaction has moved the price beyond the TWAP deviation threshold, the victim's transaction reverts.
Defense Pattern 2: Commit-Reveal Schemes
For high-value swaps, a commit-reveal pattern makes front-running structurally impossible:
// Phase 1: Commit (reveals nothing about swap direction or amount)
pub fn commit_swap(
ctx: Context<CommitSwap>,
commitment: [u8; 32], // hash(amount, min_out, nonce, user_pubkey)
deadline_slot: u64,
) -> Result<()> {
let order = &mut ctx.accounts.pending_order;
order.commitment = commitment;
order.deadline_slot = deadline_slot;
order.user = ctx.accounts.user.key();
order.committed_slot = Clock::get()?.slot;
Ok(())
}
// Phase 2: Reveal (must be in a later slot)
pub fn reveal_swap(
ctx: Context<RevealSwap>,
amount_in: u64,
minimum_amount_out: u64,
nonce: [u8; 32],
) -> Result<()> {
let order = &ctx.accounts.pending_order;
let clock = Clock::get()?;
require!(clock.slot > order.committed_slot, SwapError::SameSlotReveal);
require!(clock.slot <= order.deadline_slot, SwapError::OrderExpired);
let expected = hash(&[
&amount_in.to_le_bytes(),
&minimum_amount_out.to_le_bytes(),
&nonce,
order.user.as_ref(),
]);
require!(expected.0 == order.commitment, SwapError::InvalidReveal);
execute_swap(ctx, amount_in, minimum_amount_out)?;
Ok(())
}
Trade-off: Adds latency (~400ms minimum) and UX friction. Best for swaps above a threshold (e.g., >$10K).
Defense Pattern 3: Private Transaction Submission
Use private submission channels instead of broadcasting to public RPCs:
import { SearcherClient } from 'jito-ts/dist/sdk/searcher';
async function submitPrivateSwap(
connection: Connection,
transaction: Transaction,
tipLamports: number
) {
const searcher = SearcherClient.connect(JITO_BLOCK_ENGINE_URL, keypair);
const tipIx = SystemProgram.transfer({
fromPubkey: wallet.publicKey,
toPubkey: JITO_TIP_ACCOUNT,
lamports: tipLamports,
});
transaction.add(tipIx);
const bundle = new Bundle([transaction]);
await searcher.sendBundle(bundle);
}
Defense Pattern 4: Batch Auctions
Batch all swaps within a time window and execute at a single clearing price — eliminates sandwich attacks entirely:
pub fn settle_auction(ctx: Context<SettleAuction>) -> Result<()> {
let auction = &mut ctx.accounts.auction;
let clearing_price = find_clearing_price(
&auction.buy_orders, &auction.sell_orders,
)?;
for order in auction.buy_orders.iter().chain(auction.sell_orders.iter()) {
if order_matches(order, clearing_price) {
execute_fill(order, clearing_price)?;
}
}
auction.settled = true;
Ok(())
}
Defense Pattern 5: Dynamic Fees with Entropy
Make transactions computationally unpredictable for sandwich bots:
pub fn swap_with_entropy(
ctx: Context<Swap>,
amount_in: u64,
minimum_amount_out: u64,
) -> Result<()> {
let clock = Clock::get()?;
let entropy = hash(&[
&clock.slot.to_le_bytes(),
ctx.accounts.user.key.as_ref(),
]);
let dynamic_fee = calculate_dynamic_fee(
&ctx.accounts.pool, &entropy, clock.unix_timestamp,
)?;
let amount_out = calculate_output_with_fee(
amount_in, &ctx.accounts.pool, dynamic_fee,
)?;
require!(amount_out >= minimum_amount_out, SwapError::SlippageExceeded);
transfer_tokens(ctx.accounts.user_token_out, amount_out)?;
Ok(())
}
The Security Checklist
If you're building or auditing a Solana DeFi protocol, verify these MEV protections:
- [ ] Slippage enforcement: Is
minimum_amount_outchecked on-chain? - [ ] Oracle integration: Does the protocol detect abnormal price deviations?
- [ ] Private submission: Can users submit transactions privately?
- [ ] Batch execution: Are high-value orders batched?
- [ ] Dynamic fees: Do fees respond to volatility or MEV activity?
- [ ] Commit-reveal: Is there a commit-reveal option for large swaps?
- [ ] Priority fee guidance: Does the UI suggest appropriate priority fees?
- [ ] Simulation resistance: Is there computational unpredictability?
Conclusion
MEV protection isn't a single feature — it's a design philosophy. The most secure Solana DeFi protocols in 2026 combine multiple layers:
- On-chain slippage enforcement (minimum viable protection)
- Oracle-aware price bounds (catches mid-attack manipulation)
- Private submission channels (reduces information leakage)
- Batch auctions for high-value operations (eliminates ordering exploitation)
Build for the adversary. Your users will thank you.
DreamWork Security researches smart contract vulnerabilities across Solana and EVM ecosystems. Follow for weekly deep dives into DeFi security, audit techniques, and security best practices.
Top comments (0)