solana-jupiter-swap — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited solana-jupiter-swap (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Jupiter is the default DEX aggregator on Solana: it routes a single swap across dozens of DEXes (Orca, Raydium, Meteora, Phoenix, etc.) and RFQ market makers to give the user the best price. As of May 2026 the stable HTTP surface is the Swap V1 API at https://api.jup.ag/swap/v1 (requires a free API key from portal.jup.ag) or the keyless mirror at https://lite-api.jup.ag/swap/v1 for low-volume use. A newer Swap V2 unified router (/swap/v2/order, /build, /execute) launched in March 2026 and is the recommended target for new production systems, but V1 is still the most stable, broadly-supported path and what almost every example online uses. This skill covers V1, with a note on the V2 migration.
Skip this skill if the user wants an on-chain CPI call into Jupiter (use Jupiter's IDL + Anchor instead) or only wants a price feed (use /price or Pyth).
node -v should report ≥ 20.xnpm i @solana/web3.js@^1.98.4 @jup-ag/api@^6.0.48 (or pin exact versions)https://api.mainnet-beta.solana.com is rate-limited and will get you 429s — use a staked-connection RPC (Helius, Triton, QuickNode, Shyft) for anything past hello-world.~0.05 SOL minimum for rent + fees on top of the swap amount)api.jup.ag: a free API key from https://portal.jup.ag. For lite-api.jup.ag: nothing.GET /swap/v1/quoteQuote endpoint takes inputMint, outputMint, amount (atomic units of the input mint), and slippageBps (1% = 100 bps).
const QUOTE_HOST = "https://lite-api.jup.ag"; // or "https://api.jup.ag" with x-api-key
const SOL = "So11111111111111111111111111111111111111112";
const USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const qs = new URLSearchParams({
inputMint: SOL,
outputMint: USDC,
amount: String(0.1 * 1e9), // 0.1 SOL in lamports
slippageBps: "50", // 0.5%
});
const quote = await fetch(`${QUOTE_HOST}/swap/v1/quote?${qs}`).then(r => r.json());Fields you actually use:
inAmount / outAmount — atomic units, both as decimal stringsotherAmountThreshold — the floor you'll receive after slippageBps is applied (this is the on-chain guard)priceImpactPct — string like "0.0012" (0.12%). Bail above ~1–2% on a normal pair.routePlan — array of hops. routePlan[i].swapInfo.label tells you which DEX ("Orca (Whirlpools)", "Raydium CLMM", …).contextSlot — slot the quote was priced against. If you delay >~10s before posting, refetch.POST /swap/v1/swapSend the entire `quote` object back as quoteResponse. Don't try to reconstruct it.
const swapBody = {
quoteResponse: quote,
userPublicKey: wallet.publicKey.toBase58(),
wrapAndUnwrapSol: true, // creates/closes the wSOL ATA for you
dynamicComputeUnitLimit: true, // simulates to size CU exactly
dynamicSlippage: true, // lets Jupiter pick slippage at build time (V1 feature)
prioritizationFeeLamports: {
priorityLevelWithMaxLamports: {
maxLamports: 10_000_000, // 0.01 SOL cap — adjust per asset
priorityLevel: "veryHigh",
},
},
};
const { swapTransaction } = await fetch(`${QUOTE_HOST}/swap/v1/swap`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(swapBody),
}).then(r => r.json());Jupiter always returns a v0 VersionedTransaction (it relies on address lookup tables — there is no legacy path that fits the route). Don't try to convert it to a legacy Transaction.
import { VersionedTransaction } from "@solana/web3.js";
const tx = VersionedTransaction.deserialize(Buffer.from(swapTransaction, "base64"));
tx.sign([wallet]);
const sig = await connection.sendRawTransaction(tx.serialize(), {
skipPreflight: true, // we already simulated via dynamicComputeUnitLimit
maxRetries: 0, // we'll handle retries ourselves
});Use the same blockhash that's already inside the tx — getLatestBlockhash here is just for the confirmation strategy.
const latest = await connection.getLatestBlockhash("confirmed");
const status = await connection.confirmTransaction(
{ signature: sig, ...latest },
"confirmed",
);
if (status.value.err) throw new Error(`Swap failed: ${JSON.stringify(status.value.err)}`);
console.log(`https://solscan.io/tx/${sig}`);Two patterns:
prioritizationFeeLamports: { priorityLevelWithMaxLamports: { maxLamports, priorityLevel: "veryHigh" } }. Jupiter samples the fee market via Triton and picks. Cap at something sane so a fee spike doesn't drain you.prioritizationFeeLamports: 5_000_000 (flat lamports) or set computeUnitPriceMicroLamports yourself. Only do this if you're running your own fee oracle.dynamicSlippage: true lets Jupiter set otherAmountThreshold at build time based on observed volatility for that pair. The slippageBps in your quote becomes a cap. This is almost always what you want for production. Disable it if you're arb-ing and want exact behavior.
Always verify the mint addresses you're swapping. Use the Jupiter strict token list at https://tokens.jup.ag/tokens?tags=verified (or lite-api.jup.ag/tokens/v1/...). Passing a random mint that looks like USDC but isn't will route — and you'll receive a worthless token.
slippageBps (50 → 100 → 300 for thin tokens), turn on dynamicSlippage: true, or refetch the quote right before sending.swapTransaction too long, throw it away and call /swap again. Don't try to swap out the blockhash on the v0 tx — re-fetch.wrapAndUnwrapSol: false, you must create the wSOL ATA yourself before swapping out of SOL. Just leave it true unless you're aggregating multiple swaps and managing wSOL across them.VersionedTransaction.deserialize, not Transaction.from.api.mainnet-beta.solana.com will silently drop your sendRawTransaction under load and you'll think the swap "didn't go through" when it did. Use a staked-connection RPC (Helius, Triton, QuickNode). Verify by querying the signature.amount is atomic units of `inputMint`, not human units. 1 USDC is 1_000_000 (6 decimals), 1 SOL is 1_000_000_000 (9 decimals). Multiply by 10 ** mint.decimals, never assume.https://api.jup.ag • API key portal: https://portal.jup.aghttps://lite-api.jup.ag@jup-ag/api (typed client): https://www.npmjs.com/package/@jup-ag/apiSee scripts/quote-only.ts for a read-only quote and scripts/swap.ts for a full executed swap.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.