Unpacking MEV on Solana: Challenges, Threats, and Developer Defenses

Solana doesn't operate like Ethereum, and that reshapes the entire game of MEV. In this blog, we break down how front-running and back-running work on Solana, the technical constraints that make it harder to pull off, and what strategies (both legitimate and malicious) are evolving. If you're building on Solana, this is your crash course on MEV risks, defenses, and the invisible games validators and searchers might be playing.

Salah Ismail

Why Front-Running Is Harder on Solana

Front-running on Solana is significantly less accessible than on Ethereum. Here's why:

  • No public mempool: Pending transactions are not visible
  • Timestamp constraints: Wallets include a recentBlockhash field in each transaction, which limits its validity to roughly the last 150 blocks. This effectively acts as a Time-To-Live (TTL), meaning the transaction will automatically expire if not included quickly. This constraint limits how long a transaction can be withheld and reduces the window for front-running. 
  • Continuous block building: Transactions are streamed into blocks. Even high-priority fee txs arriving later can be included after lower-fee ones.
  • Parallel transaction threads: Validators process transactions in parallel, meaning priority fees don’t guarantee ordering.

Jito once offered pending transaction visibility for MEV searchers, but discontinued it after community pushback over the ecosystem’s negative impact. Some private pools still exist(non-related to Jito), but access is limited.

How MEV Still Happens in Solana

Despite Solana’s aforementioned architectural defenses, MEV remains alive through several tactics. To understand how, we need to distinguish between the actors (who extracts MEV) and the techniques (how they maximize it)

MEV-Capable Actors 

There are several entities positioned to extract MEV and we can distinguish them based on   the control they exert over transaction visibility. 

1. Trading Bots (e.g., BonkBot, Unibot on Telegram)

These bots allow users to trade volatile or newly listed assets directly through messaging apps. They handle wallet creation and manage private keys, offering convenience and automation for users.

However, since the bot controls transaction construction and signing, it can sandwich or front-run user trades for its own gain. Not all bots do this, but the design allows it, and less reputable clones can and often do exploit this.

2. RPC Providers

RPC operators see user transactions before they hit the blockchain. This gives them an informational edge, allowing them to front-run transactions.

This is a hypothetical but technically viable threat. If an RPC provider is also a validator , they’re even better positioned to capitalize on MEV opportunities using this visibility.

3. Validators

Validators with a large enough stake could become block leaders. This is a capital-intensive approach, but with full view of all incoming transactions during their slot, a validator could theoretically reorder or censor transactions to capture MEV.

While Solana’s continuous block building helps limit this behavior, validator-level discretion still leaves some room for manipulation, especially if the validator bypasses expected sequencing rules.

MEV Techniques

These are some of the strategies used to extract MEV, available to one or more of the actors above. Powerful MEV players often combine several of these for maximum advantage.

1. Optimistic Transactions

A spam-based technique where searchers send many transactions that only succeed if certain profitable conditions are met. Even if 90%+ of transactions fail, the few that do succeed can justify the cost, especially on Solana, where failed transactions are cheap.

This creates significant network spam, but doesn’t require privileged access. Any actor, including bots, validators, or RPCs, can use this method.

2. Searcher Colocation with Validators

By locating in the same datacenter as validators, searchers reduce latency and boost their chances of landing profitable transactions faster than the competition. 

However, visibility-based actors (like RPC providers or validators) can also colocate their infrastructure, combining informational advantage with execution speed.

3. Validator Colocation with Centralized Exchanges (CEXs)

Validators positioned near CEX infrastructure can monitor off-chain prices and land trades that exploit arbitrage gaps. This physical proximity reduces latency and increases MEV capture potential.

Again, this is typically carried out by validators but RPC providers or bots backed by validator infrastructure could also benefit from this setup.

How It All Fits Together

While we've separated actors and techniques for clarity, in practice they often overlap. For instance, a validator may also run an RPC and colocate near both a CEX and a searcher; a trading bot may combine optimistic spam with pre-chain visibility; an RPC operator might also batch searcher bundles to exploit timing; etc.

In short: visibility-based actors can and do use timing and spam-based tricks as well. The more infrastructure they control, the more tools they can combine.

Jito’s Role in Leveling the Field

Jito, now halted, helped counter MEV centralization by making the mempool more accessible to all searchers. This reduced spam and made optimistic strategies less necessary. Instead of relying on randomness, searchers could build valid, profitable bundles, lowering the barrier to entry and distributing MEV more fairly.

Developer Defenses: Protecting Solana Programs from MEV

Sandwich attacks (front/back-running)

The two widely deployed solutions to protect swaps against sandwich attacks are slippage protection and validity timestamp

  • Slippage protection reverts if the resulting amount from the swap diverges too much from the expected value (to be defined by the protocol or user), reducing the profitability of the sandwich attack. 
  • Validity timestamp prevents validators from withholding the transaction for too long, reducing their accessible outcomes.

Below, a swap example implementing both a slippage and timestamp protection:

pub fn swap_tokens_protected(
    ctx: Context<SwapTokens>, 
    amount_in: u64,
    min_amount_out: u64,  // Slippage protection
    max_timestamp: i64    // Timestamp protection
) -> Result<()> {
    let current_time = Clock::get()?.unix_timestamp;
    // reject if transaction is too old
    require!(
        current_time <= max_timestamp,
        ErrorCode::TransactionExpired
    );
    
    let pool = &mut ctx.accounts.pool;
    let amount_out = perform_swap(pool, amount_in)?;
    // ensure output meets minimum expectation
    require!(
        amount_out >= min_amount_out,
        ErrorCode::SlippageExceeded
    );
    Ok(())
}

Initialization front-running

Another type of threat emerging from front-running is account initialization. 

Suppose you are defining a Configuration account with protocol parameters and an admin control access field which will then be used and accessed by other parts of your programs. If an attacker is able to deploy that account before you and sets an address he controls as the admin , they effectively hijack it, forcing you to redeploy your program.

A simple and effective safeguards to require that the signer’s pubkey during initialization matches the program’s upgrade_authority, automatically set at runtime to the deployer's pubkey. This constraint is logical, as initialization is typically the very first step post-deployment.

Source: docs.rs/anchor-lang

use anchor_lang::prelude::*;
use crate::program::MyProgram;

declare_id!("Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e");

#[program]
pub mod my_program {
    use super::*;

    pub fn set_initial_admin(
        ctx: Context<SetInitialAdmin>,
        admin_key: Pubkey
    ) -> Result<()> {
        ctx.accounts.admin_settings.admin_key = admin_key;
        Ok(())
    }

    pub fn set_admin(...){...}
    pub fn set_settings(...){...}
}

#[account]
#[derive(Default, Debug)]
pub struct AdminSettings {
    admin_key: Pubkey
}

#[derive(Accounts)]
pub struct SetInitialAdmin<'info> {
    #[account(init, payer = authority, seeds = [b"admin"], bump)]
    pub admin_settings: Account<'info, AdminSettings>,
    #[account(mut)]
    pub authority: Signer<'info>,
    #[account(constraint = program.programdata_address()? == Some(program_data.key()))]
    pub program: Program<'info, MyProgram>,
    #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))]
    pub program_data: Account<'info, ProgramData>,
    pub system_program: Program<'info, System>,
}

Once the admin key is established, additional configuration accounts (e.g. a pauser role) can be created, each referencing the trusted admin authority.

Slashing/penalty evasion

This is the kind of situation where a user needs to be penalized, either for bad behavior or as part of a yield strategy, but is able to evade the loss by front-running the transaction applying the sanction.

Take, for example,  a vault where users deposit assets that are allocated into yield-generating strategies. While these strategies generate profit most of the time, it can happen that a loss is incurred, which could temporarily reduce the profitability of the vault. A malicious user could observe the chain state and withdraw its assets right before the loss, then redeposit its assets after. By doing this, the user is able to increase its share in the vault compared to others users, profiting from the situation. The reverse is also true: a user could spot an incoming yield spike and sandwich the opportunity with a quick deposit and withdrawal for instant gain.

Another example involves governance protocols,  where users could deposit assets to get voting rights, but could be punished/slashed if proven to be acting maliciously. Without protection, a user could simply front-run the slashing transaction with a full withdrawal of its staked assets, allowing him to simply evade the punishment.

In the vault example, setting a fine-tuned deposit/withdrawal fee is usually sufficient to make most of those situations unprofitable, as it requires the users to stay a minimum amount of time in the pool to see the fee refunded by the generated yield.

In the governance case , this would not be sufficient. The better approach here would be to force a minimum delay between actions. For example, requiring a 7-day delay between a deposit and a withdrawal: while this is easy to develop, such limitations can be user-unfriendly(and a deposit/withdrawal fee has a similar effect). 

A better solution is to implement a withdrawal queue, where assets are sitting for a defined period of time (could be a few hours), and still slashable before exiting the queue and available to the user.

Denial of service

Front-running can also be used as a tool to DoS some functionalities in protocols.
This is actually a commonly known vulnerability in Solana where as part of an instruction, an account must be initialized. 

In Anchor, when defining an account, it is possible to set a constraint called init, which tell the engine that a new data account will be initialized: 

 #[account(
        init, // will fail if account already exists
        payer = payer,
        space = 8 + 32
    )]
    pub data_account: Account<'info, DataAccount>,

But there is a pitfall: if the account is already initialized, the instruction will revert, causing a DoS for the call.

To prevent this from happening, it is possible to use the init_if_needed constraint, which will only do the initialization if the account does not exist yet.

Another way a DoS could happen is if a transaction requires specific values to be held to execute successfully. An attacker could front-run the legitimate call to (temporarily or not) update that value to force the call to revert. For example, a reward in SOL is available for a winner to be taken outside a vault account, and the program requires the instruction to provide the exact value that is held by the account.

An attacker could front-run the winner by sending 1 lamport to the vault account, making the winner request wrong:

  pub fn claim_reward(ctx: Context<ClaimReward>, expected_amount: u64) -> Result<()> {
        let vault = &ctx.accounts.vault;
        
        // If attacker sends 1 lamport to vault before this tx,
        // the expected_amount will be wrong and tx will fail
        require!(
            vault.lamports() == expected_amount,
            ErrorCode::IncorrectAmount
        );

       // Transfer the reward to the winner

Key Takeaways

  • Solana’s architecture offers natural resistance to public mempool exploitation, but MEV persists through other means.
  • Validator collusion and colocation present new, opaque threats.
  • Solutions exist but require deliberate, defensive programming.
  • Protocol developers must proactively guard against sandwiching, front-run initializations, slashing evasion, and DoS via tx manipulation.

If you're building on Solana and want to harden your defenses, fuzzing can uncover hidden edge cases and logic flaws that static analysis might miss. At Adevar Labs, we don’t just audit, we rigorously fuzz Solana programs to surface vulnerabilities before they’re exploited. Whether you're shipping a DEX, vault, or governance system, we’re here to help you ship safely.

Want to learn more? Stay tuned for more deep dives, real-world bugs, and smart contract best practices.

Follow @AdevarLabs on X / LinkedIn - Ship Safely.

Resources: