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.
“Multiply” (often called “looping” or “leveraging”) is the process of using borrowed capital to increase your exposure to an asset. Use Jupiter Lite API for swap quotes and instructions. For example, to multiply your SOL exposure using USDC debt:
  1. You have 1 SOL (collateral).
  2. Flashloan 100 USDC from Jupiter Lend (debt asset).
  3. Swap 100 USDC → SOL using Jupiter Lite API.
  4. Deposit your SOL + swapped SOL into the vault and borrow 100 USDC.
  5. Pay back the flashloan with the borrowed USDC.
All of this happens atomically in a single Solana transaction.

Multiply

1

Import Dependencies

Import Solana web3, Jupiter Lend SDKs, and BN for amounts.
npm install @solana/web3.js bn.js @jup-ag/lend @jup-ag/lend-read
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 } from "@jup-ag/lend/borrow";
import fs from "fs";
import path from "path";
2

Load Keypair and Initialise Connection

Load the signer and create the RPC connection.
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 initialCollateral = new BN(1_000_000_000); // 1 SOL @ 9 decimals
3

Get Flashloan Instructions

Use getFlashBorrowIx and getFlashPaybackIx to flashloan the debt asset (e.g. USDC).
const borrowAmount = new BN(100_000_000); // 100 USDC @ 6 decimals
const flashBorrowIx = await getFlashBorrowIx({ connection, signer, asset: usdcMint, amount: borrowAmount });
const flashPaybackIx = await getFlashPaybackIx({ connection, signer, asset: usdcMint, amount: borrowAmount });
4

Get Quote and Swap Instructions (Jupiter Lite API)

Fetch a quote from Jupiter Lite API, then POST to swap-instructions to get the swap instruction. Build your own transaction, do not use a pre-built swap transaction.
const LITE_API = "https://lite-api.jup.ag/swap/v1";
const quoteResponse = await fetch(
  `${LITE_API}/quote?inputMint=${usdcMint.toBase58()}&outputMint=${solMint.toBase58()}&amount=${borrowAmount.toString()}&slippageBps=100`
).then((r) => r.json());
if (quoteResponse.error || !quoteResponse.routePlan) throw new Error(quoteResponse.error ?? "No quote");

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());
if (swapRes.error) throw new Error(swapRes.error ?? "No swap instructions");
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"),
  });
}
5

Get Vault Operations

Compute total collateral (initial + swapped) and get operate instructions.
const swapOutputAmount = new BN(quoteResponse.outAmount);
const supplyAmount = initialCollateral.add(swapOutputAmount);

const { ixs: operateIxs, addressLookupTableAccounts } = await getOperateIx({
  vaultId: 1,
  positionId: 0,
  colAmount: supplyAmount,
  debtAmount: borrowAmount,
  connection,
  signer,
});
6

Assemble and Execute the Transaction

Fetch address lookup tables for the swap, then create a versioned transaction with instructions in order.Order: Flashloan Borrow → Swap (USDC→SOL) → Vault Operate (deposit + borrow) → 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, swapIx, ...operateIxs, flashPaybackIx],
}).compileToV0Message(allAlts);

const tx = new VersionedTransaction(message);
tx.sign([userKeypair]);
const signature = await connection.sendTransaction(tx, { skipPreflight: false, maxRetries: 3, preflightCommitment: "confirmed" });
await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, "confirmed");
console.log("Multiply successful:", signature);

Full code example

Ensure supply and borrow token ATAs exist for the user before building the transaction. Use createAssociatedTokenAccountIdempotentInstruction from @solana/spl-token if needed.
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 } from "@jup-ag/lend/borrow";
import fs from "fs";
import path from "path";

const KEYPAIR_PATH = "/path/to/your/keypair.json";
const RPC_URL = "https://api.mainnet-beta.solana.com";
const LITE_API = "https://lite-api.jup.ag/swap/v1";
const VAULT_ID = 1;
const SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");

const INITIAL_COLLATERAL = new BN(1_000_000_000); // 1 SOL @ 9 decimals
const BORROW_USDC = new BN(100_000_000); // 100 USDC @ 6 decimals

function loadKeypair(p: string): Keypair {
  return Keypair.fromSecretKey(new Uint8Array(JSON.parse(fs.readFileSync(path.resolve(p), "utf8"))));
}

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"),
  });
}

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[]);
}

async function main() {
  const userKeypair = loadKeypair(KEYPAIR_PATH);
  const connection = new Connection(RPC_URL, { commitment: "confirmed" });
  const signer = userKeypair.publicKey;

  const flashParams = { connection, signer, asset: USDC_MINT, amount: BORROW_USDC };
  const [flashBorrowIx, flashPaybackIx, quoteResponse, latestBlockhash] = await Promise.all([
    getFlashBorrowIx(flashParams),
    getFlashPaybackIx(flashParams),
    fetch(
      `${LITE_API}/quote?inputMint=${USDC_MINT.toBase58()}&outputMint=${SOL_MINT.toBase58()}&amount=${BORROW_USDC.toString()}&slippageBps=100`
    ).then((r) => r.json()),
    connection.getLatestBlockhash("confirmed"),
  ]);

  if (quoteResponse.error || !quoteResponse.routePlan) {
    throw new Error(quoteResponse.error ?? "No quote from Jupiter Lite");
  }

  const swapOutputAmount = new BN(quoteResponse.outAmount);
  const supplyAmount = INITIAL_COLLATERAL.add(swapOutputAmount);

  const { ixs: operateIxs, addressLookupTableAccounts } = await getOperateIx({
    vaultId: VAULT_ID,
    positionId: 0,
    colAmount: supplyAmount,
    debtAmount: BORROW_USDC,
    connection,
    signer,
  });

  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());

  if (swapRes.error) {
    throw new Error(swapRes.error ?? "No swap instructions from Jupiter Lite");
  }

  const swapIx = jupIxToTransactionInstruction(swapRes.swapInstruction);
  const swapAlts = await getAddressLookupTableAccounts(connection, swapRes.addressLookupTableAddresses ?? []);
  const allAlts = [...(addressLookupTableAccounts ?? []), ...swapAlts];

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

  const tx = new VersionedTransaction(message);
  tx.sign([userKeypair]);

  const signature = await connection.sendTransaction(tx, {
    skipPreflight: false,
    maxRetries: 3,
    preflightCommitment: "confirmed",
  });
  await connection.confirmTransaction(
    { signature, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight },
    "confirmed"
  );

  console.log("Multiply successful:", signature);
}

main().catch(console.error);