diff --git a/example/index.ts b/example/index.ts index faf54e8..cf55a81 100644 --- a/example/index.ts +++ b/example/index.ts @@ -3,6 +3,7 @@ import { QuoteResponse, SwapResponse, createJupiterApiClient, + JupiterQuoteApi, } from "../src/index"; import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js"; import { Wallet } from "@project-serum/anchor"; @@ -13,56 +14,73 @@ import dotenv from "dotenv"; dotenv.config(); -// If you have problem landing transactions, read this: https://dev.jup.ag/docs/swap-api/send-swap-transaction#how-jupiter-estimates-priority-fee +// --- CRITICAL ENVIRONMENT CHECKS --- +const RPC_URL = process.env.RPC_URL; +if (!RPC_URL) { + throw new Error("RPC_URL environment variable is not set. Please use a reliable endpoint like Helius or Triton."); +} -// Make sure that you are using your own RPC endpoint. -// Helius and Triton have staked SOL and they can usually land transactions better. -const connection = new Connection( - "https://api.mainnet-beta.solana.com" // We only support mainnet. -); +const PRIVATE_KEY = process.env.PRIVATE_KEY; +if (!PRIVATE_KEY) { + throw new Error("PRIVATE_KEY environment variable is not set. Cannot perform a swap."); +} +// --- END CRITICAL CHECKS --- -// Get API key from environment variables +// Establish connection to Solana Mainnet. +const connection = new Connection(RPC_URL, { commitment: "confirmed" }); + +// Get API key from environment variables (optional) const apiKey = process.env.API_KEY; // Create Jupiter API client with API key if available -const jupiterQuoteApi = createJupiterApiClient( +const jupiterQuoteApi: JupiterQuoteApi = createJupiterApiClient( apiKey ? { apiKey } : undefined ); -// Log which API endpoint is being used +// Log which API endpoint is being used for clarity console.log("Using API endpoint:", apiKey - ? "https://api.jup.ag/swap/v1 (with API key)" - : "https://lite-api.jup.ag/swap/v1 (free tier)"); + ? "https://api.jup.ag/swap/v1 (Enterprise API)" + : "https://lite-api.jup.ag/swap/v1 (Free Tier)"); -async function getQuote() { +/** + * Retrieves a swap quote for a fixed SOL to USDC amount. + */ +async function getQuote(): Promise { const params: QuoteGetRequest = { - inputMint: "So11111111111111111111111111111111111111112", // SOL - outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC - amount: 100000000, // 0.1 SOL - slippageBps: 100, // 1% + inputMint: "So11111111111111111111111111111111111111112", // SOL Mint Address + outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC Mint Address + amount: 100_000_000, // 0.1 SOL (100,000,000 Lamports) + slippageBps: 100, // 1% Slippage + onlyDirectRoutes: false, + asLegacyTransaction: false, // Always prefer Versioned Transactions }; - // get quote + // Get quote const quote = await jupiterQuoteApi.quoteGet(params); if (!quote) { - throw new Error("unable to quote"); + throw new Error("Unable to get quote from Jupiter API."); } return quote; } -async function getSwapResponse(wallet: Wallet, quote: QuoteResponse) { - // Get serialized transaction +/** + * Requests the serialized transaction from Jupiter for the given quote. + */ +async function getSwapResponse(wallet: Wallet, quote: QuoteResponse): Promise { + // Get serialized transaction with priority fee optimization const swapResponse = await jupiterQuoteApi.swapPost({ swapRequest: { quoteResponse: quote, userPublicKey: wallet.publicKey.toBase58(), - dynamicComputeUnitLimit: true, - dynamicSlippage: true, + dynamicComputeUnitLimit: true, // Recommended: Adjusts CU limit dynamically + dynamicSlippage: true, // Recommended: Adjusts slippage based on market + + // Setting explicit high priority fee for quick transaction landing prioritizationFeeLamports: { priorityLevelWithMaxLamports: { - maxLamports: 10000000, - priorityLevel: "veryHigh", // If you want to land transaction fast, set this to use `veryHigh`. You will pay on average higher priority fee. + maxLamports: 10_000_000, // Max 0.01 SOL per transaction for priority fee + priorityLevel: "veryHigh", }, }, }, @@ -70,33 +88,51 @@ async function getSwapResponse(wallet: Wallet, quote: QuoteResponse) { return swapResponse; } +/** + * Executes the quote flow and prints the result. + */ async function flowQuote() { + console.log("--- Starting Quote Flow (0.1 SOL -> USDC) ---"); const quote = await getQuote(); console.dir(quote, { depth: null }); + console.log("-------------------------------------------"); } +/** + * Executes the full quote, swap, simulation, signing, and sending flow. + */ async function flowQuoteAndSwap() { + // Decode the wallet keypair securely const wallet = new Wallet( - Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY || "")) + Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY)) ); - console.log("Wallet:", wallet.publicKey.toBase58()); + const userPubKey = wallet.publicKey.toBase58(); + console.log("Wallet Public Key:", userPubKey); + console.log("-------------------------------------------"); const quote = await getQuote(); - console.dir(quote, { depth: null }); + console.log("Quote received successfully."); + // console.dir(quote, { depth: null }); + const swapResponse = await getSwapResponse(wallet, quote); - console.dir(swapResponse, { depth: null }); + console.log("Swap transaction received from Jupiter."); + // console.dir(swapResponse, { depth: null }); - // Serialize the transaction + // 1. Deserialize the transaction const swapTransactionBuf = Uint8Array.from( Buffer.from(swapResponse.swapTransaction, "base64") ); + // Transaction is expected to be VersionedTransaction const transaction = VersionedTransaction.deserialize(swapTransactionBuf); + console.log("Transaction deserialized."); + // - // Sign the transaction + // 2. Sign the transaction transaction.sign([wallet.payer]); const signature = getSignature(transaction); + console.log("Transaction signed. Signature:", signature); - // We first simulate whether the transaction would be successful + // 3. Simulate the transaction before sending (Best Practice) const { value: simulatedTransactionResponse } = await connection.simulateTransaction(transaction, { replaceRecentBlockhash: true, @@ -105,13 +141,14 @@ async function flowQuoteAndSwap() { const { err, logs } = simulatedTransactionResponse; if (err) { - // Simulation error, we can check the logs for more details - // If you are getting an invalid account error, make sure that you have the input mint account to actually swap from. - console.error("Simulation Error:"); - console.error({ err, logs }); + console.error("!!! FATAL: Transaction Simulation Error !!!"); + console.error("Simulation Error Details:", { err, logs }); + console.error("Reason: Ensure your wallet has sufficient SOL for fees and the input token (0.1 SOL) in the correct associated token account."); return; } + console.log("Transaction simulation successful (no runtime errors)."); + // 4. Send and confirm the transaction const serializedTransaction = Buffer.from(transaction.serialize()); const blockhash = transaction.message.recentBlockhash; @@ -124,34 +161,50 @@ async function flowQuoteAndSwap() { }, }); - // If we are not getting a response back, the transaction has not confirmed. + // 5. Final Confirmation Check if (!transactionResponse) { - console.error("Transaction not confirmed"); + console.error("!!! FATAL: Transaction not confirmed after waiting !!!"); return; } if (transactionResponse.meta?.err) { - console.error(transactionResponse.meta?.err); + console.error("!!! FATAL: Transaction confirmed with an error !!!"); + console.error(transactionResponse.meta.err); + return; } - console.log(`https://solscan.io/tx/${signature}`); + console.log(`\n✅ Swap Successful!`); + console.log(`Transaction Explorer Link: https://solscan.io/tx/${signature}`); } +/** + * Main entry point to handle environment variable flow control. + */ export async function main() { - switch (process.env.FLOW) { - case "quote": { - await flowQuote(); - break; - } - - case "quoteAndSwap": { - await flowQuoteAndSwap(); - break; - } + const flow = process.env.FLOW; - default: { - console.error("Please set the FLOW environment"); + if (!flow) { + console.error("Please set the FLOW environment variable (e.g., FLOW=quote or FLOW=quoteAndSwap)"); + return; + } + + try { + switch (flow) { + case "quote": { + await flowQuote(); + break; + } + case "quoteAndSwap": { + await flowQuoteAndSwap(); + break; + } + default: { + console.error(`Invalid FLOW environment variable: ${flow}. Must be 'quote' or 'quoteAndSwap'.`); + } } + } catch (error) { + console.error("\n--- Application Error ---"); + console.error(error); } }