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

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.
npm install @solana/kit @solana-program/system dotenv

Add dontfront protection to a transaction

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

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 July 2, 2026