Skip to main content
These flows use many instructions. If you hit compute limits, add ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }) (or higher) as the first instruction in your transaction.
Close a borrow position when you hold only the collateral token (e.g. SOL), not the debt token (e.g. USDC). Use Jupiter Lite API for swap quotes and instructions. Flow:
  1. Flashloan debt asset (USDC) from Jupiter Lend
  2. Repay full debt and withdraw full collateral
  3. Swap collateral → debt (enough to cover the flashloan)
  4. Pay back the flashloan
All of this happens atomically in one transaction.

Repay with Collateral and Max Withdraw

1

Import Dependencies

import {
  AddressLookupTableAccount,
  Connection,
  Keypair,
  PublicKey,
  TransactionInstruction,
  TransactionMessage,
  VersionedTransaction,
} from "@solana/web3.js";
import BN from "bn.js";
import { getFlashBorrowIx, getFlashPaybackIx } from "@jup-ag/lend/flashloan";
import { getOperateIx, MAX_REPAY_AMOUNT, MAX_WITHDRAW_AMOUNT } from "@jup-ag/lend/borrow";
import fs from "fs";
import path from "path";
2

Set Parameters

Hardcode VAULT_ID, positionId (NFT ID of your position), debtAmount, and collateralSupply (collateral to swap, in base units).
const KEYPAIR_PATH = "/path/to/your/keypair.json";
const RPC_URL = "https://api.mainnet-beta.solana.com";
function loadKeypair(p: string): Keypair {
  return Keypair.fromSecretKey(new Uint8Array(JSON.parse(fs.readFileSync(path.resolve(p), "utf8"))));
}
const userKeypair = loadKeypair(KEYPAIR_PATH);
const connection = new Connection(RPC_URL, { commitment: "confirmed" });
const signer = userKeypair.publicKey;

const solMint = new PublicKey("So11111111111111111111111111111111111111112");
const usdcMint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");

const VAULT_ID = 1;
const positionId = 7360; // NFT ID of your position
const debtAmount = new BN(20_000_100); // debt to repay (e.g. USDC 6 decimals)
const collateralSupply = new BN(1_000_000_059); // collateral to swap (e.g. SOL lamports)
3

Get Flashloan Instructions

Flashloan the debt asset (e.g. USDC).
const flashParams = { connection, signer, asset: usdcMint, amount: debtAmount };
const flashBorrowIx = await getFlashBorrowIx(flashParams);
const flashPaybackIx = await getFlashPaybackIx(flashParams);
4

Get Vault Operations (Repay + Withdraw)

Use getOperateIx with MAX_REPAY_AMOUNT and MAX_WITHDRAW_AMOUNT to fully close the position.
const { ixs: operateIxs, addressLookupTableAccounts } = await getOperateIx({
  vaultId: VAULT_ID,
  positionId,
  colAmount: MAX_WITHDRAW_AMOUNT,
  debtAmount: MAX_REPAY_AMOUNT,
  connection,
  signer,
});
5

Get Quote and Swap Instructions (Jupiter Lite API)

Fetch a quote (swap collateral → debt). Ensure the swap output yields at least debtAmount of the debt token (account for slippage).
const LITE_API = "https://lite-api.jup.ag/swap/v1";
const quoteResponse = await fetch(
  `${LITE_API}/quote?inputMint=${solMint.toBase58()}&outputMint=${usdcMint.toBase58()}&amount=${collateralSupply.toString()}&slippageBps=100`
).then((r) => r.json());

const swapRes = await fetch(`${LITE_API}/swap-instructions`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ quoteResponse, userPublicKey: signer.toBase58() }),
}).then((r) => r.json());
const swapIx = jupIxToTransactionInstruction(swapRes.swapInstruction);
The jupIxToTransactionInstruction helper converts the JSON instruction from the API into a Solana TransactionInstruction:
function jupIxToTransactionInstruction(ix: {
  programId: string;
  accounts: Array<{ pubkey: string; isSigner: boolean; isWritable: boolean }>;
  data: string;
}): 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"),
  });
}
6

Assemble and Execute the Transaction

Order: Flashloan Borrow (debt) → Repay + Withdraw → Swap (collateral → debt) → Flashloan PaybackThe getAddressLookupTableAccounts helper resolves lookup table addresses into account objects:
async function getAddressLookupTableAccounts(
  connection: Connection,
  keys: string[]
): Promise<AddressLookupTableAccount[]> {
  if (!keys?.length) return [];
  const infos = await connection.getMultipleAccountsInfo(keys.map((k) => new PublicKey(k)));
  return infos.reduce((acc, info, i) => {
    if (info) {
      acc.push(new AddressLookupTableAccount({
        key: new PublicKey(keys[i]),
        state: AddressLookupTableAccount.deserialize(info.data),
      }));
    }
    return acc;
  }, [] as AddressLookupTableAccount[]);
}
const swapAlts = await getAddressLookupTableAccounts(connection, swapRes.addressLookupTableAddresses ?? []);
const allAlts = [...(addressLookupTableAccounts ?? []), ...swapAlts];

const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
const message = new TransactionMessage({
  payerKey: signer,
  recentBlockhash: blockhash,
  instructions: [flashBorrowIx, ...operateIxs, swapIx, flashPaybackIx],
}).compileToV0Message(allAlts);

const tx = new VersionedTransaction(message);
tx.sign([userKeypair]);
const signature = await connection.sendTransaction(tx, { skipPreflight: false, maxRetries: 3 });
console.log("Repay & Withdraw successful:", signature);