UNPKG

@kamino-finance/klend-sdk

Version:

Typescript SDK for interacting with the Kamino Lending (klend) protocol

1,644 lines (1,527 loc) 49.7 kB
import { LAMPORTS_PER_SOL, PublicKey, TransactionInstruction } from '@solana/web3.js'; import Decimal from 'decimal.js'; import { KaminoAction, KaminoMarket, KaminoObligation, KaminoReserve, lamportsToNumberDecimal as fromLamports, } from '../classes'; import { getFlashLoanInstructions } from './instructions'; import { numberToLamportsDecimal as toLamports } from '../classes'; import { LeverageObligation, MultiplyObligation, ObligationType, ObligationTypeTag, SOL_DECIMALS, U64_MAX, WRAPPED_SOL_MINT, createAtasIdempotent, getAssociatedTokenAddress, getComputeBudgetAndPriorityFeeIxns, getDepositWsolIxns, getLookupTableAccount, removeBudgetAndAtaIxns, uniqueAccounts, } from '../utils'; import { adjustDepositLeverageCalcs, adjustWithdrawLeverageCalcs, calcAdjustAmounts, depositLeverageCalcs, depositLeverageKtokenCalcs, toJson, withdrawLeverageCalcs, } from './calcs'; import { TOKEN_PROGRAM_ID, createCloseAccountInstruction, getAssociatedTokenAddressSync } from '@solana/spl-token'; import { Kamino, StrategyWithAddress } from '@kamino-finance/kliquidity-sdk'; import { getExpectedTokenBalanceAfterBorrow, getKtokenToTokenSwapper, getTokenToKtokenSwapper } from './utils'; import { FullBPS } from '@kamino-finance/kliquidity-sdk/dist/utils/CreationParameters'; import { AdjustLeverageCalcsResult, AdjustLeverageInitialInputs, AdjustLeverageIxsResponse, AdjustLeverageProps, AdjustLeverageSwapInputsProps, DepositLeverageCalcsResult, DepositLeverageInitialInputs, DepositWithLeverageProps, DepositWithLeverageSwapInputsProps, DepsoitLeverageIxsResponse, PriceAinBProvider, SwapInputs, SwapQuoteIxs, SwapQuoteIxsProvider, WithdrawLeverageCalcsResult, WithdrawLeverageInitialInputs, WithdrawLeverageIxsResponse, WithdrawWithLeverageProps, WithdrawWithLeverageSwapInputsProps, } from './types'; export async function getDepositWithLeverageSwapInputs<QuoteResponse>({ owner, kaminoMarket, debtTokenMint, collTokenMint, depositAmount, priceDebtToColl, slippagePct, obligation, referrer, currentSlot, targetLeverage, selectedTokenMint, kamino, obligationTypeTagOverride, scopeFeed, budgetAndPriorityFeeIxs, quoteBufferBps, priceAinB, isKtoken, quoter, }: DepositWithLeverageSwapInputsProps<QuoteResponse>): Promise<{ swapInputs: SwapInputs; initialInputs: DepositLeverageInitialInputs<QuoteResponse>; }> { const collReserve = kaminoMarket.getReserveByMint(collTokenMint); const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint); const solTokenReserve = kaminoMarket.getReserveByMint(WRAPPED_SOL_MINT); const flashLoanFee = collReserve!.getFlashLoanFee() || new Decimal(0); const selectedTokenIsCollToken = selectedTokenMint.equals(collTokenMint); const depositTokenIsSol = !solTokenReserve ? false : selectedTokenMint.equals(solTokenReserve!.getLiquidityMint()); const collIsKtoken = await isKtoken(collTokenMint); const strategy = collIsKtoken ? (await kamino!.getStrategyByKTokenMint(collTokenMint))! : undefined; const calcs = await getDepositWithLeverageCalcs( depositAmount, selectedTokenIsCollToken, collIsKtoken, depositTokenIsSol, priceDebtToColl, targetLeverage, slippagePct, flashLoanFee, kamino, strategy, debtTokenMint, priceAinB, debtReserve! ); console.log('Ops Calcs', toJson(calcs)); let obligationType: ObligationType; if (obligationTypeTagOverride === ObligationTypeTag.Multiply) { // multiply obligationType = new MultiplyObligation(collTokenMint, debtTokenMint, kaminoMarket.programId); } else if (obligationTypeTagOverride === ObligationTypeTag.Leverage) { // leverage obligationType = new LeverageObligation(collTokenMint, debtTokenMint, kaminoMarket.programId); } else { throw Error('Obligation type tag not supported for leverage, please use 1 - multiply or 3 - leverage'); } // Build the repay & withdraw collateral tx to get the number of accounts const klendIxs = await buildDepositWithLeverageIxns( kaminoMarket, debtReserve!, collReserve!, owner, obligation ? obligation : obligationType, referrer, currentSlot, depositTokenIsSol, scopeFeed, calcs, budgetAndPriorityFeeIxs, { preActionIxs: [], swapIxs: [], lookupTables: [], }, strategy, collIsKtoken ); const uniqueKlendAccounts = uniqueAccounts(klendIxs); const swapInputAmount = toLamports( !collIsKtoken ? calcs.swapDebtTokenIn : calcs.singleSidedDepositKtokenOnly, debtReserve!.stats.decimals ).ceil(); const swapInputsForQuote: SwapInputs = { inputAmountLamports: swapInputAmount.mul(new Decimal(1).add(quoteBufferBps.div(FullBPS))), inputMint: debtTokenMint, outputMint: collTokenMint, amountDebtAtaBalance: new Decimal(0), // Only needed for ktokens swaps }; const swapQuote = await quoter(swapInputsForQuote, uniqueKlendAccounts); const quotePriceCalcs = await getDepositWithLeverageCalcs( depositAmount, selectedTokenIsCollToken, collIsKtoken, depositTokenIsSol, swapQuote.priceAInB, targetLeverage, slippagePct, flashLoanFee, kamino, strategy, debtTokenMint, priceAinB, debtReserve! ); const swapInputAmountQuotePrice = toLamports( !collIsKtoken ? quotePriceCalcs.swapDebtTokenIn : quotePriceCalcs.singleSidedDepositKtokenOnly, debtReserve!.stats.decimals ).ceil(); let expectedDebtTokenAtaBalance = new Decimal(0); if (collIsKtoken) { let futureBalanceInAta = new Decimal(0); if (debtTokenMint.equals(WRAPPED_SOL_MINT)) { futureBalanceInAta = futureBalanceInAta.add( !collIsKtoken ? quotePriceCalcs.initDepositInSol : quotePriceCalcs.initDepositInSol ); } futureBalanceInAta = futureBalanceInAta.add( !collIsKtoken ? quotePriceCalcs.debtTokenToBorrow : quotePriceCalcs.flashBorrowInDebtTokenKtokenOnly ); expectedDebtTokenAtaBalance = await getExpectedTokenBalanceAfterBorrow( kaminoMarket.getConnection(), debtTokenMint, owner, toLamports(futureBalanceInAta.toDecimalPlaces(debtReserve!.stats.decimals), debtReserve!.stats.decimals), debtReserve!.state.liquidity.mintDecimals.toNumber() ); } return { swapInputs: { inputAmountLamports: swapInputAmountQuotePrice, inputMint: debtTokenMint, outputMint: collTokenMint, amountDebtAtaBalance: expectedDebtTokenAtaBalance, }, initialInputs: { calcs: quotePriceCalcs, swapQuote, currentSlot, collIsKtoken, strategy, obligation: obligation ? obligation : obligationType, klendAccounts: uniqueKlendAccounts, }, }; } async function getDepositWithLeverageCalcs( depositAmount: Decimal, selectedTokenIsCollToken: boolean, collIsKtoken: boolean, depositTokenIsSol: boolean, priceDebtToColl: Decimal, targetLeverage: Decimal, slippagePct: Decimal, flashLoanFee: Decimal, kamino: Kamino | undefined, strategy: StrategyWithAddress | undefined, debtTokenMint: PublicKey, priceAinB: PriceAinBProvider, debtReserve: KaminoReserve ): Promise<DepositLeverageCalcsResult> { let calcs: DepositLeverageCalcsResult; if (!collIsKtoken) { calcs = depositLeverageCalcs({ depositAmount: depositAmount, depositTokenIsCollToken: selectedTokenIsCollToken, depositTokenIsSol, priceDebtToColl, targetLeverage, slippagePct, flashLoanFee, }); } else { calcs = await depositLeverageKtokenCalcs({ kamino: kamino!, strategy: strategy!, debtTokenMint, depositAmount: depositAmount, depositTokenIsCollToken: selectedTokenIsCollToken, depositTokenIsSol, priceDebtToColl, targetLeverage, slippagePct, flashLoanFee, priceAinB, }); // Rounding to exact number of decimals so this value is passed through in all calcs without rounding inconsistencies calcs.flashBorrowInDebtTokenKtokenOnly = calcs.flashBorrowInDebtTokenKtokenOnly.toDecimalPlaces( debtReserve!.state.liquidity.mintDecimals.toNumber()!, Decimal.ROUND_CEIL ); calcs.debtTokenToBorrow = calcs.debtTokenToBorrow.toDecimalPlaces( debtReserve!.state.liquidity.mintDecimals.toNumber()!, Decimal.ROUND_CEIL ); calcs.singleSidedDepositKtokenOnly = calcs.singleSidedDepositKtokenOnly.toDecimalPlaces( debtReserve!.state.liquidity.mintDecimals.toNumber()!, Decimal.ROUND_CEIL ); } return calcs; } export async function getDepositWithLeverageIxns<QuoteResponse>({ owner, kaminoMarket, debtTokenMint, collTokenMint, depositAmount, priceDebtToColl, slippagePct, obligation, referrer, currentSlot, targetLeverage, selectedTokenMint, kamino, obligationTypeTagOverride, scopeFeed, budgetAndPriorityFeeIxs, quoteBufferBps, priceAinB, isKtoken, quoter, swapper, }: DepositWithLeverageProps<QuoteResponse>): Promise<DepsoitLeverageIxsResponse<QuoteResponse>> { const { swapInputs, initialInputs } = await getDepositWithLeverageSwapInputs({ owner, kaminoMarket, debtTokenMint, collTokenMint, depositAmount, priceDebtToColl, slippagePct, obligation, referrer, currentSlot, targetLeverage, selectedTokenMint, kamino, obligationTypeTagOverride, scopeFeed, budgetAndPriorityFeeIxs, quoteBufferBps, priceAinB, isKtoken, quoter, }); let depositSwapper: SwapQuoteIxsProvider<QuoteResponse>; if (!initialInputs.collIsKtoken) { depositSwapper = swapper; } else { if (kamino === undefined) { throw Error('Ktoken use as collateral for leverage without Kamino instance'); } depositSwapper = await getTokenToKtokenSwapper(kaminoMarket, kamino, owner, slippagePct, swapper, priceAinB, false); } const { swapIxs, lookupTables } = await depositSwapper( swapInputs, initialInputs.klendAccounts, initialInputs.swapQuote ); if (initialInputs.collIsKtoken) { if (initialInputs.strategy!.strategy.strategyLookupTable) { const strategyLut = await getLookupTableAccount( kaminoMarket.getConnection(), initialInputs.strategy!.strategy.strategyLookupTable! ); lookupTables.push(strategyLut!); } else { console.log('Strategy lookup table not found'); } } const collReserve = kaminoMarket.getReserveByMint(collTokenMint); const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint); const solTokenReserve = kaminoMarket.getReserveByMint(WRAPPED_SOL_MINT); const depositTokenIsSol = !solTokenReserve ? false : selectedTokenMint.equals(solTokenReserve!.getLiquidityMint()); const ixs = await buildDepositWithLeverageIxns( kaminoMarket, debtReserve!, collReserve!, owner, initialInputs.obligation, referrer, currentSlot, depositTokenIsSol, scopeFeed, initialInputs.calcs, budgetAndPriorityFeeIxs, { preActionIxs: [], swapIxs: swapIxs, lookupTables: lookupTables, }, initialInputs.strategy, initialInputs.collIsKtoken ); return { ixs, lookupTables, swapInputs, initialInputs, }; } async function buildDepositWithLeverageIxns( market: KaminoMarket, debtReserve: KaminoReserve, collReserve: KaminoReserve, owner: PublicKey, obligation: KaminoObligation | ObligationType | undefined, referrer: PublicKey, currentSlot: number, depositTokenIsSol: boolean, scopeFeed: string | undefined, calcs: DepositLeverageCalcsResult, budgetAndPriorityFeeIxs: TransactionInstruction[] | undefined, swapQuoteIxs: SwapQuoteIxs, strategy: StrategyWithAddress | undefined, collIsKtoken: boolean ): Promise<TransactionInstruction[]> { const budgetIxns = budgetAndPriorityFeeIxs || getComputeBudgetAndPriorityFeeIxns(3000000); const collTokenMint = collReserve.getLiquidityMint(); const debtTokenMint = debtReserve.getLiquidityMint(); const collTokenAta = getAssociatedTokenAddressSync(collTokenMint, owner); const debtTokenAta = getAssociatedTokenAddressSync(debtTokenMint, owner); // 1. Create atas & budget txns let mintsToCreateAtas: Array<{ mint: PublicKey; tokenProgram: PublicKey }>; if (collIsKtoken) { const secondTokenAta = strategy!.strategy.tokenAMint.equals(debtTokenMint) ? strategy!.strategy.tokenBMint : strategy!.strategy.tokenAMint; const secondTokenTokenProgarm = strategy!.strategy.tokenAMint.equals(debtTokenMint) ? strategy!.strategy.tokenBTokenProgram.equals(PublicKey.default) ? TOKEN_PROGRAM_ID : strategy!.strategy.tokenBTokenProgram : strategy!.strategy.tokenATokenProgram.equals(PublicKey.default) ? TOKEN_PROGRAM_ID : strategy!.strategy.tokenATokenProgram; mintsToCreateAtas = [ { mint: collTokenMint, tokenProgram: collReserve.getLiquidityTokenProgram(), }, { mint: debtTokenMint, tokenProgram: debtReserve.getLiquidityTokenProgram(), }, { mint: collReserve.getCTokenMint(), tokenProgram: TOKEN_PROGRAM_ID, }, { mint: secondTokenAta, tokenProgram: secondTokenTokenProgarm, }, ]; } else { mintsToCreateAtas = [ { mint: collTokenMint, tokenProgram: collReserve.getLiquidityTokenProgram(), }, { mint: debtTokenMint, tokenProgram: debtReserve.getLiquidityTokenProgram(), }, { mint: collReserve!.getCTokenMint(), tokenProgram: TOKEN_PROGRAM_ID, }, ]; } const atasAndCreateIxns = createAtasIdempotent(owner, mintsToCreateAtas); const fillWsolAtaIxns: TransactionInstruction[] = []; if (depositTokenIsSol) { fillWsolAtaIxns.push( ...getDepositWsolIxns( owner, getAssociatedTokenAddressSync(WRAPPED_SOL_MINT, owner), toLamports(calcs.initDepositInSol, SOL_DECIMALS).ceil() ) ); } // 2. Flash borrow & repay the collateral amount needed for given leverage // if user deposits coll, then we borrow the diff, else we borrow the entire amount const { flashBorrowIxn, flashRepayIxn } = getFlashLoanInstructions({ borrowIxnIndex: budgetIxns.length + atasAndCreateIxns.length + fillWsolAtaIxns.length, walletPublicKey: owner, lendingMarketAuthority: market.getLendingMarketAuthority(), lendingMarketAddress: market.getAddress(), reserve: !collIsKtoken ? collReserve : debtReserve, amountLamports: toLamports( !collIsKtoken ? calcs.flashBorrowInCollToken : calcs.flashBorrowInDebtTokenKtokenOnly, !collIsKtoken ? collReserve.stats.decimals : debtReserve.stats.decimals ), destinationAta: !collIsKtoken ? collTokenAta : debtTokenAta, referrerAccount: market.programId, referrerTokenState: market.programId, programId: market.programId, }); // 3. Deposit initial tokens + borrowed tokens into reserve const scopeRefresh = scopeFeed ? { includeScopeRefresh: true, scopeFeed: scopeFeed } : undefined; const kaminoDepositAndBorrowAction = await KaminoAction.buildDepositAndBorrowTxns( market, toLamports(!collIsKtoken ? calcs.collTokenToDeposit : calcs.collTokenToDeposit, collReserve.stats.decimals) .floor() .toString(), collTokenMint, toLamports(!collIsKtoken ? calcs.debtTokenToBorrow : calcs.debtTokenToBorrow, debtReserve.stats.decimals) .ceil() .toString(), debtTokenMint, owner, obligation!, 0, false, true, // emode false, // to be checked and created in a setup tx in the UI referrer, currentSlot, scopeRefresh ); // 4. Swap const { swapIxs } = swapQuoteIxs; const swapInstructions = removeBudgetAndAtaIxns(swapIxs, []); if (!collIsKtoken) { return [ ...budgetIxns, ...atasAndCreateIxns.map((x) => x.createAtaIx), ...fillWsolAtaIxns, ...[flashBorrowIxn], ...kaminoDepositAndBorrowAction.setupIxs, ...KaminoAction.actionToLendingIxs(kaminoDepositAndBorrowAction), ...kaminoDepositAndBorrowAction.cleanupIxs, ...swapInstructions, ...[flashRepayIxn], ]; } else { return [ ...budgetIxns, ...atasAndCreateIxns.map((x) => x.createAtaIx), ...fillWsolAtaIxns, ...[flashBorrowIxn], ...swapInstructions, ...kaminoDepositAndBorrowAction.setupIxs, ...KaminoAction.actionToLendingIxs(kaminoDepositAndBorrowAction), ...kaminoDepositAndBorrowAction.cleanupIxs, ...[flashRepayIxn], ]; } } export async function getWithdrawWithLeverageSwapInputs<QuoteResponse>({ owner, kaminoMarket, debtTokenMint, collTokenMint, deposited, borrowed, obligation, referrer, currentSlot, withdrawAmount, priceCollToDebt, slippagePct, isClosingPosition, selectedTokenMint, budgetAndPriorityFeeIxs, kamino, scopeFeed, quoteBufferBps, isKtoken, quoter, }: WithdrawWithLeverageSwapInputsProps<QuoteResponse>): Promise<{ swapInputs: SwapInputs; initialInputs: WithdrawLeverageInitialInputs<QuoteResponse>; }> { const collReserve = kaminoMarket.getReserveByMint(collTokenMint); const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint); const flashLoanFee = debtReserve!.getFlashLoanFee() || new Decimal(0); const selectedTokenIsCollToken = selectedTokenMint.equals(collTokenMint); const collIsKtoken = await isKtoken(collTokenMint); const strategy = collIsKtoken ? (await kamino!.getStrategyByKTokenMint(collTokenMint))! : undefined; const solTokenReserve = kaminoMarket.getReserveByMint(WRAPPED_SOL_MINT); const depositTokenIsSol = !solTokenReserve ? false : selectedTokenMint.equals(solTokenReserve!.getLiquidityMint()); const calcs = withdrawLeverageCalcs( kaminoMarket, collReserve!, debtReserve!, priceCollToDebt, withdrawAmount, deposited, borrowed, currentSlot, isClosingPosition, selectedTokenIsCollToken, selectedTokenMint, obligation, flashLoanFee, slippagePct ); const klendIxs = await buildWithdrawWithLeverageIxns( kaminoMarket, debtReserve!, collReserve!, owner, obligation, referrer, currentSlot, isClosingPosition, depositTokenIsSol, scopeFeed, calcs, budgetAndPriorityFeeIxs, { preActionIxs: [], swapIxs: [], lookupTables: [], }, strategy, collIsKtoken ); const uniqueKlendAccounts = uniqueAccounts(klendIxs); const swapInputAmount = toLamports( calcs.collTokenSwapIn, collReserve!.state.liquidity.mintDecimals.toNumber() ).ceil(); const swapInputsForQuote: SwapInputs = { inputAmountLamports: swapInputAmount.mul(new Decimal(1).add(quoteBufferBps.div(FullBPS))), inputMint: collTokenMint, outputMint: debtTokenMint, amountDebtAtaBalance: new Decimal(0), // Only needed for ktokens deposits }; const swapQuote = await quoter(swapInputsForQuote, uniqueKlendAccounts); const calcsQuotePrice = withdrawLeverageCalcs( kaminoMarket, collReserve!, debtReserve!, collIsKtoken ? swapQuote.priceAInB : priceCollToDebt, withdrawAmount, deposited, borrowed, currentSlot, isClosingPosition, selectedTokenIsCollToken, selectedTokenMint, obligation, flashLoanFee, slippagePct ); const swapInputAmountQuotePrice = toLamports( calcsQuotePrice.collTokenSwapIn, collReserve!.state.liquidity.mintDecimals.toNumber() ).ceil(); return { swapInputs: { inputAmountLamports: swapInputAmountQuotePrice, inputMint: collTokenMint, outputMint: debtTokenMint, amountDebtAtaBalance: new Decimal(0), // Only needed for ktokens deposits }, initialInputs: { calcs: calcsQuotePrice, swapQuote, currentSlot, collIsKtoken, strategy, obligation, klendAccounts: uniqueKlendAccounts, }, }; } export async function getWithdrawWithLeverageIxns<QuoteResponse>({ owner, kaminoMarket, debtTokenMint, collTokenMint, obligation, deposited, borrowed, referrer, currentSlot, withdrawAmount, priceCollToDebt, slippagePct, isClosingPosition, selectedTokenMint, budgetAndPriorityFeeIxs, kamino, scopeFeed, quoteBufferBps, isKtoken, quoter, swapper, }: WithdrawWithLeverageProps<QuoteResponse>): Promise<WithdrawLeverageIxsResponse<QuoteResponse>> { const collReserve = kaminoMarket.getReserveByMint(collTokenMint); const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint); const solTokenReserve = kaminoMarket.getReserveByMint(WRAPPED_SOL_MINT); const depositTokenIsSol = !solTokenReserve ? false : selectedTokenMint.equals(solTokenReserve!.getLiquidityMint()); const { swapInputs, initialInputs } = await getWithdrawWithLeverageSwapInputs({ owner, kaminoMarket, debtTokenMint, collTokenMint, deposited, borrowed, obligation, referrer, currentSlot, withdrawAmount, priceCollToDebt, slippagePct, isClosingPosition, selectedTokenMint, budgetAndPriorityFeeIxs, kamino, scopeFeed, quoteBufferBps, isKtoken, quoter, }); let withdrawSwapper: SwapQuoteIxsProvider<QuoteResponse>; if (initialInputs.collIsKtoken) { if (kamino === undefined) { throw Error('Ktoken use as collateral for leverage without Kamino instance'); } withdrawSwapper = await getKtokenToTokenSwapper(kaminoMarket, kamino, owner, swapper); } else { withdrawSwapper = swapper; } const { swapIxs, lookupTables } = await withdrawSwapper( swapInputs, initialInputs.klendAccounts, initialInputs.swapQuote ); if (initialInputs.collIsKtoken) { if (initialInputs.strategy!.strategy.strategyLookupTable) { const strategyLut = await getLookupTableAccount( kaminoMarket.getConnection(), initialInputs.strategy!.strategy.strategyLookupTable! ); lookupTables.push(strategyLut!); } else { console.log('Strategy lookup table not found'); } } const ixs = await buildWithdrawWithLeverageIxns( kaminoMarket, debtReserve!, collReserve!, owner, obligation, referrer, currentSlot, isClosingPosition, depositTokenIsSol, scopeFeed, initialInputs.calcs, budgetAndPriorityFeeIxs, { preActionIxs: [], swapIxs, lookupTables, }, initialInputs.strategy, initialInputs.collIsKtoken ); // Send ixns and lookup tables return { ixs, lookupTables, swapInputs, initialInputs: initialInputs, }; } export async function buildWithdrawWithLeverageIxns( market: KaminoMarket, debtReserve: KaminoReserve, collReserve: KaminoReserve, owner: PublicKey, obligation: KaminoObligation, referrer: PublicKey, currentSlot: number, isClosingPosition: boolean, depositTokenIsSol: boolean, scopeFeed: string | undefined, calcs: WithdrawLeverageCalcsResult, budgetAndPriorityFeeIxs: TransactionInstruction[] | undefined, swapQuoteIxs: SwapQuoteIxs, strategy: StrategyWithAddress | undefined, collIsKtoken: boolean ): Promise<TransactionInstruction[]> { const collTokenMint = collReserve.getLiquidityMint(); const debtTokenMint = debtReserve.getLiquidityMint(); const debtTokenAta = getAssociatedTokenAddressSync(debtTokenMint, owner); // 1. Create atas & budget txns & user metadata let mintsToCreateAtas: Array<{ mint: PublicKey; tokenProgram: PublicKey }>; if (collIsKtoken) { const secondTokenAta = strategy!.strategy.tokenAMint.equals(debtTokenMint) ? strategy!.strategy.tokenBMint : strategy!.strategy.tokenAMint; const secondTokenTokenProgram = strategy!.strategy.tokenAMint.equals(debtTokenMint) ? strategy!.strategy.tokenBTokenProgram.equals(PublicKey.default) ? TOKEN_PROGRAM_ID : strategy!.strategy.tokenBTokenProgram : strategy!.strategy.tokenATokenProgram.equals(PublicKey.default) ? TOKEN_PROGRAM_ID : strategy!.strategy.tokenATokenProgram; mintsToCreateAtas = [ { mint: collTokenMint, tokenProgram: collReserve.getLiquidityTokenProgram(), }, { mint: debtTokenMint, tokenProgram: debtReserve.getLiquidityTokenProgram(), }, { mint: collReserve.getCTokenMint(), tokenProgram: TOKEN_PROGRAM_ID, }, { mint: secondTokenAta, tokenProgram: secondTokenTokenProgram, }, ]; } else { mintsToCreateAtas = [ { mint: collTokenMint, tokenProgram: collReserve!.getLiquidityTokenProgram(), }, { mint: debtTokenMint, tokenProgram: debtReserve!.getLiquidityTokenProgram(), }, { mint: collReserve!.getCTokenMint(), tokenProgram: TOKEN_PROGRAM_ID, }, ]; } const atasAndCreateIxns = createAtasIdempotent(owner, mintsToCreateAtas); const closeWsolAtaIxns: TransactionInstruction[] = []; if (depositTokenIsSol || debtTokenMint.equals(WRAPPED_SOL_MINT)) { const wsolAta = getAssociatedTokenAddress(WRAPPED_SOL_MINT, owner, false); closeWsolAtaIxns.push(createCloseAccountInstruction(wsolAta, owner, owner, [], TOKEN_PROGRAM_ID)); } const budgetIxns = budgetAndPriorityFeeIxs || getComputeBudgetAndPriorityFeeIxns(3000000); // TODO: Might be worth removing as it's only needed for Ktokens // This is here so that we have enough wsol to repay in case the kAB swapped to sol after estimates is not enough const fillWsolAtaIxns: TransactionInstruction[] = []; if (debtTokenMint.equals(WRAPPED_SOL_MINT)) { const halfSolBalance = (await market.getConnection().getBalance(owner)) / LAMPORTS_PER_SOL / 2; const balanceToWrap = halfSolBalance < 0.1 ? halfSolBalance : 0.1; fillWsolAtaIxns.push( ...getDepositWsolIxns( owner, getAssociatedTokenAddressSync(WRAPPED_SOL_MINT, owner), toLamports(balanceToWrap, SOL_DECIMALS).ceil() ) ); } // 2. Prepare the flash borrow and flash repay amounts and ixns // We borrow exactly how much we need to repay // and repay that + flash amount fee const { flashBorrowIxn, flashRepayIxn } = getFlashLoanInstructions({ borrowIxnIndex: budgetIxns.length + atasAndCreateIxns.length + fillWsolAtaIxns.length, walletPublicKey: owner, lendingMarketAuthority: market.getLendingMarketAuthority(), lendingMarketAddress: market.getAddress(), reserve: debtReserve!, amountLamports: toLamports(calcs.repayAmount, debtReserve!.stats.decimals), destinationAta: debtTokenAta, referrerAccount: market.programId, referrerTokenState: market.programId, programId: market.programId, }); // 6. Repay borrowed tokens and Withdraw tokens from reserve that will be swapped to repay flash loan const repayAndWithdrawAction = await KaminoAction.buildRepayAndWithdrawTxns( market, isClosingPosition ? U64_MAX : toLamports(calcs.repayAmount, debtReserve!.stats.decimals).floor().toString(), debtTokenMint, isClosingPosition ? U64_MAX : toLamports(calcs.depositTokenWithdrawAmount, collReserve!.stats.decimals).ceil().toString(), collTokenMint, owner, currentSlot, obligation, 0, false, false, false, // to be checked and created in a setup tx in the UI (won't be the case for withdraw anyway as this would be created in deposit) isClosingPosition, referrer, { includeScopeRefresh: true, scopeFeed: scopeFeed! } ); const swapInstructions = removeBudgetAndAtaIxns(swapQuoteIxs.swapIxs, []); return [ ...budgetIxns, ...atasAndCreateIxns.map((x) => x.createAtaIx), ...fillWsolAtaIxns, ...[flashBorrowIxn], ...repayAndWithdrawAction.setupIxs, ...KaminoAction.actionToLendingIxs(repayAndWithdrawAction), ...repayAndWithdrawAction.cleanupIxs, ...swapInstructions, ...[flashRepayIxn], ...closeWsolAtaIxns, ]; } export async function getAdjustLeverageSwapInputs<QuoteResponse>({ owner, kaminoMarket, debtTokenMint, collTokenMint, obligation, depositedLamports, borrowedLamports, referrer, currentSlot, targetLeverage, priceCollToDebt, priceDebtToColl, slippagePct, budgetAndPriorityFeeIxs, kamino, scopeFeed, quoteBufferBps, isKtoken, quoter, }: AdjustLeverageSwapInputsProps<QuoteResponse>): Promise<{ swapInputs: SwapInputs; initialInputs: AdjustLeverageInitialInputs<QuoteResponse>; }> { const collReserve = kaminoMarket.getReserveByMint(collTokenMint); const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint); const deposited = fromLamports(depositedLamports, collReserve!.stats.decimals); const borrowed = fromLamports(borrowedLamports, debtReserve!.stats.decimals); const collIsKtoken = await isKtoken(collTokenMint); const strategy = collIsKtoken ? (await kamino!.getStrategyByKTokenMint(collTokenMint))! : undefined; // Getting current flash loan fee const currentLeverage = obligation.refreshedStats.leverage; const isDepositViaLeverage = targetLeverage.gte(new Decimal(currentLeverage)); let flashLoanFee; if (isDepositViaLeverage) { flashLoanFee = collReserve!.getFlashLoanFee() || new Decimal(0); } else { flashLoanFee = debtReserve!.getFlashLoanFee() || new Decimal(0); } const { adjustDepositPosition, adjustBorrowPosition } = calcAdjustAmounts({ currentDepositPosition: deposited, currentBorrowPosition: borrowed, targetLeverage: targetLeverage, priceCollToDebt: priceCollToDebt, flashLoanFee: new Decimal(flashLoanFee), }); const isDeposit = adjustDepositPosition.gte(0) && adjustBorrowPosition.gte(0); if (isDepositViaLeverage !== isDeposit) { throw new Error('Invalid target leverage'); } if (isDeposit) { const calcs = await adjustDepositLeverageCalcs( kaminoMarket, owner, debtReserve!, adjustDepositPosition, adjustBorrowPosition, priceDebtToColl, flashLoanFee, slippagePct, collIsKtoken ); // Build the repay & withdraw collateral tx to get the number of accounts const klendIxs = await buildIncreaseLeverageIxns( owner, kaminoMarket, collTokenMint, debtTokenMint, obligation, referrer, currentSlot, calcs, strategy, scopeFeed, collIsKtoken, { preActionIxs: [], swapIxs: [], lookupTables: [], }, budgetAndPriorityFeeIxs ); const uniqueKlendAccounts = uniqueAccounts(klendIxs); const swapInputAmount = toLamports( !collIsKtoken ? calcs.borrowAmount : calcs.amountToFlashBorrowDebt, debtReserve!.state.liquidity.mintDecimals.toNumber() ).ceil(); const swapInputsForQuote: SwapInputs = { inputAmountLamports: swapInputAmount.mul(new Decimal(1).add(quoteBufferBps.div(FullBPS))), inputMint: debtTokenMint, outputMint: collTokenMint, amountDebtAtaBalance: new Decimal(0), // Only needed for ktokens swaps }; const swapQuote = await quoter(swapInputsForQuote, uniqueKlendAccounts); const { adjustDepositPosition: adjustDepositPositionQuotePrice, adjustBorrowPosition: adjustBorrowPositionQuotePrice, } = calcAdjustAmounts({ currentDepositPosition: deposited, currentBorrowPosition: borrowed, targetLeverage: targetLeverage, priceCollToDebt: new Decimal(1).div(swapQuote.priceAInB), flashLoanFee: new Decimal(flashLoanFee), }); const calcsQuotePrice = await adjustDepositLeverageCalcs( kaminoMarket, owner, debtReserve!, adjustDepositPositionQuotePrice, adjustBorrowPositionQuotePrice, swapQuote.priceAInB, flashLoanFee, slippagePct, collIsKtoken ); const swapInputAmountQuotePrice = toLamports( !collIsKtoken ? calcsQuotePrice.borrowAmount : calcsQuotePrice.amountToFlashBorrowDebt, debtReserve!.state.liquidity.mintDecimals.toNumber() ).ceil(); let expectedDebtTokenAtaBalance = new Decimal(0); if (collIsKtoken) { expectedDebtTokenAtaBalance = await getExpectedTokenBalanceAfterBorrow( kaminoMarket.getConnection(), debtTokenMint, owner, toLamports( !collIsKtoken ? calcsQuotePrice.borrowAmount : calcsQuotePrice.amountToFlashBorrowDebt, debtReserve!.stats.decimals ).floor(), debtReserve!.state.liquidity.mintDecimals.toNumber() ); } return { swapInputs: { inputAmountLamports: swapInputAmountQuotePrice, inputMint: debtTokenMint, outputMint: collTokenMint, amountDebtAtaBalance: expectedDebtTokenAtaBalance, }, initialInputs: { calcs: calcsQuotePrice, swapQuote, currentSlot, collIsKtoken, strategy, obligation: obligation, klendAccounts: uniqueKlendAccounts, isDeposit: isDeposit, }, }; } else { const calcs = adjustWithdrawLeverageCalcs(adjustDepositPosition, adjustBorrowPosition, flashLoanFee, slippagePct); const klendIxs = await buildDecreaseLeverageIxns( owner, kaminoMarket, collTokenMint, debtTokenMint, obligation, referrer, currentSlot, calcs, strategy, scopeFeed, collIsKtoken, { preActionIxs: [], swapIxs: [], lookupTables: [], }, budgetAndPriorityFeeIxs ); const uniqueKlendAccounts = uniqueAccounts(klendIxs); const swapInputAmount = toLamports( calcs.withdrawAmountWithSlippageAndFlashLoanFee, collReserve!.state.liquidity.mintDecimals.toNumber() ).ceil(); const swapInputsForQuote: SwapInputs = { inputAmountLamports: swapInputAmount.mul(new Decimal(1).add(quoteBufferBps.div(FullBPS))), inputMint: collTokenMint, outputMint: debtTokenMint, amountDebtAtaBalance: new Decimal(0), // Only needed for ktokens deposits }; const swapQuote = await quoter(swapInputsForQuote, uniqueKlendAccounts); const { adjustDepositPosition: adjustDepositPositionQuotePrice, adjustBorrowPosition: adjustBorrowPositionQuotePrice, } = calcAdjustAmounts({ currentDepositPosition: deposited, currentBorrowPosition: borrowed, targetLeverage: targetLeverage, priceCollToDebt: swapQuote.priceAInB, flashLoanFee: new Decimal(flashLoanFee), }); const calcsQuotePrice = adjustWithdrawLeverageCalcs( adjustDepositPositionQuotePrice, adjustBorrowPositionQuotePrice, flashLoanFee, slippagePct ); const swapInputAmountQuotePrice = toLamports( calcsQuotePrice.withdrawAmountWithSlippageAndFlashLoanFee, collReserve!.state.liquidity.mintDecimals.toNumber() ).ceil(); return { swapInputs: { inputAmountLamports: swapInputAmountQuotePrice, inputMint: collTokenMint, outputMint: debtTokenMint, amountDebtAtaBalance: new Decimal(0), // Only needed for ktokens deposits }, initialInputs: { calcs: calcsQuotePrice, swapQuote, currentSlot, collIsKtoken, strategy, obligation, klendAccounts: uniqueKlendAccounts, isDeposit, }, }; } } export async function getAdjustLeverageIxns<QuoteResponse>({ owner, kaminoMarket, debtTokenMint, collTokenMint, obligation, depositedLamports, borrowedLamports, referrer, currentSlot, targetLeverage, priceCollToDebt, priceDebtToColl, slippagePct, budgetAndPriorityFeeIxs, kamino, scopeFeed, quoteBufferBps, priceAinB, isKtoken, quoter, swapper, }: AdjustLeverageProps<QuoteResponse>): Promise<AdjustLeverageIxsResponse<QuoteResponse>> { const { swapInputs, initialInputs } = await getAdjustLeverageSwapInputs({ owner, kaminoMarket, debtTokenMint, collTokenMint, obligation, depositedLamports, borrowedLamports, referrer, currentSlot, targetLeverage, priceCollToDebt, priceDebtToColl, slippagePct, budgetAndPriorityFeeIxs, kamino, scopeFeed, quoteBufferBps, priceAinB, isKtoken, quoter, }); // leverage increased so we need to deposit and borrow more if (initialInputs.isDeposit) { let depositSwapper: SwapQuoteIxsProvider<QuoteResponse>; if (initialInputs.collIsKtoken) { if (kamino === undefined) { throw Error('Ktoken use as collateral for leverage without Kamino instance'); } depositSwapper = await getTokenToKtokenSwapper( kaminoMarket, kamino, owner, slippagePct, swapper, priceAinB, false ); } else { depositSwapper = swapper; } const { swapIxs, lookupTables } = await depositSwapper( swapInputs, initialInputs.klendAccounts, initialInputs.swapQuote ); // TODO: marius why are we not using both adjustDepositPosition & adjustBorrowPosition const ixs = await buildIncreaseLeverageIxns( owner, kaminoMarket, collTokenMint, debtTokenMint, obligation, referrer, currentSlot, initialInputs.calcs, initialInputs.strategy, scopeFeed, initialInputs.collIsKtoken, { preActionIxs: [], swapIxs, lookupTables, }, budgetAndPriorityFeeIxs ); return { ixs, lookupTables, swapInputs, initialInputs, }; } else { console.log('Decreasing leverage'); let withdrawSwapper: SwapQuoteIxsProvider<QuoteResponse>; if (initialInputs.collIsKtoken) { if (kamino === undefined) { throw Error('Ktoken use as collateral for leverage without Kamino instance'); } withdrawSwapper = await getKtokenToTokenSwapper(kaminoMarket, kamino, owner, swapper); } else { withdrawSwapper = swapper; } // 5. Get swap ixns const { swapIxs, lookupTables } = await withdrawSwapper( swapInputs, initialInputs.klendAccounts, initialInputs.swapQuote ); const ixs = await buildDecreaseLeverageIxns( owner, kaminoMarket, collTokenMint, debtTokenMint, obligation, referrer, currentSlot, initialInputs.calcs, initialInputs.strategy, scopeFeed, initialInputs.collIsKtoken, { preActionIxs: [], swapIxs, lookupTables, }, budgetAndPriorityFeeIxs ); return { ixs, lookupTables, swapInputs, initialInputs, }; } } /** * Deposit and borrow tokens if leverage increased */ async function buildIncreaseLeverageIxns( owner: PublicKey, kaminoMarket: KaminoMarket, collTokenMint: PublicKey, debtTokenMint: PublicKey, obligation: KaminoObligation, referrer: PublicKey, currentSlot: number, calcs: AdjustLeverageCalcsResult, strategy: StrategyWithAddress | undefined, scopeFeed: string | undefined, collIsKtoken: boolean, swapQuoteIxs: SwapQuoteIxs, budgetAndPriorityFeeIxns: TransactionInstruction[] | undefined ): Promise<TransactionInstruction[]> { const collReserve = kaminoMarket.getReserveByMint(collTokenMint); const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint); const debtTokenAta = getAssociatedTokenAddressSync(debtTokenMint, owner); const collTokenAta = getAssociatedTokenAddressSync(collTokenMint, owner); // 1. Create atas & budget txns const budgetIxns = budgetAndPriorityFeeIxns || getComputeBudgetAndPriorityFeeIxns(3000000); let mintsToCreateAtas: Array<{ mint: PublicKey; tokenProgram: PublicKey }>; if (collIsKtoken) { const secondTokenAta = strategy!.strategy.tokenAMint.equals(debtTokenMint) ? strategy!.strategy.tokenBMint : strategy!.strategy.tokenAMint; const secondTokenTokenProgarm = strategy?.strategy.tokenAMint.equals(debtTokenMint) ? strategy!.strategy.tokenBTokenProgram.equals(PublicKey.default) ? TOKEN_PROGRAM_ID : strategy!.strategy.tokenBTokenProgram : strategy!.strategy.tokenATokenProgram.equals(PublicKey.default) ? TOKEN_PROGRAM_ID : strategy!.strategy.tokenATokenProgram; mintsToCreateAtas = [ { mint: collTokenMint, tokenProgram: collReserve!.getLiquidityTokenProgram(), }, { mint: debtTokenMint, tokenProgram: debtReserve!.getLiquidityTokenProgram(), }, { mint: collReserve!.getCTokenMint(), tokenProgram: TOKEN_PROGRAM_ID, }, { mint: secondTokenAta, tokenProgram: secondTokenTokenProgarm, }, ]; } else { mintsToCreateAtas = [ { mint: collTokenMint, tokenProgram: collReserve!.getLiquidityTokenProgram(), }, { mint: debtTokenMint, tokenProgram: debtReserve!.getLiquidityTokenProgram(), }, { mint: collReserve!.getCTokenMint(), tokenProgram: TOKEN_PROGRAM_ID, }, ]; } const atasAndCreateIxns = createAtasIdempotent(owner, mintsToCreateAtas); // 2. Create borrow flash loan instruction const { flashBorrowIxn, flashRepayIxn } = getFlashLoanInstructions({ borrowIxnIndex: budgetIxns.length + atasAndCreateIxns.length, // TODO: how about user metadata ixns walletPublicKey: owner, lendingMarketAuthority: kaminoMarket.getLendingMarketAuthority(), lendingMarketAddress: kaminoMarket.getAddress(), reserve: !collIsKtoken ? collReserve! : debtReserve!, amountLamports: toLamports( !collIsKtoken ? calcs.adjustDepositPosition : calcs.amountToFlashBorrowDebt, !collIsKtoken ? collReserve!.stats.decimals : debtReserve!.stats.decimals ), destinationAta: !collIsKtoken ? collTokenAta : debtTokenAta, referrerAccount: kaminoMarket.programId, referrerTokenState: kaminoMarket.programId, programId: kaminoMarket.programId, }); const depositAction = await KaminoAction.buildDepositTxns( kaminoMarket, toLamports(calcs.adjustDepositPosition, collReserve!.stats.decimals).floor().toString(), collTokenMint, owner, obligation, 0, false, false, false, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit) referrer, currentSlot, { includeScopeRefresh: true, scopeFeed: scopeFeed! } ); // 4. Borrow tokens in borrow token reserve that will be swapped to repay flash loan const borrowAction = await KaminoAction.buildBorrowTxns( kaminoMarket, toLamports(calcs.borrowAmount, debtReserve!.stats.decimals).ceil().toString(), debtTokenMint, owner, obligation, 0, false, false, false, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit) referrer, currentSlot, { includeScopeRefresh: true, scopeFeed: scopeFeed! } ); const swapInstructions = removeBudgetAndAtaIxns(swapQuoteIxs.swapIxs, []); const ixs = !collIsKtoken ? [ ...budgetIxns, ...atasAndCreateIxns.map((x) => x.createAtaIx), ...[flashBorrowIxn], ...depositAction.setupIxs, ...depositAction.lendingIxs, ...depositAction.cleanupIxs, ...borrowAction.setupIxs, ...borrowAction.lendingIxs, ...borrowAction.cleanupIxs, ...swapInstructions, ...[flashRepayIxn], ] : [ ...budgetIxns, ...atasAndCreateIxns.map((x) => x.createAtaIx), ...[flashBorrowIxn], ...swapInstructions, ...depositAction.setupIxs, ...depositAction.lendingIxs, ...depositAction.cleanupIxs, ...borrowAction.setupIxs, ...borrowAction.lendingIxs, ...borrowAction.cleanupIxs, ...[flashRepayIxn], ]; return ixs; } /** * Withdraw and repay tokens if leverage decreased */ async function buildDecreaseLeverageIxns( owner: PublicKey, kaminoMarket: KaminoMarket, collTokenMint: PublicKey, debtTokenMint: PublicKey, obligation: KaminoObligation, referrer: PublicKey, currentSlot: number, calcs: AdjustLeverageCalcsResult, strategy: StrategyWithAddress | undefined, scopeFeed: string | undefined, collIsKtoken: boolean, swapQuoteIxs: SwapQuoteIxs, budgetAndPriorityFeeIxns: TransactionInstruction[] | undefined ): Promise<TransactionInstruction[]> { const collReserve = kaminoMarket.getReserveByMint(collTokenMint); const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint); const debtTokenAta = getAssociatedTokenAddressSync(debtTokenMint, owner); // 1. Create atas & budget txns const budgetIxns = budgetAndPriorityFeeIxns || getComputeBudgetAndPriorityFeeIxns(3000000); let mintsToCreateAtas: Array<{ mint: PublicKey; tokenProgram: PublicKey }>; if (collIsKtoken) { const secondTokenAta = strategy!.strategy.tokenAMint.equals(debtTokenMint) ? strategy!.strategy.tokenBMint : strategy!.strategy.tokenAMint; const secondTokenTokenProgarm = strategy?.strategy.tokenAMint.equals(debtTokenMint) ? strategy!.strategy.tokenBTokenProgram.equals(PublicKey.default) ? TOKEN_PROGRAM_ID : strategy!.strategy.tokenBTokenProgram : strategy!.strategy.tokenATokenProgram.equals(PublicKey.default) ? TOKEN_PROGRAM_ID : strategy!.strategy.tokenATokenProgram; mintsToCreateAtas = [ { mint: collTokenMint, tokenProgram: collReserve!.getLiquidityTokenProgram(), }, { mint: debtTokenMint, tokenProgram: debtReserve!.getLiquidityTokenProgram(), }, { mint: collReserve!.getCTokenMint(), tokenProgram: TOKEN_PROGRAM_ID, }, { mint: secondTokenAta, tokenProgram: secondTokenTokenProgarm, }, ]; } else { mintsToCreateAtas = [ { mint: collTokenMint, tokenProgram: collReserve!.getLiquidityTokenProgram(), }, { mint: debtTokenMint, tokenProgram: debtReserve!.getLiquidityTokenProgram(), }, { mint: collReserve!.getCTokenMint(), tokenProgram: TOKEN_PROGRAM_ID, }, ]; } const atasAndCreateIxns = createAtasIdempotent(owner, mintsToCreateAtas); // TODO: Mihai/Marius check if we can improve this logic and not convert any SOL // This is here so that we have enough wsol to repay in case the kAB swapped to sol after estimates is not enough const closeWsolAtaIxns: TransactionInstruction[] = []; const fillWsolAtaIxns: TransactionInstruction[] = []; if (debtTokenMint.equals(WRAPPED_SOL_MINT)) { const wsolAta = getAssociatedTokenAddress(WRAPPED_SOL_MINT, owner, false); closeWsolAtaIxns.push(createCloseAccountInstruction(wsolAta, owner, owner, [], TOKEN_PROGRAM_ID)); const halfSolBalance = (await kaminoMarket.getConnection().getBalance(owner)) / LAMPORTS_PER_SOL / 2; const balanceToWrap = halfSolBalance < 0.1 ? halfSolBalance : 0.1; fillWsolAtaIxns.push( ...getDepositWsolIxns(owner, wsolAta, toLamports(balanceToWrap, debtReserve!.stats.decimals).ceil()) ); } // 3. Flash borrow & repay amount to repay (debt) const { flashBorrowIxn, flashRepayIxn } = getFlashLoanInstructions({ borrowIxnIndex: budgetIxns.length + atasAndCreateIxns.length + fillWsolAtaIxns.length, walletPublicKey: owner, lendingMarketAuthority: kaminoMarket.getLendingMarketAuthority(), lendingMarketAddress: kaminoMarket.getAddress(), reserve: debtReserve!, amountLamports: toLamports(Decimal.abs(calcs.adjustBorrowPosition), debtReserve!.stats.decimals), destinationAta: debtTokenAta, referrerAccount: kaminoMarket.programId, referrerTokenState: kaminoMarket.programId, programId: kaminoMarket.programId, }); // 4. Actually do the repay of the flash borrowed amounts const scopeRefresh = scopeFeed ? { includeScopeRefresh: true, scopeFeed: scopeFeed } : undefined; const repayAction = await KaminoAction.buildRepayTxns( kaminoMarket, toLamports(Decimal.abs(calcs.adjustBorrowPosition), debtReserve!.stats.decimals).floor().toString(), debtTokenMint, owner, obligation, currentSlot, undefined, 0, false, false, false, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit) referrer, scopeRefresh ); // 6. Withdraw collateral (a little bit more to be able to pay for the slippage on swap) const withdrawAction = await KaminoAction.buildWithdrawTxns( kaminoMarket, toLamports(calcs.withdrawAmountWithSlippageAndFlashLoanFee, collReserve!.stats.decimals).ceil().toString(), collTokenMint, owner, obligation, 0, false, false, false, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit) referrer, currentSlot, { includeScopeRefresh: true, scopeFeed: scopeFeed! } ); const swapInstructions = removeBudgetAndAtaIxns(swapQuoteIxs.swapIxs, []); const ixns = [ ...budgetIxns, ...atasAndCreateIxns.map((x) => x.createAtaIx), ...fillWsolAtaIxns, ...[flashBorrowIxn], ...repayAction.setupIxs, ...repayAction.lendingIxs, ...repayAction.cleanupIxs, ...withdrawAction.setupIxs, ...withdrawAction.lendingIxs, ...withdrawAction.cleanupIxs, ...swapInstructions, ...[flashRepayIxn], ...closeWsolAtaIxns, ]; return ixns; }