Sam Jeston
GitHubXFarcaster

Submitting Jupiter Transactions through Jito Bundles

June 23rd, 2025

In mid-2024, I explored arbitrage opportunities on Solana. I'm finally getting round to sharing my approach and associated code.

This approach used to be somewhat profitable, however it no longer is, likely due to increased sophistication and competition. With this in mind, I share this for education purposes only.

Fundamentals

Essentially we are trying to find two swaps in opposite directions (i.e Sol → USDC, then USDC → Sol), where the input of the first swap is less than the output of the second swap. From here we can submit these swaps, in order, through a Jito bundle. If the bundle is accepted, we have guaranteed profit. If the bundle fails, no action occurs and therefore nothing is lost.

The Jupiter API

Jupiter expose an API that can be used to both quote swaps, and to get the associated transaction instructions for said swaps.

Fetching Quotes

export async function getQuoteResponse(
  inputMint: string,
  outputMint: string,
  amount: string,
  slippage: number,
  swapMode: "ExactIn" | "ExactOut"
): Promise<QuoteResponse> {
  const quoteResponse = await (
    await fetch(
      `${getOrFail(Configuration.JupiterApiPath)}/quote?inputMint=${inputMint}\
&outputMint=${outputMint}\
&amount=${amount}\
&slippageBps=${slippage}\
&restrictIntermediateTokens=true\
&swapMode=${swapMode}`
    )
  ).json();

  return quoteResponse;
}

Fetching Swap Instructions

export async function getSwapInstructions(
  quoteResponse: QuoteResponse,
  publicKey: PublicKey,
  priorityFee = 1
): Promise<SwapInstructions> {
  const instructions = await (
    await fetch(
      `${getOrFail(Configuration.JupiterApiPath)}/swap-instructions`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          quoteResponse,
          userPublicKey: publicKey.toBase58(),
          wrapAndUnwrapSol: true,
          computeUnitPriceMicroLamports: priorityFee,
        }),
      }
    )
  ).json();

  if (instructions.error) {
    throw new Error("Failed to get swap instructions: " + instructions.error);
  }

  return instructions;
}

Jito Bundles

Jito allow the submission of transaction bundles that are executed as all-or-nothing. Subsequently, if the input for tx2 requires the output from tx1, the bundle will only succeed when the output from tx1 is sufficient. And by using ExactOut quoting strategies for tx2, it is easy to ensure the amount of tokens output from the two transactions is greater than the input.

const searcherClient = searcher.searcherClient(
  getOrFail(Configuration.BlockEngineUrl),
  tradingKeypair
);
const bun = new bundle.Bundle([tx1, tx2], 2);
await searcherClient.sendBundle(bun);

Piecing it all together

The core loop of this approach can be seen on Github here.

For the configured token mint, every 5 seconds an ExactIn quote is fetched for tx1, and an ExactOut quote is fetched for tx2. If the output of tx2 is higher than the input of tx1, based on the configured PROFIT, swap instructions are fetched and the bundle is submitted.

For testing purposes, set the PROFIT to a very small negative number. This way you can see bundles being successfully submitted and better understand the end-to-end loop. If you look for a real profit (i.e >0), you will probably be waiting for a long time!

Where to Next

There are lots of problems with the approach described above. Most significantly it suffers significant latency interacting with the Jupiter API, and the strategy is "blind" to the actual size of potential opportunities.

If you really want to explore arbitrage on Solana, you will need a more sophisticated strategy. One such approach may be tracking liquidity in price bins on Meteora and Orca, and when a discrepancy arises, you could execute a bundle. The benefit of this type of approach is that the exact size of the opportunity would be known.