Skip to main content

Swapping Tokens with Jupiter using Swig

This guide demonstrates how to perform token swaps on Solana using Jupiter Protocol with a Swig wallet. We’ll create a simple example that swaps SOL for USDC using Jupiter’s optimized routing.

Prerequisites

Before you begin, make sure you have the following installed:
  • Bun (latest version)
  • A Solana wallet with some SOL for transaction fees
  • A keypair file for the root user

Setup

First, clone the swig-ts repository and navigate to the swap example:
git clone https://github.com/anagrambuild/swig-ts.git
cd swig-ts/examples/swap
bun install
The example already includes all necessary dependencies:
  • @jup-ag/api for Jupiter API integration
  • @swig-wallet/classic for Swig wallet functionality
  • @solana/web3.js for Solana interactions
  • chalk for colorful console output

Understanding the Code

The example demonstrates several key concepts:
  1. Setting up Swig with proper compute budget handling
  2. Managing token accounts (ATAs)
  3. Using Jupiter’s API for quotes and swap instructions
  4. Properly separating and signing instructions
Let’s break down the main components:

1. Setting Up Token Accounts

This isnt technically necessary, but we leave it here for your benefit. Jupiter will create the token accounts for you if they don’t exisst in the setup instructions that are returned from the Jupiter API.
Before swapping, we need to ensure the Swig account has the necessary token accounts:
// Check if Swig has USDC ATA
const swigUsdcAta = await getAssociatedTokenAddress(usdcMint, swigAddress, true);
let createAtaIx: TransactionInstruction | null = null;

try {
  await getAccount(connection, swigUsdcAta);
  console.log("✓ USDC token account exists");
} catch (error) {
  console.log("⚠️  Creating USDC token account for Swig...");
  createAtaIx = createAssociatedTokenAccountInstruction(
    rootUser.publicKey,
    swigUsdcAta,
    swigAddress,
    usdcMint
  );
  // Create ATA in a separate transaction
  const createAtaTx = new Transaction().add(createAtaIx);
  await sendAndConfirmTransaction(connection, createAtaTx, [rootUser]);
}

2. Getting Jupiter Quote

const jupiterQuoteApi = createJupiterApiClient();

const quoteResponse = await jupiterQuoteApi.quoteGet({
  inputMint: "So11111111111111111111111111111111111111112", // SOL
  outputMint: usdcMint.toBase58(), // USDC
  amount: transferAmount,
  slippageBps: 50, // 0.5%
  maxAccounts: 64, // Important: Account for Swig's overhead
});

3. Handling Compute Budget and Instructions

The most critical part is properly separating and handling instructions:
// Outer instructions (not signed by Swig)
const outerInstructions: TransactionInstruction[] = [
  ComputeBudgetProgram.setComputeUnitLimit({
    units: 150_000,
  }),
  ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: 50,
  }),
];

// Instructions that need Swig signatures
const swigInstructions: TransactionInstruction[] = [
  ...(swapInstructions.setupInstructions || []).map(toTransactionInstruction),
  toTransactionInstruction(swapInstructions.swapInstruction),
];

// Sign each instruction individually
const signIxs = await Promise.all(
  swigInstructions.map(async (instruction) => {
    return await signInstruction(
      rootRole,
      rootUser.publicKey,
      [instruction]
    );
  })
);

4. Creating and Sending the Transaction

const messageV0 = new TransactionMessage({
  payerKey: rootUser.publicKey,
  recentBlockhash: blockhash,
  instructions: [...outerInstructions, ...signIxs],
}).compileToV0Message(addressLookupTableAccounts);

const transaction = new VersionedTransaction(messageV0);
transaction.sign([rootUser]);

const signature = await connection.sendTransaction(transaction, {
  skipPreflight: true,
  preflightCommitment: 'confirmed'
});

Important Considerations

  1. Compute Budget Management:
    • Set appropriate compute unit limit and price
    • These instructions must be outside of Swig-signed instructions
  2. Account Management:
    • Create ATAs in separate transactions before swapping
    • Account for Swig’s overhead when setting maxAccounts
  3. Transaction Structure:
    • Properly separate outer instructions from Swig-signed instructions
    • Sign each Swig instruction individually
    • Use versioned transactions with address lookup tables
  4. Error Handling:
    • Verify ATA existence and creation
    • Check transaction confirmation status
    • Verify token balances after swap

Running the Example

  1. Prepare your keypair file
  2. Run the example:
bun run index.ts path/to/keypair.json [optional-swig-address]
The example will:
  • Create or use an existing Swig wallet
  • Set up necessary token accounts
  • Get the best swap route from Jupiter
  • Execute the swap with proper instruction handling

Production Considerations

  1. Security:
    • Securely manage keypairs and private keys
    • Implement proper error recovery mechanisms
    • Add transaction timeout handling
  2. Performance:
    • Adjust compute budget based on swap complexity
    • Consider using RPC endpoints with higher rate limits
    • Implement proper retry mechanisms
  3. User Experience:
    • Add progress indicators for multi-step operations
    • Implement proper balance checking and updates
    • Add slippage protection options
For more information, check out: