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
npm install @solana/web3.js@1 bs58import {
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
pip install requests "solders>=0.29"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
/buildincludes the compute-unit price but not the limit — simulate as above, or set your own limit. - Add the BlockSprint tip yourself — Jupiter's
tipAmountoption 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%).
