@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
1,539 lines (1,372 loc) • 104 kB
text/typescript
import {
Connection,
PublicKey,
RpcResponseAndContext,
SimulatedTransactionResponse,
SystemProgram,
SYSVAR_INSTRUCTIONS_PUBKEY,
SYSVAR_RENT_PUBKEY,
Transaction,
TransactionInstruction,
TransactionSignature,
} from '@solana/web3.js';
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
NATIVE_MINT,
TOKEN_PROGRAM_ID,
createCloseAccountInstruction,
createSyncNativeInstruction,
} from '@solana/spl-token';
import BN from 'bn.js';
import Decimal from 'decimal.js';
import {
borrowObligationLiquidity,
depositObligationCollateral,
depositReserveLiquidity,
depositReserveLiquidityAndObligationCollateral,
initObligation,
initObligationFarmsForReserve,
InitObligationFarmsForReserveAccounts,
InitObligationFarmsForReserveArgs,
initReferrerTokenState,
initUserMetadata,
liquidateObligationAndRedeemReserveCollateral,
redeemReserveCollateral,
refreshObligation,
refreshObligationFarmsForReserve,
RefreshObligationFarmsForReserveAccounts,
RefreshObligationFarmsForReserveArgs,
refreshReserve,
repayObligationLiquidity,
requestElevationGroup,
RequestElevationGroupAccounts,
RequestElevationGroupArgs,
withdrawObligationCollateralAndRedeemReserveCollateral,
withdrawReferrerFees,
} from '../idl_codegen/instructions';
import {
buildComputeBudgetIx,
createAssociatedTokenAccountIdempotentInstruction,
ObligationType,
U64_MAX,
referrerTokenStatePda,
userMetadataPda,
createLookupTableIx,
isNotNullPubkey,
PublicKeySet,
WRAPPED_SOL_MINT,
getAssociatedTokenAddress,
ScopeRefresh,
createAtasIdempotent,
} from '../utils';
import { KaminoMarket } from './market';
import { KaminoObligation } from './obligation';
import { KaminoReserve } from './reserve';
import { ReserveFarmKind } from '../idl_codegen/types';
import { farmsId } from '@kamino-finance/farms-sdk';
import { Reserve } from '../idl_codegen/accounts';
import { VanillaObligation } from '../utils/ObligationType';
import { PROGRAM_ID } from '../lib';
import { U16_MAX } from '@kamino-finance/scope-sdk';
export const POSITION_LIMIT = 10;
export const BORROWS_LIMIT = 5;
export const DEPOSITS_LIMIT = 8;
const SOL_PADDING_FOR_INTEREST = new BN('1000000');
export type ActionType =
| 'deposit'
| 'borrow'
| 'withdraw'
| 'repay'
| 'mint'
| 'redeem'
| 'depositCollateral'
| 'liquidate'
| 'depositAndBorrow'
| 'repayAndWithdraw'
| 'refreshObligation'
| 'requestElevationGroup'
| 'withdrawReferrerFees';
export type AuxiliaryIx = 'setup' | 'inBetween' | 'cleanup';
export class KaminoAction {
kaminoMarket: KaminoMarket;
reserve: KaminoReserve;
outflowReserve: KaminoReserve | undefined;
owner: PublicKey;
payer: PublicKey;
obligation: KaminoObligation | null = null;
referrer: PublicKey;
userTokenAccountAddress: PublicKey;
userCollateralAccountAddress: PublicKey;
additionalTokenAccountAddress?: PublicKey;
/**
* Null unless the obligation is not passed
*/
obligationType: ObligationType | null = null;
mint: PublicKey;
secondaryMint?: PublicKey;
positions?: number;
amount: BN;
outflowAmount?: BN;
computeBudgetIxs: Array<TransactionInstruction>;
computeBudgetIxsLabels: Array<string>;
setupIxs: Array<TransactionInstruction>;
setupIxsLabels: Array<string>;
inBetweenIxs: Array<TransactionInstruction>;
inBetweenIxsLabels: Array<string>;
lendingIxs: Array<TransactionInstruction>;
lendingIxsLabels: Array<string>;
cleanupIxs: Array<TransactionInstruction>;
cleanupIxsLabels: Array<string>;
preTxnIxs: Array<TransactionInstruction>;
preTxnIxsLabels: Array<string>;
postTxnIxs: Array<TransactionInstruction>;
postTxnIxsLabels: Array<string>;
refreshFarmsCleanupTxnIxs: Array<TransactionInstruction>;
refreshFarmsCleanupTxnIxsLabels: Array<string>;
depositReserves: Array<PublicKey>;
borrowReserves: Array<PublicKey>;
preLoadedDepositReservesSameTx: Array<PublicKey>;
preLoadedBorrowReservesSameTx: Array<PublicKey>;
currentSlot: number;
private constructor(
kaminoMarket: KaminoMarket,
owner: PublicKey,
obligation: KaminoObligation | ObligationType | null,
userTokenAccountAddress: PublicKey,
userCollateralAccountAddress: PublicKey,
mint: PublicKey,
positions: number,
amount: string | BN,
depositReserves: Array<PublicKey>,
borrowReserves: Array<PublicKey>,
reserveState: KaminoReserve,
currentSlot: number,
secondaryMint?: PublicKey,
additionalTokenAccountAddress?: PublicKey,
outflowReserveState?: KaminoReserve,
outflowAmount?: string | BN,
referrer?: PublicKey,
payer?: PublicKey
) {
if (obligation instanceof KaminoObligation) {
this.obligation = obligation;
} else if (obligation !== null) {
this.obligationType = obligation;
}
this.kaminoMarket = kaminoMarket;
this.owner = owner;
this.payer = payer ?? owner;
this.amount = new BN(amount);
this.mint = mint;
this.positions = positions;
this.userTokenAccountAddress = userTokenAccountAddress;
this.userCollateralAccountAddress = userCollateralAccountAddress;
this.computeBudgetIxs = [];
this.computeBudgetIxsLabels = [];
this.setupIxs = [];
this.setupIxsLabels = [];
this.inBetweenIxs = [];
this.inBetweenIxsLabels = [];
this.lendingIxs = [];
this.lendingIxsLabels = [];
this.cleanupIxs = [];
this.cleanupIxsLabels = [];
this.preTxnIxs = [];
this.preTxnIxsLabels = [];
this.postTxnIxs = [];
this.postTxnIxsLabels = [];
this.refreshFarmsCleanupTxnIxs = [];
this.refreshFarmsCleanupTxnIxsLabels = [];
this.depositReserves = depositReserves;
this.borrowReserves = borrowReserves;
this.additionalTokenAccountAddress = additionalTokenAccountAddress;
this.secondaryMint = secondaryMint;
this.reserve = reserveState;
this.outflowReserve = outflowReserveState;
this.outflowAmount = outflowAmount ? new BN(outflowAmount) : undefined;
this.preLoadedDepositReservesSameTx = [];
this.preLoadedBorrowReservesSameTx = [];
this.referrer = referrer ? referrer : PublicKey.default;
this.currentSlot = currentSlot;
}
static async initialize(
action: ActionType,
amount: string | BN,
mint: PublicKey,
owner: PublicKey,
kaminoMarket: KaminoMarket,
obligation: KaminoObligation | ObligationType,
referrer: PublicKey = PublicKey.default,
currentSlot: number = 0,
payer?: PublicKey
) {
const reserve = kaminoMarket.getReserveByMint(mint);
if (reserve === undefined) {
throw new Error(`Reserve ${mint} not found in market ${kaminoMarket.getAddress().toBase58()}`);
}
const { userTokenAccountAddress, userCollateralAccountAddress } = KaminoAction.getUserAccountAddresses(
payer ?? owner,
reserve.state
);
const { kaminoObligation, depositReserves, borrowReserves, distinctReserveCount } =
await KaminoAction.loadObligation(action, kaminoMarket, owner, reserve.address, obligation);
const referrerKey = await this.getReferrerKey(kaminoMarket, owner, kaminoObligation, referrer);
return new KaminoAction(
kaminoMarket,
owner,
kaminoObligation || obligation,
userTokenAccountAddress,
userCollateralAccountAddress,
mint,
distinctReserveCount,
amount,
depositReserves,
borrowReserves,
reserve,
currentSlot,
undefined,
undefined,
undefined,
undefined,
referrerKey,
payer
);
}
private static getUserAccountAddresses(owner: PublicKey, reserve: Reserve) {
const userTokenAccountAddress = getAssociatedTokenAddress(
reserve.liquidity.mintPubkey,
owner,
true,
reserve.liquidity.tokenProgram,
ASSOCIATED_TOKEN_PROGRAM_ID
);
const userCollateralAccountAddress = getAssociatedTokenAddress(
reserve.collateral.mintPubkey,
owner,
true,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
);
return { userTokenAccountAddress, userCollateralAccountAddress };
}
private static async loadObligation(
action: ActionType,
kaminoMarket: KaminoMarket,
owner: PublicKey,
reserve: PublicKey,
obligation: KaminoObligation | ObligationType,
outflowReserve?: PublicKey
) {
let kaminoObligation: KaminoObligation | null;
const depositReserves: Array<PublicKey> = [];
const borrowReserves: Array<PublicKey> = [];
if (obligation instanceof KaminoObligation) {
kaminoObligation = obligation;
} else {
const obligationAddress = 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 PublicKeySet<PublicKey>([
...borrowReserves.map((e) => e),
...(action === 'borrow' ? [reserve] : []),
...(action === 'depositAndBorrow' ? [reserve] : []),
]).toArray().length +
new PublicKeySet<PublicKey>([
...depositReserves.map((e) => e),
...(action === 'deposit' ? [reserve] : []),
...(action === 'depositAndBorrow' ? [outflowReserve!] : []),
]).toArray().length;
if (distinctReserveCount > POSITION_LIMIT) {
throw Error(`Obligation already has max number of positions: ${POSITION_LIMIT}`);
}
return {
kaminoObligation,
depositReserves,
borrowReserves,
distinctReserveCount,
};
}
static async buildRefreshObligationTxns(
kaminoMarket: KaminoMarket,
payer: PublicKey,
obligation: KaminoObligation,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
currentSlot: number = 0
) {
// placeholder for action initialization
const firstReserve = obligation.state.deposits[0].depositReserve;
const firstKaminoReserve = kaminoMarket.getReserveByAddress(firstReserve);
if (!firstKaminoReserve) {
throw new Error(`Reserve ${firstReserve.toBase58()} not found`);
}
const axn = await KaminoAction.initialize(
'refreshObligation',
'0',
firstKaminoReserve?.getLiquidityMint(),
obligation.state.owner,
kaminoMarket,
obligation,
undefined,
currentSlot
);
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
axn.addRefreshObligation(payer);
return axn;
}
static async buildRequestElevationGroupTxns(
kaminoMarket: KaminoMarket,
payer: PublicKey,
obligation: KaminoObligation,
elevationGroup: number,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
currentSlot: number = 0
) {
const firstReserve = obligation.state.deposits.find(
(x) => !x.depositReserve.equals(PublicKey.default)
)!.depositReserve;
const firstKaminoReserve = kaminoMarket.getReserveByAddress(firstReserve);
if (!firstKaminoReserve) {
throw new Error(`Reserve ${firstReserve.toBase58()} not found`);
}
const axn = await KaminoAction.initialize(
'requestElevationGroup',
'0',
firstKaminoReserve?.getLiquidityMint(),
obligation.state.owner,
kaminoMarket,
obligation,
undefined,
currentSlot
);
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
axn.addRefreshObligation(payer);
axn.addRequestElevationIx(elevationGroup, 'setup');
return axn;
}
static async buildDepositTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: PublicKey,
owner: PublicKey,
obligation: KaminoObligation | ObligationType,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
includeUserMetadata: boolean = true, // if true it includes user metadata
referrer: PublicKey = PublicKey.default,
currentSlot: number = 0,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' },
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.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'deposit',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarm,
undefined,
overrideElevationGroupRequest
);
axn.addDepositIx();
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
getTokenIdsForScopeRefresh(kaminoMarket: KaminoMarket, reserves: PublicKey[]): number[] {
const tokenIds: number[] = [];
for (const reserveAddress of reserves) {
const reserve = kaminoMarket.getReserveByAddress(reserveAddress);
if (!reserve) {
throw new Error(`Reserve not found for reserve ${reserveAddress.toBase58()}`);
}
if (!reserve.state.config.tokenInfo.scopeConfiguration.priceFeed.equals(PublicKey.default)) {
reserve.state.config.tokenInfo.scopeConfiguration.priceChain.map((x) => {
if (x !== U16_MAX) {
tokenIds.push(x);
}
});
reserve.state.config.tokenInfo.scopeConfiguration.twapChain.map((x) => {
if (x !== U16_MAX) {
tokenIds.push(x);
}
});
}
}
return tokenIds;
}
async addScopeRefreshIxs(tokens: number[], feed: string = 'hubble') {
this.preTxnIxsLabels.unshift(`refreshScopePrices`);
this.preTxnIxs.unshift(
await this.kaminoMarket.scope.refreshPriceListIx(
{
feed: feed,
},
tokens
)
);
}
static async buildBorrowTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: PublicKey,
owner: PublicKey,
obligation: KaminoObligation | ObligationType,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
includeUserMetadata: boolean = true, // if true it includes user metadata
referrer: PublicKey = PublicKey.default,
currentSlot: number = 0,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' },
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.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'borrow',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarm,
undefined,
overrideElevationGroupRequest
);
axn.addBorrowIx();
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildDepositReserveLiquidityTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: PublicKey,
owner: PublicKey,
obligation: KaminoObligation | ObligationType,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas
requestElevationGroup: boolean = false,
includeUserMetadata: boolean = true, // if true it includes user metadata
referrer: PublicKey = PublicKey.default,
currentSlot: number = 0,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }
) {
const axn = await KaminoAction.initialize(
'mint',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'mint',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarm
);
axn.addDepositReserveLiquidityIx();
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildRedeemReserveCollateralTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: PublicKey,
owner: PublicKey,
obligation: KaminoObligation | ObligationType,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas
requestElevationGroup: boolean = false,
includeUserMetadata: boolean = true, // if true it includes user metadata,
referrer: PublicKey = PublicKey.default,
currentSlot: number = 0,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }
) {
const axn = await KaminoAction.initialize(
'redeem',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'redeem',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarm
);
axn.addRedeemReserveCollateralIx();
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildDepositObligationCollateralTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: PublicKey,
owner: PublicKey,
obligation: KaminoObligation | ObligationType,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas
requestElevationGroup: boolean = false,
includeUserMetadata: boolean = true, // if true it includes user metadata
referrer: PublicKey = PublicKey.default,
currentSlot: number = 0,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }
) {
const axn = await KaminoAction.initialize(
'depositCollateral',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'depositCollateral',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarm
);
axn.addDepositObligationCollateralIx();
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildDepositAndBorrowTxns(
kaminoMarket: KaminoMarket,
depositAmount: string | BN,
depositMint: PublicKey,
borrowAmount: string | BN,
borrowMint: PublicKey,
payer: PublicKey,
obligation: KaminoObligation | ObligationType,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
includeUserMetadata: boolean = true, // if true it includes user metadata,
referrer: PublicKey = PublicKey.default,
currentSlot: number = 0,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }
) {
const axn = await KaminoAction.initializeMultiTokenAction(
kaminoMarket,
'depositAndBorrow',
depositAmount,
depositMint,
borrowMint,
payer,
payer,
obligation,
borrowAmount,
referrer,
currentSlot
);
const addInitObligationForFarmForDeposit = true;
const addInitObligationForFarmForBorrow = false;
const twoTokenAction = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
axn.outflowReserve!.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'deposit',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarmForDeposit,
twoTokenAction
);
await axn.addDepositAndBorrowIx();
await axn.addInBetweenIxs(
'depositAndBorrow',
includeAtaIxns,
requestElevationGroup,
addInitObligationForFarmForBorrow
);
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildRepayAndWithdrawTxns(
kaminoMarket: KaminoMarket,
repayAmount: string | BN,
repayMint: PublicKey,
withdrawAmount: string | BN,
withdrawMint: PublicKey,
payer: PublicKey,
currentSlot: number,
obligation: KaminoObligation | ObligationType,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
includeUserMetadata: boolean = true, // if true it includes user metadata,
isClosingPosition: boolean = false,
referrer: PublicKey = PublicKey.default,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }
) {
const axn = await KaminoAction.initializeMultiTokenAction(
kaminoMarket,
'repayAndWithdraw',
repayAmount,
repayMint,
withdrawMint,
payer,
payer,
obligation,
withdrawAmount,
referrer,
currentSlot
);
const addInitObligationForFarmForRepay = true;
const addInitObligationForFarmForWithdraw = false;
const twoTokenAction = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
axn.outflowReserve!.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'repay',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarmForRepay,
twoTokenAction
);
await axn.addRepayAndWithdrawIxs();
await axn.addInBetweenIxs(
'repayAndWithdraw',
includeAtaIxns,
requestElevationGroup,
addInitObligationForFarmForWithdraw,
isClosingPosition
);
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildWithdrawTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: PublicKey,
owner: PublicKey,
obligation: KaminoObligation | ObligationType,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas,
requestElevationGroup: boolean = false,
includeUserMetadata: boolean = true, // if true it includes user metadata
referrer: PublicKey = PublicKey.default,
currentSlot: number = 0,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }
) {
const axn = await KaminoAction.initialize(
'withdraw',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'withdraw',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarm
);
await axn.addWithdrawIx();
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
/**
*
* @param kaminoMarket
* @param amount
* @param mint
* @param owner
* @param obligation - obligation to repay or the PDA seeds
* @param currentSlot
* @param payer - if not set then owner is used
* @param extraComputeBudget - if > 0 then adds the ixn
* @param includeAtaIxns - if true it includes create and close wsol and token atas
* @param requestElevationGroup
* @param includeUserMetadata - if true it includes user metadata
* @param referrer
*/
static async buildRepayTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
mint: PublicKey,
owner: PublicKey,
obligation: KaminoObligation | ObligationType,
currentSlot: number,
payer: PublicKey | undefined = undefined,
extraComputeBudget: number = 1_000_000,
includeAtaIxns: boolean = true,
requestElevationGroup: boolean = false,
includeUserMetadata: boolean = true,
referrer: PublicKey = PublicKey.default,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }
) {
const axn = await KaminoAction.initialize(
'repay',
amount,
mint,
owner,
kaminoMarket,
obligation,
referrer,
currentSlot,
payer
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'repay',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarm
);
await axn.addRepayIx();
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildLiquidateTxns(
kaminoMarket: KaminoMarket,
amount: string | BN,
minCollateralReceiveAmount: string | BN,
repayTokenMint: PublicKey,
withdrawTokenMint: PublicKey,
liquidator: PublicKey,
obligationOwner: PublicKey,
obligation: KaminoObligation | ObligationType,
extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
includeAtaIxns: 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,
includeUserMetadata: boolean = true, // if true it includes user metadata
referrer: PublicKey = PublicKey.default,
maxAllowedLtvOverridePercent: number = 0,
currentSlot: number = 0,
scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }
) {
const axn = await KaminoAction.initializeMultiTokenAction(
kaminoMarket,
'liquidate',
amount,
repayTokenMint,
withdrawTokenMint,
liquidator,
obligationOwner,
obligation,
minCollateralReceiveAmount,
referrer,
currentSlot
);
const addInitObligationForFarm = true;
if (extraComputeBudget > 0) {
axn.addComputeBudgetIxn(extraComputeBudget);
}
const allReserves = new PublicKeySet<PublicKey>([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
axn.outflowReserve!.address,
]).toArray();
const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
}
await axn.addSupportIxs(
'liquidate',
includeAtaIxns,
requestElevationGroup,
includeUserMetadata,
addInitObligationForFarm
);
await axn.addLiquidateIx(maxAllowedLtvOverridePercent);
axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
return axn;
}
static async buildWithdrawReferrerFeeTxns(
owner: PublicKey,
tokenMint: PublicKey,
kaminoMarket: KaminoMarket,
currentSlot: number = 0
) {
const { axn, createAtaIxs } = await KaminoAction.initializeWithdrawReferrerFees(
tokenMint,
owner,
kaminoMarket,
currentSlot
);
axn.preTxnIxs.push(...createAtaIxs);
axn.preTxnIxsLabels.push(`createAtasIxs[${axn.userTokenAccountAddress.toString()}]`);
axn.addRefreshReserveIxs([axn.reserve.address]);
axn.addWithdrawReferrerFeesIxs();
return axn;
}
async getTransactions() {
const txns: {
preLendingTxn: Transaction | null;
lendingTxn: Transaction | null;
postLendingTxn: Transaction | null;
} = {
preLendingTxn: null,
lendingTxn: null,
postLendingTxn: null,
};
if (this.preTxnIxs.length) {
txns.preLendingTxn = new Transaction({
feePayer: this.owner,
recentBlockhash: (await this.kaminoMarket.getConnection().getLatestBlockhash()).blockhash,
}).add(...this.preTxnIxs);
}
if (this.lendingIxs.length === 2) {
txns.lendingTxn = new Transaction({
feePayer: this.owner,
recentBlockhash: (await this.kaminoMarket.getConnection().getLatestBlockhash()).blockhash,
}).add(
...this.setupIxs,
...[this.lendingIxs[0]],
...this.inBetweenIxs,
...[this.lendingIxs[1]],
...this.cleanupIxs
);
} else {
txns.lendingTxn = new Transaction({
feePayer: this.owner,
recentBlockhash: (await this.kaminoMarket.getConnection().getLatestBlockhash()).blockhash,
}).add(...this.setupIxs, ...this.lendingIxs, ...this.cleanupIxs);
}
if (this.postTxnIxs.length) {
txns.postLendingTxn = new Transaction({
feePayer: this.owner,
recentBlockhash: (await this.kaminoMarket.getConnection().getLatestBlockhash()).blockhash,
}).add(...this.postTxnIxs);
}
return txns;
}
async sendTransactions(sendTransaction: (txn: Transaction, connection: Connection) => Promise<TransactionSignature>) {
const txns = await this.getTransactions();
await this.sendSingleTransaction(txns.preLendingTxn, sendTransaction);
const signature = await this.sendSingleTransaction(txns.lendingTxn, sendTransaction);
await this.sendSingleTransaction(txns.postLendingTxn, sendTransaction);
return signature;
}
private async sendSingleTransaction(
txn: Transaction | null,
sendTransaction: (txn: Transaction, connection: Connection) => Promise<TransactionSignature>
) {
if (!txn) return '';
const signature = await sendTransaction(txn, this.kaminoMarket.getConnection());
await this.kaminoMarket.getConnection().confirmTransaction(signature);
return signature;
}
async simulateTransactions(
sendTransaction: (
txn: Transaction,
connection: Connection
) => Promise<RpcResponseAndContext<SimulatedTransactionResponse>>
) {
const txns = await this.getTransactions();
await this.simulateSingleTransaction(txns.preLendingTxn, sendTransaction);
const signature = await this.simulateSingleTransaction(txns.lendingTxn, sendTransaction);
await this.simulateSingleTransaction(txns.postLendingTxn, sendTransaction);
return signature;
}
private async simulateSingleTransaction(
txn: Transaction | null,
sendTransaction: (
txn: Transaction,
connection: Connection
) => Promise<RpcResponseAndContext<SimulatedTransactionResponse>>
) {
if (!txn) return '';
return await sendTransaction(txn, this.kaminoMarket.getConnection());
}
addDepositIx() {
this.lendingIxsLabels.push(`depositReserveLiquidityAndObligationCollateral`);
this.lendingIxs.push(
depositReserveLiquidityAndObligationCollateral(
{
liquidityAmount: this.amount,
},
{
owner: this.owner,
obligation: this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: 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: this.userTokenAccountAddress,
placeholderUserDestinationCollateral: this.kaminoMarket.programId,
collateralTokenProgram: TOKEN_PROGRAM_ID,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
)
);
}
addDepositReserveLiquidityIx() {
this.lendingIxsLabels.push(`depositReserveLiquidity`);
this.lendingIxs.push(
depositReserveLiquidity(
{
liquidityAmount: this.amount,
},
{
owner: this.owner,
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: this.kaminoMarket.getLendingMarketAuthority(),
reserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveLiquiditySupply: this.reserve.state.liquidity.supplyVault,
reserveCollateralMint: this.reserve.getCTokenMint(),
userSourceLiquidity: this.userTokenAccountAddress,
userDestinationCollateral: this.userCollateralAccountAddress,
collateralTokenProgram: TOKEN_PROGRAM_ID,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
)
);
}
addRedeemReserveCollateralIx() {
this.lendingIxsLabels.push(`redeemReserveCollateral`);
this.lendingIxs.push(
redeemReserveCollateral(
{
collateralAmount: this.amount,
},
{
owner: this.owner,
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: this.kaminoMarket.getLendingMarketAuthority(),
reserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveLiquiditySupply: this.reserve.state.liquidity.supplyVault,
reserveCollateralMint: this.reserve.getCTokenMint(),
userSourceCollateral: this.userCollateralAccountAddress,
userDestinationLiquidity: this.userTokenAccountAddress,
collateralTokenProgram: TOKEN_PROGRAM_ID,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
)
);
}
addDepositObligationCollateralIx() {
this.lendingIxsLabels.push(`depositObligationCollateral`);
this.lendingIxs.push(
depositObligationCollateral(
{
collateralAmount: this.amount,
},
{
owner: this.owner,
obligation: this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
depositReserve: this.reserve.address,
reserveDestinationCollateral: this.reserve.state.collateral.supplyVault,
userSourceCollateral: this.userCollateralAccountAddress,
tokenProgram: TOKEN_PROGRAM_ID,
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
)
);
}
addBorrowIx() {
this.lendingIxsLabels.push(`borrowObligationLiquidity`);
const depositReservesList = this.getAdditionalDepositReservesList();
const depositReserveAccountMetas = depositReservesList.map((reserve) => {
return { pubkey: reserve, isSigner: false, isWritable: true };
});
const borrowIx = borrowObligationLiquidity(
{
liquidityAmount: this.amount,
},
{
owner: this.owner,
obligation: this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: this.kaminoMarket.getLendingMarketAuthority(),
borrowReserve: this.reserve.address,
borrowReserveLiquidityMint: this.reserve.getLiquidityMint(),
reserveSourceLiquidity: this.reserve.state.liquidity.supplyVault,
userDestinationLiquidity: this.userTokenAccountAddress,
borrowReserveLiquidityFeeReceiver: this.reserve.state.liquidity.feeVault,
referrerTokenState: referrerTokenStatePda(this.referrer, this.reserve.address, this.kaminoMarket.programId)[0],
tokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
);
borrowIx.keys =
this.obligation!.state.elevationGroup > 0 || this.obligation!.refreshedStats.potentialElevationGroupUpdate > 0
? borrowIx.keys.concat([...depositReserveAccountMetas])
: borrowIx.keys;
this.lendingIxs.push(borrowIx);
}
async addDepositAndBorrowIx() {
this.lendingIxsLabels.push(`depositReserveLiquidityAndObligationCollateral`);
this.lendingIxsLabels.push(`borrowObligationLiquidity`);
this.lendingIxs.push(
depositReserveLiquidityAndObligationCollateral(
{
liquidityAmount: this.amount,
},
{
owner: this.owner,
obligation: this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: 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: this.userTokenAccountAddress,
placeholderUserDestinationCollateral: this.kaminoMarket.programId,
collateralTokenProgram: TOKEN_PROGRAM_ID,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
)
);
if (!this.outflowReserve) {
throw new Error(`outflowReserve not set`);
}
if (!this.additionalTokenAccountAddress) {
throw new Error(`additionalTokenAccountAddress not set`);
}
if (!this.outflowAmount) {
throw new Error(`outflowAmount not set`);
}
const depositReservesList = this.getAdditionalDepositReservesList();
if (depositReservesList.length === 0) {
depositReservesList.push(this.reserve.address);
}
const depositReserveAccountMetas = depositReservesList.map((reserve) => {
return { pubkey: reserve, isSigner: false, isWritable: true };
});
const borrowIx = borrowObligationLiquidity(
{
liquidityAmount: this.outflowAmount,
},
{
owner: this.owner,
obligation: this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: this.kaminoMarket.getLendingMarketAuthority(),
borrowReserve: this.outflowReserve.address,
borrowReserveLiquidityMint: this.outflowReserve.getLiquidityMint(),
reserveSourceLiquidity: this.outflowReserve.state.liquidity.supplyVault,
userDestinationLiquidity: this.additionalTokenAccountAddress,
borrowReserveLiquidityFeeReceiver: this.outflowReserve.state.liquidity.feeVault,
referrerTokenState: referrerTokenStatePda(
this.referrer,
this.outflowReserve.address,
this.kaminoMarket.programId
)[0],
tokenProgram: this.outflowReserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
);
borrowIx.keys = borrowIx.keys.concat([...depositReserveAccountMetas]);
this.lendingIxs.push(borrowIx);
}
async addRepayAndWithdrawIxs() {
this.lendingIxsLabels.push(
`repayObligationLiquidity(reserve=${this.reserve!.address})(obligation=${this.getObligationPda()})`
);
this.lendingIxsLabels.push(`withdrawObligationCollateralAndRedeemReserveCollateral`);
const depositReservesList = this.getAdditionalDepositReservesList();
const depositReserveAccountMetas = depositReservesList.map((reserve) => {
return { pubkey: reserve, isSigner: false, isWritable: true };
});
const repayIx = repayObligationLiquidity(
{
liquidityAmount: this.amount,
},
{
owner: this.owner,
obligation: this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
repayReserve: this.reserve!.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
userSourceLiquidity: this.userTokenAccountAddress,
reserveDestinationLiquidity: this.reserve.state.liquidity.supplyVault,
tokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
);
repayIx.keys = repayIx.keys.concat([...depositReserveAccountMetas]);
this.lendingIxs.push(repayIx);
if (!this.outflowReserve) {
throw new Error(`outflowReserve not set`);
}
if (!this.additionalTokenAccountAddress) {
throw new Error(`additionalTokenAccountAddress not set`);
}
if (!this.outflowAmount) {
throw new Error(`outflowAmount not set`);
}
const collateralExchangeRate = this.outflowReserve.getEstimatedCollateralExchangeRate(
this.currentSlot,
this.kaminoMarket.state.referralFeeBps
);
this.lendingIxs.push(
withdrawObligationCollateralAndRedeemReserveCollateral(
{
collateralAmount: this.outflowAmount.eq(new BN(U64_MAX))
? this.outflowAmount
: new BN(new Decimal(this.outflowAmount.toString()).mul(collateralExchangeRate).ceil().toString()),
},
{
owner: this.owner,
obligation: this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: this.kaminoMarket.getLendingMarketAuthority(),
withdrawReserve: this.outflowReserve.address,
reserveLiquidityMint: this.outflowReserve.getLiquidityMint(),
reserveCollateralMint: this.outflowReserve.getCTokenMint(),
reserveLiquiditySupply: this.outflowReserve.state.liquidity.supplyVault,
reserveSourceCollateral: this.outflowReserve.state.collateral.supplyVault,
userDestinationLiquidity: this.additionalTokenAccountAddress,
placeholderUserDestinationCollateral: this.kaminoMarket.programId,
collateralTokenProgram: TOKEN_PROGRAM_ID,
liquidityTokenProgram: this.outflowReserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
)
);
}
async addWithdrawIx() {
const collateralExchangeRate = this.reserve.getEstimatedCollateralExchangeRate(
this.currentSlot,
this.kaminoMarket.state.referralFeeBps
);
const collateralAmount = this.amount.eq(new BN(U64_MAX))
? this.amount
: new BN(new Decimal(this.amount.toString()).mul(collateralExchangeRate).ceil().toString());
this.lendingIxsLabels.push(`withdrawObligationCollateralAndRedeemReserveCollateral`);
this.lendingIxs.push(
withdrawObligationCollateralAndRedeemReserveCollateral(
{
collateralAmount,
},
{
owner: this.owner,
obligation: this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: 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: this.userTokenAccountAddress,
placeholderUserDestinationCollateral: this.kaminoMarket.programId,
collateralTokenProgram: TOKEN_PROGRAM_ID,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
)
);
}
async addRepayIx() {
this.lendingIxsLabels.push(
`repayObligationLiquidity(reserve=${this.reserve.address})(obligation=${this.getObligationPda()})`
);
const depositReservesList = this.getAdditionalDepositReservesList();
const depositReserveAccountMetas = depositReservesList.map((reserve) => {
return { pubkey: reserve, isSigner: false, isWritable: true };
});
const repayIx = repayObligationLiquidity(
{
liquidityAmount: this.amount,
},
{
owner: this.payer,
obligation: this.getObligationPda(),
lendingMarket: this.kaminoMarket.getAddress(),
repayReserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
userSourceLiquidity: this.userTokenAccountAddress,
reserveDestinationLiquidity: this.reserve.state.liquidity.supplyVault,
tokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
},
this.kaminoMarket.programId
);
repayIx.keys =
this.obligation!.state.elevationGroup > 0 ? repayIx.keys.concat([...depositReserveAccountMetas]) : repayIx.keys;
this.lendingIxs.push(repayIx);
}
async addLiquidateIx(maxAllowedLtvOverridePercent: number = 0) {
this.lendingIxsLabels.push(`liquidateObligationAndRedeemReserveCollateral`);
if (!this.outflowReserve) {
throw Error(`Withdraw reserve during liquidation is not defined`);
}
if (!this.additionalTokenAccountAddress) {
throw Error(`Liquidating token account address is not defined`);
}
const depositReservesList = this.getAdditionalDepositReservesList();
const depositReserveAccountMetas = depositReservesList.map((reserve)