Skip to content

jupiter swap ​

Complete example of executing a Jupiter swap through BlockSprint.

The example uses Jupiter's Swap API /build endpoint, which returns the swap as instructions so you can add the BlockSprint tip before signing. Jupiter's API needs an API key from portal.jup.ag.

JavaScript / TypeScript ​

bash
npm install @solana/web3.js@1 bs58
typescript
import {
  AddressLookupTableAccount,
  ComputeBudgetProgram,
  Connection,
  Keypair,
  PublicKey,
  SystemProgram,
  TransactionInstruction,
  TransactionMessage,
  VersionedTransaction,
} from "@solana/web3.js";
import bs58 from "bs58";

const BLOCKSPRINT_ENDPOINT = "https://fra.blocksprint.io/api/v1/YOUR_API_KEY";
const RPC_URL = "https://api.mainnet-beta.solana.com"; // your Solana RPC
const JUPITER_API = "https://api.jup.ag/swap/v2";
const JUPITER_API_KEY = process.env.JUPITER_API_KEY ?? "";

// BlockSprint tip addresses
const TIP_ADDRESSES = [
  "Sp1x2AqpQckPLaWnWCJUNg8k6qQexfaEWcSRKf5JcDV",
  "Sp4JHSh9cksfzXbgK7Pq2ovtn8LirLQydaJKTsiNT77",
  "Sp1xMS2cbw83SZDNr4AGqkBYYLjb3LvVnmDSrTMaHkr",
  "SpagSJmnh8E9cGT5Y431xPPaS2c1xLREGGCWN9yDeUf",
  "SpWrza9E63MQuHeGnnfzmtLVCs3pBdjyKPXUABPo9nq",
  "SpXyoqMHJEhsNQ9fHh3DTGGzrGroZ5zfDtJwFvN4kXi",
  "Spv2g3L837bVeHwkyb18LhA5P1bvJbkvaZbxcZXbwC2",
  "SpDUe5MyFGG97SmdBQMWNPbx2Et9bZt2TCbZm3TujgX",
  "SpKZ28tFzQ13kVjHhsyDEiQfkPGjA7Wmk185KcEDyuh",
  "SpxPqegfoR3QkBN7guRUiU6QfhtTW1CnKSTSSapumqm",
];
const TIP_LAMPORTS = 1_000_000; // 0.001 SOL minimum
const MAX_COMPUTE_UNITS = 1_400_000;

function getRandomTipAddress(): PublicKey {
  return new PublicKey(
    TIP_ADDRESSES[Math.floor(Math.random() * TIP_ADDRESSES.length)]
  );
}

interface ApiInstruction {
  programId: string;
  accounts: { pubkey: string; isSigner: boolean; isWritable: boolean }[];
  data: string; // base64
}

interface JupiterBuild {
  inAmount: string;
  outAmount: string;
  computeBudgetInstructions: ApiInstruction[]; // compute-unit price only
  setupInstructions: ApiInstruction[];
  swapInstruction: ApiInstruction;
  cleanupInstruction: ApiInstruction | null;
  otherInstructions: ApiInstruction[];
  addressesByLookupTableAddress: Record<string, string[]> | null;
  blockhashWithMetadata: { blockhash: number[]; lastValidBlockHeight: number };
}

function toInstruction(ix: ApiInstruction): TransactionInstruction {
  return new TransactionInstruction({
    programId: new PublicKey(ix.programId),
    keys: ix.accounts.map((a) => ({
      pubkey: new PublicKey(a.pubkey),
      isSigner: a.isSigner,
      isWritable: a.isWritable,
    })),
    data: Buffer.from(ix.data, "base64"),
  });
}

function toLookupTables(
  raw: Record<string, string[]> | null
): AddressLookupTableAccount[] {
  return Object.entries(raw ?? {}).map(
    ([key, addresses]) =>
      new AddressLookupTableAccount({
        key: new PublicKey(key),
        state: {
          deactivationSlot: BigInt("18446744073709551615"),
          lastExtendedSlot: 0,
          lastExtendedSlotStartIndex: 0,
          addresses: addresses.map((a) => new PublicKey(a)),
        },
      })
  );
}

async function getJupiterBuild(
  inputMint: string,
  outputMint: string,
  amount: number,
  taker: PublicKey,
  slippageBps: number = 50
): Promise<JupiterBuild> {
  const params = new URLSearchParams({
    inputMint,
    outputMint,
    amount: amount.toString(),
    taker: taker.toBase58(),
    slippageBps: slippageBps.toString(),
    // Fewer route accounts leave room for the tip: a v0 transaction is limited to 1232 bytes
    maxAccounts: "40",
  });

  const response = await fetch(`${JUPITER_API}/build?${params}`, {
    headers: { "x-api-key": JUPITER_API_KEY },
  });
  if (!response.ok) {
    throw new Error(`Jupiter /build failed: ${response.status} ${await response.text()}`);
  }
  return (await response.json()) as JupiterBuild;
}

async function sendViaBlockSprint(transaction: VersionedTransaction): Promise<string> {
  const response = await fetch(BLOCKSPRINT_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "sendTransaction",
      params: [
        Buffer.from(transaction.serialize()).toString("base64"),
        { encoding: "base64", skipPreflight: true, maxRetries: 0 },
      ],
    }),
  });
  const result = await response.json();
  if (result.error) {
    throw new Error(`${result.error.code}: ${result.error.message}`);
  }
  return result.result; // signature
}

async function executeJupiterSwap(
  wallet: Keypair,
  inputMint: string,
  outputMint: string,
  amountLamports: number
) {
  const connection = new Connection(RPC_URL, "confirmed");

  // 1. Get quote and swap instructions
  console.log("Getting swap instructions...");
  const build = await getJupiterBuild(inputMint, outputMint, amountLamports, wallet.publicKey);
  console.log(`Quote: ${build.inAmount} -> ${build.outAmount}`);

  // 2. Add the BlockSprint tip as the last instruction
  const instructions = [
    ...build.setupInstructions.map(toInstruction),
    toInstruction(build.swapInstruction),
    ...(build.cleanupInstruction ? [toInstruction(build.cleanupInstruction)] : []),
    ...build.otherInstructions.map(toInstruction),
    SystemProgram.transfer({
      fromPubkey: wallet.publicKey,
      toPubkey: getRandomTipAddress(),
      lamports: TIP_LAMPORTS,
    }),
  ];

  const lookupTables = toLookupTables(build.addressesByLookupTableAddress);
  const recentBlockhash = bs58.encode(Uint8Array.from(build.blockhashWithMetadata.blockhash));

  const buildTransaction = (computeUnits: number) => {
    const transaction = new VersionedTransaction(
      new TransactionMessage({
        payerKey: wallet.publicKey,
        recentBlockhash,
        instructions: [
          ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits }),
          ...build.computeBudgetInstructions.map(toInstruction), // compute-unit price
          ...instructions,
        ],
      }).compileToV0Message(lookupTables)
    );
    try {
      transaction.serialize(); // throws if the transaction is over 1232 bytes
    } catch {
      throw new Error("Swap transaction too large: request a quote with a lower maxAccounts");
    }
    return transaction;
  };

  // 3. Size the compute-unit limit: simulate on your RPC, add 20%
  const simulation = await connection.simulateTransaction(buildTransaction(MAX_COMPUTE_UNITS), {
    sigVerify: false,
    replaceRecentBlockhash: true,
  });
  if (simulation.value.err) {
    throw new Error(`Simulation failed: ${JSON.stringify(simulation.value.err)}`);
  }
  const computeUnits = Math.min(
    Math.ceil((simulation.value.unitsConsumed ?? MAX_COMPUTE_UNITS) * 1.2),
    MAX_COMPUTE_UNITS
  );

  // 4. Build and sign the final transaction
  const transaction = buildTransaction(computeUnits);
  transaction.sign([wallet]);

  // 5. Send via BlockSprint
  console.log("Sending via BlockSprint...");
  const signature = await sendViaBlockSprint(transaction);

  console.log(`Signature: ${signature}`);
  console.log(`Explorer: https://solscan.io/tx/${signature}`);

  return signature;
}

// Example: Swap SOL to USDC
async function main() {
  const wallet = Keypair.fromSecretKey(bs58.decode(process.env.PAYER_SECRET_BASE58!));

  const SOL_MINT = "So11111111111111111111111111111111111111112";
  const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";

  await executeJupiterSwap(
    wallet,
    SOL_MINT,
    USDC_MINT,
    100_000_000 // 0.1 SOL
  );
}

main().catch(console.error);

Get quote and swap instructions ​

Call Jupiter's /build endpoint. It returns a quote plus the swap as raw instructions, the address lookup tables and a recent blockhash. maxAccounts keeps the route small enough to fit your tip.

Reference: getJupiterBuild in the example code.

Add tip instruction ​

Append a BlockSprint tip (SystemProgram.transfer) as the last instruction. Don't use Jupiter's own tipAmount parameter: it does not tip BlockSprint.

Set the compute-unit limit ​

/build includes the compute-unit price but not the limit. Simulate once with the maximum limit on your own RPC, then use the units consumed plus 20%.

Build and sign ​

Compile a v0 transaction with Jupiter's address lookup tables and sign it with the user's Keypair.

Send via BlockSprint ​

Serialize and send the signed transaction to the BlockSprint endpoint with JSON-RPC sendTransaction (base64). Then check it on your Solana RPC, and re-send the same signed transaction every ~2 seconds until it confirms or the blockhash expires.

Python ​

bash
pip install requests "solders>=0.29"
python
import base64
import os
import random

import requests
from solders.address_lookup_table_account import AddressLookupTableAccount
from solders.compute_budget import set_compute_unit_limit
from solders.hash import Hash
from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair
from solders.message import MessageV0
from solders.pubkey import Pubkey
from solders.signature import Signature
from solders.system_program import TransferParams, transfer
from solders.transaction import VersionedTransaction

BLOCKSPRINT_ENDPOINT = "https://fra.blocksprint.io/api/v1/YOUR_API_KEY"
RPC_URL = "https://api.mainnet-beta.solana.com"  # your Solana RPC
JUPITER_API = "https://api.jup.ag/swap/v2"
JUPITER_API_KEY = os.environ.get("JUPITER_API_KEY", "")

# BlockSprint tip addresses
TIP_ADDRESSES = [
    "Sp1x2AqpQckPLaWnWCJUNg8k6qQexfaEWcSRKf5JcDV",
    "Sp4JHSh9cksfzXbgK7Pq2ovtn8LirLQydaJKTsiNT77",
    "Sp1xMS2cbw83SZDNr4AGqkBYYLjb3LvVnmDSrTMaHkr",
    "SpagSJmnh8E9cGT5Y431xPPaS2c1xLREGGCWN9yDeUf",
    "SpWrza9E63MQuHeGnnfzmtLVCs3pBdjyKPXUABPo9nq",
    "SpXyoqMHJEhsNQ9fHh3DTGGzrGroZ5zfDtJwFvN4kXi",
    "Spv2g3L837bVeHwkyb18LhA5P1bvJbkvaZbxcZXbwC2",
    "SpDUe5MyFGG97SmdBQMWNPbx2Et9bZt2TCbZm3TujgX",
    "SpKZ28tFzQ13kVjHhsyDEiQfkPGjA7Wmk185KcEDyuh",
    "SpxPqegfoR3QkBN7guRUiU6QfhtTW1CnKSTSSapumqm",
]
TIP_LAMPORTS = 1_000_000  # 0.001 SOL minimum
MAX_COMPUTE_UNITS = 1_400_000

def rpc_call(url: str, method: str, params: list):
    response = requests.post(
        url, json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params}, timeout=10
    )
    result = response.json()
    if "error" in result:
        raise Exception(f"RPC Error {result['error']['code']}: {result['error']['message']}")
    return result["result"]

def get_jupiter_build(input_mint: str, output_mint: str, amount: int, taker: Pubkey, slippage_bps: int = 50) -> dict:
    response = requests.get(
        f"{JUPITER_API}/build",
        params={
            "inputMint": input_mint,
            "outputMint": output_mint,
            "amount": str(amount),
            "taker": str(taker),
            "slippageBps": str(slippage_bps),
            # Fewer route accounts leave room for the tip: a v0 transaction is limited to 1232 bytes
            "maxAccounts": "40",
        },
        headers={"x-api-key": JUPITER_API_KEY},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()

def to_instruction(ix: dict) -> Instruction:
    return Instruction(
        Pubkey.from_string(ix["programId"]),
        base64.b64decode(ix["data"]),
        [AccountMeta(Pubkey.from_string(a["pubkey"]), a["isSigner"], a["isWritable"]) for a in ix["accounts"]],
    )

def execute_jupiter_swap(wallet: Keypair, input_mint: str, output_mint: str, amount: int) -> str:
    # 1. Get quote and swap instructions
    build = get_jupiter_build(input_mint, output_mint, amount, wallet.pubkey())
    print(f"Quote: {build['inAmount']} -> {build['outAmount']}")

    # 2. Add the BlockSprint tip as the last instruction
    instructions = [to_instruction(ix) for ix in build["setupInstructions"]]
    instructions.append(to_instruction(build["swapInstruction"]))
    if build.get("cleanupInstruction"):
        instructions.append(to_instruction(build["cleanupInstruction"]))
    instructions += [to_instruction(ix) for ix in build["otherInstructions"]]
    instructions.append(
        transfer(
            TransferParams(
                from_pubkey=wallet.pubkey(),
                to_pubkey=Pubkey.from_string(random.choice(TIP_ADDRESSES)),
                lamports=TIP_LAMPORTS,
            )
        )
    )

    lookup_tables = [
        AddressLookupTableAccount(Pubkey.from_string(key), [Pubkey.from_string(a) for a in addresses])
        for key, addresses in (build.get("addressesByLookupTableAddress") or {}).items()
    ]
    blockhash = Hash(bytes(build["blockhashWithMetadata"]["blockhash"]))
    price_instructions = [to_instruction(ix) for ix in build["computeBudgetInstructions"]]

    def compile_message(compute_units: int) -> MessageV0:
        return MessageV0.try_compile(
            wallet.pubkey(),
            [set_compute_unit_limit(compute_units), *price_instructions, *instructions],
            lookup_tables,
            blockhash,
        )

    # 3. Size the compute-unit limit: simulate on your RPC, add 20%
    unsigned = VersionedTransaction.populate(compile_message(MAX_COMPUTE_UNITS), [Signature.default()])
    if len(bytes(unsigned)) > 1232:
        raise Exception("Swap transaction too large: request a quote with a lower maxAccounts")
    simulation = rpc_call(
        RPC_URL,
        "simulateTransaction",
        [
            base64.b64encode(bytes(unsigned)).decode(),
            {"encoding": "base64", "sigVerify": False, "replaceRecentBlockhash": True},
        ],
    )["value"]
    if simulation["err"]:
        raise Exception(f"Simulation failed: {simulation['err']}")
    compute_units = min(int((simulation.get("unitsConsumed") or MAX_COMPUTE_UNITS) * 1.2), MAX_COMPUTE_UNITS)

    # 4. Build and sign the final transaction
    transaction = VersionedTransaction(compile_message(compute_units), [wallet])

    # 5. Send via BlockSprint
    signature = rpc_call(
        BLOCKSPRINT_ENDPOINT,
        "sendTransaction",
        [
            base64.b64encode(bytes(transaction)).decode(),
            {"encoding": "base64", "skipPreflight": True, "maxRetries": 0},
        ],
    )
    print(f"Signature: {signature}")
    return signature

# Example: swap 0.1 SOL to USDC
wallet = Keypair.from_base58_string(os.environ["PAYER_SECRET_BASE58"])  # your funded wallet
SOL_MINT = "So11111111111111111111111111111111111111112"
USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"

signature = execute_jupiter_swap(wallet, SOL_MINT, USDC_MINT, 100_000_000)

Notes ​

INFO

  • Jupiter's /build includes the compute-unit price but not the limit — simulate as above, or set your own limit.
  • Add the BlockSprint tip yourself — Jupiter's tipAmount option does not tip BlockSprint.
  • The tip address must be in the transaction's own account list, not loaded through an address lookup table.
  • Use skipPreflight: true.
  • Slippage — Set appropriate slippage (50 bps = 0.5%).