Skip to main content

Documentation Index

Fetch the complete documentation index at: https://kamino.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

Deleveraging a multiply position reduces your leverage by selling collateral to repay debt in a single atomic transaction. This operation uses flash loans and KSwap to convert TSLAx collateral into USDC to repay debt without requiring external funds.

Multiply positions use MultiplyObligation which derives a unique PDA (Program Derived Address) based on your wallet, market, collateral token (TSLAx), and debt token (USDC). Each collateral/debt pair creates a separate obligation address, allowing you to have multiple multiply positions simultaneously.

Deleverage Multiply Position with xStocks

Reduce leverage on a TSLAx multiply position by selling collateral to repay USDC debt.
1

Import Dependencies

Import the required packages for Solana RPC communication, Kamino SDK operations, KSwap routing, Scope oracle, and transaction building.
import {
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  address,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstructions,
  signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory,
  getSignatureFromTransaction,
  none,
  compressTransactionMessageUsingAddressLookupTables,
} from '@solana/kit';
import type { Address } from '@solana/kit';
import {
  KaminoMarket,
  MultiplyObligation,
  PROGRAM_ID,
  parseKeypairFile,
  getWithdrawWithLeverageIxs,
  getUserLutAddressAndSetupIxs,
  getScopeRefreshIxForObligationAndReserves,
  getComputeBudgetAndPriorityFeeIxs,
  lamportsToNumberDecimal,
  simulateTx,
  DEFAULT_RECENT_SLOT_DURATION_MS,
} from '@kamino-finance/klend-sdk';
import { KswapSdk } from '@kamino-finance/kswap-sdk';
import { Scope } from '@kamino-finance/scope-sdk';
import Decimal from 'decimal.js';
import { getKswapQuoter, getKswapSwapper } from './kswap_utils.js';
import { fetchAllAddressLookupTable } from '@solana-program/address-lookup-table';
2

Load Configuration and Initialize SDKs

Load the keypair and initialize RPC connections, market, Scope oracle, and KSwap SDK.
const KEYPAIR_FILE = '/path/to/your/keypair.json';
const CDN_ENDPOINT = 'https://cdn.kamino.finance';

const signer = await parseKeypairFile(KEYPAIR_FILE);

const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com');

const marketPubkey = address('5wJeMrUYECGq41fxRESKALVcHnNX26TAWy4W98yULsua'); // xStocks Market
const debtTokenMint = address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); // USDC
const XSTOCKS_MARKET_LUT = address('8ofreL6hKfEet1DnhHVGvCTnSdz4pg85PpbuCUHnEcKm'); // xStocks Market LUT

const market = await KaminoMarket.load(rpc, marketPubkey, DEFAULT_RECENT_SLOT_DURATION_MS);
const scope = new Scope('mainnet-beta', rpc);
const kswapSdk = new KswapSdk('https://api.kamino.finance/kswap', rpc, rpcSubscriptions);
3

Find TSLAx Reserve and Fetch Multiply LUTs

Dynamically discover the TSLAx reserve and fetch multiply-specific lookup tables from Kamino’s CDN.
const tslaReserve = Array.from(market!.reserves.values()).find((reserve) => reserve.symbol === 'TSLAx');
if (!tslaReserve) {
  console.log('TSLAx reserve not found in xStocks market');
}
const collTokenMint = tslaReserve.getLiquidityMint();

const kaminoResourcesResponse = await fetch(`${CDN_ENDPOINT}/resources.json`);
const kaminoResourcesData = await kaminoResourcesResponse.json();
const kaminoResources = kaminoResourcesData['mainnet-beta'];
const multiplyColPairs = kaminoResources.multiplyLUTsPairs[collTokenMint] || {};
const multiplyLut = multiplyColPairs[debtTokenMint] || [];
const multiplyLutKeys = multiplyLut.map((lut: string) => address(lut));
4

Configure Deleverage Parameters

Set the withdrawal amount and slippage tolerance.
const withdrawAmount = new Decimal(3); // $3 worth to deleverage
const slippageBps = 100; // 1% slippage (xStocks may need higher slippage)
Some assets may require higher slippage than others.
5

Get User Lookup Table

Retrieve the user-specific lookup table created during the first multiply deposit.
const multiplyMints: { coll: Address; debt: Address }[] = [{ coll: collTokenMint, debt: debtTokenMint }];
const leverageMints: { coll: Address; debt: Address }[] = [];

const [userLookupTable] = await getUserLutAddressAndSetupIxs(
  market!,
  signer,
  none(),
  true,
  multiplyMints,
  leverageMints
);
6

Fetch Multiply Obligation and Position Data

Create the multiply obligation type, derive its PDA, and fetch current position data.
const currentSlot = await rpc.getSlot().send();
const collTokenReserve = market!.getReserveByMint(collTokenMint)!;
const debtTokenReserve = market!.getReserveByMint(debtTokenMint)!;

const obligationType = new MultiplyObligation(collTokenMint, debtTokenMint, PROGRAM_ID);
const obligationAddress = await obligationType.toPda(market!.getAddress(), signer.address);
const obligation = await market!.getObligationByAddress(obligationAddress);

if (!obligation) {
  console.log('No multiply obligation found. You must have an active multiply position to deleverage.');
}

const deposited = lamportsToNumberDecimal(
  Array.from(obligation.deposits.values())[0]?.amount.toString() || '0',
  collTokenReserve.state.liquidity.mintDecimals.toNumber()
);
const borrowed = lamportsToNumberDecimal(
  Array.from(obligation.borrows.values())[0]?.amount.toString() || '0',
  debtTokenReserve.state.liquidity.mintDecimals.toNumber()
);

console.log(`Current position: ${deposited.toString()} TSLAx deposited, ${borrowed.toString()} USDC borrowed`);
The obligation PDA is derived from the market address, wallet address, collateral mint (TSLAx), and debt mint (USDC). This means a TSLAx/USDC multiply position has a different address than a NVDAx/USDC multiply position for the same wallet.
7

Setup Scope Oracle and Price Data

Get Scope oracle refresh instructions and price ratios for the swap.
const scopeConfiguration = { scope, scopeConfigurations: await scope.getAllConfigurations() };
const scopeRefreshIx = await getScopeRefreshIxForObligationAndReserves(
  market!,
  collTokenReserve,
  debtTokenReserve,
  obligation,
  scopeConfiguration
);

const collPriceUsd = collTokenReserve.getOracleMarketPrice();
const debtPriceUsd = debtTokenReserve.getOracleMarketPrice();
const priceCollToDebt = collPriceUsd.div(debtPriceUsd);
8

Build Deleverage Instructions with Multiple Routes

Set compute budget and build withdrawal with leverage instructions using KSwap for multi-route optimization.
const computeIxs = getComputeBudgetAndPriorityFeeIxs(1_400_000, new Decimal(500000));

const userSolBalanceLamports = Number.parseInt(
  (await rpc.getBalance(signer.address).send()).value.toString()
);

const withdrawWithLeverageRoutes = await getWithdrawWithLeverageIxs({
  owner: signer,
  kaminoMarket: market!,
  debtTokenMint: debtTokenMint,
  collTokenMint: collTokenMint,
  obligation: obligation,
  deposited: deposited,
  borrowed: borrowed,
  referrer: none(),
  currentSlot,
  withdrawAmount,
  priceCollToDebt,
  slippagePct: new Decimal(slippageBps / 100),
  isClosingPosition: false, // Set to true to close the entire position
  selectedTokenMint: debtTokenMint, // Withdraw into USDC
  budgetAndPriorityFeeIxs: computeIxs,
  scopeRefreshIx,
  quoteBufferBps: new Decimal(1000),
  quoter: getKswapQuoter(kswapSdk, signer.address, slippageBps, collTokenReserve, debtTokenReserve),
  swapper: getKswapSwapper(kswapSdk, signer.address, slippageBps),
  useV2Ixs: true,
  userSolBalanceLamports,
});
getWithdrawWithLeverageIxs handles the complex flow: flash borrow debt token → repay debt → withdraw freed collateral → swap collateral to debt token → repay flash loan. All in one atomic transaction.
9

Simulate Routes and Select Best

Prepare lookup tables, simulate all routes, and select the best performing route based on price.
const klendLookupTableKeys: Address[] = [];
klendLookupTableKeys.push(userLookupTable);
klendLookupTableKeys.push(...multiplyLutKeys);
klendLookupTableKeys.push(XSTOCKS_MARKET_LUT);

const klendLutAccounts = await fetchAllAddressLookupTable(rpc, klendLookupTableKeys);

const simulationResults = await Promise.all(
  withdrawWithLeverageRoutes.map(async (route) => {
    const lookupTables = route.lookupTables;
    lookupTables.push(...klendLutAccounts);

    const simulation = await simulateTx(rpc, signer.address, route.ixs, lookupTables).catch(() => undefined);

    if (!simulation || simulation.value.err) {
      return undefined;
    }

    return {
      ixs: route.ixs,
      luts: lookupTables.map((l) => l.address),
      routeOutput: route.quote!,
      swapInputs: route.swapInputs,
    };
  })
);

const passingSimulations = simulationResults.filter((tx) => tx !== undefined);

const bestRoute =
  passingSimulations.length > 0
    ? passingSimulations.reduce((best, current) => {
        const inputMintReserve = market!.getReserveByMint(best.swapInputs.inputMint)!;
        const outputMintReserve = market!.getReserveByMint(best.swapInputs.outputMint)!;

        const bestPrice = new Decimal(best.routeOutput.amountsExactIn.amountOutGuaranteed.toString())
          .div(outputMintReserve.getMintFactor())
          .div(new Decimal(best.routeOutput.amountsExactIn.amountIn.toString()).div(inputMintReserve.getMintFactor()));

        const currentPrice = new Decimal(current.routeOutput.amountsExactIn.amountOutGuaranteed.toString())
          .div(outputMintReserve.getMintFactor())
          .div(
            new Decimal(current.routeOutput.amountsExactIn.amountIn.toString()).div(inputMintReserve.getMintFactor())
          );

        return bestPrice.greaterThan(currentPrice) ? best : current;
      })
    : (() => {
        const lookupTables = withdrawWithLeverageRoutes[0].lookupTables;
        lookupTables.push(...klendLutAccounts);
        return { ixs: withdrawWithLeverageRoutes[0].ixs, luts: lookupTables.map((l) => l.address) };
      })();

if (!bestRoute) {
  console.log('No route found');
}
Simulation-based route selection ensures the transaction will succeed before sending it. Routes that fail simulation are filtered out, and the best price among passing routes is selected.
10

Build, Sign, and Send Transaction

Wait briefly to avoid rate limiting, get a fresh blockhash, compress the transaction with LUTs, and send it.
await new Promise((resolve) => setTimeout(resolve, 2000));

const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'finalized' }).send();

const lutsByAddress: Record<Address, Address[]> = {};
const bestRouteLutAccounts = await fetchAllAddressLookupTable(rpc, bestRoute.luts || []);
for (const acc of bestRouteLutAccounts) {
  lutsByAddress[acc.address] = acc.data.addresses;
}

const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => appendTransactionMessageInstructions(bestRoute.ixs, tx),
  (tx) => setTransactionMessageFeePayerSigner(signer, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
  (tx) => compressTransactionMessageUsingAddressLookupTables(tx, lutsByAddress)
);

const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
const signature = getSignatureFromTransaction(signedTransaction);

await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction, {
  commitment: 'processed',
  preflightCommitment: 'processed',
  skipPreflight: true,
});

console.log(`Multiply deleverage successful! Sold ~$${withdrawAmount.toString()} of collateral to repay debt.`);
console.log(`Transaction signature: ${signature}`);
The deleverage transaction is complete. Your leverage is reduced, debt is partially repaid, and remaining value is available as USDC or collateral based on the selectedTokenMint parameter.

Borrow and Multiply SDK Methods

MethodOperationWhen to Use
getDepositWithLeverageIxsOpen position or add collateralCreating or adding to a leveraged position
buildRepayTxnsRepay with wallet fundsReducing debt using USDC, SOL, etc. from wallet
buildWithdrawTxnsWithdraw collateralWithdrawing collateral after debt is repaid
getWithdrawWithLeverageIxsDeleverage and withdrawSell collateral to repay debt and receive remaining value in wallet
getRepayWithCollIxsRepay debt with collateralSell collateral to repay debt without withdrawing (value stays in position)
Multiply operations use MultiplyObligation. Vanilla operations use VanillaObligation. The obligation type determines which PDA is derived for your position.