@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
1,548 lines (1,431 loc) • 132 kB
text/typescript
import {
AccountRole,
Address,
fetchEncodedAccount,
AccountMeta,
Instruction,
isNone,
isSome,
none,
Option,
Slot,
some,
TransactionSigner,
} from '@solana/kit';
import BN from 'bn.js';
import Decimal from 'decimal.js';
import {
borrowObligationLiquidity,
borrowObligationLiquidityV2,
depositAndWithdraw,
depositObligationCollateral,
depositObligationCollateralV2,
depositReserveLiquidity,
depositReserveLiquidityAndObligationCollateral,
depositReserveLiquidityAndObligationCollateralV2,
initObligation,
initObligationFarmsForReserve,
InitObligationFarmsForReserveAccounts,
InitObligationFarmsForReserveArgs,
initReferrerTokenState,
initUserMetadata,
liquidateObligationAndRedeemReserveCollateral,
liquidateObligationAndRedeemReserveCollateralV2,
redeemReserveCollateral,
refreshObligation,
refreshObligationFarmsForReserve,
RefreshObligationFarmsForReserveAccounts,
RefreshObligationFarmsForReserveArgs,
refreshReserve,
repayAndWithdrawAndRedeem,
repayObligationLiquidity,
repayObligationLiquidityV2,
requestElevationGroup,
RequestElevationGroupAccounts,
RequestElevationGroupArgs,
setObligationOrder,
withdrawObligationCollateralAndRedeemReserveCollateral,
withdrawObligationCollateralAndRedeemReserveCollateralV2,
withdrawReferrerFees,
} from '../@codegen/klend/instructions';
import {
buildComputeBudgetIx,
createAssociatedTokenAccountIdempotentInstruction,
createAtasIdempotent,
createLookupTableIx,
DEFAULT_PUBLIC_KEY,
getAssociatedTokenAddress,
isNotNullPubkey,
obligationFarmStatePda,
ObligationType,
referrerTokenStatePda,
ScopePriceRefreshConfig,
SOL_PADDING_FOR_INTEREST,
U64_MAX,
userMetadataPda,
WRAPPED_SOL_MINT,
} from '../utils';
import { getTokenIdsForScopeRefresh, KaminoMarket } from './market';
import { isKaminoObligation, KaminoObligation } from './obligation';
import { KaminoReserve } from './reserve';
import { ReserveFarmKind } from '../@codegen/klend/types';
import { PROGRAM_ID as FARMS_PROGRAM_ID } from '@kamino-finance/farms-sdk/dist/@codegen/farms/programId';
import { Reserve } from '../@codegen/klend/accounts';
import { VanillaObligation } from '../utils/ObligationType';
import { Scope } from '@kamino-finance/scope-sdk';
import { ObligationOrderAtIndex } from './obligationOrder';
import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
import { SYSVAR_INSTRUCTIONS_ADDRESS, SYSVAR_RENT_ADDRESS } from '@solana/sysvars';
import { getCloseAccountInstruction, getSyncNativeInstruction } from '@solana-program/token-2022';
import { getTransferSolInstruction, SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
import { noopSigner } from '../utils/signer';
export type ActionType =
| 'deposit'
| 'borrow'
| 'withdraw'
| 'repay'
| 'mint'
| 'redeem'
| 'depositCollateral'
| 'liquidate'
| 'depositAndBorrow'
| 'repayAndWithdraw'
| 'refreshObligation'
| 'requestElevationGroup'
| 'withdrawReferrerFees'
| 'repayAndWithdrawV2'
| 'depositAndWithdraw';
export type AuxiliaryIx = 'setup' | 'inBetween' | 'cleanup';
export class KaminoAction {
kaminoMarket: KaminoMarket;
reserve: KaminoReserve;
outflowReserve: KaminoReserve | undefined;
owner: TransactionSigner;
payer: TransactionSigner;
obligation: KaminoObligation | ObligationType;
referrer: Option<Address>;
/**
* Null unless the obligation is not passed
*/
obligationType: ObligationType | null = null;
mint: Address;
secondaryMint?: Address;
positions?: number;
amount: BN;
outflowAmount?: BN;
computeBudgetIxs: Array<Instruction>;
computeBudgetIxsLabels: Array<string>;
setupIxs: Array<Instruction>;
setupIxsLabels: Array<string>;
inBetweenIxs: Array<Instruction>;
inBetweenIxsLabels: Array<string>;
lendingIxs: Array<Instruction>;
lendingIxsLabels: Array<string>;
cleanupIxs: Array<Instruction>;
cleanupIxsLabels: Array<string>;
refreshFarmsCleanupTxnIxs: Array<Instruction>;
refreshFarmsCleanupTxnIxsLabels: Array<string>;
depositReserves: Array<Address>;
borrowReserves: Array<Address>;
preLoadedDepositReservesSameTx: Array<Address>;
currentSlot: Slot;
private constructor(
kaminoMarket: KaminoMarket,
owner: TransactionSigner,
obligation: KaminoObligation | ObligationType,
mint: Address,
positions: number,
amount: string | BN,
depositReserves: Array<Address>,
borrowReserves: Array<Address>,
reserveState: KaminoReserve,
currentSlot: Slot,
secondaryMint?: Address,
outflowReserveState?: KaminoReserve,
outflowAmount?: string | BN,
referrer: Option<Address> = none(),
payer?: TransactionSigner
) {
this.kaminoMarket = kaminoMarket;
this.obligation = obligation;
this.owner = owner;
this.payer = payer ?? owner;
this.amount = new BN(amount);
this.mint = mint;
this.positions = positions;
this.computeBudgetIxs = [];
this.computeBudgetIxsLabels = [];
this.setupIxs = [];
this.setupIxsLabels = [];
this.inBetweenIxs = [];
this.inBetweenIxsLabels = [];
this.lendingIxs = [];
this.lendingIxsLabels = [];
this.cleanupIxs = [];
this.cleanupIxsLabels = [];
this.refreshFarmsCleanupTxnIxs = [];
this.refreshFarmsCleanupTxnIxsLabels = [];
this.depositReserves = depositReserves;
this.borrowReserves = borrowReserves;
this.secondaryMint = secondaryMint;
this.reserve = reserveState;
this.outflowReserve = outflowReserveState;
this.outflowAmount = outflowAmount ? new BN(outflowAmount) : undefined;
this.preLoadedDepositReservesSameTx = [];
this.referrer = referrer;
this.currentSlot = currentSlot;
}
static async initialize(
action: ActionType,
amount: string | BN,
mint: Address,
owner: TransactionSigner,
kaminoMarket: KaminoMarket,
obligation: KaminoObligation | ObligationType,
referrer: Option<Address> = none(),
currentSlot: Slot = 0n,
payer: TransactionSigner = owner
) {
const reserve = kaminoMarket.getReserveByMint(mint);
if (reserve === undefined) {
throw new Error(`Reserve ${mint} not found in market ${kaminoMarket.getAddress()}`);
}
const { kaminoObligation, depositReserves, borrowReserves, distinctReserveCount } =
await KaminoAction.loadObligation(action, kaminoMarket, owner.address, reserve.address, obligation);
const referrerKey = await this.getReferrerKey(kaminoMarket, owner.address, kaminoObligation, referrer);
return new KaminoAction(
kaminoMarket,
owner,
kaminoObligation || obligation,
mint,
distinctReserveCount,
amount,
depositReserves,
borrowReserves,
reserve,
currentSlot,
undefined,
undefined,
undefined,
referrerKey,
payer
);
}
private static async getUserAccountAddresses(owner: Address, reserve: Reserve) {
const [userTokenAccountAddress, userCollateralAccountAddress] = await Promise.all([
getAssociatedTokenAddress(
reserve.liquidity.mintPubkey,
owner,
reserve.liquidity.tokenProgram,
ASSOCIATED_TOKEN_PROGRAM_ADDRESS
),
getAssociatedTokenAddress(
reserve.collateral.mintPubkey,
owner,
TOKEN_PROGRAM_ADDRESS,
ASSOCIATED_TOKEN_PROGRAM_ADDRESS
),
]);
return { userTokenAccountAddress, userCollateralAccountAddress };
}
private static async loadObligation(
action: ActionType,
kaminoMarket: KaminoMarket,
owner: Address,
reserve: Address,
obligation: KaminoObligation | ObligationType,
outflowReserve?: Address
) {
let kaminoObligation: KaminoObligation | null;
const depositReserves: Array<Address> = [];
const borrowReserves: Array<Address> = [];
if (obligation instanceof KaminoObligation) {
kaminoObligation = obligation;
} else {
const obligationAddress = await obligation.toPda(kaminoMarket.getAddress(), owner);
kaminoObligation = await KaminoObligation.load(kaminoMarket, obligationAddress);
}
if (kaminoObligation !== null) {
depositReserves.push(...[...kaminoObligation.deposits.keys()]);
borrowReserves.push(...[...kaminoObligation.borrows.keys()]);
}
if (!outflowReserve && action === 'depositAndBorrow') {
throw new Error(`Outflow reserve has not been set for depositAndBorrow`);
}
// Union of addresses
const distinctReserveCount =
new Set<Address>([
...borrowReserves.map((e) => e),
...(action === 'borrow' ? [reserve] : []),
...(action === 'depositAndBorrow' ? [reserve] : []),
]).size +
new Set<Address>([
...depositReserves.map((e) => e),
...(action === 'deposit' ? [reserve] : []),
...(action === 'depositAndBorrow' ? [outflowReserve!] : []),
]).size;
return {
kaminoObligation,
depositReserves,
borrowReserves,
distinctReserveCount,
};
}
static async buildRefreshObligationTxns(
kaminoMarket: KaminoMarket,
payer: TransactionSigner,
obligation: KaminoObligation,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
currentSlot: Slot = 0n
) {
// placeholder for action initialization
const firstReserve = obligation.getDeposits()[0].reserveAddress;
const firstKaminoReserve = kaminoMarket.getReserveByAddress(firstReserve);
if (!firstKaminoReserve) {
throw new Error(`Reserve ${firstReserve} not found`);
}
const axn = await KaminoAction.initialize(
'refreshObligation',
'0',
firstKaminoReserve?.getLiquidityMint(),
noopSigner(obligation.state.owner), // owner does not need to sign for refresh
kaminoMarket,
obligation,
undefined,
currentSlot
);
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addRefreshObligation(payer);
return axn;
}
static async buildRequestElevationGroupTxns(
kaminoMarket: KaminoMarket,
owner: TransactionSigner,
obligation: KaminoObligation,
elevationGroup: number,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
currentSlot: Slot = 0n
) {
const firstReserve = obligation.state.deposits.find((x) => x.depositReserve !== DEFAULT_PUBLIC_KEY)!.depositReserve;
const firstKaminoReserve = kaminoMarket.getReserveByAddress(firstReserve);
if (!firstKaminoReserve) {
throw new Error(`Reserve ${firstReserve} not found`);
}
const axn = await KaminoAction.initialize(
'requestElevationGroup',
'0',
firstKaminoReserve?.getLiquidityMint(),
owner,
kaminoMarket,
obligation,
undefined,
currentSlot
);
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addRefreshObligation(owner);
await axn.addRequestElevationIx(elevationGroup, 'setup');
return axn;
}
static async buildDepositTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: Address,
owner: TransactionSigner,
obligation: KaminoObligation | ObligationType,
useV2Ixs: boolean,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false, // to be requested *before* the deposit
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none(),
currentSlot: Slot = 0n,
overrideElevationGroupRequest: number | undefined = undefined // if set, when an elevationgroup request is made, it will use this value
) {
const axn = await KaminoAction.initialize(
'deposit',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addSupportIxs(
'deposit',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarm,
useV2Ixs,
scopeRefreshConfig,
initUserMetadata,
undefined,
overrideElevationGroupRequest
);
if (useV2Ixs) {
await axn.addDepositIxV2();
} else {
await axn.addDepositIx();
}
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
async addScopeRefreshIxs(scope: Scope, tokens: number[], scopeConfig: Address) {
const refreshIx = await scope.refreshPriceListIx({ config: scopeConfig }, tokens);
if (refreshIx) {
this.setupIxsLabels.unshift(`refreshScopePrices`);
this.setupIxs.unshift(refreshIx);
}
}
static async buildBorrowTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: Address,
owner: TransactionSigner,
obligation: KaminoObligation | ObligationType,
useV2Ixs: boolean,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none(),
currentSlot: Slot = 0n,
overrideElevationGroupRequest: number | undefined = undefined // if set, when an elevationgroup request is made, it will use this value
) {
const axn = await KaminoAction.initialize(
'borrow',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
if (isSome(axn.referrer)) {
const referrerTokenState = await referrerTokenStatePda(
axn.referrer.value,
axn.reserve.address,
axn.kaminoMarket.programId
);
const account = await fetchEncodedAccount(kaminoMarket.getRpc(), referrerTokenState);
if (!account.exists) {
axn.addInitReferrerTokenStateIx(axn.reserve, referrerTokenState);
}
}
await axn.addSupportIxs(
'borrow',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarm,
useV2Ixs,
scopeRefreshConfig,
initUserMetadata,
undefined,
overrideElevationGroupRequest
);
if (useV2Ixs) {
await axn.addBorrowIxV2();
} else {
await axn.addBorrowIx();
}
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildDepositReserveLiquidityTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: Address,
owner: TransactionSigner,
obligation: KaminoObligation | ObligationType,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas
requestElevationGroup: boolean = false,
referrer: Option<Address> = none(),
currentSlot: Slot = 0n
) {
const axn = await KaminoAction.initialize(
'mint',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addSupportIxs(
'mint',
includeAtaIxs,
requestElevationGroup,
false,
addInitObligationForFarm,
scopeRefreshConfig,
{ skipInitialization: true, skipLutCreation: true }
);
await axn.addDepositReserveLiquidityIx();
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildRedeemReserveCollateralTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: Address,
owner: TransactionSigner,
obligation: KaminoObligation | ObligationType,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas
requestElevationGroup: boolean = false,
referrer: Option<Address> = none(),
currentSlot: Slot = 0n
) {
const axn = await KaminoAction.initialize(
'redeem',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addSupportIxs(
'redeem',
includeAtaIxs,
requestElevationGroup,
false,
addInitObligationForFarm,
scopeRefreshConfig,
{ skipInitialization: true, skipLutCreation: true }
);
await axn.addRedeemReserveCollateralIx();
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildDepositObligationCollateralTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: Address,
owner: TransactionSigner,
obligation: KaminoObligation | ObligationType,
useV2Ixs: boolean,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas
requestElevationGroup: boolean = false,
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none(),
currentSlot: Slot = 0n
) {
const axn = await KaminoAction.initialize(
'depositCollateral',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addSupportIxs(
'depositCollateral',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarm,
useV2Ixs,
scopeRefreshConfig,
initUserMetadata
);
if (useV2Ixs) {
await axn.addDepositObligationCollateralIxV2();
} else {
await axn.addDepositObligationCollateralIx();
}
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildDepositAndBorrowTxns(
kaminoMarket: KaminoMarket,
depositAmount: string | BN,
depositMint: Address,
borrowAmount: string | BN,
borrowMint: Address,
owner: TransactionSigner,
obligation: KaminoObligation | ObligationType,
useV2Ixs: boolean,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none(),
currentSlot: Slot = 0n
) {
const axn = await KaminoAction.initializeMultiTokenAction(
kaminoMarket,
'depositAndBorrow',
depositAmount,
depositMint,
borrowMint,
owner,
owner.address,
obligation,
borrowAmount,
referrer,
currentSlot
);
const addInitObligationForFarmForDeposit = true;
const addInitObligationForFarmForBorrow = false;
const twoTokenAction = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
if (isSome(axn.referrer)) {
const referrerTokenState = await referrerTokenStatePda(
axn.referrer.value,
axn.outflowReserve!.address,
axn.kaminoMarket.programId
);
const account = await fetchEncodedAccount(axn.kaminoMarket.getRpc(), referrerTokenState);
if (!account.exists) {
axn.addInitReferrerTokenStateIx(axn.outflowReserve!, referrerTokenState);
}
}
await axn.addSupportIxs(
'deposit',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarmForDeposit,
useV2Ixs,
undefined,
initUserMetadata,
twoTokenAction
);
if (useV2Ixs) {
await axn.addDepositAndBorrowIxV2();
} else {
await axn.addDepositAndBorrowIx();
}
await axn.addInBetweenIxs(
'depositAndBorrow',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarmForBorrow,
useV2Ixs
);
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
// Create the scope refresh ix in here to ensure it's the first ix in the tx
const allReserves = [
...new Set<Address>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
...(axn.outflowReserve ? [axn.outflowReserve.address] : []),
...(axn.preLoadedDepositReservesSameTx ? axn.preLoadedDepositReservesSameTx : []),
]),
];
const scopeTokensMap = getTokenIdsForScopeRefresh(axn.kaminoMarket, allReserves);
if (scopeTokensMap.size > 0 && scopeRefreshConfig) {
for (const [configPubkey, config] of scopeRefreshConfig.scopeConfigurations) {
const tokenIds = scopeTokensMap.get(config.oraclePrices);
if (tokenIds && tokenIds.length > 0) {
await axn.addScopeRefreshIxs(scopeRefreshConfig.scope, tokenIds, configPubkey);
}
}
}
return axn;
}
static async buildDepositAndWithdrawV2Txns(
kaminoMarket: KaminoMarket,
depositAmount: string | BN,
depositMint: Address,
withdrawAmount: string | BN,
withdrawMint: Address,
owner: TransactionSigner,
currentSlot: Slot,
obligation: KaminoObligation | ObligationType,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none()
) {
const axn = await KaminoAction.initializeMultiTokenAction(
kaminoMarket,
'depositAndWithdraw',
depositAmount,
depositMint,
withdrawMint,
owner,
owner.address,
obligation,
withdrawAmount,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
const twoTokenAction = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addSupportIxs(
'depositAndWithdraw',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarm,
true,
scopeRefreshConfig,
initUserMetadata,
twoTokenAction
);
const withdrawCollateralAmount = axn.getWithdrawCollateralAmount(axn.outflowReserve!, axn.outflowAmount!);
await axn.addDepositAndWithdrawV2Ixs(withdrawCollateralAmount);
return axn;
}
static async buildRepayAndWithdrawV2Txns(
kaminoMarket: KaminoMarket,
repayAmount: string | BN,
repayMint: Address,
withdrawAmount: string | BN,
withdrawMint: Address,
payer: TransactionSigner,
currentSlot: Slot,
obligation: KaminoObligation | ObligationType,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none()
) {
const axn = await KaminoAction.initializeMultiTokenAction(
kaminoMarket,
'repayAndWithdrawV2',
repayAmount,
repayMint,
withdrawMint,
payer,
payer.address,
obligation,
withdrawAmount,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
const twoTokenAction = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addSupportIxs(
'repayAndWithdrawV2',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarm,
true,
scopeRefreshConfig,
initUserMetadata,
twoTokenAction
);
const withdrawCollateralAmount = axn.getWithdrawCollateralAmount(axn.outflowReserve!, axn.outflowAmount!);
await axn.addRepayAndWithdrawV2Ixs(withdrawCollateralAmount);
return axn;
}
static async buildRepayAndWithdrawTxns(
kaminoMarket: KaminoMarket,
repayAmount: string | BN,
repayMint: Address,
withdrawAmount: string | BN,
withdrawMint: Address,
payer: TransactionSigner,
currentSlot: Slot,
obligation: KaminoObligation | ObligationType,
useV2Ixs: boolean,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none()
) {
const axn = await KaminoAction.initializeMultiTokenAction(
kaminoMarket,
'repayAndWithdraw',
repayAmount,
repayMint,
withdrawMint,
payer,
payer.address,
obligation,
withdrawAmount,
referrer,
currentSlot
);
const addInitObligationForFarmForRepay = true;
const addInitObligationForFarmForWithdraw = false;
const twoTokenAction = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addSupportIxs(
'repay',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarmForRepay,
useV2Ixs,
undefined,
initUserMetadata,
twoTokenAction
);
const withdrawCollateralAmount = axn.getWithdrawCollateralAmount(axn.outflowReserve!, axn.outflowAmount!);
if (useV2Ixs) {
await axn.addRepayAndWithdrawIxsV2(withdrawCollateralAmount);
} else {
await axn.addRepayAndWithdrawIxs(withdrawCollateralAmount);
}
await axn.addInBetweenIxs(
'repayAndWithdraw',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarmForWithdraw,
useV2Ixs
);
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
// Create the scope refresh ix in here to ensure it's the first ix in the tx
const allReserves = [
...new Set<Address>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
...(axn.outflowReserve ? [axn.outflowReserve.address] : []),
...(axn.preLoadedDepositReservesSameTx ? axn.preLoadedDepositReservesSameTx : []),
]),
];
const scopeTokensMap = getTokenIdsForScopeRefresh(axn.kaminoMarket, allReserves);
if (scopeTokensMap.size > 0 && scopeRefreshConfig) {
for (const [configPubkey, config] of scopeRefreshConfig.scopeConfigurations) {
const tokenIds = scopeTokensMap.get(config.oraclePrices);
if (tokenIds && tokenIds.length > 0) {
await axn.addScopeRefreshIxs(scopeRefreshConfig.scope, tokenIds, configPubkey);
}
}
}
return axn;
}
static async buildWithdrawTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: Address,
owner: TransactionSigner,
obligation: KaminoObligation | ObligationType,
useV2Ixs: boolean,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false, // to be requested *after* the withdraw
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none(),
currentSlot: Slot = 0n,
overrideElevationGroupRequest?: number,
// Optional customizations which may be needed if the obligation was mutated by some previous ix.
obligationCustomizations?: {
// Any newly-added deposit reserves.
addedDepositReserves?: Address[];
}
) {
const axn = await KaminoAction.initialize(
'withdraw',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
axn.depositReserves.push(...(obligationCustomizations?.addedDepositReserves || []));
await axn.addSupportIxs(
'withdraw',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarm,
useV2Ixs,
scopeRefreshConfig,
initUserMetadata,
false,
overrideElevationGroupRequest
);
const collateralAmount = axn.getWithdrawCollateralAmount(axn.reserve, axn.amount);
if (useV2Ixs) {
await axn.addWithdrawIxV2(collateralAmount);
} else {
await axn.addWithdrawIx(collateralAmount);
}
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
/**
*
* @param kaminoMarket
* @param amount
* @param mint
* @param owner
* @param obligation - obligation to repay or the PDA seeds
* @param useV2Ixs
* @param scopeRefreshConfig
* @param currentSlot
* @param payer - if not set then owner is used
* @param extraComputeBudget - if > 0 then adds the ix
* @param includeAtaIxs - if true it includes create and close wsol and token atas
* @param requestElevationGroup
* @param initUserMetadata
* @param referrer
*/
static async buildRepayTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: Address,
owner: TransactionSigner,
obligation: KaminoObligation | ObligationType,
useV2Ixs: boolean,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
currentSlot: Slot,
payer: TransactionSigner = owner,
extraComputeBudget: number = 1_000_000,
includeAtaIxs: boolean = true,
requestElevationGroup: boolean = false,
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none()
) {
const axn = await KaminoAction.initialize(
'repay',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot,
payer
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addSupportIxs(
'repay',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarm,
useV2Ixs,
scopeRefreshConfig,
initUserMetadata
);
if (useV2Ixs) {
await axn.addRepayIxV2();
} else {
await axn.addRepayIx();
}
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildLiquidateTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
minCollateralReceiveAmount: string | BN,
repayTokenMint: Address,
withdrawTokenMint: Address,
liquidator: TransactionSigner,
obligationOwner: Address,
obligation: KaminoObligation | ObligationType,
useV2Ixs: boolean,
scopeRefreshConfig: ScopePriceRefreshConfig | undefined = undefined,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ix
includeAtaIxs: boolean = true, // if true it includes create and close wsol and token atas, and creates all other token atas if they don't exist
requestElevationGroup: boolean = false,
initUserMetadata: { skipInitialization: boolean; skipLutCreation: boolean } = {
skipInitialization: false,
skipLutCreation: false,
},
referrer: Option<Address> = none(),
maxAllowedLtvOverridePercent: number = 0,
currentSlot: Slot = 0n
): Promise<KaminoAction> {
const axn = await KaminoAction.initializeMultiTokenAction(
kaminoMarket,
'liquidate',
amount,
repayTokenMint,
withdrawTokenMint,
liquidator,
obligationOwner,
obligation,
minCollateralReceiveAmount,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIx(extraComputeBudget);
}
await axn.addSupportIxs(
'liquidate',
includeAtaIxs,
requestElevationGroup,
addInitObligationForFarm,
useV2Ixs,
scopeRefreshConfig,
initUserMetadata
);
if (useV2Ixs) {
await axn.addLiquidateIxV2(maxAllowedLtvOverridePercent);
} else {
await axn.addLiquidateIx(maxAllowedLtvOverridePercent);
}
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildWithdrawReferrerFeeTxns(
owner: TransactionSigner,
tokenMint: Address,
kaminoMarket: KaminoMarket,
currentSlot: Slot = 0n
) {
const { axn, createAtaIxs } = await KaminoAction.initializeWithdrawReferrerFees(
tokenMint,
owner,
kaminoMarket,
currentSlot
);
axn.setupIxs.push(...createAtaIxs);
axn.setupIxsLabels.push(`createAtasIxs[${axn.owner.toString()}]`);
if (isSome(axn.referrer)) {
const referrerTokenState = await referrerTokenStatePda(
axn.referrer.value,
axn.reserve.address,
axn.kaminoMarket.programId
);
const account = await fetchEncodedAccount(axn.kaminoMarket.getRpc(), referrerTokenState);
if (!account.exists) {
axn.addInitReferrerTokenStateIx(axn.reserve, referrerTokenState);
}
}
axn.addRefreshReserveIxs([axn.reserve.address]);
await axn.addWithdrawReferrerFeesIxs();
return axn;
}
/**
* Builds an instruction for setting the new state of one of the given obligation's orders.
*
* In other words: it will overwrite the given slot in the {@link Obligation.orders} array. This possibly includes
* setting the `null` state (i.e. cancelling the order).
*/
static buildSetObligationOrderIxn(
owner: TransactionSigner,
kaminoMarket: KaminoMarket,
obligation: KaminoObligation,
orderAtIndex: ObligationOrderAtIndex
): Instruction {
return setObligationOrder(
{
index: orderAtIndex.index,
order: orderAtIndex.orderState(),
},
{
lendingMarket: kaminoMarket.getAddress(),
obligation: obligation.obligationAddress,
owner,
},
undefined,
kaminoMarket.programId
);
}
async addDepositReserveLiquidityIx() {
this.lendingIxsLabels.push(`depositReserveLiquidity`);
this.lendingIxs.push(
depositReserveLiquidity(
{
liquidityAmount: this.amount,
},
{
owner: this.owner,
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
reserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveLiquiditySupply: this.reserve.state.liquidity.supplyVault,
reserveCollateralMint: this.reserve.getCTokenMint(),
userSourceLiquidity: await this.getUserTokenAccountAddress(this.reserve),
userDestinationCollateral: await this.getUserCollateralAccountAddress(this.reserve),
collateralTokenProgram: TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
undefined,
this.kaminoMarket.programId
)
);
}
async addRedeemReserveCollateralIx() {
this.lendingIxsLabels.push(`redeemReserveCollateral`);
this.lendingIxs.push(
redeemReserveCollateral(
{
collateralAmount: this.amount,
},
{
owner: this.owner,
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
reserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveLiquiditySupply: this.reserve.state.liquidity.supplyVault,
reserveCollateralMint: this.reserve.getCTokenMint(),
userSourceCollateral: await this.getUserCollateralAccountAddress(this.reserve),
userDestinationLiquidity: await this.getUserTokenAccountAddress(this.reserve),
collateralTokenProgram: TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
undefined,
this.kaminoMarket.programId
)
);
}
// @deprecated -- use addDepositIxV2 instead
async addDepositIx() {
this.lendingIxsLabels.push(`depositReserveLiquidityAndObligationCollateral`);
this.lendingIxs.push(
depositReserveLiquidityAndObligationCollateral(
{
liquidityAmount: this.amount,
},
{
owner: this.owner,
obligation: await this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
reserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveLiquiditySupply: this.reserve.state.liquidity.supplyVault,
reserveCollateralMint: this.reserve.getCTokenMint(),
reserveDestinationDepositCollateral: this.reserve.state.collateral.supplyVault, // destinationCollateral
userSourceLiquidity: await this.getUserTokenAccountAddress(this.reserve),
placeholderUserDestinationCollateral: none(),
collateralTokenProgram: TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
undefined,
this.kaminoMarket.programId
)
);
}
async addDepositIxV2() {
const { collateralFarmAccounts: farmsAccounts } = await KaminoAction.getFarmAccountsForReserve(
await this.getObligationPda(),
this.reserve
);
this.lendingIxsLabels.push(`depositReserveLiquidityAndObligationCollateralV2`);
this.lendingIxs.push(
depositReserveLiquidityAndObligationCollateralV2(
{
liquidityAmount: this.amount,
},
{
depositAccounts: {
owner: this.owner,
obligation: await this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
reserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveLiquiditySupply: this.reserve.state.liquidity.supplyVault,
reserveCollateralMint: this.reserve.getCTokenMint(),
reserveDestinationDepositCollateral: this.reserve.state.collateral.supplyVault, // destinationCollateral
userSourceLiquidity: await this.getUserTokenAccountAddress(this.reserve),
placeholderUserDestinationCollateral: none(),
collateralTokenProgram: TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
farmsAccounts,
farmsProgram: FARMS_PROGRAM_ID,
},
undefined,
this.kaminoMarket.programId
)
);
}
/// @deprecated -- use addDepositObligationCollateralIxV2 instead
async addDepositObligationCollateralIx() {
this.lendingIxsLabels.push(`depositObligationCollateral`);
this.lendingIxs.push(
depositObligationCollateral(
{
collateralAmount: this.amount,
},
{
owner: this.owner,
obligation: await this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
depositReserve: this.reserve.address,
reserveDestinationCollateral: this.reserve.state.collateral.supplyVault,
userSourceCollateral: await this.getUserCollateralAccountAddress(this.reserve),
tokenProgram: TOKEN_PROGRAM_ADDRESS,
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
undefined,
this.kaminoMarket.programId
)
);
}
async addDepositObligationCollateralIxV2() {
const obligationAddress = await this.getObligationPda();
const { collateralFarmAccounts: farmsAccounts } = await KaminoAction.getFarmAccountsForReserve(
obligationAddress,
this.reserve
);
this.lendingIxsLabels.push(`depositObligationCollateralV2`);
this.lendingIxs.push(
depositObligationCollateralV2(
{
collateralAmount: this.amount,
},
{
depositAccounts: {
owner: this.owner,
obligation: obligationAddress,
lendingMarket: this.kaminoMarket.getAddress(),
depositReserve: this.reserve.address,
reserveDestinationCollateral: this.reserve.state.collateral.supplyVault,
userSourceCollateral: await this.getUserCollateralAccountAddress(this.reserve),
tokenProgram: TOKEN_PROGRAM_ADDRESS,
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
farmsAccounts,
farmsProgram: FARMS_PROGRAM_ID,
},
undefined,
this.kaminoMarket.programId
)
);
}
/// @deprecated -- use addDepositObligationCollateralIxV2 instead
async addBorrowIx() {
this.lendingIxsLabels.push(`borrowObligationLiquidity`);
const depositReservesList = this.getAdditionalDepositReservesList();
const depositReserveAccountMetas: AccountMeta[] = depositReservesList.map((reserve) => {
return { address: reserve, role: AccountRole.WRITABLE };
});
let borrowIx = borrowObligationLiquidity(
{
liquidityAmount: this.amount,
},
{
owner: this.owner,
obligation: await this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
borrowReserve: this.reserve.address,
borrowReserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveSourceLiquidity: this.reserve.state.liquidity.supplyVault,
userDestinationLiquidity: await this.getUserTokenAccountAddress(this.reserve),
borrowReserveLiquidityFeeReceiver: this.reserve.state.liquidity.feeVault,
referrerTokenState: await this.getReferrerTokenStateAddress(this.reserve.address),
tokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
undefined,
this.kaminoMarket.programId
);
borrowIx = {
...borrowIx,
accounts:
isKaminoObligation(this.obligation) &&
(this.obligation.state.elevationGroup > 0 || this.obligation.refreshedStats.potentialElevationGroupUpdate > 0)
? borrowIx.accounts!.concat(depositReserveAccountMetas)
: borrowIx.accounts,
};
this.lendingIxs.push(borrowIx);
}
async addBorrowIxV2() {
this.lendingIxsLabels.push(`borrowObligationLiquidityV2`);
const depositReservesList = this.getAdditionalDepositReservesList();
const depositReserveAccountMetas: AccountMeta[] = depositReservesList.map((reserve) => {
return { address: reserve, role: AccountRole.WRITABLE };
});
const obligationAddress = await this.getObligationPda();
const { debtFarmAccounts: farmsAccounts } = await KaminoAction.getFarmAccountsForReserve(
obligationAddress,
this.reserve
);
let borrowIx = borrowObligationLiquidityV2(
{
liquidityAmount: this.amount,
},
{
borrowAccounts: {
owner: this.owner,
obligation: obligationAddress,
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
borrowReserve: this.reserve.address,
borrowReserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveSourceLiquidity: this.reserve.state.liquidity.supplyVault,
userDestinationLiquidity: await this.getUserTokenAccountAddress(this.reserve),
borrowReserveLiquidityFeeReceiver: this.reserve.state.liquidity.feeVault,
referrerTokenState: await this.getReferrerTokenStateAddress(this.reserve.address),
tokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
farmsAccounts,
farmsProgram: FARMS_PROGRAM_ID,
},
undefined,
this.kaminoMarket.programId
);
borrowIx = {
...borrowIx,
accounts:
isKaminoObligation(this.obligation) &&
(this.obligation.state.elevationGroup > 0 || this.obligation.refreshedStats.potentialElevationGroupUpdate > 0)
? borrowIx.accounts!.concat(depositReserveAccountMetas)
: borrowIx.accounts,
};
this.lendingIxs.push(borrowIx);
}
/// @deprecated -- use addWithdrawIxV2 instead
async addWithdrawIx(collateralAmount: BN) {
this.lendingIxsLabels.push(`withdrawObligationCollateralAndRedeemReserveCollateral`);
this.lendingIxs.push(
withdrawObligationCollateralAndRedeemReserveCollateral(
{
collateralAmount,
},
{
owner: this.owner,
obligation: await this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
withdrawReserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveCollateralMint: this.reserve.getCTokenMint(),
reserveLiquiditySupply: this.reserve.state.liquidity.supplyVault,
reserveSourceCollateral: this.reserve.state.collateral.supplyVault,
userDestinationLiquidity: await this.getUserTokenAccountAddress(this.reserve),
placeholderUserDestinationCollateral: none(),
collateralTokenProgram: TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
undefined,
this.kaminoMarket.programId
)
);
}
async addWithdrawIxV2(collateralAmount: BN): Promise<void> {
const obligationAddress = await this.getObligationPda();
const { collateralFarmAccounts: farmsAccounts } = await KaminoAction.getFarmAccountsForReserve(
obligationAddress,
this.reserve
);
this.lendingIxsLabels.push(`withdrawObligationCollateralAndRedeemReserveCollateralV2`);
this.lendingIxs.push(
withdrawObligationCollateralAndRedeemReserveCollateralV2(
{
collateralAmount,
},
{
withdrawAccounts: {
owner: this.owner,
obligation: obligationAddress,
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
withdrawReserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveCollateralMint: this.reserve.getCTokenMint(),
reserveLiquiditySupply: this.reserve.state.liquidity.supplyVault,
reserveSourceCollateral: this.reserve.state.collateral.supplyVault,
userDestinationLiquidity: await this.getUserTokenAccountAddress(this.reserve),
placeholderUserDestinationCollateral: none(),
collateralTokenProgram: TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
},
farmsAccounts: farmsAccounts,
farmsProgram: FARMS_PROGRAM_ID,
},
undefined,
this.kaminoMarket.programId
)
);
}
/// @deprecated -- use addRepayIxV2 instead
async addRepayIx() {
const obligationAddress = await this.getObligationPda();
this.lendingIxsLabels.push(
`repayObligationLiquidity(reserve=${this.reserve.address})(obligation=${obligationAddress})`
);
const depositReservesList = this.getAdditionalDepositReservesList();
const depositReserveAccountMetas: AccountMeta[] = depositReservesList.map(