Skip to main content
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 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.
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.

Get your own node endpoint today

Start for free and get your app to production levels immediately. No credit card required.You can sign up with your GitHub, X, Google, or Microsoft account.

Implementation

Prerequisites

npm install @solana/web3.js dotenv

Add dontfront protection to a transaction

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();

Chainstack Warp transactions vs Jito dontfront

Chainstack offers two transaction acceleration paths. They serve different purposes:
Warp transactionsJito dontfront
GoalMaximum speed — fastest landingMEV protection — prevent sandwich
MechanismbloXroute relay for optimized propagationJito block engine enforces bundle index 0
EndpointYour Chainstack RPC (with Warp enabled)Jito block engine (mainnet.block-engine.jito.wtf)
Front-running protectionNo — speed-optimized, protection disabledYes — core feature
Tip requiredNoYes (minimum 1000 lamports)
Best forArbitrage, sniping, latency-sensitive tradesSwaps, 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.

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

Last modified on April 16, 2026