> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chainstack.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Solana: MEV protection with Jito

> Protect Solana transactions from sandwich attacks using Jito dontfront, Chainstack Warp transactions, and Trader Nodes for MEV-resistant execution.

**TLDR:**

* Sandwich attacks front-run and back-run your transaction within the same bundle, extracting value at your expense.
* Jito's `dontfront` feature prevents this by ensuring your transaction must appear at index 0 in any bundle.
* You add a pubkey starting with `jitodontfront` as a read-only account to any instruction, then send through Jito's block engine.
* Chainstack [Warp transactions](/docs/warp-transactions) optimize for **speed** (bloXroute relay). Jito `dontfront` optimizes for **protection**. Choose based on your use case.

## What is a sandwich attack?

A sandwich attack is when a searcher:

1. **Front-runs** your swap by buying the same token before you, pushing the price up
2. Your transaction executes at the inflated price
3. **Back-runs** by selling immediately after, profiting from the price difference

This happens at the bundle level — the attacker places their transactions around yours in the same block. The result: you get worse execution, the attacker takes the difference.

## How Jito `dontfront` works

Jito's block engine processes transaction bundles. The `dontfront` feature is a simple opt-in:

1. Add a pubkey starting with `jitodontfront` (e.g., `jitodontfront111111111111111111111111111111`) as a **read-only account** to any instruction in your transaction
2. Send the transaction through Jito's block engine endpoint (not a standard RPC)
3. The block engine enforces that your transaction must be at **index 0** in any bundle that includes it

Since the attacker's front-running transaction can't appear before yours in the bundle, the sandwich is impossible.

<Warning>
  This feature requires sending transactions through Jito's block engine, not standard RPC nodes. The Jito endpoint is `https://mainnet.block-engine.jito.wtf/api/v1/transactions`.
</Warning>

## Implementation

### Prerequisites

The example is shown in both Solana JavaScript libraries — both are actively maintained, so use whichever fits your project. `@solana/kit` is the newer, tree-shakable, functional SDK; `@solana/web3.js` is the classic `Connection`/`PublicKey` API.

<Tabs>
  <Tab title="@solana/kit">
    ```bash theme={"system"}
    npm install @solana/kit @solana-program/system dotenv
    ```
  </Tab>

  <Tab title="@solana/web3.js (classic)">
    ```bash theme={"system"}
    npm install @solana/web3.js bs58 dotenv
    ```
  </Tab>
</Tabs>

### Add `dontfront` protection to a transaction

<Tabs>
  <Tab title="@solana/kit">
    ```typescript TypeScript theme={"system"}
    import {
      createSolanaRpc,
      address,
      lamports,
      pipe,
      createKeyPairSignerFromBytes,
      getBase58Encoder,
      createTransactionMessage,
      setTransactionMessageFeePayerSigner,
      setTransactionMessageLifetimeUsingBlockhash,
      appendTransactionMessageInstructions,
      signTransactionMessageWithSigners,
      getBase64EncodedWireTransaction,
      AccountRole,
    } from "@solana/kit";
    import { getTransferSolInstruction } from "@solana-program/system";
    import "dotenv/config";

    // Use your Chainstack endpoint for reads
    const rpc = createSolanaRpc(process.env.SOLANA_RPC!);
    // @solana/kit decodes base58, so no separate bs58 package is needed
    const payer = await createKeyPairSignerFromBytes(
      getBase58Encoder().encode(process.env.PRIVATE_KEY!)
    );

    // Jito block engine for sends
    const JITO_ENDPOINT =
      "https://mainnet.block-engine.jito.wtf/api/v1/transactions";

    // Any valid pubkey starting with "jitodontfront". Does not need to exist on-chain.
    const DONT_FRONT = address("jitodontfront111111111111111111111111111111");

    // Jito tip accounts — pick one at random to reduce contention
    const TIP_ACCOUNTS = [
      "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
      "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
      "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
      "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
      "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
      "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
      "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
      "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
    ];

    // Append the dontfront marker as a read-only account to any instruction
    function addDontFront(instruction) {
      return {
        ...instruction,
        accounts: [
          ...(instruction.accounts ?? []),
          { address: DONT_FRONT, role: AccountRole.READONLY },
        ],
      };
    }

    function randomTipAccount() {
      return address(TIP_ACCOUNTS[Math.floor(Math.random() * TIP_ACCOUNTS.length)]);
    }

    async function sendWithMEVProtection() {
      const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

      // Your actual instruction — add dontfront to it
      const transferIx = addDontFront(
        getTransferSolInstruction({
          source: payer,
          destination: payer.address,
          amount: lamports(1_000_000n), // 0.001 SOL
        })
      );

      // Jito tip — minimum 1000 lamports
      const tipIx = getTransferSolInstruction({
        source: payer,
        destination: randomTipAccount(),
        amount: lamports(1000n),
      });

      const message = pipe(
        createTransactionMessage({ version: 0 }),
        (m) => setTransactionMessageFeePayerSigner(payer, m),
        (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
        (m) => appendTransactionMessageInstructions([transferIx, tipIx], m)
      );

      const signedTransaction = await signTransactionMessageWithSigners(message);

      // Send through Jito block engine, NOT standard RPC
      const serialized = getBase64EncodedWireTransaction(signedTransaction);
      const response = await fetch(JITO_ENDPOINT, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          jsonrpc: "2.0",
          id: 1,
          method: "sendTransaction",
          params: [serialized, { encoding: "base64" }],
        }),
      });

      const result = await response.json();
      console.log("Sent with dontfront protection:", result.result);
    }

    sendWithMEVProtection();
    ```
  </Tab>

  <Tab title="@solana/web3.js (classic)">
    ```typescript TypeScript theme={"system"}
    import {
      Connection,
      Keypair,
      PublicKey,
      SystemProgram,
      TransactionMessage,
      VersionedTransaction,
      TransactionInstruction,
      LAMPORTS_PER_SOL,
    } from "@solana/web3.js";
    import bs58 from "bs58";
    import "dotenv/config";

    // Use your Chainstack endpoint for reads
    const connection = new Connection(process.env.SOLANA_RPC!);
    const payer = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY!));

    // Jito block engine for sends
    const JITO_ENDPOINT =
      "https://mainnet.block-engine.jito.wtf/api/v1/transactions";

    // Any valid pubkey starting with "jitodontfront". Does not need to exist on-chain.
    const DONT_FRONT = new PublicKey(
      "jitodontfront111111111111111111111111111111"
    );

    // Jito tip accounts — pick one at random to reduce contention
    const TIP_ACCOUNTS = [
      "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
      "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
      "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
      "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
      "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
      "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
      "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
      "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
    ];

    function addDontFront(instruction: TransactionInstruction): TransactionInstruction {
      return new TransactionInstruction({
        ...instruction,
        keys: [
          ...instruction.keys,
          { pubkey: DONT_FRONT, isSigner: false, isWritable: false },
        ],
      });
    }

    function randomTipAccount(): PublicKey {
      const idx = Math.floor(Math.random() * TIP_ACCOUNTS.length);
      return new PublicKey(TIP_ACCOUNTS[idx]);
    }

    async function sendWithMEVProtection() {
      const latestBlockhash = await connection.getLatestBlockhash();

      // Your actual instruction — add dontfront to it
      const transferIx = addDontFront(
        SystemProgram.transfer({
          fromPubkey: payer.publicKey,
          toPubkey: payer.publicKey,
          lamports: 0.001 * LAMPORTS_PER_SOL,
        })
      );

      // Jito tip — minimum 1000 lamports
      const tipIx = SystemProgram.transfer({
        fromPubkey: payer.publicKey,
        toPubkey: randomTipAccount(),
        lamports: 1000,
      });

      const message = new TransactionMessage({
        payerKey: payer.publicKey,
        recentBlockhash: latestBlockhash.blockhash,
        instructions: [transferIx, tipIx],
      }).compileToV0Message();

      const transaction = new VersionedTransaction(message);
      transaction.sign([payer]);

      // Send through Jito block engine, NOT standard RPC
      const serialized = Buffer.from(transaction.serialize()).toString("base64");
      const response = await fetch(JITO_ENDPOINT, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          jsonrpc: "2.0",
          id: 1,
          method: "sendTransaction",
          params: [serialized, { encoding: "base64" }],
        }),
      });

      const result = await response.json();
      console.log("Sent with dontfront protection:", result.result);
    }

    sendWithMEVProtection();
    ```
  </Tab>
</Tabs>

## Chainstack Warp transactions vs Jito `dontfront`

Chainstack offers two transaction acceleration paths. They serve different purposes:

|                              | [Warp transactions](/docs/warp-transactions) | Jito `dontfront`                                       |
| ---------------------------- | -------------------------------------------- | ------------------------------------------------------ |
| **Goal**                     | Maximum speed — fastest landing              | MEV protection — prevent sandwich                      |
| **Mechanism**                | bloXroute relay for optimized propagation    | Jito block engine enforces bundle index 0              |
| **Endpoint**                 | Your Chainstack RPC (with Warp enabled)      | Jito block engine (`mainnet.block-engine.jito.wtf`)    |
| **Front-running protection** | No — speed-optimized, protection disabled    | Yes — core feature                                     |
| **Tip required**             | No                                           | Yes (minimum 1000 lamports)                            |
| **Best for**                 | Arbitrage, sniping, latency-sensitive trades | Swaps, DeFi interactions where execution price matters |

**Use Warp** when you need to land first (you are the one competing for position).
**Use `dontfront`** when you need to protect your execution price (others are competing against you).

For reads (getAccountInfo, getBlock, etc.), always use your [Chainstack Solana endpoint](/docs/solana-tooling).

## Tips

* **Jito tip amounts** should be minimum 1000 lamports. For `sendTransaction`, use a 70/30 split between priority fee and Jito tip.
* **Pick tip accounts at random** to reduce contention across the 8 Jito tip accounts.
* When using `sendBundle`, `dontfront` transactions must be contiguous at the front of the bundle with overlapping signers.
* `dontfront` is **mainnet and testnet only** — it does not work on devnet.

## Additional resources

* [Solana Trader Nodes with Warp transactions](/docs/solana-trader-nodes) — Chainstack's speed-optimized transaction path
* [Priority fees for faster transactions](/docs/solana-how-to-priority-fees-faster-transactions) — set optimal priority fees
* [How to handle the transaction expiry error](/docs/solana-how-to-handle-the-transaction-expiry-error) — retry logic
