@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
877 lines (876 loc) • 130 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.KaminoAction = void 0;
const kit_1 = require("@solana/kit");
const bn_js_1 = __importDefault(require("bn.js"));
const decimal_js_1 = __importDefault(require("decimal.js"));
const instructions_1 = require("../@codegen/klend/instructions");
const utils_1 = require("../utils");
const market_1 = require("./market");
const obligation_1 = require("./obligation");
const types_1 = require("../@codegen/klend/types");
const programId_1 = require("@kamino-finance/farms-sdk/dist/@codegen/farms/programId");
const ObligationType_1 = require("../utils/ObligationType");
const token_1 = require("@solana-program/token");
const sysvars_1 = require("@solana/sysvars");
const token_2022_1 = require("@solana-program/token-2022");
const system_1 = require("@solana-program/system");
const signer_1 = require("../utils/signer");
class KaminoAction {
kaminoMarket;
reserve;
outflowReserve;
owner;
payer;
obligation;
referrer;
/**
* Null unless the obligation is not passed
*/
obligationType = null;
mint;
secondaryMint;
positions;
amount;
outflowAmount;
computeBudgetIxs;
computeBudgetIxsLabels;
setupIxs;
setupIxsLabels;
inBetweenIxs;
inBetweenIxsLabels;
lendingIxs;
lendingIxsLabels;
cleanupIxs;
cleanupIxsLabels;
refreshFarmsCleanupTxnIxs;
refreshFarmsCleanupTxnIxsLabels;
depositReserves;
borrowReserves;
preLoadedDepositReservesSameTx;
currentSlot;
constructor(kaminoMarket, owner, obligation, mint, positions, amount, depositReserves, borrowReserves, reserveState, currentSlot, secondaryMint, outflowReserveState, outflowAmount, referrer = (0, kit_1.none)(), payer) {
this.kaminoMarket = kaminoMarket;
this.obligation = obligation;
this.owner = owner;
this.payer = payer ?? owner;
this.amount = new bn_js_1.default(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_js_1.default(outflowAmount) : undefined;
this.preLoadedDepositReservesSameTx = [];
this.referrer = referrer;
this.currentSlot = currentSlot;
}
static async initialize(action, amount, mint, owner, kaminoMarket, obligation, referrer = (0, kit_1.none)(), currentSlot = 0n, payer = 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);
}
static async getUserAccountAddresses(owner, reserve) {
const [userTokenAccountAddress, userCollateralAccountAddress] = await Promise.all([
(0, utils_1.getAssociatedTokenAddress)(reserve.liquidity.mintPubkey, owner, reserve.liquidity.tokenProgram, token_1.ASSOCIATED_TOKEN_PROGRAM_ADDRESS),
(0, utils_1.getAssociatedTokenAddress)(reserve.collateral.mintPubkey, owner, token_1.TOKEN_PROGRAM_ADDRESS, token_1.ASSOCIATED_TOKEN_PROGRAM_ADDRESS),
]);
return { userTokenAccountAddress, userCollateralAccountAddress };
}
static async loadObligation(action, kaminoMarket, owner, reserve, obligation, outflowReserve) {
let kaminoObligation;
const depositReserves = [];
const borrowReserves = [];
if (obligation instanceof obligation_1.KaminoObligation) {
kaminoObligation = obligation;
}
else {
const obligationAddress = await obligation.toPda(kaminoMarket.getAddress(), owner);
kaminoObligation = await obligation_1.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([
...borrowReserves.map((e) => e),
...(action === 'borrow' ? [reserve] : []),
...(action === 'depositAndBorrow' ? [reserve] : []),
]).size +
new Set([
...depositReserves.map((e) => e),
...(action === 'deposit' ? [reserve] : []),
...(action === 'depositAndBorrow' ? [outflowReserve] : []),
]).size;
return {
kaminoObligation,
depositReserves,
borrowReserves,
distinctReserveCount,
};
}
static async buildRefreshObligationTxns(kaminoMarket, payer, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
currentSlot = 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(), (0, signer_1.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, owner, obligation, elevationGroup, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
currentSlot = 0n) {
const firstReserve = obligation.state.deposits.find((x) => x.depositReserve !== utils_1.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, amount, mint, owner, obligation, useV2Ixs, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas,
requestElevationGroup = false, // to be requested *before* the deposit
initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.none)(), currentSlot = 0n, overrideElevationGroupRequest = 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, tokens, scopeConfig) {
const refreshIx = await scope.refreshPriceListIx({ config: scopeConfig }, tokens);
if (refreshIx) {
this.setupIxsLabels.unshift(`refreshScopePrices`);
this.setupIxs.unshift(refreshIx);
}
}
static async buildBorrowTxns(kaminoMarket, amount, mint, owner, obligation, useV2Ixs, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas,
requestElevationGroup = false, initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.none)(), currentSlot = 0n, overrideElevationGroupRequest = 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 ((0, kit_1.isSome)(axn.referrer)) {
const referrerTokenState = await (0, utils_1.referrerTokenStatePda)(axn.referrer.value, axn.reserve.address, axn.kaminoMarket.programId);
const account = await (0, kit_1.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, amount, mint, owner, obligation, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas
requestElevationGroup = false, referrer = (0, kit_1.none)(), currentSlot = 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, amount, mint, owner, obligation, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas
requestElevationGroup = false, referrer = (0, kit_1.none)(), currentSlot = 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, amount, mint, owner, obligation, useV2Ixs, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas
requestElevationGroup = false, initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.none)(), currentSlot = 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, depositAmount, depositMint, borrowAmount, borrowMint, owner, obligation, useV2Ixs, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas,
requestElevationGroup = false, initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.none)(), currentSlot = 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 ((0, kit_1.isSome)(axn.referrer)) {
const referrerTokenState = await (0, utils_1.referrerTokenStatePda)(axn.referrer.value, axn.outflowReserve.address, axn.kaminoMarket.programId);
const account = await (0, kit_1.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([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
...(axn.outflowReserve ? [axn.outflowReserve.address] : []),
...(axn.preLoadedDepositReservesSameTx ? axn.preLoadedDepositReservesSameTx : []),
]),
];
const scopeTokensMap = (0, market_1.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, depositAmount, depositMint, withdrawAmount, withdrawMint, owner, currentSlot, obligation, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas,
requestElevationGroup = false, initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.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, repayAmount, repayMint, withdrawAmount, withdrawMint, payer, currentSlot, obligation, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas,
requestElevationGroup = false, initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.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, repayAmount, repayMint, withdrawAmount, withdrawMint, payer, currentSlot, obligation, useV2Ixs, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas,
requestElevationGroup = false, initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.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([
...axn.depositReserves,
...axn.borrowReserves,
axn.reserve.address,
...(axn.outflowReserve ? [axn.outflowReserve.address] : []),
...(axn.preLoadedDepositReservesSameTx ? axn.preLoadedDepositReservesSameTx : []),
]),
];
const scopeTokensMap = (0, market_1.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, amount, mint, owner, obligation, useV2Ixs, scopeRefreshConfig, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas,
requestElevationGroup = false, // to be requested *after* the withdraw
initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.none)(), currentSlot = 0n, overrideElevationGroupRequest,
// Optional customizations which may be needed if the obligation was mutated by some previous ix.
obligationCustomizations) {
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, amount, mint, owner, obligation, useV2Ixs, scopeRefreshConfig, currentSlot, payer = owner, extraComputeBudget = 1_000_000, includeAtaIxs = true, requestElevationGroup = false, initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.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, amount, minCollateralReceiveAmount, repayTokenMint, withdrawTokenMint, liquidator, obligationOwner, obligation, useV2Ixs, scopeRefreshConfig = undefined, extraComputeBudget = 1_000_000, // if > 0 then adds the ix
includeAtaIxs = true, // if true it includes create and close wsol and token atas, and creates all other token atas if they don't exist
requestElevationGroup = false, initUserMetadata = {
skipInitialization: false,
skipLutCreation: false,
}, referrer = (0, kit_1.none)(), maxAllowedLtvOverridePercent = 0, currentSlot = 0n) {
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, tokenMint, kaminoMarket, currentSlot = 0n) {
const { axn, createAtaIxs } = await KaminoAction.initializeWithdrawReferrerFees(tokenMint, owner, kaminoMarket, currentSlot);
axn.setupIxs.push(...createAtaIxs);
axn.setupIxsLabels.push(`createAtasIxs[${axn.owner.toString()}]`);
if ((0, kit_1.isSome)(axn.referrer)) {
const referrerTokenState = await (0, utils_1.referrerTokenStatePda)(axn.referrer.value, axn.reserve.address, axn.kaminoMarket.programId);
const account = await (0, kit_1.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, kaminoMarket, obligation, orderAtIndex) {
return (0, instructions_1.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((0, instructions_1.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_1.TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
}, undefined, this.kaminoMarket.programId));
}
async addRedeemReserveCollateralIx() {
this.lendingIxsLabels.push(`redeemReserveCollateral`);
this.lendingIxs.push((0, instructions_1.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_1.TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
}, undefined, this.kaminoMarket.programId));
}
// @deprecated -- use addDepositIxV2 instead
async addDepositIx() {
this.lendingIxsLabels.push(`depositReserveLiquidityAndObligationCollateral`);
this.lendingIxs.push((0, instructions_1.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: (0, kit_1.none)(),
collateralTokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.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((0, instructions_1.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: (0, kit_1.none)(),
collateralTokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
},
farmsAccounts,
farmsProgram: programId_1.PROGRAM_ID,
}, undefined, this.kaminoMarket.programId));
}
/// @deprecated -- use addDepositObligationCollateralIxV2 instead
async addDepositObligationCollateralIx() {
this.lendingIxsLabels.push(`depositObligationCollateral`);
this.lendingIxs.push((0, instructions_1.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_1.TOKEN_PROGRAM_ADDRESS,
instructionSysvarAccount: sysvars_1.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((0, instructions_1.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_1.TOKEN_PROGRAM_ADDRESS,
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
},
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
farmsAccounts,
farmsProgram: programId_1.PROGRAM_ID,
}, undefined, this.kaminoMarket.programId));
}
/// @deprecated -- use addDepositObligationCollateralIxV2 instead
async addBorrowIx() {
this.lendingIxsLabels.push(`borrowObligationLiquidity`);
const depositReservesList = this.getAdditionalDepositReservesList();
const depositReserveAccountMetas = depositReservesList.map((reserve) => {
return { address: reserve, role: kit_1.AccountRole.WRITABLE };
});
let borrowIx = (0, instructions_1.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: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
}, undefined, this.kaminoMarket.programId);
borrowIx = {
...borrowIx,
accounts: (0, obligation_1.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 = depositReservesList.map((reserve) => {
return { address: reserve, role: kit_1.AccountRole.WRITABLE };
});
const obligationAddress = await this.getObligationPda();
const { debtFarmAccounts: farmsAccounts } = await KaminoAction.getFarmAccountsForReserve(obligationAddress, this.reserve);
let borrowIx = (0, instructions_1.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: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
},
farmsAccounts,
farmsProgram: programId_1.PROGRAM_ID,
}, undefined, this.kaminoMarket.programId);
borrowIx = {
...borrowIx,
accounts: (0, obligation_1.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) {
this.lendingIxsLabels.push(`withdrawObligationCollateralAndRedeemReserveCollateral`);
this.lendingIxs.push((0, instructions_1.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: (0, kit_1.none)(),
collateralTokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
}, undefined, this.kaminoMarket.programId));
}
async addWithdrawIxV2(collateralAmount) {
const obligationAddress = await this.getObligationPda();
const { collateralFarmAccounts: farmsAccounts } = await KaminoAction.getFarmAccountsForReserve(obligationAddress, this.reserve);
this.lendingIxsLabels.push(`withdrawObligationCollateralAndRedeemReserveCollateralV2`);
this.lendingIxs.push((0, instructions_1.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: (0, kit_1.none)(),
collateralTokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
},
farmsAccounts: farmsAccounts,
farmsProgram: programId_1.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 = depositReservesList.map((reserve) => {
return { address: reserve, role: kit_1.AccountRole.WRITABLE };
});
let repayIx = (0, instructions_1.repayObligationLiquidity)({
liquidityAmount: this.amount,
}, {
owner: this.payer,
obligation: obligationAddress,
lendingMarket: this.kaminoMarket.getAddress(),
repayReserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
userSourceLiquidity: await this.getTokenAccountAddressByUser(this.reserve, this.payer.address),
reserveDestinationLiquidity: this.reserve.state.liquidity.supplyVault,
tokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
}, undefined, this.kaminoMarket.programId);
repayIx = {
...repayIx,
accounts: (0, obligation_1.isKaminoObligation)(this.obligation) && this.obligation.state.elevationGroup > 0
? repayIx.accounts.concat(depositReserveAccountMetas)
: repayIx.accounts,
};
this.lendingIxs.push(repayIx);
}
async addRepayIxV2() {
const obligationAddress = await this.getObligationPda();
this.lendingIxsLabels.push(`repayObligationLiquidityV2(reserve=${this.reserve.address})(obligation=${obligationAddress})`);
const depositReservesList = this.getAdditionalDepositReservesList();
const { debtFarmAccounts: farmsAccounts } = await KaminoAction.getFarmAccountsForReserve(obligationAddress, this.reserve);
const depositReserveAccountMetas = depositReservesList.map((reserve) => {
return { address: reserve, role: kit_1.AccountRole.WRITABLE };
});
let repayIx = (0, instructions_1.repayObligationLiquidityV2)({
liquidityAmount: this.amount,
}, {
repayAccounts: {
owner: this.payer,
obligation: obligationAddress,
lendingMarket: this.kaminoMarket.getAddress(),
repayReserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
userSourceLiquidity: await this.getTokenAccountAddressByUser(this.reserve, this.payer.address),
reserveDestinationLiquidity: this.reserve.state.liquidity.supplyVault,
tokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
},
lendingMarketAuthority: await this.kaminoMarket.getLendingMarketAuthority(),
farmsAccounts,
farmsProgram: programId_1.PROGRAM_ID,
}, undefined, this.kaminoMarket.programId);
repayIx = {
...repayIx,
// TODO: potential elev group update?
accounts: (0, obligation_1.isKaminoObligation)(this.obligation) && this.obligation.state.elevationGroup > 0
? repayIx.accounts.concat(depositReserveAccountMetas)
: repayIx.accounts,
};
this.lendingIxs.push(repayIx);
}
async addRepayAndWithdrawV2Ixs(withdrawCollateralAmount) {
const obligationAddress = await this.getObligationPda();
this.lendingIxsLabels.push(`repayAndWithdrawAndRedeem(repayReserve=${this.reserve.address})(withdrawReserve=${this.outflowReserve.address})(obligation=${obligationAddress})`);
const depositReservesList = this.getAdditionalDepositReservesList();
const depositReserveAccountMetas = depositReservesList.map((reserve) => {
return { address: reserve, role: kit_1.AccountRole.WRITABLE };
});
const borrowReserveAccountMetas = this.borrowReserves.map((reserve) => {
return { address: reserve, role: kit_1.AccountRole.WRITABLE };
});
if (!this.outflowAmount) {
throw new Error(`outflowAmount not set`);
}
if (!this.outflowReserve) {
throw new Error(`outflowReserve not set`);
}
const { collateralFarmAccounts } = await KaminoAction.getFarmAccountsForReserve(obligationAddress, this.outflowReserve);
const { debtFarmAccounts } = await KaminoAction.getFarmAccountsForReserve(obligationAddress, this.reserve);
let repayAndWithdrawIx = (0, instructions_1.repayAndWithdrawAndRedeem)({
repayAmount: this.amount,
withdrawCollateralAmount,
}, {
repayAccounts: {
owner: this.owner,
obligation: obligationAddress,
lendingMarket: this.kaminoMarket.getAddress(),
repayReserve: this.reserve.address,
reserveLiquidityMint: this.reserve.getLiquidityMint(),
userSourceLiquidity: await this.getUserTokenAccountAddress(this.reserve),
reserveDestinationLiquidity: this.reserve.state.liquidity.supplyVault,
tokenProgram: this.reserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
},
withdrawAccounts: {
owner: this.owner,
obligation: obligationAddress,
lendingMarket: this.kaminoMarket.getAddress(),
lendingMarketAuthority: await 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: await this.getUserTokenAccountAddress(this.outflowReserve),
placeholderUserDestinationCollateral: (0, kit_1.none)(),
collateralTokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
liquidityTokenProgram: this.outflowReserve.getLiquidityTokenProgram(),
instructionSysvarAccount: sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
},
collateralFarmsAccounts: collateralFarmAccounts,
debtFarmsAccounts: debtFarmAccounts,
farmsProgram: programId_1.PROGRAM_ID,
}, undefined, this.kaminoMarket.programId);
repayAndWithdrawIx = {
...repayAndWithdrawIx,